Initial android app

This commit is contained in:
Syer10
2022-02-27 14:26:31 -05:00
parent c533c3c980
commit 223281372f
16 changed files with 319 additions and 32 deletions

96
android/build.gradle.kts Normal file
View File

@@ -0,0 +1,96 @@
import org.jetbrains.compose.compose
plugins {
kotlin("android")
id("com.android.application")
id("org.jetbrains.compose")
id("com.google.devtools.ksp")
id("org.jmailen.kotlinter")
}
dependencies {
implementation(projects.core)
implementation(projects.i18n)
implementation(projects.data)
implementation(projects.uiCore)
implementation(projects.presentation)
// UI (Compose)
implementation(libs.voyagerCore)
implementation(libs.voyagerNavigation)
implementation(libs.voyagerTransitions)
implementation(libs.accompanistPager)
implementation(libs.accompanistFlowLayout)
implementation(libs.kamel)
implementation(libs.materialDialogsCore)
// Android
implementation(libs.appCompat)
implementation(libs.activityCompose)
// Android Lifecycle
implementation(libs.lifecycleCommon)
implementation(libs.lifecycleProcess)
implementation(libs.lifecycleRuntime)
// Threading
implementation(libs.coroutinesCore)
implementation(libs.coroutinesAndroid)
// Json
implementation(libs.json)
// Xml
implementation(libs.xmlUtilCore)
implementation(libs.xmlUtilSerialization)
// Dependency Injection
implementation(libs.kotlinInjectRuntime)
ksp(libs.kotlinInjectCompiler)
// Http client
implementation(libs.ktorCore)
implementation(libs.ktorOkHttp)
implementation(libs.ktorSerialization)
implementation(libs.ktorLogging)
implementation(libs.ktorWebsockets)
implementation(libs.ktorAuth)
// Logging
implementation(libs.slf4jApi)
implementation(libs.slf4jAndroid)
implementation(libs.ktlogging)
// Storage
implementation(libs.okio)
// Preferences
implementation(libs.multiplatformSettingsCore)
implementation(libs.multiplatformSettingsSerialization)
implementation(libs.multiplatformSettingsCoroutines)
// Utility
implementation(libs.krokiCoroutines)
// Localization
implementation(libs.mokoCore)
implementation(libs.mokoCompose)
// Testing
testImplementation(kotlin("test-junit"))
testImplementation(compose("org.jetbrains.compose.ui:ui-test-junit4"))
testImplementation(libs.coroutinesTest)
}
android {
defaultConfig {
applicationId = "ca.gosyer.tachidesk.jui.android"
versionCode = 1
versionName = version.toString()
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
}
}
}

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="ca.gosyer.jui.android">
<!-- Internet -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- Storage -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:name=".App"
android:hardwareAccelerated="true"
android:allowBackup="false"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar"
android:networkSecurityConfig="@xml/network_security_config">
<activity android:name=".MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -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.android
import android.app.Application
import androidx.appcompat.app.AppCompatDelegate
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.lifecycleScope
import ca.gosyer.core.prefs.getAsFlow
import ca.gosyer.data.ui.model.ThemeMode
import ca.gosyer.ui.AppComponent
import kotlinx.coroutines.flow.launchIn
class App : Application(), DefaultLifecycleObserver {
override fun onCreate() {
super<Application>.onCreate()
/*if (BuildConfig.DEBUG) {
System.setProperty("kotlinx.coroutines.debug", "on")
}*/
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
val appComponent = AppComponent.getInstance(this)
appComponent.dataComponent.uiPreferences.themeMode()
.getAsFlow {
AppCompatDelegate.setDefaultNightMode(
when (it) {
ThemeMode.System -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
ThemeMode.Light -> AppCompatDelegate.MODE_NIGHT_NO
ThemeMode.Dark -> AppCompatDelegate.MODE_NIGHT_YES
}
)
}
.launchIn(ProcessLifecycleOwner.get().lifecycleScope)
}
}

View File

