Skip to content

Commit

Permalink
Merge pull request #58 from TheXtremeLabs/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
MichaelStH authored Sep 21, 2021
2 parents 234b83c + 828d7a1 commit 140ebcb
Show file tree
Hide file tree
Showing 12 changed files with 362 additions and 3 deletions.
11 changes: 9 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,13 @@
android:label="@string/activity_title_google_drive"
android:parentActivityName=".ui.mainactivity.MainActivity"
android:theme="@style/Theme.TheLab" />

<activity
android:name=".ui.download.DownloadActivity"
android:label="@string/activity_title_download"
android:parentActivityName=".ui.mainactivity.MainActivity"
android:screenOrientation="portrait"
tools:ignore="LockedOrientationActivity" />
<!-- ////////////// ACTIVITIES ////////////// -->


Expand Down Expand Up @@ -459,8 +466,8 @@
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
tools:node="remove"
tools:ignore="MissingClass" />
tools:ignore="MissingClass"
tools:node="remove" />
</application>

</manifest>
5 changes: 5 additions & 0 deletions app/src/main/java/com/riders/thelab/data/RepositoryImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.google.firebase.storage.StorageReference
import com.riders.thelab.TheLabApplication
import com.riders.thelab.data.local.DbImpl
import com.riders.thelab.data.local.model.Contact
import com.riders.thelab.data.local.model.Download
import com.riders.thelab.data.local.model.Video
import com.riders.thelab.data.local.model.app.App
import com.riders.thelab.data.local.model.weather.CityModel
Expand Down Expand Up @@ -215,4 +216,8 @@ class RepositoryImpl @Inject constructor(
override suspend fun getBulkWeatherCitiesFile(): ResponseBody {
return mApiImpl.getBulkWeatherCitiesFile()
}

override suspend fun getBulkDownload(): Flow<Download> {
return mApiImpl.getBulkDownload()
}
}
11 changes: 11 additions & 0 deletions app/src/main/java/com/riders/thelab/data/local/model/Download.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.riders.thelab.data.local.model

import java.io.File

sealed class Download {
data class Started(val started: Boolean) : Download()
data class Progress(val percent: Int) : Download()
data class Done(val done: Boolean) : Download()
data class Error(val error: Boolean) : Download()
data class Finished(val file: File) : Download()
}
85 changes: 85 additions & 0 deletions app/src/main/java/com/riders/thelab/data/remote/ApiImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,21 @@ import android.widget.Toast
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.storage.FirebaseStorage
import com.google.firebase.storage.StorageReference
import com.riders.thelab.TheLabApplication
import com.riders.thelab.data.local.model.Download
import com.riders.thelab.data.local.model.Video
import com.riders.thelab.data.remote.api.*
import com.riders.thelab.data.remote.dto.artist.Artist
import com.riders.thelab.data.remote.dto.weather.OneCallWeatherResponse
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.tasks.await
import okhttp3.ResponseBody
import timber.log.Timber
import java.io.File
import javax.inject.Inject

class ApiImpl @Inject constructor(
Expand Down Expand Up @@ -91,4 +99,81 @@ class ApiImpl @Inject constructor(
Timber.e("get cities bulk file()")
return mWeatherBulkApiService.getCitiesGZipFile()
}


override suspend fun getBulkDownload(): Flow<Download> {
return mWeatherBulkApiService.getCitiesGZipFile()
.downloadCitiesFile(
TheLabApplication.getInstance().getContext().externalCacheDir!!,
"my_file"
)
}


private suspend fun ResponseBody.downloadCitiesFile(
directory: File,
filename: String
): Flow<Download> = flow {

emit(Download.Started(true))
Timber.d("downloadToFileWithProgress()")
emit(Download.Progress(0))

// flag to delete file if download errors or is cancelled
var deleteFile = true
val file = File(directory, "${filename}.${contentType()?.subtype}")

try {

byteStream().use { inputStream ->
file.outputStream().use { outputStream ->
val totalBytes = contentLength()
val data = ByteArray(8_192)
var progressBytes = 0L

while (true) {
val bytes = inputStream.read(data)

if (bytes == -1) {
break
}

outputStream.channel
outputStream.write(data, 0, bytes)
progressBytes += bytes

emit(Download.Progress(percent = ((progressBytes * 100) / totalBytes).toInt()))
}

when {
progressBytes < totalBytes -> {
emit(Download.Done(true))
throw Exception("missing bytes")
}
progressBytes > totalBytes -> {
emit(Download.Done(true))
throw Exception("too many bytes")
}
else ->
deleteFile = false
}
}
}

emit(Download.Finished(file))
} catch (exception: Exception) {
exception.printStackTrace()
emit(Download.Error(true))
} finally {
// check if download was successful
emit(Download.Done(true))

if (deleteFile) {
file.delete()
}
}
}
.flowOn(Dispatchers.IO)
.distinctUntilChanged()

}
3 changes: 3 additions & 0 deletions app/src/main/java/com/riders/thelab/data/remote/IApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.riders.thelab.data.remote
import android.app.Activity
import android.location.Location
import com.google.firebase.storage.StorageReference
import com.riders.thelab.data.local.model.Download
import com.riders.thelab.data.local.model.Video
import com.riders.thelab.data.remote.dto.artist.Artist
import com.riders.thelab.data.remote.dto.weather.OneCallWeatherResponse
Expand All @@ -22,4 +23,6 @@ interface IApi {
suspend fun getVideos(): List<Video>
suspend fun getWeatherOneCallAPI(location: Location): OneCallWeatherResponse?
suspend fun getBulkWeatherCitiesFile(): ResponseBody
suspend fun getBulkDownload(): Flow<Download>

}
116 changes: 116 additions & 0 deletions app/src/main/java/com/riders/thelab/ui/download/DownloadActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package com.riders.thelab.ui.download

