mirror of
https://github.com/Suwayomi/TachideskJUI.git
synced 2025-12-10 06:42:05 +01:00
Start adding iOS support to presentation
This commit is contained in:
@@ -7,7 +7,9 @@
|
||||
package ca.gosyer.jui.core.io
|
||||
|
||||
import ca.gosyer.jui.core.lang.withIOContext
|
||||
import okio.Buffer
|
||||
import okio.BufferedSink
|
||||
import okio.BufferedSource
|
||||
import okio.FileSystem
|
||||
import okio.Path
|
||||
import okio.Source
|
||||
@@ -32,3 +34,7 @@ suspend fun Source.copyTo(sink: BufferedSink) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ByteArray.source(): BufferedSource {
|
||||
return Buffer().write(this)
|
||||
}
|
||||
@@ -25,6 +25,9 @@ kotlin {
|
||||
}
|
||||
}
|
||||
}
|
||||
iosX64()
|
||||
iosArm64()
|
||||
iosSimulatorArm64()
|
||||
|
||||
sourceSets {
|
||||
all {
|
||||
@@ -53,13 +56,16 @@ kotlin {
|
||||
api(libs.dateTime)
|
||||
api(libs.immutableCollections)
|
||||
api(libs.aboutLibraries.core)
|
||||
api(libs.aboutLibraries.ui)
|
||||
|
||||
api(projects.core)
|
||||
api(projects.i18n)
|
||||
api(projects.domain)
|
||||
api(projects.data)
|
||||
api(projects.uiCore)
|
||||
api(compose.desktop.currentOs)
|
||||
api(compose.runtime)
|
||||
api(compose.foundation)
|
||||
api(compose.ui)
|
||||
api(compose.material)
|
||||
api(compose("org.jetbrains.compose.ui:ui-util"))
|
||||
api(compose.materialIconsExtended)
|
||||
}
|
||||
@@ -75,6 +81,8 @@ kotlin {
|
||||
dependsOn(commonMain)
|
||||
dependencies {
|
||||
api(kotlin("stdlib-jdk8"))
|
||||
api(libs.aboutLibraries.ui)
|
||||
api(compose.desktop.currentOs)
|
||||
}
|
||||
}
|
||||
val jvmTest by creating {
|
||||
@@ -107,6 +115,22 @@ kotlin {
|
||||
val androidTest by getting {
|
||||
dependsOn(jvmTest)
|
||||
}
|
||||
|
||||
val iosMain by creating {
|
||||
dependsOn(commonMain)
|
||||
}
|
||||
val iosTest by creating {
|
||||
dependsOn(commonTest)
|
||||
}
|
||||
|
||||
listOf(
|
||||
"iosX64",
|
||||
"iosArm64",
|
||||
"iosSimulatorArm64",
|
||||
).forEach {
|
||||
getByName(it + "Main").dependsOn(iosMain)
|
||||
getByName(it + "Test").dependsOn(iosTest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import com.mikepenz.aboutlibraries.Libs
|
||||
import com.mikepenz.aboutlibraries.entity.Library
|
||||
import com.mikepenz.aboutlibraries.ui.compose.Libraries
|
||||
import com.mikepenz.aboutlibraries.ui.compose.LibraryColors
|
||||
import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults
|
||||
import com.mikepenz.aboutlibraries.util.withContext
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@@ -35,6 +36,10 @@ actual fun getLicenses(): Libs? {
|
||||
return libs
|
||||
}
|
||||
|
||||
actual typealias LibraryDefaults = LibraryDefaults
|
||||
|
||||
actual typealias LibraryColors = LibraryColors
|
||||
|
||||
@Composable
|
||||
actual fun InternalAboutLibraries(
|
||||
libraries: ImmutableList<Library>,
|
||||
|
||||
@@ -12,8 +12,6 @@ import androidx.compose.ui.Modifier
|
||||
actual fun Modifier.sourceSideMenuItem(
|
||||
onSourceTabClick: () -> Unit,
|
||||
onSourceCloseTabClick: () -> Unit
|
||||
): Modifier = Modifier.clickable(
|
||||
onClick = {
|
||||
onSourceTabClick()
|
||||
}
|
||||
): Modifier = clickable(
|
||||
onClick = onSourceTabClick
|
||||
)
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
package ca.gosyer.jui.ui.base.state
|
||||
|
||||
import ca.gosyer.jui.uicore.vm.ViewModel
|
||||
import kotlinx.coroutines.InternalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.internal.SynchronizedObject
|
||||
import kotlinx.coroutines.internal.synchronized
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
@@ -17,14 +20,17 @@ fun <T> SavedStateHandle.getStateFlow(
|
||||
return SavedStateHandleDelegate(this, initialValue)
|
||||
}
|
||||
|
||||
@OptIn(InternalCoroutinesApi::class)
|
||||
class SavedStateHandleDelegate<T>(
|
||||
private val savedStateHandle: SavedStateHandle,
|
||||
private val initialValue: () -> T
|
||||
) : ReadOnlyProperty<ViewModel, SavedStateHandleStateFlow<T>> {
|
||||
private val synchronizedObject = SynchronizedObject()
|
||||
|
||||
private var item: SavedStateHandleStateFlow<T>? = null
|
||||
|
||||
override fun getValue(thisRef: ViewModel, property: KProperty<*>): SavedStateHandleStateFlow<T> {
|
||||
return item ?: synchronized(this) {
|
||||
return item ?: synchronized(synchronizedObject) {
|
||||
if (item == null) {
|
||||
savedStateHandle.getSavedStateFlow(property.name, initialValue)
|
||||
.also { item = it }
|
||||
|
||||
@@ -13,6 +13,7 @@ import ca.gosyer.jui.ui.viewModel
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||
import kotlin.jvm.Transient
|
||||
|
||||
expect class CategoriesLauncher {
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import ca.gosyer.jui.domain.updates.interactor.UpdateLibrary
|
||||
import ca.gosyer.jui.i18n.MR
|
||||
import ca.gosyer.jui.ui.base.state.SavedStateHandle
|
||||
import ca.gosyer.jui.ui.base.state.getStateFlow
|
||||
import ca.gosyer.jui.ui.util.lang.Collator
|
||||
import ca.gosyer.jui.ui.util.lang.CollatorComparator
|
||||
import ca.gosyer.jui.uicore.vm.ContextWrapper
|
||||
import ca.gosyer.jui.uicore.vm.ViewModel
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
@@ -191,7 +191,7 @@ class LibraryScreenViewModel @Inject constructor(
|
||||
val sortFn: (Manga, Manga) -> Int = when (sortMode) {
|
||||
Sort.ALPHABETICAL -> {
|
||||
val locale = Locale.current
|
||||
val collator = Collator(locale);
|
||||
val collator = CollatorComparator(locale);
|
||||
|
||||
{ a, b ->
|
||||
collator.compare(a.title.toLowerCase(locale), b.title.toLowerCase(locale))
|
||||
|
||||
@@ -19,11 +19,14 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.contentColorFor
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.unit.dp
|
||||
import ca.gosyer.jui.i18n.MR
|
||||
@@ -38,8 +41,6 @@ import ca.gosyer.jui.uicore.insets.statusBars
|
||||
import ca.gosyer.jui.uicore.resources.stringResource
|
||||
import com.mikepenz.aboutlibraries.Libs
|
||||
import com.mikepenz.aboutlibraries.entity.Library
|
||||
import com.mikepenz.aboutlibraries.ui.compose.LibraryColors
|
||||
import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@@ -60,6 +61,18 @@ internal expect fun InternalAboutLibraries(
|
||||
onLibraryClick: ((Library) -> Unit)?
|
||||
)
|
||||
|
||||
expect interface LibraryColors
|
||||
expect object LibraryDefaults {
|
||||
@Composable
|
||||
fun libraryColors(
|
||||
backgroundColor: Color = MaterialTheme.colors.background,
|
||||
contentColor: Color = contentColorFor(backgroundColor),
|
||||
badgeBackgroundColor: Color = MaterialTheme.colors.primary,
|
||||
badgeContentColor: Color = contentColorFor(badgeBackgroundColor),
|
||||
): LibraryColors
|
||||
val ContentPadding: PaddingValues
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AboutLibraries(
|
||||
libraries: ImmutableList<Library>,
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
package ca.gosyer.jui.ui.reader.loader
|
||||
|
||||
import ca.gosyer.jui.core.io.SYSTEM
|
||||
import ca.gosyer.jui.core.lang.IO
|
||||
import ca.gosyer.jui.core.lang.PriorityChannel
|
||||
import ca.gosyer.jui.core.lang.throwIfCancellation
|
||||
import ca.gosyer.jui.domain.chapter.interactor.GetChapterPage
|
||||
@@ -39,6 +41,7 @@ import kotlinx.coroutines.launch
|
||||
import okio.BufferedSource
|
||||
import okio.FileSystem
|
||||
import okio.buffer
|
||||
import okio.use
|
||||
import org.lighthousegames.logging.logging
|
||||
|
||||
class TachideskPageLoader(
|
||||
|
||||
@@ -36,8 +36,10 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import ca.gosyer.jui.core.io.SYSTEM
|
||||
import ca.gosyer.jui.core.io.copyTo
|
||||
import ca.gosyer.jui.core.io.saveTo
|
||||
import ca.gosyer.jui.core.lang.IO
|
||||
import ca.gosyer.jui.core.lang.throwIfCancellation
|
||||
import ca.gosyer.jui.domain.backup.interactor.ExportBackupFile
|
||||
import ca.gosyer.jui.domain.backup.interactor.ImportBackupFile
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
package ca.gosyer.jui.ui.sources.globalsearch
|
||||
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
||||
import ca.gosyer.jui.core.lang.IO
|
||||
import ca.gosyer.jui.domain.manga.model.Manga
|
||||
import ca.gosyer.jui.domain.source.interactor.GetSearchManga
|
||||
import ca.gosyer.jui.domain.source.interactor.GetSourceList
|
||||
|
||||
@@ -8,8 +8,6 @@ package ca.gosyer.jui.ui.util.lang
|
||||
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
|
||||
expect fun Collator(locale: Locale): Collator
|
||||
|
||||
expect class Collator {
|
||||
fun compare(source: String, target: String): Int
|
||||
expect class CollatorComparator(locale: Locale) : Comparator<String> {
|
||||
override fun compare(source: String, target: String): Int
|
||||
}
|
||||
|
||||
@@ -9,4 +9,4 @@ package ca.gosyer.jui.ui.util.lang
|
||||
import io.ktor.utils.io.ByteReadChannel
|
||||
import okio.Source
|
||||
|
||||
expect fun ByteReadChannel.toSource(): Source
|
||||
expect suspend fun ByteReadChannel.toSource(): Source
|
||||
|
||||
@@ -18,6 +18,7 @@ import com.mikepenz.aboutlibraries.Libs
|
||||
import com.mikepenz.aboutlibraries.entity.Library
|
||||
import com.mikepenz.aboutlibraries.ui.compose.Libraries
|
||||
import com.mikepenz.aboutlibraries.ui.compose.LibraryColors
|
||||
import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Composable
|
||||
@@ -33,6 +34,10 @@ actual fun getLicenses(): Libs? {
|
||||
return libs
|
||||
}
|
||||
|
||||
actual typealias LibraryDefaults = LibraryDefaults
|
||||
|
||||
actual typealias LibraryColors = LibraryColors
|
||||
|
||||
@Composable
|
||||
actual fun InternalAboutLibraries(
|
||||
libraries: ImmutableList<Library>,
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisallowComposableCalls
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import ca.gosyer.jui.ui.base.LocalViewModels
|
||||
import ca.gosyer.jui.ui.base.state.SavedStateHandle
|
||||
import ca.gosyer.jui.uicore.vm.ViewModel
|
||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
|
||||
actual interface ViewModelComponent : SharedViewModelComponent
|
||||
|
||||
@Composable
|
||||
actual inline fun <reified VM : ViewModel> Screen.stateViewModel(
|
||||
tag: String?,
|
||||
crossinline factory: @DisallowComposableCalls ViewModelComponent.(SavedStateHandle) -> VM
|
||||
): VM {
|
||||
val viewModelFactory = LocalViewModels.current
|
||||
val savedStateHandle = rememberSaveable { SavedStateHandle() }
|
||||
return rememberScreenModel(tag) { viewModelFactory.factory(savedStateHandle) }
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui.base.components
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
|
||||
actual interface TooltipPlacement
|
||||
|
||||
actual class CursorPoint actual constructor(
|
||||
offset: DpOffset,
|
||||
alignment: Alignment,
|
||||
windowMargin: Dp
|
||||
) : TooltipPlacement
|
||||
|
||||
actual class ComponentRect actual constructor(
|
||||
anchor: Alignment,
|
||||
alignment: Alignment,
|
||||
offset: DpOffset
|
||||
) : TooltipPlacement
|
||||
|
||||
@Composable
|
||||
actual fun TooltipArea(
|
||||
tooltip: @Composable () -> Unit,
|
||||
modifier: Modifier,
|
||||
delayMillis: Int,
|
||||
tooltipPlacement: TooltipPlacement,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
Box(Modifier) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui.base.file
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import okio.Source
|
||||
|
||||
actual class FileChooser(private val onFileFound: (Source) -> Unit) {
|
||||
actual fun launch(extension: String) {
|
||||
TODO()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
actual fun rememberFileChooser(onFileFound: (Source) -> Unit): FileChooser {
|
||||
return remember { FileChooser(onFileFound) }
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui.base.file
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import okio.Sink
|
||||
|
||||
actual class FileSaver(
|
||||
private val onFileSelected: (Sink) -> Unit,
|
||||
private val onCancel: () -> Unit,
|
||||
private val onError: () -> Unit,
|
||||
) {
|
||||
actual fun save(name: String) {
|
||||
TODO()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
actual fun rememberFileSaver(
|
||||
onFileSelected: (Sink) -> Unit,
|
||||
onCancel: () -> Unit,
|
||||
onError: () -> Unit
|
||||
): FileSaver {
|
||||
return remember { FileSaver(onFileSelected, onCancel, onError) }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui.base.image
|
||||
|
||||
import ca.gosyer.jui.uicore.vm.ContextWrapper
|
||||
import com.seiko.imageloader.component.decoder.Decoder
|
||||
import com.seiko.imageloader.component.decoder.SkiaImageDecoder
|
||||
|
||||
actual class BitmapDecoderFactory actual constructor(contextWrapper: ContextWrapper) :
|
||||
Decoder.Factory by SkiaImageDecoder.Factory()
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui.base.image
|
||||
|
||||
import ca.gosyer.jui.uicore.vm.ContextWrapper
|
||||
import com.seiko.imageloader.ImageLoaderBuilder
|
||||
import com.seiko.imageloader.cache.disk.DiskCache
|
||||
import com.seiko.imageloader.cache.disk.DiskCacheBuilder
|
||||
import com.seiko.imageloader.cache.memory.MemoryCache
|
||||
import com.seiko.imageloader.cache.memory.MemoryCacheBuilder
|
||||
import com.seiko.imageloader.request.Options
|
||||
import okio.Path.Companion.toPath
|
||||
import platform.Foundation.NSCachesDirectory
|
||||
import platform.Foundation.NSFileManager
|
||||
import platform.Foundation.NSUserDomainMask
|
||||
|
||||
actual val imageConfig: Options.ImageConfig = Options.ImageConfig.ARGB_8888
|
||||
|
||||
actual fun imageLoaderBuilder(contextWrapper: ContextWrapper): ImageLoaderBuilder {
|
||||
return ImageLoaderBuilder()
|
||||
}
|
||||
|
||||
actual fun diskCache(contextWrapper: ContextWrapper, cacheDir: String): DiskCache {
|
||||
return DiskCacheBuilder()
|
||||
.directory(getCacheDir().toPath() / cacheDir)
|
||||
.maxSizeBytes(1024 * 1024 * 150) // 150 MB
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun getCacheDir(): String {
|
||||
return NSFileManager.defaultManager.URLForDirectory(
|
||||
NSCachesDirectory, NSUserDomainMask, null, true, null
|
||||
)!!.path.orEmpty()
|
||||
}
|
||||
|
||||
actual fun memoryCache(contextWrapper: ContextWrapper): MemoryCache {
|
||||
return MemoryCacheBuilder()
|
||||
.maxSizePercent(0.25)
|
||||
.build()
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui.base.navigation
|
||||
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
|
||||
// todo
|
||||
@Composable
|
||||
actual fun ActionIcon(onClick: () -> Unit, contentDescription: String, icon: ImageVector) {
|
||||
IconButton(onClick = onClick) {
|
||||
Icon(icon, contentDescription)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui.base.navigation
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@Composable
|
||||
actual fun BackHandler(enabled: Boolean, onBack: () -> Unit) {}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui.base.prefs
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import kotlinx.cinterop.alloc
|
||||
import kotlinx.cinterop.memScoped
|
||||
import kotlinx.cinterop.ptr
|
||||
import kotlinx.cinterop.value
|
||||
import platform.CoreGraphics.CGFloatVar
|
||||
import platform.UIKit.UIColor
|
||||
|
||||
fun Color.toUIColor() = UIColor(red.toDouble(), green.toDouble(), blue.toDouble(), 1.0)
|
||||
|
||||
internal actual fun Color.toHsv(): FloatArray = memScoped {
|
||||
val uiColor = toUIColor()
|
||||
val hue = alloc<CGFloatVar>()
|
||||
val saturation = alloc<CGFloatVar>()
|
||||
val brightness = alloc<CGFloatVar>()
|
||||
val alpha = alloc<CGFloatVar>()
|
||||
uiColor.getHue(hue.ptr, saturation.ptr, brightness.ptr, alpha.ptr)
|
||||
|
||||
floatArrayOf(hue.value.toFloat(), saturation.value.toFloat(), brightness.value.toFloat())
|
||||
}
|
||||
|
||||
internal actual fun hexStringToColor(hex: String): Color? {
|
||||
return try {
|
||||
val i = hex.removePrefix("#").toInt(16)
|
||||
Color(i shr 16 and 0xFF, i shr 8 and 0xFF, i and 0xFF)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui.base.screen
|
||||
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||
|
||||
actual abstract class BaseScreen : Screen {
|
||||
|
||||
override val key: ScreenKey = uniqueScreenKey
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui.base.state
|
||||
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
actual class SavedStateHandle {
|
||||
private val regular = mutableMapOf<String, Any?>()
|
||||
private val flows = mutableMapOf<String, MutableStateFlow<Any?>>()
|
||||
|
||||
actual operator fun <T> get(key: String): T? {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return regular[key] as T?
|
||||
}
|
||||
|
||||
actual operator fun <T> set(key: String, value: T?) {
|
||||
regular[key] = value
|
||||
flows[key]?.value = value
|
||||
}
|
||||
|
||||
actual fun <T> remove(key: String): T? {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val latestValue = regular.remove(key) as T?
|
||||
flows.remove(key)
|
||||
return latestValue
|
||||
}
|
||||
|
||||
actual fun <T> getStateFlow(
|
||||
key: String,
|
||||
initialValue: T
|
||||
): StateFlow<T> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
// If a flow exists we should just return it, and since it is a StateFlow and a value must
|
||||
// always be set, we know a value must already be available
|
||||
return flows.getOrPut(key) {
|
||||
// If there is not a value associated with the key, add the initial value, otherwise,
|
||||
// use the one we already have.
|
||||
if (!regular.containsKey(key)) {
|
||||
regular[key] = initialValue
|
||||
}
|
||||
MutableStateFlow(regular[key]).apply { flows[key] = this }
|
||||
}.asStateFlow() as StateFlow<T>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui.base.theme
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import ca.gosyer.jui.uicore.components.ScrollbarStyle
|
||||
|
||||
actual object ThemeScrollbarStyle {
|
||||
private val defaultScrollbarStyle = ScrollbarStyle()
|
||||
|
||||
@Stable
|
||||
@Composable
|
||||
actual fun getScrollbarStyle(): ScrollbarStyle {
|
||||
return defaultScrollbarStyle
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui.categories
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.Navigator
|
||||
|
||||
actual class CategoriesLauncher(private val navigator: Navigator?) {
|
||||
|
||||
actual fun open() {
|
||||
navigator?.push(CategoriesScreen())
|
||||
}
|
||||
|
||||
@Composable
|
||||
actual fun CategoriesWindow() {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
actual fun rememberCategoriesLauncher(notifyFinished: () -> Unit): CategoriesLauncher {
|
||||
val navigator = LocalNavigator.current
|
||||
return remember(navigator) { CategoriesLauncher(navigator) }
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui.downloads
|
||||
|
||||
import ca.gosyer.jui.domain.base.WebsocketService
|
||||
import ca.gosyer.jui.domain.download.service.DownloadService
|
||||
import ca.gosyer.jui.uicore.vm.ContextWrapper
|
||||
|
||||
internal actual fun startDownloadService(
|
||||
contextWrapper: ContextWrapper,
|
||||
downloadService: DownloadService,
|
||||
actions: WebsocketService.Actions
|
||||
) {
|
||||
downloadService.init()
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui.library.components
|
||||
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.material.DropdownMenu
|
||||
import androidx.compose.material.DropdownMenuItem
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import ca.gosyer.jui.i18n.MR
|
||||
import ca.gosyer.jui.uicore.resources.stringResource
|
||||
|
||||
actual fun Modifier.libraryMangaModifier(
|
||||
onClickManga: () -> Unit,
|
||||
onClickRemoveManga: () -> Unit
|
||||
): Modifier = composed {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
DropdownMenu(
|
||||
expanded,
|
||||
onDismissRequest = { expanded = false }
|
||||
) {
|
||||
listOf(
|
||||
stringResource(MR.strings.action_remove_favorite) to onClickRemoveManga
|
||||
).forEach { (label, onClick) ->
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
expanded = false
|
||||
onClick()
|
||||
}
|
||||
) {
|
||||
Text(text = label)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Modifier.combinedClickable(
|
||||
onClick = { onClickManga() },
|
||||
onLongClick = {
|
||||
expanded = true
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui.main.about.components
|
||||
|
||||
import ca.gosyer.jui.presentation.build.BuildKonfig
|
||||
import platform.UIKit.UIDevice
|
||||
|
||||
actual fun getDebugInfo(): String {
|
||||
val device = UIDevice.currentDevice
|
||||
return """
|
||||
App version: ${BuildKonfig.VERSION} (${ if (BuildKonfig.DEBUG) "Debug" else "Standard"}, ${BuildKonfig.MIGRATION_CODE})
|
||||
Preview build: r${BuildKonfig.PREVIEW_BUILD}
|
||||
Device name: ${device.name}
|
||||
Device model: ${device.model}
|
||||
System name: ${device.systemName}
|
||||
System version: ${device.systemVersion}
|
||||
""".trimIndent()
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui.main.about.licenses.components
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import ca.gosyer.jui.core.lang.withIOContext
|
||||
import ca.gosyer.jui.i18n.MR
|
||||
import com.mikepenz.aboutlibraries.Libs
|
||||
import com.mikepenz.aboutlibraries.entity.Library
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Composable
|
||||
actual fun getLicenses(): Libs? {
|
||||
val libs by produceState<Libs?>(
|
||||
null
|
||||
) {
|
||||
withIOContext {
|
||||
val json = MR.files.aboutlibraries.readText()
|
||||
value = Libs.Builder().withJson(json).build()
|
||||
}
|
||||
}
|
||||
return libs
|
||||
}
|
||||
|
||||
actual object LibraryDefaults {
|
||||
@Composable
|
||||
actual fun libraryColors(
|
||||
backgroundColor: Color,
|
||||
contentColor: Color,
|
||||
badgeBackgroundColor: Color,
|
||||
badgeContentColor: Color,
|
||||
): LibraryColors = object : LibraryColors {}
|
||||
|
||||
actual val ContentPadding: PaddingValues get() = PaddingValues()
|
||||
}
|
||||
|
||||
actual interface LibraryColors
|
||||
|
||||
@Composable
|
||||
actual fun InternalAboutLibraries(
|
||||
libraries: ImmutableList<Library>,
|
||||
modifier: Modifier,
|
||||
lazyListState: LazyListState,
|
||||
contentPadding: PaddingValues,
|
||||
showAuthor: Boolean,
|
||||
showVersion: Boolean,
|
||||
showLicenseBadges: Boolean,
|
||||
colors: LibraryColors,
|
||||
itemContentPadding: PaddingValues,
|
||||
onLibraryClick: ((Library) -> Unit)?
|
||||
) {
|
||||
/*Libraries(
|
||||
libraries = libraries,
|
||||
modifier = modifier,
|
||||
lazyListState = lazyListState,
|
||||
contentPadding = contentPadding,
|
||||
showAuthor = showAuthor,
|
||||
showVersion = showVersion,
|
||||
showLicenseBadges = showLicenseBadges,
|
||||
colors = colors,
|
||||
itemContentPadding = itemContentPadding,
|
||||
onLibraryClick = onLibraryClick
|
||||
)*/
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui.main.components
|
||||
|
||||
import ca.gosyer.jui.uicore.vm.ContextWrapper
|
||||
import ca.gosyer.jui.uicore.vm.ViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import me.tatarka.inject.annotations.Inject
|
||||
|
||||
actual class DebugOverlayViewModel @Inject constructor(contextWrapper: ContextWrapper) : ViewModel(contextWrapper) {
|
||||
actual val maxMemory: String
|
||||
get() = ""
|
||||
actual val usedMemoryFlow: MutableStateFlow<String> = MutableStateFlow("")
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui.main.components
|
||||
|
||||
import ca.gosyer.jui.domain.base.WebsocketService
|
||||
import ca.gosyer.jui.domain.library.service.LibraryUpdateService
|
||||
import ca.gosyer.jui.uicore.vm.ContextWrapper
|
||||
|
||||
internal actual fun startLibraryUpdatesService(
|
||||
contextWrapper: ContextWrapper,
|
||||
libraryUpdatesService: LibraryUpdateService,
|
||||
actions: WebsocketService.Actions
|
||||
) {
|
||||
libraryUpdatesService.init()
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui.manga.components
|
||||
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
actual fun Modifier.chapterItemModifier(
|
||||
onClick: () -> Unit,
|
||||
markRead: (() -> Unit)?,
|
||||
markUnread: (() -> Unit)?,
|
||||
bookmarkChapter: (() -> Unit)?,
|
||||
unBookmarkChapter: (() -> Unit)?,
|
||||
markPreviousAsRead: () -> Unit,
|
||||
onSelectChapter: (() -> Unit)?,
|
||||
onUnselectChapter: (() -> Unit)?
|
||||
): Modifier = combinedClickable(
|
||||
onClick = onUnselectChapter ?: onClick,
|
||||
onLongClick = onSelectChapter
|
||||
)
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui.reader
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import ca.gosyer.jui.ui.base.model.StableHolder
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.Navigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
|
||||
class ReaderScreen(val chapterIndex: Int, val mangaId: Long) : Screen {
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
ReaderMenu(
|
||||
chapterIndex,
|
||||
mangaId,
|
||||
remember { StableHolder(MutableSharedFlow()) },
|
||||
navigator::pop
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
actual class ReaderLauncher(private val navigator: Navigator?) {
|
||||
|
||||
actual fun launch(
|
||||
chapterIndex: Int,
|
||||
mangaId: Long
|
||||
) {
|
||||
navigator?.push(ReaderScreen(chapterIndex, mangaId))
|
||||
}
|
||||
|
||||
@Composable
|
||||
actual fun Reader() {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
actual fun rememberReaderLauncher(): ReaderLauncher {
|
||||
val navigator = LocalNavigator.current
|
||||
return remember(navigator) { ReaderLauncher(navigator) }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui.settings
|
||||
|
||||
actual val showWindowDecorationsOption: Boolean = false
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui.settings
|
||||
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.runtime.Composable
|
||||
import ca.gosyer.jui.uicore.vm.ContextWrapper
|
||||
import ca.gosyer.jui.uicore.vm.ViewModel
|
||||
import me.tatarka.inject.annotations.Inject
|
||||
|
||||
@Composable
|
||||
actual fun getServerHostItems(viewModel: @Composable () -> SettingsServerHostViewModel): LazyListScope.() -> Unit {
|
||||
return {}
|
||||
}
|
||||
|
||||
actual class SettingsServerHostViewModel @Inject constructor(contextWrapper: ContextWrapper) : ViewModel(contextWrapper)
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui.sources.components
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
actual fun Modifier.sourceSideMenuItem(
|
||||
onSourceTabClick: () -> Unit,
|
||||
onSourceCloseTabClick: () -> Unit
|
||||
): Modifier = clickable(
|
||||
onClick = onSourceTabClick
|
||||
)
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui.updates.components
|
||||
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
actual fun Modifier.updatesItemModifier(
|
||||
onClick: () -> Unit,
|
||||
markRead: (() -> Unit)?,
|
||||
markUnread: (() -> Unit)?,
|
||||
bookmarkChapter: (() -> Unit)?,
|
||||
unBookmarkChapter: (() -> Unit)?,
|
||||
onSelectChapter: (() -> Unit)?,
|
||||
onUnselectChapter: (() -> Unit)?
|
||||
): Modifier = combinedClickable(
|
||||
onClick = onUnselectChapter ?: onClick,
|
||||
onLongClick = onSelectChapter
|
||||
)
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui.util.compose
|
||||
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.asComposeImageBitmap
|
||||
import androidx.compose.ui.graphics.toComposeImageBitmap
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.statement.HttpResponse
|
||||
import org.jetbrains.skia.Image
|
||||
import com.seiko.imageloader.Image as ImageLoaderImage
|
||||
|
||||
actual suspend fun HttpResponse.toImageBitmap(): ImageBitmap {
|
||||
return Image.makeFromEncoded(body<ByteArray>()).toComposeImageBitmap()
|
||||
}
|
||||
|
||||
actual fun ImageLoaderImage.asImageBitmap(): ImageBitmap {
|
||||
return asComposeImageBitmap()
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui.util.compose
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import platform.Foundation.NSString
|
||||
import platform.Foundation.stringWithFormat
|
||||
|
||||
actual fun Color.toHexString(): String {
|
||||
return NSString.stringWithFormat("#%06X", (0xFFFFFF and toArgb()))
|
||||
}
|
||||
|
||||
actual fun Color.toLong() = NSString.stringWithFormat("%06X", 0xFFFFFF and toArgb()).toLong(16)
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui.util.lang
|
||||
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import platform.Foundation.NSString
|
||||
import platform.Foundation.localizedCaseInsensitiveCompare
|
||||
|
||||
actual class CollatorComparator() : Comparator<String> {
|
||||
|
||||
actual constructor(locale: Locale) : this()
|
||||
|
||||
actual override fun compare(source: String, target: String): Int {
|
||||
@Suppress("CAST_NEVER_SUCCEEDS")
|
||||
return (source as NSString).localizedCaseInsensitiveCompare(target).toInt()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui.util.lang
|
||||
|
||||
import ca.gosyer.jui.core.io.source
|
||||
import io.ktor.util.toByteArray
|
||||
import io.ktor.utils.io.ByteReadChannel
|
||||
import okio.Source
|
||||
|
||||
actual suspend fun ByteReadChannel.toSource(): Source {
|
||||
return this.toByteArray().source()
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.ui.util.lang
|
||||
|
||||
import platform.Foundation.NSString
|
||||
import platform.Foundation.stringWithFormat
|
||||
|
||||
// Taken from https://github.com/icerockdev/moko-resources/blob/master/resources/src/appleMain/kotlin/dev/icerock/moko/resources/desc/Utils.kt
|
||||
actual fun stringFormat(string: String, vararg args: Any?): String {
|
||||
// NSString format works with NSObjects via %@, we should change standard format to %@
|
||||
val objcFormat = string.replace(Regex("%((?:\\.|\\d|\\$)*)[abcdefs]"), "%$1@")
|
||||
// bad but objc interop limited :(
|
||||
// When calling variadic C functions spread operator is supported only for *arrayOf(...)
|
||||
@Suppress("MagicNumber")
|
||||
return when (args.size) {
|
||||
0 -> NSString.stringWithFormat(objcFormat)
|
||||
1 -> NSString.stringWithFormat(objcFormat, args[0])
|
||||
2 -> NSString.stringWithFormat(objcFormat, args[0], args[1])
|
||||
3 -> NSString.stringWithFormat(objcFormat, args[0], args[1], args[2])
|
||||
4 -> NSString.stringWithFormat(objcFormat, args[0], args[1], args[2], args[3])
|
||||
5 -> NSString.stringWithFormat(objcFormat, args[0], args[1], args[2], args[3], args[4])
|
||||
6 -> NSString.stringWithFormat(
|
||||
objcFormat,
|
||||
args[0],
|
||||
args[1],
|
||||
args[2],
|
||||
args[3],
|
||||
args[4],
|
||||
args[5]
|
||||
)
|
||||
7 -> NSString.stringWithFormat(
|
||||
objcFormat,
|
||||
args[0],
|
||||
args[1],
|
||||
args[2],
|
||||
args[3],
|
||||
args[4],
|
||||
args[5],
|
||||
args[6]
|
||||
)
|
||||
8 -> NSString.stringWithFormat(
|
||||
objcFormat,
|
||||
args[0],
|
||||
args[1],
|
||||
args[2],
|
||||
args[3],
|
||||
args[4],
|
||||
args[5],
|
||||
args[6],
|
||||
args[7]
|
||||
)
|
||||
9 -> NSString.stringWithFormat(
|
||||
objcFormat,
|
||||
args[0],
|
||||
args[1],
|
||||
args[2],
|
||||
args[3],
|
||||
args[4],
|
||||
args[5],
|
||||
args[6],
|
||||
args[7],
|
||||
args[8]
|
||||
)
|
||||
else -> throw IllegalArgumentException("can't handle more then 9 arguments now")
|
||||
}
|
||||
}
|
||||
@@ -8,17 +8,17 @@ package ca.gosyer.jui.ui.util.lang
|
||||
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import ca.gosyer.jui.core.lang.toPlatform
|
||||
import java.text.Collator as JvmCollator
|
||||
import java.text.Collator
|
||||
|
||||
actual fun Collator(locale: Locale): Collator {
|
||||
return Collator(JvmCollator.getInstance(locale.toPlatform()))
|
||||
}
|
||||
actual class CollatorComparator(private val collator: Collator) : Comparator<String> {
|
||||
|
||||
actual constructor(locale: Locale) : this(Collator.getInstance(locale.toPlatform()))
|
||||
|
||||
actual class Collator(private val jvmCollator: JvmCollator) {
|
||||
init {
|
||||
jvmCollator.strength = JvmCollator.PRIMARY
|
||||
collator.strength = Collator.PRIMARY
|
||||
}
|
||||
actual fun compare(source: String, target: String): Int {
|
||||
return jvmCollator.compare(source, target)
|
||||
|
||||
actual override fun compare(source: String, target: String): Int {
|
||||
return collator.compare(source, target)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,6 @@ import io.ktor.utils.io.jvm.javaio.toInputStream
|
||||
import okio.Source
|
||||
import okio.source
|
||||
|
||||
actual fun ByteReadChannel.toSource(): Source {
|
||||
actual suspend fun ByteReadChannel.toSource(): Source {
|
||||
return toInputStream().source()
|
||||
}
|
||||
|
||||
@@ -6,16 +6,8 @@
|
||||
|
||||
package ca.gosyer.jui.uicore.resources
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import dev.icerock.moko.resources.PluralsResource
|
||||
import dev.icerock.moko.resources.desc.Plural
|
||||
import dev.icerock.moko.resources.desc.PluralFormatted
|
||||
import dev.icerock.moko.resources.desc.StringDesc
|
||||
import dev.icerock.moko.resources.desc.PluralFormattedStringDesc
|
||||
import dev.icerock.moko.resources.desc.PluralStringDesc
|
||||
|
||||
@Composable
|
||||
actual fun stringResource(resource: PluralsResource, quantity: Int): String =
|
||||
StringDesc.Plural(resource, quantity).localized()
|
||||
|
||||
@Composable
|
||||
actual fun stringResource(resource: PluralsResource, quantity: Int, vararg args: Any): String =
|
||||
StringDesc.PluralFormatted(resource, quantity, *args).localized()
|
||||
actual fun PluralStringDesc.localized(): String = localized()
|
||||
actual fun PluralFormattedStringDesc.localized() = localized()
|
||||
@@ -7,11 +7,19 @@
|
||||
package ca.gosyer.jui.uicore.resources
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import dev.icerock.moko.resources.PluralsResource
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import dev.icerock.moko.resources.desc.Plural
|
||||
import dev.icerock.moko.resources.desc.PluralFormatted
|
||||
import dev.icerock.moko.resources.desc.PluralFormattedStringDesc
|
||||
import dev.icerock.moko.resources.desc.PluralStringDesc
|
||||
import dev.icerock.moko.resources.desc.Resource
|
||||
import dev.icerock.moko.resources.desc.ResourceFormatted
|
||||
import dev.icerock.moko.resources.desc.StringDesc
|
||||
|
||||
expect fun PluralStringDesc.localized(): String
|
||||
expect fun PluralFormattedStringDesc.localized(): String
|
||||
|
||||
@Composable
|
||||
actual fun stringResource(resource: StringResource): String =
|
||||
StringDesc.Resource(resource).localized()
|
||||
@@ -20,7 +28,6 @@ actual fun stringResource(resource: StringResource): String =
|
||||
actual fun stringResource(resource: StringResource, vararg args: Any): String =
|
||||
StringDesc.ResourceFormatted(resource, *args).localized()
|
||||
|
||||
/* TODO the commonizer chokes on .localized()
|
||||
@Composable
|
||||
actual fun stringResource(resource: PluralsResource, quantity: Int): String =
|
||||
StringDesc.Plural(resource, quantity).localized()
|
||||
@@ -28,4 +35,3 @@ actual fun stringResource(resource: PluralsResource, quantity: Int): String =
|
||||
@Composable
|
||||
actual fun stringResource(resource: PluralsResource, quantity: Int, vararg args: Any): String =
|
||||
StringDesc.PluralFormatted(resource, quantity, *args).localized()
|
||||
*/
|
||||
|
||||
@@ -6,17 +6,28 @@
|
||||
|
||||
package ca.gosyer.jui.uicore.vm
|
||||
|
||||
import ca.gosyer.jui.core.lang.launchDefault
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import dev.icerock.moko.resources.desc.desc
|
||||
import dev.icerock.moko.resources.format
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import me.tatarka.inject.annotations.Inject
|
||||
|
||||
actual class ContextWrapper @Inject constructor() {
|
||||
private val _toasts = MutableSharedFlow<Pair<String, Length>>()
|
||||
val toasts = _toasts.asSharedFlow()
|
||||
|
||||
actual fun toPlatformString(stringResource: StringResource): String {
|
||||
return stringResource.localized()
|
||||
return stringResource.desc().localized()
|
||||
}
|
||||
actual fun toPlatformString(stringResource: StringResource, vararg args: Any): String {
|
||||
return stringResource.format(*args).localized()
|
||||
}
|
||||
actual fun toast(string: String, length: Length) {
|
||||
GlobalScope.launchDefault {
|
||||
_toasts.emit(string to length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,16 +6,8 @@
|
||||
|
||||
package ca.gosyer.jui.uicore.resources
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import dev.icerock.moko.resources.PluralsResource
|
||||
import dev.icerock.moko.resources.desc.Plural
|
||||
import dev.icerock.moko.resources.desc.PluralFormatted
|
||||
import dev.icerock.moko.resources.desc.StringDesc
|
||||
import dev.icerock.moko.resources.desc.PluralFormattedStringDesc
|
||||
import dev.icerock.moko.resources.desc.PluralStringDesc
|
||||
|
||||
@Composable
|
||||
actual fun stringResource(resource: PluralsResource, quantity: Int): String =
|
||||
StringDesc.Plural(resource, quantity).localized()
|
||||
|
||||
@Composable
|
||||
actual fun stringResource(resource: PluralsResource, quantity: Int, vararg args: Any): String =
|
||||
StringDesc.PluralFormatted(resource, quantity, *args).localized()
|
||||
actual fun PluralStringDesc.localized(): String = localized()
|
||||
actual fun PluralFormattedStringDesc.localized() = localized()
|
||||
@@ -6,16 +6,8 @@
|
||||
|
||||
package ca.gosyer.jui.uicore.resources
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import dev.icerock.moko.resources.PluralsResource
|
||||
import dev.icerock.moko.resources.desc.Plural
|
||||
import dev.icerock.moko.resources.desc.PluralFormatted
|
||||
import dev.icerock.moko.resources.desc.StringDesc
|
||||
import dev.icerock.moko.resources.desc.PluralFormattedStringDesc
|
||||
import dev.icerock.moko.resources.desc.PluralStringDesc
|
||||
|
||||
@Composable
|
||||
actual fun stringResource(resource: PluralsResource, quantity: Int): String =
|
||||
StringDesc.Plural(resource, quantity).localized()
|
||||
|
||||
@Composable
|
||||
actual fun stringResource(resource: PluralsResource, quantity: Int, vararg args: Any): String =
|
||||
StringDesc.PluralFormatted(resource, quantity, *args).localized()
|
||||
actual fun PluralStringDesc.localized(): String = localized()
|
||||
actual fun PluralFormattedStringDesc.localized() = localized()
|
||||
|
||||
Reference in New Issue
Block a user