Skip to content

Commit

Permalink
Add back progress indicator
Browse files Browse the repository at this point in the history
Closes #52
  • Loading branch information
lberrymage committed Jan 26, 2023
1 parent 2faf116 commit 8597d0f
Show file tree
Hide file tree
Showing 8 changed files with 90 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package app.accrescent.client.data

data class DownloadProgress(val part: Long, val total: Long)
18 changes: 17 additions & 1 deletion app/src/main/java/app/accrescent/client/ui/AppDetailsScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import app.accrescent.client.BuildConfig
import app.accrescent.client.R
import app.accrescent.client.data.DownloadProgress
import app.accrescent.client.data.InstallStatus
import app.accrescent.client.util.isPrivileged

Expand Down Expand Up @@ -85,6 +86,7 @@ fun AppDetailsScreen(
}
},
onOpenClicked = { viewModel.openApp(viewModel.uiState.appId) },
downloadProgress = viewModel.uiState.downloadProgress,
)
else -> AppNotFoundError()
}
Expand Down Expand Up @@ -124,6 +126,7 @@ fun AppDetails(
onInstallClicked: () -> Unit,
onUninstallClicked: () -> Unit,
onOpenClicked: () -> Unit,
downloadProgress: DownloadProgress?,
) {
val context = LocalContext.current

Expand Down Expand Up @@ -202,8 +205,21 @@ fun AppDetails(
}
}
}