import android.annotation.SuppressLint
import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.riders.thelab.R
import com.riders.thelab.data.local.model.Download
import com.riders.thelab.databinding.ActivityDownloadBinding
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.*
import timber.log.Timber
import java.net.UnknownHostException
import kotlin.coroutines.CoroutineContext

@AndroidEntryPoint
class DownloadActivity
: AppCompatActivity(), CoroutineScope {

override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + Job()


private var _viewBinding: ActivityDownloadBinding? = null

// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _viewBinding!!

private val mViewModel: DownloadViewModel by viewModels()


@DelicateCoroutinesApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_viewBinding = ActivityDownloadBinding.inflate(layoutInflater)
setContentView(binding.root)

initViewsModelsObservers()

downloadFile()
}

override fun onDestroy() {
super.onDestroy()
Timber.e("onDestroy()")
_viewBinding = null
}

@SuppressLint("SetTextI18n")
private fun initViewsModelsObservers() {
mViewModel.getPercentData().observe(this, {
binding.progressBar.progress = it
})

mViewModel.getDownloadStatus().observe(this, {
when (it) {
is Download.Started -> {
Timber.d("${it.started}")
binding.tvDownloadStatus.text = "Started"
}

is Download.Done -> {
Timber.d("${it.done}")
binding.progressBar.progress = 100
binding.progressBar.setIndicatorColor(
ContextCompat.getColor(this@DownloadActivity, R.color.success)
)

binding.tvDownloadStatus.text = "Success ! Download Done"
}

is Download.Error -> {
Timber.d("${it.error}")
showDownloadError("Error ! Download Failed")
}
is Download.Finished -> { // Ignored
}
is Download.Progress -> { // Ignored
}
}
})
}


@SuppressLint("SetTextI18n")
@DelicateCoroutinesApi
private fun downloadFile() {

GlobalScope.launch {
try {
supervisorScope {
mViewModel.downloadFile()
}
} catch (exception: Exception) {
exception.printStackTrace()

if (exception is UnknownHostException) {
Timber.e("Check your connection, cannot reach host ${exception.message}, cause : ${exception.cause}")
showDownloadError("Check your connection, cannot reach host ${exception.message}")
}
}
}
}

private fun showDownloadError(message: String) {
CoroutineScope(coroutineContext).launch {
binding.progressBar.progress = 100
binding.progressBar.setIndicatorColor(
ContextCompat.getColor(this@DownloadActivity, R.color.error)
)
binding.tvDownloadStatus.text = message
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.riders.thelab.ui.download

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.riders.thelab.data.IRepository
import com.riders.thelab.data.local.model.Download
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.withContext
import timber.log.Timber
import javax.inject.Inject

@HiltViewModel
class DownloadViewModel @Inject constructor(
private val repository: IRepository
) : ViewModel() {

private val downloadStatus: MutableLiveData<Download> = MutableLiveData()
private val percentData: MutableLiveData<Int> = MutableLiveData()

fun getDownloadStatus(): LiveData<Download> {
return downloadStatus
}

fun getPercentData(): LiveData<Int> {
return percentData
}


suspend fun downloadFile() {
Timber.d("downloadFile()")
repository.getBulkDownload()
.collect { download ->

withContext(Dispatchers.Main) {
when (download) {
is Download.Started -> {
downloadStatus.value = download
}

is Download.Progress -> {
// update ui with progress
Timber.d("percent : ${download.percent}%")

percentData.value = download.percent
}

is Download.Done -> {
downloadStatus.value = download
}

is Download.Error -> {
downloadStatus.value = download
}

is Download.Finished -> {
// update ui with file
Timber.d("download status : ${download.file.toString()}")
}
}
}
}
}
}
12 changes: 12 additions & 0 deletions app/src/main/java/com/riders/thelab/utils/AppBuilderUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.riders.thelab.ui.colors.ColorActivity
import com.riders.thelab.ui.contacts.ContactsActivity
import com.riders.thelab.ui.customtoast.CustomToastActivity
import com.riders.thelab.ui.deviceinformation.DeviceInformationActivity
import com.riders.thelab.ui.download.DownloadActivity
import com.riders.thelab.ui.filterlistview.FilterListViewActivity
import com.riders.thelab.ui.floatinglabels.FloatingLabelsActivity
import com.riders.thelab.ui.floatingview.FloatingViewActivity
Expand Down Expand Up @@ -391,6 +392,17 @@ class AppBuilderUtils {
.build()
list.add(googleDrive)

// download
val download =
AppBuilder
.withId(25L)
.withActivityTitle("Download")
.withActivityDescription("Download file using Kotlin Flow...")
.withActivityIcon(getDrawableFromIntResource(context, R.drawable.ic_download))
.withActivityClass(DownloadActivity::class.java)
.build()
list.add(download)

// Wip
val wip =
AppBuilder
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/ic_download.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M16,13h-3V3h-2v10H8l4,4 4,-4zM4,19v2h16v-2H4z" />
</vector>
Loading

0 comments on commit 140ebcb

Please sign in to comment.