@@ -0,0 +1,28 @@
package ca.gosyer.jui.android
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.CompositionLocalProvider
import ca.gosyer.ui.AppComponent
import ca.gosyer.ui.base.theme.AppTheme
import ca.gosyer.ui.main.MainMenu
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val appComponent = AppComponent.getInstance(applicationContext)
if (savedInstanceState == null) {
appComponent.dataComponent.migrations.runMigrations()
}
val uiHooks = appComponent.uiComponent.getHooks()
setContent {
CompositionLocalProvider(*uiHooks) {
AppTheme {
MainMenu()
}
}
}
}
}

View File

@@ -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/.
#
showThread=true
showName=short
level.logger-prefix=SUPPRESS|ERROR|WARN|INFO|DEBUG|VERBOSE|NATIVE

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config xmlns:tools="http://schemas.android.com/tools">
<!-- Need to allow cleartext traffic for some sources -->
<base-config
cleartextTrafficPermitted="true"
tools:ignore="InsecureBaseConfiguration">
<trust-anchors>
<!-- Trust preinstalled CAs -->
<certificates src="system" />
<!-- Additionally trust user added CAs -->
<certificates
src="user"
tools:ignore="AcceptsUserCertificates" />
</trust-anchors>
</base-config>
</network-security-config>

View File

@@ -133,11 +133,26 @@ subprojects {
plugins.withType<org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper> {
configure<org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension> {
if (!Config.androidDev) {
sourceSets.addSrcDir("desktopMain", "jvmMain")
sourceSets.addSrcDir("desktopTest", "jvmTest")
sourceSets.addSrcDir("desktopMain", "src/jvmMain/kotlin")
sourceSets.addSrcDir("desktopTest", "src/jvmTest/kotlin")
}
sourceSets.addSrcDir("androidMain", "src/jvmMain/kotlin")
sourceSets.addSrcDir("androidTest", "src/jvmTest/kotlin")
plugins.withType<com.google.devtools.ksp.gradle.KspGradleSubplugin> {
sourceSets.addSrcDir("commonMain", "build/generated/ksp/commonMain/kotlin")
sourceSets.addSrcDir("commonTest", "build/generated/ksp/commonTest/kotlin")
sourceSets.addSrcDir("desktopMain", "build/generated/ksp/desktopMain/kotlin")
sourceSets.addSrcDir("desktopTest", "build/generated/ksp/desktopTest/kotlin")
if (gradle.startParameter.taskRequests.toString().contains("Release")) {
sourceSets.addSrcDir("androidMain", "build/generated/ksp/androidRelease/kotlin")
sourceSets.addSrcDir("androidTest", "build/generated/ksp/androidRelease/kotlin")
} else {
sourceSets.addSrcDir("androidMain", "build/generated/ksp/androidDebug/kotlin")
sourceSets.addSrcDir("androidTest", "build/generated/ksp/androidDebug/kotlin")
}
}
sourceSets.addSrcDir("androidMain", "jvmMain")
sourceSets.addSrcDir("androidTest", "jvmTest")
}
}
}
@@ -146,7 +161,7 @@ subprojects {
fun NamedDomainObjectContainer<org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet>.addSrcDir(configuration: String, srcDir: String) {
filter { it.name.contains(configuration) }
.forEach {
it.kotlin.srcDir("src/$srcDir/kotlin")
it.kotlin.srcDir(srcDir)
}
}

View File

@@ -13,5 +13,5 @@ object Config {
val desktopJvmTarget = JavaVersion.VERSION_16
val androidJvmTarget = JavaVersion.VERSION_11
const val androidDev = false
const val androidDev = true
}

View File

@@ -39,7 +39,6 @@ kotlin {
}
}
val commonMain by getting {
kotlin.srcDir("build/generated/ksp/commonMain/kotlin")
dependencies {
api(kotlin("stdlib-common"))
api(libs.coroutinesCore)
@@ -55,7 +54,6 @@ kotlin {
}
}
val commonTest by getting {
kotlin.srcDir("build/generated/ksp/commonTest/kotlin")
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
@@ -63,24 +61,20 @@ kotlin {
}
val desktopMain by getting {
kotlin.srcDir("build/generated/ksp/desktopMain/kotlin")
dependencies {
api(kotlin("stdlib-jdk8"))
api(libs.appDirs)
}
}
val desktopTest by getting {
kotlin.srcDir("build/generated/ksp/desktopTest/kotlin")
}
val androidMain by getting {
kotlin.srcDir("build/generated/ksp/androidRelease/kotlin")
dependencies {
api(kotlin("stdlib-jdk8"))
}
}
val androidTest by getting {
kotlin.srcDir("build/generated/ksp/androidReleaseTest/kotlin")
}
}
}

