Add iOS module

This commit is contained in:
Syer10
2022-11-17 20:50:26 -05:00
parent d45a9c31a5
commit 481888d2c8
5 changed files with 381 additions and 1 deletions

View File

@@ -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.ios
import ca.gosyer.jui.core.di.AppScope
import ca.gosyer.jui.data.DataComponent
import ca.gosyer.jui.domain.DomainComponent
import ca.gosyer.jui.ui.ViewModelComponent
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.Provides
@AppScope
@Component
abstract class AppComponent(
@get:Provides
val context: ContextWrapper
) : ViewModelComponent, DataComponent, DomainComponent, UiComponent {
abstract val appMigrations: AppMigrations
@get:AppScope
@get:Provides
protected val appMigrationsFactory: AppMigrations
get() = AppMigrations(migrationPreferences, contextWrapper)
val bind: ViewModelComponent
@Provides get() = this
companion object {
private var appComponentInstance: AppComponent? = null
fun getInstance(context: ContextWrapper) = appComponentInstance ?: create(context)
.also { appComponentInstance = it }
}
}

View File

@@ -0,0 +1,32 @@
/*
* 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.ios
import ca.gosyer.jui.domain.migration.service.MigrationPreferences
import ca.gosyer.jui.ios.build.BuildKonfig
import ca.gosyer.jui.uicore.vm.ContextWrapper
import me.tatarka.inject.annotations.Inject
class AppMigrations @Inject constructor(
private val migrationPreferences: MigrationPreferences,
private val contextWrapper: ContextWrapper
) {
fun runMigrations(): Boolean {
val oldVersion = migrationPreferences.appVersion().get()
if (oldVersion < BuildKonfig.MIGRATION_CODE) {
migrationPreferences.appVersion().set(BuildKonfig.MIGRATION_CODE)
// Fresh install
if (oldVersion == 0) {
return false
}
return true
}
return false
}
}

View File

@@ -0,0 +1,146 @@
package ca.gosyer.jui.ios
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Application
import ca.gosyer.jui.ui.base.theme.AppTheme
import ca.gosyer.jui.ui.main.MainMenu
import ca.gosyer.jui.uicore.vm.ContextWrapper
import ca.gosyer.jui.uicore.vm.Length
import kotlinx.cinterop.autoreleasepool
import kotlinx.cinterop.cstr
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.toCValues
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import platform.Foundation.NSStringFromClass
import platform.UIKit.UIApplication
import platform.UIKit.UIApplicationDelegateProtocol
import platform.UIKit.UIApplicationDelegateProtocolMeta
import platform.UIKit.UIApplicationMain
import platform.UIKit.UIResponder
import platform.UIKit.UIResponderMeta
import platform.UIKit.UIScreen
import platform.UIKit.UIWindow
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
fun main() {
val args = emptyArray<String>()
memScoped {
val argc = args.size + 1
val argv = (arrayOf("skikoApp") + args).map { it.cstr.ptr }.toCValues()
autoreleasepool {
UIApplicationMain(argc, argv, null, NSStringFromClass(SkikoAppDelegate))
}
}
}
class SkikoAppDelegate @OverrideInit constructor() : UIResponder(), UIApplicationDelegateProtocol {
companion object : UIResponderMeta(), UIApplicationDelegateProtocolMeta
private var _window: UIWindow? = null
override fun window() = _window
override fun setWindow(window: UIWindow?) {
_window = window
}
private val context = ContextWrapper()
private val appComponent = AppComponent.getInstance(context)
init {
appComponent.migrations.runMigrations()
appComponent.appMigrations.runMigrations()
appComponent.downloadService.init()
appComponent.libraryUpdateService.init()
}
val uiHooks = appComponent.hooks
override fun application(application: UIApplication, didFinishLaunchingWithOptions: Map<Any?, *>?): Boolean {
window = UIWindow(frame = UIScreen.mainScreen.bounds).apply {
rootViewController = Application("Tachidesk-JUI") {
CompositionLocalProvider(*uiHooks) {
AppTheme {
Box(Modifier.fillMaxSize()) {
MainMenu()
ToastOverlay(
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(bottom = 64.dp),
context = context
)
}
}
}
}
makeKeyAndVisible()
}
return true
}
}
@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 -> Duration.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
)
}
}
})
}