Box(Modifier.fillMaxSize()) {
Text(id, Modifier.align(Alignment.BottomCenter))
Column(
modifier = Modifier.align(Alignment.BottomCenter),
horizontalAlignment = Alignment.CenterHorizontally,
) {
if (downloadProgress != null) {
CircularProgressIndicator(
modifier = Modifier.size(96.dp),
progress = downloadProgress.part.toFloat() / downloadProgress.total,
)
}

Text(id, Modifier.padding(top = 48.dp))
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package app.accrescent.client.ui

import app.accrescent.client.data.DownloadProgress

data class AppDetailsUiState(
val isFetchingData: Boolean = false,
var error: String? = null,
Expand All @@ -8,4 +10,5 @@ data class AppDetailsUiState(
val appName: String = "",
val versionName: String = "",
val versionCode: Long = 0,
val downloadProgress: DownloadProgress? = null,
)
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,10 @@ class AppDetailsViewModel @Inject constructor(
val context = getApplication<Accrescent>().applicationContext

try {
packageManager.downloadAndInstall(appId)
packageManager.downloadAndInstall(appId) {
uiState = uiState.copy(downloadProgress = it)
}
uiState = uiState.copy(downloadProgress = null)
} catch (e: ConnectException) {
uiState.error = context.getString(R.string.network_error, e.message)
} catch (e: FileNotFoundException) {
Expand All @@ -113,7 +116,6 @@ class AppDetailsViewModel @Inject constructor(
}
}


fun uninstallApp(appId: String) {
uiState.error = null

Expand Down
33 changes: 27 additions & 6 deletions app/src/main/java/app/accrescent/client/util/ApkDownloader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import android.util.DisplayMetrics
import android.util.Log
import app.accrescent.client.R
import app.accrescent.client.data.Apk
import app.accrescent.client.data.DownloadProgress
import app.accrescent.client.data.REPOSITORY_URL
import app.accrescent.client.data.RepoDataRepository
import app.accrescent.client.data.net.AppRepoData
Expand All @@ -25,7 +26,10 @@ class ApkDownloader @Inject constructor(
@ApplicationContext private val context: Context,
private val repoDataRepository: RepoDataRepository,
) {
suspend fun downloadApp(appId: String): List<Apk> {
suspend fun downloadApp(
appId: String,
onProgressUpdate: (DownloadProgress) -> Unit = {},
): List<Apk> {
Log.i(TAG, "Downloading app $appId")
val appInfo = repoDataRepository.getAppRepoData(appId)

Expand All @@ -37,7 +41,7 @@ class ApkDownloader @Inject constructor(
}

val apkNames = resolveApkNames(appInfo)
val apks = downloadApks("$REPOSITORY_URL/apps/$appId/$version", apkNames)
val apks = downloadApks("$REPOSITORY_URL/apps/$appId/$version", apkNames, onProgressUpdate)
val baseApk = apks[0].file

verifyPackageInfo(appId, appInfo, baseApk)
Expand Down Expand Up @@ -168,13 +172,30 @@ class ApkDownloader @Inject constructor(
}
}

private fun downloadApks(baseDownloadUri: String, names: List<String>): List<Apk> {
private fun downloadApks(
baseDownloadUri: String,
names: List<String>,
onProgressUpdate: (DownloadProgress) -> Unit = {},
): List<Apk> {
var totalBytesToDownload = 0L
var totalBytesDownloaded = 0L

val apks = mutableListOf<Apk>()
val connections = mutableListOf<HttpConnection>()
for (name in names) {
val conn = URL("$baseDownloadUri/$name").openHttpConnection()
totalBytesToDownload += conn.getContentLength()
connections += conn
}

for ((name, conn) in names.zip(connections)) {
val apk = newTemporaryFile()
URL("$baseDownloadUri/$name")
.openHttpConnection()
.use { it.downloadTo(apk.descriptor) }
conn.use {
it.downloadTo(apk.descriptor) { bytes ->
totalBytesDownloaded += bytes
onProgressUpdate(DownloadProgress(totalBytesDownloaded, totalBytesToDownload))
}
}
apks += Apk(name, apk)
}

Expand Down
10 changes: 8 additions & 2 deletions app/src/main/java/app/accrescent/client/util/HttpConnection.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@ import java.net.HttpURLConnection
import java.net.URL

class HttpConnection(private val connection: HttpURLConnection) : AutoCloseable {
fun downloadTo(out: OutputStream) = connection.inputStream.copyTo(out)
fun downloadTo(out: OutputStream, onProgressUpdate: (Long) -> Unit = {}) {
connection.inputStream.copyTo(out, onProgressUpdate)
}

fun downloadTo(fd: FileDescriptor) = FileOutputStream(fd).use { this.downloadTo(it) }
fun downloadTo(fd: FileDescriptor, onProgressUpdate: (Long) -> Unit = {}) {
FileOutputStream(fd).use { this.downloadTo(it, onProgressUpdate) }
}

fun getContentLength() = connection.getHeaderField("Content-Length").toLongOrNull() ?: 0

override fun close() = connection.disconnect()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import android.os.UserManager
import android.system.Os
import app.accrescent.client.R
import app.accrescent.client.data.Apk
import app.accrescent.client.data.DownloadProgress
import app.accrescent.client.di.IoDispatcher
import app.accrescent.client.receivers.AppInstallBroadcastReceiver
import app.accrescent.client.receivers.AppUninstallBroadcastReceiver
Expand All @@ -27,9 +28,12 @@ class PackageManager @Inject constructor(
private val apkDownloader: ApkDownloader,
@IoDispatcher private val dispatcher: CoroutineDispatcher,
) {
suspend fun downloadAndInstall(appId: String) {
suspend fun downloadAndInstall(
appId: String,
onProgressUpdate: (DownloadProgress) -> Unit = {}
) {
withContext(dispatcher) {
installApp(apkDownloader.downloadApp(appId))
installApp(apkDownloader.downloadApp(appId, onProgressUpdate))
}
}

Expand Down
22 changes: 22 additions & 0 deletions app/src/main/java/app/accrescent/client/util/Util.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,30 @@ package app.accrescent.client.util
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import java.io.InputStream
import java.io.OutputStream

fun Context.isPrivileged(): Boolean {
return this.checkSelfPermission(Manifest.permission.INSTALL_PACKAGES) ==
PackageManager.PERMISSION_GRANTED
}

fun InputStream.copyTo(
out: OutputStream,
onProgressUpdate: (Long) -> Unit,
bufferSize: Int = DEFAULT_BUFFER_SIZE,
): Long {
var bytesCopied: Long = 0
val buffer = ByteArray(bufferSize)
var bytes = read(buffer)
onProgressUpdate(bytes.toLong())

while (bytes >= 0) {
out.write(buffer, 0, bytes)
bytesCopied += bytes
bytes = read(buffer)
onProgressUpdate(bytes.toLong())
}

return bytesCopied
}

0 comments on commit 8597d0f

Please sign in to comment.