View File

@@ -14,7 +14,7 @@ actual class PreferenceStoreFactory @Inject constructor(private val context: Con
actual fun create(vararg names: String): PreferenceStore {
return AndroidPreferenceStore(
AndroidSettings(
context.getSharedPreferences(names.joinToString(separator = "/"), Context.MODE_PRIVATE)
context.getSharedPreferences(names.joinToString(separator = "_"), Context.MODE_PRIVATE)
)
)
}

View File

@@ -31,7 +31,6 @@ kotlin {
}
}
val commonMain by getting {
kotlin.srcDir("build/generated/ksp/commonMain/kotlin")
dependencies {
api(kotlin("stdlib-common"))
api(libs.coroutinesCore)
@@ -49,7 +48,6 @@ kotlin {
}
}
val commonTest by getting {
kotlin.srcDir("build/generated/ksp/commonTest/kotlin")
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
@@ -57,23 +55,19 @@ kotlin {
}
val desktopMain by getting {
kotlin.srcDir("build/generated/ksp/desktopMain/kotlin")
dependencies {
api(kotlin("stdlib-jdk8"))
}
}
val desktopTest by getting {
kotlin.srcDir("build/generated/ksp/desktopTest/kotlin")
}
val androidMain by getting {
kotlin.srcDir("build/generated/ksp/androidRelease/kotlin")
dependencies {
api(kotlin("stdlib-jdk8"))
}
}
val androidTest by getting {
kotlin.srcDir("build/generated/ksp/androidReleaseTest/kotlin")
}
}
}

View File

@@ -14,8 +14,12 @@ kamel = "0.3.0"
materialDialogs = "0.6.4"
# Android
appCompat = "1.4.1"
activityCompose = "1.3.1"
# Android Lifecycle
lifecycle = "2.4.1"
# Swing
darklaf = "2.7.3"
@@ -27,6 +31,7 @@ ktor = "1.6.7"
# Logging
slf4j = "1.7.35"
slf4jAndroid = "1.7.35-0"
log4j = "2.17.1"
ktlogging = "2.1.21"
@@ -48,6 +53,7 @@ moko = "0.18.0"
# Kotlin
coroutinesCore = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
coroutinesSwing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "coroutines" }
coroutinesAndroid = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
coroutinesTest = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
# Serialization
@@ -65,8 +71,14 @@ kamel = { module = "com.alialbaali.kamel:kamel-image", version.ref = "kamel" }
materialDialogsCore = { module = "ca.gosyer:compose-material-dialogs-core", version.ref = "materialDialogs" }
# Android
appCompat = { module = "androidx.appcompat:appcompat", version.ref = "appCompat" }
activityCompose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
# Android Lifecycle
lifecycleCommon = { module = "androidx.lifecycle:lifecycle-common", version.ref = "lifecycle" }
lifecycleProcess = { module = "androidx.lifecycle:lifecycle-process", version.ref = "lifecycle" }
lifecycleRuntime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" }
# Swing
darklaf = { module = "com.github.weisj:darklaf-core", version.ref = "darklaf" }
@@ -85,6 +97,7 @@ ktorAuth = { module = "io.ktor:ktor-client-auth", version.ref = "ktor" }
# Logging
slf4jApi = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
slf4jJul = { module = "org.slf4j:jul-to-slf4j", version.ref = "slf4j" }
slf4jAndroid = { module = "uk.uuid.slf4j:slf4j-android", version.ref = "slf4jAndroid" }
log4jApi = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j" }
log4jCore = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" }
log4jSlf4j = { module = "org.apache.logging.log4j:log4j-slf4j-impl", version.ref = "log4j" }

View File

