Skip to content

Commit

Permalink
Implement source update
Browse files Browse the repository at this point in the history
Refactor codes for testability
  • Loading branch information
tom5079 committed Apr 30, 2022
1 parent ca310c2 commit ef4c581
Show file tree
Hide file tree
Showing 13 changed files with 334 additions and 209 deletions.
17 changes: 10 additions & 7 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ android {
dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar"))))
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2")
implementation(Kotlin.SERIALIZATION)
implementation(Kotlin.COROUTINE)

implementation("androidx.activity:activity-compose:1.4.0")
implementation("androidx.navigation:navigation-compose:2.4.2")
Expand All @@ -110,10 +110,10 @@ dependencies {

implementation("io.coil-kt:coil-compose:1.4.0")

implementation("io.ktor:ktor-client-core:2.0.0")
implementation("io.ktor:ktor-client-okhttp:2.0.0")
implementation("io.ktor:ktor-client-content-negotiation:2.0.0")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.0.0")
implementation(KtorClient.CORE)
implementation(KtorClient.OKHTTP)
implementation(KtorClient.CONTENT_NEGOTIATION)
implementation(KtorClient.SERIALIZATION)

implementation("androidx.room:room-runtime:2.4.2")
annotationProcessor("androidx.room:room-compiler:2.4.2")
Expand Down Expand Up @@ -144,15 +144,18 @@ dependencies {
implementation("xyz.quaver:subsampledimage:0.0.1-alpha19-SNAPSHOT")

implementation("org.kodein.log:kodein-log:0.12.0")
// debugImplementation("com.squareup.leakcanary:leakcanary-android:2.8.1")
debugImplementation("com.squareup.leakcanary:leakcanary-android:2.8.1")

testImplementation("junit:junit:4.13.2")
testImplementation("org.mockito:mockito-inline:4.4.0")
testImplementation(KtorClient.TEST)
testImplementation(Kotlin.COROUTINE_TEST)

androidTestImplementation("androidx.test.ext:junit:1.1.3")
androidTestImplementation("androidx.test:rules:1.4.0")
androidTestImplementation("androidx.test:runner:1.4.0")
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
androidTestImplementation(KtorClient.TEST)

androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.1.1")
}
Expand Down
10 changes: 3 additions & 7 deletions app/src/main/java/xyz/quaver/pupil/Pupil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,27 +30,23 @@ import com.google.android.gms.security.ProviderInstaller
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
import org.kodein.di.*
import org.kodein.di.android.x.androidXModule
import xyz.quaver.pupil.sources.core.NetworkCache
import xyz.quaver.pupil.sources.core.settingsDataStore
import xyz.quaver.pupil.util.ApkDownloadManager
import xyz.quaver.pupil.util.PupilHttpClient

class Pupil : Application(), DIAware {

override val di: DI by DI.lazy {
import(androidXModule(this@Pupil))

bind { singleton { NetworkCache(this@Pupil) } }
bindSingleton { ApkDownloadManager(this@Pupil, instance()) }

bindSingleton { settingsDataStore }

bind { singleton {
HttpClient(OkHttp) {
install(ContentNegotiation)
}
} }
bind { singleton { PupilHttpClient(OkHttp.create()) } }
}

override fun onCreate() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import android.app.Application
import android.content.Context
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalContext
import dalvik.system.PathClassLoader
Expand All @@ -31,26 +30,35 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import xyz.quaver.pupil.sources.core.Source
import java.util.concurrent.ConcurrentHashMap

@Composable
fun rememberLocalSourceList(context: Context = LocalContext.current): State<List<SourceEntry>> = produceState(emptyList()) {
while (true) {
value = loadSourceList(context)
delay(1000)
}
}

suspend fun loadSource(context: Context, sourceEntry: SourceEntry): Source = coroutineScope {
sourceCacheMutex.withLock {
sourceCache[sourceEntry.packageName] ?: run {
val classLoader = PathClassLoader(sourceEntry.sourceDir, null, context.classLoader)

Class.forName("${sourceEntry.packagePath}${sourceEntry.sourcePath}", false, classLoader)
.getConstructor(Application::class.java)
.newInstance(context.applicationContext) as Source
}.also { sourceCache[sourceEntry.packageName] = it }
}
}

private const val SOURCES_FEATURE = "pupil.sources"
private const val SOURCES_PACKAGE_PREFIX = "xyz.quaver.pupil.sources"
private const val SOURCES_PATH = "pupil.sources.path"

data class SourceEntry(
val packageName: String,
val packagePath: String,
val sourceName: String,
val sourcePath: String,
val sourceDir: String,
val icon: Drawable,
val version: String
)

val PackageInfo.isSourceFeatureEnabled
private val PackageInfo.isSourceFeatureEnabled
get() = this.reqFeatures.orEmpty().any { it.name == SOURCES_FEATURE }

fun loadSource(context: Context, packageInfo: PackageInfo): List<SourceEntry> {
private fun loadSource(context: Context, packageInfo: PackageInfo): List<SourceEntry> {
val packageManager = context.packageManager

val applicationInfo = packageInfo.applicationInfo
Expand Down Expand Up @@ -84,19 +92,7 @@ fun loadSource(context: Context, packageInfo: PackageInfo): List<SourceEntry> {
private val sourceCacheMutex = Mutex()
private val sourceCache = mutableMapOf<String, Source>()

suspend fun loadSource(context: Context, sourceEntry: SourceEntry): Source = coroutineScope {
sourceCacheMutex.withLock {
sourceCache[sourceEntry.packageName] ?: run {
val classLoader = PathClassLoader(sourceEntry.sourceDir, null, context.classLoader)

Class.forName("${sourceEntry.packagePath}${sourceEntry.sourcePath}", false, classLoader)
.getConstructor(Application::class.java)
.newInstance(context.applicationContext) as Source
}.also { sourceCache[sourceEntry.packageName] = it }
}
}

fun updateSources(context: Context): List<SourceEntry> {
private fun loadSourceList(context: Context): List<SourceEntry> {
val packageManager = context.packageManager

val packages = packageManager.getInstalledPackages(
Expand All @@ -109,19 +105,4 @@ fun updateSources(context: Context): List<SourceEntry> {
else
emptyList()
}
}

@Composable
fun rememberSources(): State<List<SourceEntry>> {
val sources = remember { mutableStateOf<List<SourceEntry>>(emptyList()) }
val context = LocalContext.current

LaunchedEffect(Unit) {
while (true) {
sources.value = updateSources(context)
delay(1000)
}
}

return sources
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2019 tom5079
* Copyright (C) 2022 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand All @@ -16,26 +16,21 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

@file:Suppress("UNUSED_VARIABLE", "IncorrectScope")
package xyz.quaver.pupil.sources

package xyz.quaver.pupil
import androidx.compose.runtime.*
import kotlinx.coroutines.delay
import org.kodein.di.compose.localDI
import org.kodein.di.compose.rememberInstance
import org.kodein.di.direct
import org.kodein.di.instance
import xyz.quaver.pupil.util.PupilHttpClient
import xyz.quaver.pupil.util.RemoteSourceInfo

import io.ktor.client.*
import kotlinx.coroutines.runBlocking
import org.junit.Test
import xyz.quaver.pupil.sources.manatoki.getItem
import org.junit.Assert.*;

/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/

class ExampleUnitTest {

@Test
fun test() {
@Composable
fun rememberRemoteSourceList(client: PupilHttpClient = localDI().direct.instance()) = produceState<Map<String, RemoteSourceInfo>?>(null) {
while (true) {
value = client.getRemoteSourceList()
delay(1000)
}

}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2021 tom5079
* Copyright (C) 2022 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand All @@ -16,12 +16,16 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package xyz.quaver.pupil.migrate
package xyz.quaver.pupil.sources

class Migrate {
import android.graphics.drawable.Drawable

fun migrate() {

}

}
data class SourceEntry(
val packageName: String,
val packagePath: String,
val sourceName: String,
val sourcePath: String,
val sourceDir: String,
val icon: Drawable,
val version: String
)
2 changes: 0 additions & 2 deletions app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,6 @@ import xyz.quaver.pupil.ui.theme.PupilTheme
class MainActivity : ComponentActivity(), DIAware {
override val di by closestDI()

private val logger = newLogger(LoggerFactory.default)

@SuppressLint("UnusedCrossfadeTargetStateParameter")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand Down
Loading

0 comments on commit ef4c581

Please sign in to comment.