mirror of
https://github.com/Suwayomi/TachideskJUI.git
synced 2025-12-10 06:42:05 +01:00
Update Ktor, use new progress feature, fix backup creation
This commit is contained in:
@@ -46,7 +46,7 @@ dependencies {
|
||||
kapt("com.github.stephanenicolas.toothpick:toothpick-compiler:3.1.0")
|
||||
|
||||
// Http client
|
||||
val ktorVersion = "1.5.4"
|
||||
val ktorVersion = "1.6.0"
|
||||
implementation("io.ktor:ktor-client-core:$ktorVersion")
|
||||
implementation("io.ktor:ktor-client-okhttp:$ktorVersion")
|
||||
implementation("io.ktor:ktor-client-serialization:$ktorVersion")
|
||||
|
||||
@@ -14,13 +14,13 @@ import ca.gosyer.data.server.requests.backupFileExportRequest
|
||||
import ca.gosyer.data.server.requests.backupFileImportRequest
|
||||
import ca.gosyer.data.server.requests.backupImportRequest
|
||||
import ca.gosyer.util.lang.withIOContext
|
||||
import io.ktor.client.request.HttpRequestBuilder
|
||||
import io.ktor.client.request.forms.formData
|
||||
import io.ktor.client.request.forms.submitFormWithBinaryData
|
||||
import io.ktor.client.statement.HttpResponse
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.Headers
|
||||
import io.ktor.http.HttpHeaders
|
||||
import io.ktor.http.content.MultiPartData
|
||||
import io.ktor.http.contentType
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
@@ -30,7 +30,7 @@ class BackupInteractionHandler @Inject constructor(
|
||||
serverPreferences: ServerPreferences
|
||||
) : BaseInteractionHandler(client, serverPreferences) {
|
||||
|
||||
suspend fun importBackupFile(file: File) = withIOContext {
|
||||
suspend fun importBackupFile(file: File, block: HttpRequestBuilder.() -> Unit = {}) = withIOContext {
|
||||
client.submitFormWithBinaryData<HttpResponse>(
|
||||
serverUrl + backupFileImportRequest(),
|
||||
formData = formData {
|
||||
@@ -41,28 +41,32 @@ class BackupInteractionHandler @Inject constructor(
|
||||
append(HttpHeaders.ContentDisposition, "filename=backup.json")
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
block = block
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun importBackup(backup: Backup) = withIOContext {
|
||||
suspend fun importBackup(backup: Backup, block: HttpRequestBuilder.() -> Unit = {}) = withIOContext {
|
||||
client.postRepeat<HttpResponse>(
|
||||
serverUrl + backupImportRequest()
|
||||
) {
|
||||
contentType(ContentType.Application.Json)
|
||||
body = backup
|
||||
block()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun exportBackupFile() = withIOContext {
|
||||
client.getRepeat<MultiPartData>(
|
||||
serverUrl + backupFileExportRequest()
|
||||
suspend fun exportBackupFile(block: HttpRequestBuilder.() -> Unit = {}) = withIOContext {
|
||||
client.getRepeat<HttpResponse>(
|
||||
serverUrl + backupFileExportRequest(),
|
||||
block
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun exportBackup() = withIOContext {
|
||||
suspend fun exportBackup(block: HttpRequestBuilder.() -> Unit = {}) = withIOContext {
|
||||
client.getRepeat<Backup>(
|
||||
serverUrl + backupExportRequest()
|
||||
serverUrl + backupExportRequest(),
|
||||
block
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,9 +87,9 @@ open class BaseInteractionHandler(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun imageFromUrl(client: Http, imageUrl: String): ImageBitmap {
|
||||
suspend fun imageFromUrl(client: Http, imageUrl: String, block: HttpRequestBuilder.() -> Unit): ImageBitmap {
|
||||
return repeat {
|
||||
ca.gosyer.util.compose.imageFromUrl(client, imageUrl)
|
||||
ca.gosyer.util.compose.imageFromUrl(client, imageUrl, block)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import ca.gosyer.data.server.requests.getMangaChaptersQuery
|
||||
import ca.gosyer.data.server.requests.getPageQuery
|
||||
import ca.gosyer.data.server.requests.updateChapterRequest
|
||||
import ca.gosyer.util.lang.withIOContext
|
||||
import io.ktor.client.request.HttpRequestBuilder
|
||||
import io.ktor.client.request.parameter
|
||||
import io.ktor.client.statement.HttpResponse
|
||||
import io.ktor.http.HttpMethod
|
||||
@@ -113,16 +114,17 @@ class ChapterInteractionHandler @Inject constructor(
|
||||
markPreviousRead
|
||||
)
|
||||
|
||||
suspend fun getPage(mangaId: Long, chapterIndex: Int, pageNum: Int) = withIOContext {
|
||||
suspend fun getPage(mangaId: Long, chapterIndex: Int, pageNum: Int, block: HttpRequestBuilder.() -> Unit) = withIOContext {
|
||||
imageFromUrl(
|
||||
client,
|
||||
serverUrl + getPageQuery(mangaId, chapterIndex, pageNum)
|
||||
serverUrl + getPageQuery(mangaId, chapterIndex, pageNum),
|
||||
block
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getPage(chapter: Chapter, pageNum: Int) = getPage(chapter.mangaId, chapter.index, pageNum)
|
||||
suspend fun getPage(chapter: Chapter, pageNum: Int, block: HttpRequestBuilder.() -> Unit) = getPage(chapter.mangaId, chapter.index, pageNum, block)
|
||||
|
||||
suspend fun getPage(manga: Manga, chapterIndex: Int, pageNum: Int) = getPage(manga.id, chapterIndex, pageNum)
|
||||
suspend fun getPage(manga: Manga, chapterIndex: Int, pageNum: Int, block: HttpRequestBuilder.() -> Unit) = getPage(manga.id, chapterIndex, pageNum, block)
|
||||
|
||||
suspend fun getPage(manga: Manga, chapter: Chapter, pageNum: Int) = getPage(manga.id, chapter.index, pageNum)
|
||||
suspend fun getPage(manga: Manga, chapter: Chapter, pageNum: Int, block: HttpRequestBuilder.() -> Unit) = getPage(manga.id, chapter.index, pageNum, block)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import ca.gosyer.data.server.requests.apkUninstallQuery
|
||||
import ca.gosyer.data.server.requests.apkUpdateQuery
|
||||
import ca.gosyer.data.server.requests.extensionListQuery
|
||||
import ca.gosyer.util.lang.withIOContext
|
||||
import io.ktor.client.request.HttpRequestBuilder
|
||||
import io.ktor.client.statement.HttpResponse
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -47,10 +48,11 @@ class ExtensionInteractionHandler @Inject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getApkIcon(extension: Extension) = withIOContext {
|
||||
suspend fun getApkIcon(extension: Extension, block: HttpRequestBuilder.() -> Unit) = withIOContext {
|
||||
imageFromUrl(
|
||||
client,
|
||||
serverUrl + apkIconQuery(extension.apkName)
|
||||
serverUrl + apkIconQuery(extension.apkName),
|
||||
block
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import ca.gosyer.data.server.ServerPreferences
|
||||
import ca.gosyer.data.server.requests.mangaQuery
|
||||
import ca.gosyer.data.server.requests.mangaThumbnailQuery
|
||||
import ca.gosyer.util.lang.withIOContext
|
||||
import io.ktor.client.request.HttpRequestBuilder
|
||||
import io.ktor.client.request.parameter
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -34,10 +35,11 @@ class MangaInteractionHandler @Inject constructor(
|
||||
|
||||
suspend fun getManga(manga: Manga, refresh: Boolean = false) = getManga(manga.id, refresh)
|
||||
|
||||
suspend fun getMangaThumbnail(mangaId: Long) = withIOContext {
|
||||
suspend fun getMangaThumbnail(mangaId: Long, block: HttpRequestBuilder.() -> Unit) = withIOContext {
|
||||
imageFromUrl(
|
||||
client,
|
||||
serverUrl + mangaThumbnailQuery(mangaId)
|
||||
serverUrl + mangaThumbnailQuery(mangaId),
|
||||
block
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,10 +24,13 @@ import ca.gosyer.common.di.AppScope
|
||||
import ca.gosyer.data.server.Http
|
||||
import ca.gosyer.util.compose.imageFromUrl
|
||||
import ca.gosyer.util.lang.throwIfCancellation
|
||||
import io.ktor.client.features.onDownload
|
||||
import io.ktor.client.request.HttpRequestBuilder
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.max
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
@Composable
|
||||
@@ -46,6 +49,7 @@ fun KtorImage(
|
||||
BoxWithConstraints {
|
||||
val drawable: MutableState<ImageBitmap?> = remember { mutableStateOf(null) }
|
||||
val loading: MutableState<Boolean> = remember { mutableStateOf(true) }
|
||||
val progress: MutableState<Float?> = remember { mutableStateOf(null) }
|
||||
val error: MutableState<String?> = remember { mutableStateOf(null) }
|
||||
DisposableEffect(imageUrl) {
|
||||
val handler = CoroutineExceptionHandler { _, throwable ->
|
||||
@@ -54,7 +58,11 @@ fun KtorImage(
|
||||
}
|
||||
val job = GlobalScope.launch(handler) {
|
||||
if (drawable.value == null) {
|
||||
drawable.value = getImage(client, imageUrl, retries)
|
||||
drawable.value = getImage(client, imageUrl, retries) {
|
||||
onDownload { bytesSentTotal, contentLength ->
|
||||
progress.value = max(bytesSentTotal.toFloat() / contentLength, 1.0F)
|
||||
}
|
||||
}
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
@@ -77,17 +85,17 @@ fun KtorImage(
|
||||
colorFilter = colorFilter
|
||||
)
|
||||
} else {
|
||||
LoadingScreen(loading.value, loadingModifier, error.value)
|
||||
LoadingScreen(loading.value, loadingModifier, progress.value, error.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getImage(client: Http, imageUrl: String, retries: Int = 3): ImageBitmap {
|
||||
private suspend fun getImage(client: Http, imageUrl: String, retries: Int = 3, block: HttpRequestBuilder.() -> Unit): ImageBitmap {
|
||||
var attempt = 1
|
||||
var lastException: Exception
|
||||
do {
|
||||
try {
|
||||
return imageFromUrl(client, imageUrl)
|
||||
return imageFromUrl(client, imageUrl, block)
|
||||
} catch (e: Exception) {
|
||||
e.throwIfCancellation()
|
||||
lastException = e
|
||||
|
||||
@@ -21,6 +21,8 @@ import androidx.compose.ui.unit.min
|
||||
fun LoadingScreen(
|
||||
isLoading: Boolean = true,
|
||||
modifier: Modifier = Modifier.fillMaxSize(),
|
||||
/*@FloatRange(from = 0.0, to = 1.0)*/
|
||||
progress: Float? = null,
|
||||
errorMessage: String? = null,
|
||||
retryMessage: String = "Retry",
|
||||
retry: (() -> Unit)? = null
|
||||
@@ -31,7 +33,11 @@ fun LoadingScreen(
|
||||
val size = remember(maxHeight, maxWidth) {
|
||||
min(maxHeight, maxWidth) / 2
|
||||
}
|
||||
if (progress != null) {
|
||||
CircularProgressIndicator(progress, Modifier.align(Alignment.Center).size(size))
|
||||
} else {
|
||||
CircularProgressIndicator(Modifier.align(Alignment.Center).size(size))
|
||||
}
|
||||
} else {
|
||||
ErrorScreen(errorMessage, modifier, retryMessage, retry)
|
||||
}
|
||||
|
||||
@@ -201,6 +201,7 @@ fun ReaderMenu(chapterIndex: Int, mangaId: Long, setHotkeys: (List<KeyboardShort
|
||||
fun ReaderImage(
|
||||
imageIndex: Int,
|
||||
drawable: ImageBitmap?,
|
||||
progress: Float?,
|
||||
status: ReaderPage.Status,
|
||||
error: String?,
|
||||
imageModifier: Modifier = Modifier.fillMaxSize(),
|
||||
@@ -216,7 +217,7 @@ fun ReaderImage(
|
||||
contentScale = contentScale
|
||||
)
|
||||
} else {
|
||||
LoadingScreen(status == ReaderPage.Status.QUEUE, loadingModifier, error) { retry(imageIndex) }
|
||||
LoadingScreen(status == ReaderPage.Status.QUEUE, loadingModifier, progress, error) { retry(imageIndex) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import ca.gosyer.ui.reader.model.ReaderPage
|
||||
import ca.gosyer.util.lang.throwIfCancellation
|
||||
import ca.gosyer.util.system.CKLogger
|
||||
import io.github.kerubistan.kroki.coroutines.priorityChannel
|
||||
import io.ktor.client.features.onDownload
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
@@ -22,6 +23,7 @@ import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.math.max
|
||||
|
||||
class TachideskPageLoader(
|
||||
context: CoroutineContext,
|
||||
@@ -58,7 +60,11 @@ class TachideskPageLoader(
|
||||
debug { "Loading page ${page.index}" }
|
||||
if (page.status.value == ReaderPage.Status.QUEUE) {
|
||||
try {
|
||||
page.bitmap.value = chapterHandler.getPage(chapter.chapter, page.index)
|
||||
page.bitmap.value = chapterHandler.getPage(chapter.chapter, page.index) {
|
||||
onDownload { bytesSentTotal, contentLength ->
|
||||
page.progress.value = max(bytesSentTotal.toFloat() / contentLength, 1.0F)
|
||||
}
|
||||
}
|
||||
page.status.value = ReaderPage.Status.READY
|
||||
page.error.value = null
|
||||
} catch (e: Exception) {
|
||||
@@ -107,6 +113,7 @@ class TachideskPageLoader(
|
||||
ReaderPage(
|
||||
it,
|
||||
MutableStateFlow(null),
|
||||
MutableStateFlow(null),
|
||||
MutableStateFlow(ReaderPage.Status.QUEUE),
|
||||
MutableStateFlow(null)
|
||||
)
|
||||
|
||||
@@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
data class ReaderPage(
|
||||
val index: Int,
|
||||
val bitmap: MutableStateFlow<ImageBitmap?>,
|
||||
val progress: MutableStateFlow<Float?>,
|
||||
val status: MutableStateFlow<Status>,
|
||||
val error: MutableStateFlow<String?>
|
||||
) {
|
||||
|
||||
@@ -63,6 +63,7 @@ fun ContinuousReader(
|
||||
ReaderImage(
|
||||
image.index,
|
||||
image.bitmap.collectAsState().value,
|
||||
image.progress.collectAsState().value,
|
||||
image.status.collectAsState().value,
|
||||
image.error.collectAsState().value,
|
||||
loadingModifier = pageModifier,
|
||||
|
||||
@@ -101,6 +101,7 @@ fun HandlePager(
|
||||
ReaderImage(
|
||||
image.index,
|
||||
image.bitmap.collectAsState().value,
|
||||
image.progress.collectAsState().value,
|
||||
image.status.collectAsState().value,
|
||||
image.error.collectAsState().value,
|
||||
loadingModifier = pageModifier,
|
||||
|
||||
@@ -13,6 +13,7 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material.icons.filled.Warning
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
@@ -32,39 +33,53 @@ import ca.gosyer.util.system.CKLogger
|
||||
import ca.gosyer.util.system.filePicker
|
||||
import ca.gosyer.util.system.fileSaver
|
||||
import com.github.zsoltk.compose.router.BackStack
|
||||
import io.ktor.client.features.onDownload
|
||||
import io.ktor.client.features.onUpload
|
||||
import io.ktor.utils.io.jvm.javaio.copyTo
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.max
|
||||
|
||||
class SettingsBackupViewModel @Inject constructor(
|
||||
private val backupHandler: BackupInteractionHandler
|
||||
) : ViewModel() {
|
||||
private val _restoring = MutableStateFlow(false)
|
||||
val restoring = _restoring.asStateFlow()
|
||||
private val _restoreError = MutableStateFlow(false)
|
||||
val restoreError = _restoreError.asStateFlow()
|
||||
private val _restoringProgress = MutableStateFlow<Float?>(null)
|
||||
val restoringProgress = _restoringProgress.asStateFlow()
|
||||
private val _restoreStatus = MutableStateFlow<Status>(Status.Nothing)
|
||||
internal val restoreStatus = _restoreStatus.asStateFlow()
|
||||
|
||||
private val _creating = MutableStateFlow(false)
|
||||
val creating = _creating.asStateFlow()
|
||||
private val _creatingError = MutableStateFlow(false)
|
||||
val creatingError = _creatingError.asStateFlow()
|
||||
private val _creatingProgress = MutableStateFlow<Float?>(null)
|
||||
val creatingProgress = _creatingProgress.asStateFlow()
|
||||
private val _creatingStatus = MutableStateFlow<Status>(Status.Nothing)
|
||||
internal val creatingStatus = _creatingStatus.asStateFlow()
|
||||
|
||||
fun restoreFile(file: File?) {
|
||||
scope.launch {
|
||||
if (file == null || !file.exists()) {
|
||||
info { "Invalid file ${file?.absolutePath}" }
|
||||
} else {
|
||||
_restoreError.value = false
|
||||
_restoreStatus.value = Status.Nothing
|
||||
_restoringProgress.value = null
|
||||
_restoring.value = true
|
||||
try {
|
||||
backupHandler.importBackupFile(file)
|
||||
backupHandler.importBackupFile(file) {
|
||||
onUpload { bytesSentTotal, contentLength ->
|
||||
_restoringProgress.value = max(bytesSentTotal.toFloat() / contentLength, 1.0F)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
info(e) { "Error importing backup" }
|
||||
_restoreError.value = true
|
||||
_restoreStatus.value = Status.Error
|
||||
} finally {
|
||||
_restoring.value = false
|
||||
_restoreStatus.value = Status.Success
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,20 +91,36 @@ class SettingsBackupViewModel @Inject constructor(
|
||||
info { "Invalid file ${file?.absolutePath}" }
|
||||
} else {
|
||||
if (file.exists()) file.delete()
|
||||
_creatingError.value = false
|
||||
_creatingStatus.value = Status.Nothing
|
||||
_creatingProgress.value = null
|
||||
_creating.value = true
|
||||
try {
|
||||
val backup = backupHandler.exportBackupFile()
|
||||
val backup = backupHandler.exportBackupFile {
|
||||
onDownload { bytesSentTotal, contentLength ->
|
||||
_creatingProgress.value = max(bytesSentTotal.toFloat() / contentLength, 0.99F)
|
||||
}
|
||||
}
|
||||
file.outputStream().use {
|
||||
backup.content.copyTo(it)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
info(e) { "Error exporting backup" }
|
||||
_creatingError.value = true
|
||||
_creatingStatus.value = Status.Error
|
||||
} finally {
|
||||
_creatingProgress.value = 1.0F
|
||||
_creating.value = false
|
||||
_creatingStatus.value = Status.Success
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class Status {
|
||||
object Nothing : Status()
|
||||
object Success : Status()
|
||||
object Error : Status()
|
||||
}
|
||||
|
||||
private companion object : CKLogger({})
|
||||
}
|
||||
|
||||
@@ -97,9 +128,11 @@ class SettingsBackupViewModel @Inject constructor(
|
||||
fun SettingsBackupScreen(navController: BackStack<Route>) {
|
||||
val vm = viewModel<SettingsBackupViewModel>()
|
||||
val restoring by vm.restoring.collectAsState()
|
||||
val restoreError by vm.restoreError.collectAsState()
|
||||
val restoringProgress by vm.restoringProgress.collectAsState()
|
||||
val restoreStatus by vm.restoreStatus.collectAsState()
|
||||
val creating by vm.creating.collectAsState()
|
||||
val creatingError by vm.creatingError.collectAsState()
|
||||
val creatingProgress by vm.creatingProgress.collectAsState()
|
||||
val creatingStatus by vm.creatingStatus.collectAsState()
|
||||
Column {
|
||||
Toolbar("Backup Settings", navController, true)
|
||||
LazyColumn {
|
||||
@@ -108,7 +141,8 @@ fun SettingsBackupScreen(navController: BackStack<Route>) {
|
||||
"Restore Backup",
|
||||
"Restore a backup into Tachidesk",
|
||||
restoring,
|
||||
restoreError
|
||||
restoringProgress,
|
||||
restoreStatus
|
||||
) {
|
||||
filePicker("json") {
|
||||
vm.restoreFile(it.selectedFile)
|
||||
@@ -118,9 +152,10 @@ fun SettingsBackupScreen(navController: BackStack<Route>) {
|
||||
"Create Backup",
|
||||
"Create a backup from Tachidesk",
|
||||
creating,
|
||||
creatingError
|
||||
creatingProgress,
|
||||
creatingStatus
|
||||
) {
|
||||
fileSaver("test.json", "json") {
|
||||
fileSaver("backup.json", "json") {
|
||||
vm.createFile(it.selectedFile)
|
||||
}
|
||||
}
|
||||
@@ -130,7 +165,7 @@ fun SettingsBackupScreen(navController: BackStack<Route>) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PreferenceFile(title: String, subtitle: String, working: Boolean, error: Boolean, onClick: () -> Unit) {
|
||||
private fun PreferenceFile(title: String, subtitle: String, working: Boolean, progress: Float?, status: SettingsBackupViewModel.Status, onClick: () -> Unit) {
|
||||
PreferenceRow(
|
||||
title = title,
|
||||
onClick = onClick,
|
||||
@@ -144,16 +179,31 @@ fun PreferenceFile(title: String, subtitle: String, working: Boolean, error: Boo
|
||||
val modifier = Modifier.align(Alignment.Center)
|
||||
.size(size)
|
||||
if (working) {
|
||||
if (progress != null) {
|
||||
CircularProgressIndicator(
|
||||
progress,
|
||||
modifier
|
||||
)
|
||||
} else {
|
||||
CircularProgressIndicator(
|
||||
modifier
|
||||
)
|
||||
} else if (error) {
|
||||
Icon(
|
||||
}
|
||||
} else if (status != SettingsBackupViewModel.Status.Nothing) {
|
||||
when (status) {
|
||||
SettingsBackupViewModel.Status.Error -> Icon(
|
||||
Icons.Default.Warning,
|
||||
contentDescription = null,
|
||||
modifier = modifier,
|
||||
tint = Color.Red
|
||||
)
|
||||
SettingsBackupViewModel.Status.Success -> Icon(
|
||||
Icons.Default.Check,
|
||||
contentDescription = null,
|
||||
modifier = modifier
|
||||
)
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ package ca.gosyer.util.compose
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import ca.gosyer.data.server.Http
|
||||
import io.ktor.client.request.HttpRequestBuilder
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.statement.HttpResponse
|
||||
import io.ktor.client.statement.readBytes
|
||||
@@ -19,6 +20,6 @@ fun imageFromFile(file: File): ImageBitmap {
|
||||
return Image.makeFromEncoded(file.readBytes()).asImageBitmap()
|
||||
}
|
||||
|
||||
suspend fun imageFromUrl(client: Http, url: String): ImageBitmap {
|
||||
return Image.makeFromEncoded(client.get<HttpResponse>(url).readBytes()).asImageBitmap()
|
||||
suspend fun imageFromUrl(client: Http, url: String, block: HttpRequestBuilder.() -> Unit): ImageBitmap {
|
||||
return Image.makeFromEncoded(client.get<HttpResponse>(url, block).readBytes()).asImageBitmap()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user