@@ -38,7 +38,6 @@ kotlin {
}
}
val commonMain by getting {
kotlin.srcDir("build/generated/ksp/commonMain/kotlin")
dependencies {
api(kotlin("stdlib-common"))
api(libs.coroutinesCore)
@@ -60,7 +59,6 @@ kotlin {
}
}
val commonTest by getting {
kotlin.srcDir("build/generated/ksp/commonTest/kotlin")
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
@@ -68,25 +66,21 @@ kotlin {
}
val desktopMain by getting {
kotlin.srcDir("build/generated/ksp/desktopMain/kotlin")
dependencies {
api(kotlin("stdlib-jdk8"))
api(libs.coroutinesSwing)
}
}
val desktopTest by getting {
kotlin.srcDir("build/generated/ksp/desktopTest/kotlin")
}
val androidMain by getting {
kotlin.srcDir("build/generated/ksp/androidRelease/kotlin")
dependencies {
api(kotlin("stdlib-jdk8"))
api(libs.activityCompose)
}
}
val androidTest by getting {
kotlin.srcDir("build/generated/ksp/androidReleaseTest/kotlin")
}
}
}

View File

@@ -7,6 +7,27 @@
package ca.gosyer.ui.base.vm
import ca.gosyer.ui.base.theme.AppThemeViewModel
import ca.gosyer.ui.categories.CategoriesScreenViewModel
import ca.gosyer.ui.downloads.DownloadsScreenViewModel
import ca.gosyer.ui.extensions.ExtensionsScreenViewModel
import ca.gosyer.ui.library.LibraryScreenViewModel
import ca.gosyer.ui.main.MainViewModel
import ca.gosyer.ui.main.components.DebugOverlayViewModel
import ca.gosyer.ui.manga.MangaScreenViewModel
import ca.gosyer.ui.reader.ReaderMenuViewModel
import ca.gosyer.ui.settings.SettingsAdvancedViewModel
import ca.gosyer.ui.settings.SettingsBackupViewModel
import ca.gosyer.ui.settings.SettingsGeneralViewModel
import ca.gosyer.ui.settings.SettingsLibraryViewModel
import ca.gosyer.ui.settings.SettingsReaderViewModel
import ca.gosyer.ui.settings.SettingsServerHostViewModel
import ca.gosyer.ui.settings.SettingsServerViewModel
import ca.gosyer.ui.settings.ThemesViewModel
import ca.gosyer.ui.sources.SourcesScreenViewModel
import ca.gosyer.ui.sources.browse.SourceScreenViewModel
import ca.gosyer.ui.sources.browse.filter.SourceFiltersViewModel
import ca.gosyer.ui.sources.home.SourceHomeScreenViewModel
import ca.gosyer.ui.sources.settings.SourceSettingsScreenViewModel
import ca.gosyer.ui.updates.UpdatesScreenViewModel
import ca.gosyer.uicore.vm.ViewModel
import ca.gosyer.uicore.vm.ViewModelFactory
@@ -16,6 +37,27 @@ import kotlin.reflect.KClass
@Inject
actual class ViewModelFactoryImpl(
private val appThemeFactory: () -> AppThemeViewModel,
private val categoryFactory: () -> CategoriesScreenViewModel,
private val downloadsFactory: (Boolean) -> DownloadsScreenViewModel,
private val extensionsFactory: () -> ExtensionsScreenViewModel,
private val libraryFactory: () -> LibraryScreenViewModel,
private val debugOverlayFactory: () -> DebugOverlayViewModel,
private val mainFactory: () -> MainViewModel,
private val mangaFactory: (params: MangaScreenViewModel.Params) -> MangaScreenViewModel,
private val readerFactory: (params: ReaderMenuViewModel.Params) -> ReaderMenuViewModel,
private val settingsAdvancedFactory: () -> SettingsAdvancedViewModel,
private val themesFactory: () -> ThemesViewModel,
private val settingsBackupFactory: () -> SettingsBackupViewModel,
private val settingsGeneralFactory: () -> SettingsGeneralViewModel,
private val settingsLibraryFactory: () -> SettingsLibraryViewModel,
private val settingsReaderFactory: () -> SettingsReaderViewModel,
private val settingsServerFactory: () -> SettingsServerViewModel,
private val settingsServerHostFactory: () -> SettingsServerHostViewModel,
private val sourceFiltersFactory: (params: SourceFiltersViewModel.Params) -> SourceFiltersViewModel,
private val sourceSettingsFactory: (params: SourceSettingsScreenViewModel.Params) -> SourceSettingsScreenViewModel,
private val sourceHomeFactory: () -> SourceHomeScreenViewModel,
private val sourceFactory: (params: SourceScreenViewModel.Params) -> SourceScreenViewModel,
private val sourcesFactory: () -> SourcesScreenViewModel,
private val updatesFactory: () -> UpdatesScreenViewModel
) : ViewModelFactory() {
@@ -23,6 +65,27 @@ actual class ViewModelFactoryImpl(
@Suppress("UNCHECKED_CAST", "IMPLICIT_CAST_TO_ANY")
return when (klass) {
AppThemeViewModel::class -> appThemeFactory()
CategoriesScreenViewModel::class -> categoryFactory()
DownloadsScreenViewModel::class -> downloadsFactory(arg1 as Boolean)
ExtensionsScreenViewModel::class -> extensionsFactory()
LibraryScreenViewModel::class -> libraryFactory()
DebugOverlayViewModel::class -> debugOverlayFactory()
MainViewModel::class -> mainFactory()
MangaScreenViewModel::class -> mangaFactory(arg1 as MangaScreenViewModel.Params)
ReaderMenuViewModel::class -> readerFactory(arg1 as ReaderMenuViewModel.Params)
SettingsAdvancedViewModel::class -> settingsAdvancedFactory()
ThemesViewModel::class -> themesFactory()
SettingsBackupViewModel::class -> settingsBackupFactory()
SettingsGeneralViewModel::class -> settingsGeneralFactory()
SettingsLibraryViewModel::class -> settingsLibraryFactory()
SettingsReaderViewModel::class -> settingsReaderFactory()
SettingsServerViewModel::class -> settingsServerFactory()
SettingsServerHostViewModel::class -> settingsServerHostFactory()
SourceFiltersViewModel::class -> sourceFiltersFactory(arg1 as SourceFiltersViewModel.Params)
SourceSettingsScreenViewModel::class -> sourceSettingsFactory(arg1 as SourceSettingsScreenViewModel.Params)
SourceHomeScreenViewModel::class -> sourceHomeFactory()
SourceScreenViewModel::class -> sourceFactory(arg1 as SourceScreenViewModel.Params)
SourcesScreenViewModel::class -> sourcesFactory()
UpdatesScreenViewModel::class -> updatesFactory()
else -> throw IllegalArgumentException("Unknown ViewModel $klass")
} as VM

View File

@@ -15,6 +15,7 @@ include("i18n")
include("data")
include("ui-core")
include("presentation")
include("android")
enableFeaturePreview("VERSION_CATALOGS")
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")

View File

@@ -32,7 +32,6 @@ kotlin {
}
}
val commonMain by getting {
kotlin.srcDir("build/generated/ksp/commonMain/kotlin")
dependencies {
api(kotlin("stdlib-common"))
api(libs.coroutinesCore)
@@ -45,7 +44,6 @@ kotlin {
}
}
val commonTest by getting {
kotlin.srcDir("build/generated/ksp/commonTest/kotlin")
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
@@ -53,24 +51,19 @@ kotlin {
}
val desktopMain by getting {
kotlin.srcDir("build/generated/ksp/desktopMain/kotlin")
dependencies {
api(kotlin("stdlib-jdk8"))
api(libs.coroutinesSwing)
}
}
val desktopTest by getting {
kotlin.srcDir("build/generated/ksp/desktopTest/kotlin")
}
val androidMain by getting {
kotlin.srcDir("build/generated/ksp/androidRelease/kotlin")
dependencies {
api(kotlin("stdlib-jdk8"))
}
}
val androidTest by getting {
kotlin.srcDir("build/generated/ksp/androidReleaseTest/kotlin")
}
}
}