mirror of
https://github.com/Suwayomi/TachideskJUI.git
synced 2025-12-22 12:32:31 +01:00
Support toasts on desktop, start working on toasting errors
This commit is contained in:
@@ -11,12 +11,16 @@ import ca.gosyer.jui.data.DataComponent
|
|||||||
import ca.gosyer.jui.domain.DomainComponent
|
import ca.gosyer.jui.domain.DomainComponent
|
||||||
import ca.gosyer.jui.ui.ViewModelComponent
|
import ca.gosyer.jui.ui.ViewModelComponent
|
||||||
import ca.gosyer.jui.ui.base.UiComponent
|
import ca.gosyer.jui.ui.base.UiComponent
|
||||||
|
import ca.gosyer.jui.uicore.vm.ContextWrapper
|
||||||
import me.tatarka.inject.annotations.Component
|
import me.tatarka.inject.annotations.Component
|
||||||
import me.tatarka.inject.annotations.Provides
|
import me.tatarka.inject.annotations.Provides
|
||||||
|
|
||||||
@AppScope
|
@AppScope
|
||||||
@Component
|
@Component
|
||||||
abstract class AppComponent : ViewModelComponent, DataComponent, DomainComponent, UiComponent {
|
abstract class AppComponent(
|
||||||
|
@get:Provides
|
||||||
|
val context: ContextWrapper
|
||||||
|
) : ViewModelComponent, DataComponent, DomainComponent, UiComponent {
|
||||||
|
|
||||||
abstract val appMigrations: AppMigrations
|
abstract val appMigrations: AppMigrations
|
||||||
|
|
||||||
@@ -31,7 +35,7 @@ abstract class AppComponent : ViewModelComponent, DataComponent, DomainComponent
|
|||||||
companion object {
|
companion object {
|
||||||
private var appComponentInstance: AppComponent? = null
|
private var appComponentInstance: AppComponent? = null
|
||||||
|
|
||||||
fun getInstance() = appComponentInstance ?: create()
|
fun getInstance(context: ContextWrapper) = appComponentInstance ?: create(context)
|
||||||
.also { appComponentInstance = it }
|
.also { appComponentInstance = it }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,21 +8,35 @@ package ca.gosyer.jui.desktop
|
|||||||
|
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.sizeIn
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.Card
|
||||||
import androidx.compose.material.Surface
|
import androidx.compose.material.Surface
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.configureSwingGlobalsForCompose
|
import androidx.compose.ui.configureSwingGlobalsForCompose
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.toPainter
|
import androidx.compose.ui.graphics.toPainter
|
||||||
import androidx.compose.ui.input.key.Key
|
import androidx.compose.ui.input.key.Key
|
||||||
import androidx.compose.ui.input.key.KeyEventType
|
import androidx.compose.ui.input.key.KeyEventType
|
||||||
import androidx.compose.ui.input.key.key
|
import androidx.compose.ui.input.key.key
|
||||||
import androidx.compose.ui.input.key.type
|
import androidx.compose.ui.input.key.type
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.DpSize
|
import androidx.compose.ui.unit.DpSize
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.compose.ui.window.Window
|
import androidx.compose.ui.window.Window
|
||||||
import androidx.compose.ui.window.awaitApplication
|
import androidx.compose.ui.window.awaitApplication
|
||||||
import androidx.compose.ui.window.rememberWindowState
|
import androidx.compose.ui.window.rememberWindowState
|
||||||
@@ -44,6 +58,8 @@ import ca.gosyer.jui.ui.util.compose.WindowGet
|
|||||||
import ca.gosyer.jui.uicore.components.LoadingScreen
|
import ca.gosyer.jui.uicore.components.LoadingScreen
|
||||||
import ca.gosyer.jui.uicore.prefs.asStateIn
|
import ca.gosyer.jui.uicore.prefs.asStateIn
|
||||||
import ca.gosyer.jui.uicore.resources.stringResource
|
import ca.gosyer.jui.uicore.resources.stringResource
|
||||||
|
import ca.gosyer.jui.uicore.vm.ContextWrapper
|
||||||
|
import ca.gosyer.jui.uicore.vm.Length
|
||||||
import com.github.weisj.darklaf.LafManager
|
import com.github.weisj.darklaf.LafManager
|
||||||
import com.github.weisj.darklaf.theme.DarculaTheme
|
import com.github.weisj.darklaf.theme.DarculaTheme
|
||||||
import com.github.weisj.darklaf.theme.IntelliJTheme
|
import com.github.weisj.darklaf.theme.IntelliJTheme
|
||||||
@@ -53,6 +69,7 @@ import com.vanpra.composematerialdialogs.rememberMaterialDialogState
|
|||||||
import com.vanpra.composematerialdialogs.title
|
import com.vanpra.composematerialdialogs.title
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
@@ -61,6 +78,8 @@ import org.jetbrains.skiko.SystemTheme
|
|||||||
import org.jetbrains.skiko.currentSystemTheme
|
import org.jetbrains.skiko.currentSystemTheme
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
import kotlin.time.Duration.Companion.ZERO
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
suspend fun main() {
|
suspend fun main() {
|
||||||
@@ -70,7 +89,9 @@ suspend fun main() {
|
|||||||
System.setProperty("kotlinx.coroutines.debug", "on")
|
System.setProperty("kotlinx.coroutines.debug", "on")
|
||||||
}
|
}
|
||||||
|
|
||||||
val appComponent = AppComponent.getInstance()
|
val context = ContextWrapper()
|
||||||
|
|
||||||
|
val appComponent = AppComponent.getInstance(context)
|
||||||
appComponent.migrations.runMigrations()
|
appComponent.migrations.runMigrations()
|
||||||
appComponent.appMigrations.runMigrations()
|
appComponent.appMigrations.runMigrations()
|
||||||
|
|
||||||
@@ -191,6 +212,12 @@ suspend fun main() {
|
|||||||
if (displayDebugInfo) {
|
if (displayDebugInfo) {
|
||||||
DebugOverlay()
|
DebugOverlay()
|
||||||
}
|
}
|
||||||
|
ToastOverlay(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
.padding(bottom = 64.dp),
|
||||||
|
context = context
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ServerResult.STARTING, ServerResult.FAILED -> {
|
ServerResult.STARTING, ServerResult.FAILED -> {
|
||||||
@@ -224,3 +251,50 @@ suspend fun main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ToastOverlay(modifier: Modifier, context: ContextWrapper) {
|
||||||
|
var toast by remember { mutableStateOf<Pair<String, Length>?>(null) }
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
context.toasts
|
||||||
|
.onEach {
|
||||||
|
toast = it
|
||||||
|
}
|
||||||
|
.launchIn(this)
|
||||||
|
}
|
||||||
|
LaunchedEffect(toast) {
|
||||||
|
if (toast != null) {
|
||||||
|
delay(
|
||||||
|
when (toast?.second) {
|
||||||
|
Length.SHORT -> 2.seconds
|
||||||
|
Length.LONG -> 5.seconds
|
||||||
|
else -> ZERO
|
||||||
|
}
|
||||||
|
)
|
||||||
|
toast = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Suppress("NAME_SHADOWING")
|
||||||
|
Crossfade(
|
||||||
|
toast?.first,
|
||||||
|
modifier = modifier
|
||||||
|
) { toast ->
|
||||||
|
if (toast != null) {
|
||||||
|
Card(
|
||||||
|
Modifier.sizeIn(maxWidth = 200.dp),
|
||||||
|
shape = CircleShape,
|
||||||
|
backgroundColor = Color.DarkGray,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
toast,
|
||||||
|
Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
color = Color.White,
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,12 +15,18 @@ import org.lighthousegames.logging.logging
|
|||||||
|
|
||||||
class GetMangaCategories @Inject constructor(private val categoryRepository: CategoryRepository) {
|
class GetMangaCategories @Inject constructor(private val categoryRepository: CategoryRepository) {
|
||||||
|
|
||||||
suspend fun await(mangaId: Long) = asFlow(mangaId)
|
suspend fun await(mangaId: Long, onError: suspend (Throwable) -> Unit = {}) = asFlow(mangaId)
|
||||||
.catch { log.warn(it) { "Failed to get categories for $mangaId" } }
|
.catch {
|
||||||
|
onError(it)
|
||||||
|
log.warn(it) { "Failed to get categories for $mangaId" }
|
||||||
|
}
|
||||||
.singleOrNull()
|
.singleOrNull()
|
||||||
|
|
||||||
suspend fun await(manga: Manga) = asFlow(manga)
|
suspend fun await(manga: Manga, onError: suspend (Throwable) -> Unit = {}) = asFlow(manga)
|
||||||
.catch { log.warn(it) { "Failed to get categories for ${manga.title}(${manga.id})" } }
|
.catch {
|
||||||
|
onError(it)
|
||||||
|
log.warn(it) { "Failed to get categories for ${manga.title}(${manga.id})" }
|
||||||
|
}
|
||||||
.singleOrNull()
|
.singleOrNull()
|
||||||
|
|
||||||
fun asFlow(mangaId: Long) = categoryRepository.getMangaCategories(mangaId)
|
fun asFlow(mangaId: Long) = categoryRepository.getMangaCategories(mangaId)
|
||||||
|
|||||||
@@ -15,12 +15,18 @@ import org.lighthousegames.logging.logging
|
|||||||
|
|
||||||
class GetManga @Inject constructor(private val mangaRepository: MangaRepository) {
|
class GetManga @Inject constructor(private val mangaRepository: MangaRepository) {
|
||||||
|
|
||||||
suspend fun await(mangaId: Long) = asFlow(mangaId)
|
suspend fun await(mangaId: Long, onError: suspend (Throwable) -> Unit = {}) = asFlow(mangaId)
|
||||||
.catch { log.warn(it) { "Failed to get manga $mangaId" } }
|
.catch {
|
||||||
|
onError(it)
|
||||||
|
log.warn(it) { "Failed to get manga $mangaId" }
|
||||||
|
}
|
||||||
.singleOrNull()
|
.singleOrNull()
|
||||||
|
|
||||||
suspend fun await(manga: Manga) = asFlow(manga)
|
suspend fun await(manga: Manga, onError: suspend (Throwable) -> Unit = {}) = asFlow(manga)
|
||||||
.catch { log.warn(it) { "Failed to get manga ${manga.title}(${manga.id})" } }
|
.catch {
|
||||||
|
onError(it)
|
||||||
|
log.warn(it) { "Failed to get manga ${manga.title}(${manga.id})" }
|
||||||
|
}
|
||||||
.singleOrNull()
|
.singleOrNull()
|
||||||
|
|
||||||
fun asFlow(mangaId: Long) = mangaRepository.getManga(mangaId)
|
fun asFlow(mangaId: Long) = mangaRepository.getManga(mangaId)
|
||||||
|
|||||||
@@ -15,12 +15,18 @@ import org.lighthousegames.logging.logging
|
|||||||
|
|
||||||
class RefreshManga @Inject constructor(private val mangaRepository: MangaRepository) {
|
class RefreshManga @Inject constructor(private val mangaRepository: MangaRepository) {
|
||||||
|
|
||||||
suspend fun await(mangaId: Long) = asFlow(mangaId)
|
suspend fun await(mangaId: Long, onError: suspend (Throwable) -> Unit = {}) = asFlow(mangaId)
|
||||||
.catch { log.warn(it) { "Failed to refresh manga $mangaId" } }
|
.catch {
|
||||||
|
onError(it)
|
||||||
|
log.warn(it) { "Failed to refresh manga $mangaId" }
|
||||||
|
}
|
||||||
.singleOrNull()
|
.singleOrNull()
|
||||||
|
|
||||||
suspend fun await(manga: Manga) = asFlow(manga)
|
suspend fun await(manga: Manga, onError: suspend (Throwable) -> Unit = {}) = asFlow(manga)
|
||||||
.catch { log.warn(it) { "Failed to refresh manga ${manga.title}(${manga.id})" } }
|
.catch {
|
||||||
|
onError(it)
|
||||||
|
log.warn(it) { "Failed to refresh manga ${manga.title}(${manga.id})" }
|
||||||
|
}
|
||||||
.singleOrNull()
|
.singleOrNull()
|
||||||
|
|
||||||
fun asFlow(mangaId: Long) = mangaRepository.getManga(mangaId, true)
|
fun asFlow(mangaId: Long) = mangaRepository.getManga(mangaId, true)
|
||||||
|
|||||||
@@ -15,12 +15,18 @@ import org.lighthousegames.logging.logging
|
|||||||
|
|
||||||
class GetLatestManga @Inject constructor(private val sourceRepository: SourceRepository) {
|
class GetLatestManga @Inject constructor(private val sourceRepository: SourceRepository) {
|
||||||
|
|
||||||
suspend fun await(source: Source, page: Int) = asFlow(source.id, page)
|
suspend fun await(source: Source, page: Int, onError: suspend (Throwable) -> Unit = {}) = asFlow(source.id, page)
|
||||||
.catch { log.warn(it) { "Failed to get latest manga from ${source.displayName} on page $page" } }
|
.catch {
|
||||||
|
onError(it)
|
||||||
|
log.warn(it) { "Failed to get latest manga from ${source.displayName} on page $page" }
|
||||||
|
}
|
||||||
.singleOrNull()
|
.singleOrNull()
|
||||||
|
|
||||||
suspend fun await(sourceId: Long, page: Int) = asFlow(sourceId, page)
|
suspend fun await(sourceId: Long, page: Int, onError: suspend (Throwable) -> Unit = {}) = asFlow(sourceId, page)
|
||||||
.catch { log.warn(it) { "Failed to get latest manga from $sourceId on page $page" } }
|
.catch {
|
||||||
|
onError(it)
|
||||||
|
log.warn(it) { "Failed to get latest manga from $sourceId on page $page" }
|
||||||
|
}
|
||||||
.singleOrNull()
|
.singleOrNull()
|
||||||
|
|
||||||
fun asFlow(source: Source, page: Int) = sourceRepository.getLatestManga(source.id, page)
|
fun asFlow(source: Source, page: Int) = sourceRepository.getLatestManga(source.id, page)
|
||||||
|
|||||||
@@ -15,12 +15,18 @@ import org.lighthousegames.logging.logging
|
|||||||
|
|
||||||
class GetPopularManga @Inject constructor(private val sourceRepository: SourceRepository) {
|
class GetPopularManga @Inject constructor(private val sourceRepository: SourceRepository) {
|
||||||
|
|
||||||
suspend fun await(source: Source, page: Int) = asFlow(source.id, page)
|
suspend fun await(source: Source, page: Int, onError: suspend (Throwable) -> Unit = {}) = asFlow(source.id, page)
|
||||||
.catch { log.warn(it) { "Failed to get popular manga from ${source.displayName} on page $page" } }
|
.catch {
|
||||||
|
onError(it)
|
||||||
|
log.warn(it) { "Failed to get popular manga from ${source.displayName} on page $page" }
|
||||||
|
}
|
||||||
.singleOrNull()
|
.singleOrNull()
|
||||||
|
|
||||||
suspend fun await(sourceId: Long, page: Int) = asFlow(sourceId, page)
|
suspend fun await(sourceId: Long, page: Int, onError: suspend (Throwable) -> Unit = {}) = asFlow(sourceId, page)
|
||||||
.catch { log.warn(it) { "Failed to get popular manga from $sourceId on page $page" } }
|
.catch {
|
||||||
|
onError(it)
|
||||||
|
log.warn(it) { "Failed to get popular manga from $sourceId on page $page" }
|
||||||
|
}
|
||||||
.singleOrNull()
|
.singleOrNull()
|
||||||
|
|
||||||
fun asFlow(source: Source, page: Int) = sourceRepository.getPopularManga(source.id, page)
|
fun asFlow(source: Source, page: Int) = sourceRepository.getPopularManga(source.id, page)
|
||||||
|
|||||||
@@ -15,12 +15,18 @@ import org.lighthousegames.logging.logging
|
|||||||
|
|
||||||
class GetSearchManga @Inject constructor(private val sourceRepository: SourceRepository) {
|
class GetSearchManga @Inject constructor(private val sourceRepository: SourceRepository) {
|
||||||
|
|
||||||
suspend fun await(source: Source, searchTerm: String?, page: Int) = asFlow(source.id, searchTerm, page)
|
suspend fun await(source: Source, searchTerm: String?, page: Int, onError: suspend (Throwable) -> Unit = {}) = asFlow(source.id, searchTerm, page)
|
||||||
.catch { log.warn(it) { "Failed to get search results from ${source.displayName} on page $page with query '$searchTerm'" } }
|
.catch {
|
||||||
|
onError(it)
|
||||||
|
log.warn(it) { "Failed to get search results from ${source.displayName} on page $page with query '$searchTerm'" }
|
||||||
|
}
|
||||||
.singleOrNull()
|
.singleOrNull()
|
||||||
|
|
||||||
suspend fun await(sourceId: Long, searchTerm: String?, page: Int) = asFlow(sourceId, searchTerm, page)
|
suspend fun await(sourceId: Long, searchTerm: String?, page: Int, onError: suspend (Throwable) -> Unit = {}) = asFlow(sourceId, searchTerm, page)
|
||||||
.catch { log.warn(it) { "Failed to get search results from $sourceId on page $page with query '$searchTerm'" } }
|
.catch {
|
||||||
|
onError(it)
|
||||||
|
log.warn(it) { "Failed to get search results from $sourceId on page $page with query '$searchTerm'" }
|
||||||
|
}
|
||||||
.singleOrNull()
|
.singleOrNull()
|
||||||
|
|
||||||
fun asFlow(source: Source, searchTerm: String?, page: Int) = sourceRepository.getSearchResults(
|
fun asFlow(source: Source, searchTerm: String?, page: Int) = sourceRepository.getSearchResults(
|
||||||
|
|||||||
@@ -151,21 +151,17 @@ class MangaScreenViewModel @Inject constructor(
|
|||||||
private suspend fun refreshMangaAsync(mangaId: Long, refresh: Boolean = false) = withIOContext {
|
private suspend fun refreshMangaAsync(mangaId: Long, refresh: Boolean = false) = withIOContext {
|
||||||
async {
|
async {
|
||||||
val manga = if (refresh) {
|
val manga = if (refresh) {
|
||||||
refreshManga.await(mangaId)
|
refreshManga.await(mangaId, onError = { toast(it.message.orEmpty()) })
|
||||||
} else {
|
} else {
|
||||||
getManga.await(mangaId)
|
getManga.await(mangaId, onError = { toast(it.message.orEmpty()) })
|
||||||
}
|
}
|
||||||
if (manga != null) {
|
if (manga != null) {
|
||||||
_manga.value = manga
|
_manga.value = manga
|
||||||
} else {
|
|
||||||
// TODO: 2022-07-01 Error toast
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val mangaCategories = getMangaCategories.await(mangaId)
|
val mangaCategories = getMangaCategories.await(mangaId, onError = { toast(it.message.orEmpty()) })
|
||||||
if (mangaCategories != null) {
|
if (mangaCategories != null) {
|
||||||
_mangaCategories.value = mangaCategories.toImmutableList()
|
_mangaCategories.value = mangaCategories.toImmutableList()
|
||||||
} else {
|
|
||||||
// TODO: 2022-07-01 Error toast
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,13 +134,14 @@ class SourceScreenViewModel(
|
|||||||
|
|
||||||
private suspend fun getPage(): MangaPage? {
|
private suspend fun getPage(): MangaPage? {
|
||||||
return when {
|
return when {
|
||||||
isLatest.value -> getLatestManga.await(source, pageNum.value)
|
isLatest.value -> getLatestManga.await(source, pageNum.value, onError = { toast(it.message.orEmpty()) })
|
||||||
_query.value != null || _usingFilters.value -> getSearchManga.await(
|
_query.value != null || _usingFilters.value -> getSearchManga.await(
|
||||||
source.id,
|
sourceId = source.id,
|
||||||
_query.value,
|
searchTerm = _query.value,
|
||||||
pageNum.value
|
page = pageNum.value,
|
||||||
|
onError = { toast(it.message.orEmpty()) }
|
||||||
)
|
)
|
||||||
else -> getPopularManga.await(source.id, pageNum.value)
|
else -> getPopularManga.await(source.id, pageNum.value, onError = { toast(it.message.orEmpty()) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ abstract class ViewModel(private val contextWrapper: ContextWrapper) : ScreenMod
|
|||||||
fun StringResource.toPlatformString(vararg args: Any): String {
|
fun StringResource.toPlatformString(vararg args: Any): String {
|
||||||
return contextWrapper.toPlatformString(this, *args)
|
return contextWrapper.toPlatformString(this, *args)
|
||||||
}
|
}
|
||||||
fun toast(string: String, length: Length) {
|
fun toast(string: String, length: Length = Length.SHORT) {
|
||||||
scope.launchUI {
|
scope.launchUI {
|
||||||
contextWrapper.toast(string, length)
|
contextWrapper.toast(string, length)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,19 @@
|
|||||||
|
|
||||||
package ca.gosyer.jui.uicore.vm
|
package ca.gosyer.jui.uicore.vm
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import ca.gosyer.jui.core.lang.launchDefault
|
||||||
import dev.icerock.moko.resources.StringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
import dev.icerock.moko.resources.format
|
import dev.icerock.moko.resources.format
|
||||||
import me.tatarka.inject.annotations.Inject
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
actual class ContextWrapper {
|
||||||
|
private val _toasts = MutableSharedFlow<Pair<String, Length>>()
|
||||||
|
val toasts = _toasts.asSharedFlow()
|
||||||
|
|
||||||
actual class ContextWrapper @Inject constructor() {
|
|
||||||
actual fun toPlatformString(stringResource: StringResource): String {
|
actual fun toPlatformString(stringResource: StringResource): String {
|
||||||
return stringResource.localized()
|
return stringResource.localized()
|
||||||
}
|
}
|
||||||
@@ -18,5 +26,8 @@ actual class ContextWrapper @Inject constructor() {
|
|||||||
return stringResource.format(*args).localized()
|
return stringResource.format(*args).localized()
|
||||||
}
|
}
|
||||||
actual fun toast(string: String, length: Length) {
|
actual fun toast(string: String, length: Length) {
|
||||||
|
GlobalScope.launchDefault {
|
||||||
|
_toasts.emit(string to length)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user