diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
deleted file mode 100644
index 538b1cb5..00000000
--- a/.idea/deploymentTargetDropDown.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index f86326c0..79e6e0f1 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -72,8 +72,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.5.2-native-mt")
- implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0")
+ implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2")
implementation("androidx.activity:activity-compose:1.4.0")
implementation("androidx.navigation:navigation-compose:2.4.0-rc01")
@@ -100,7 +100,7 @@ dependencies {
implementation("io.ktor:ktor-client-okhttp:1.6.7")
implementation("io.ktor:ktor-client-serialization:1.6.7")
- implementation("androidx.appcompat:appcompat:1.4.0")
+ implementation("androidx.appcompat:appcompat:1.4.1")
implementation("androidx.activity:activity-ktx:1.4.0")
implementation("androidx.fragment:fragment-ktx:1.4.0")
implementation("androidx.preference:preference-ktx:1.1.1")
@@ -110,17 +110,17 @@ dependencies {
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.4.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.4.0")
- implementation("androidx.room:room-runtime:2.4.0")
- annotationProcessor("androidx.room:room-compiler:2.4.0")
- kapt("androidx.room:room-compiler:2.4.0")
- implementation("androidx.room:room-ktx:2.4.0")
+ implementation("androidx.room:room-runtime:2.4.1")
+ annotationProcessor("androidx.room:room-compiler:2.4.1")
+ kapt("androidx.room:room-compiler:2.4.1")
+ implementation("androidx.room:room-ktx:2.4.1")
implementation("androidx.datastore:datastore:1.0.0")
implementation("androidx.datastore:datastore-preferences:1.0.0")
implementation("org.kodein.di:kodein-di-framework-compose:7.10.0")
- implementation("com.google.android.material:material:1.4.0")
+ implementation("com.google.android.material:material:1.5.0")
implementation(platform("com.google.firebase:firebase-bom:29.0.3"))
implementation("com.google.firebase:firebase-analytics-ktx")
@@ -135,9 +135,9 @@ dependencies {
implementation("ru.noties.markwon:core:3.1.0")
- implementation("xyz.quaver.pupil.sources:core:0.0.1-alpha01-DEV10")
+ implementation("xyz.quaver.pupil.sources:core:0.0.1-alpha01-DEV16")
- implementation("xyz.quaver:documentfilex:0.7.1")
+ implementation("xyz.quaver:documentfilex:0.7.2")
implementation("xyz.quaver:subsampledimage:0.0.1-alpha19-SNAPSHOT")
implementation("com.google.guava:guava:31.0.1-jre")
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c643728e..7a3c0bbc 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,14 +3,11 @@
xmlns:tools="http://schemas.android.com/tools"
package="xyz.quaver.pupil">
-
-
-
@@ -38,17 +35,8 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/java/xyz/quaver/pupil/Pupil.kt b/app/src/main/java/xyz/quaver/pupil/Pupil.kt
index 4a048efa..ca909135 100644
--- a/app/src/main/java/xyz/quaver/pupil/Pupil.kt
+++ b/app/src/main/java/xyz/quaver/pupil/Pupil.kt
@@ -23,41 +23,31 @@ import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
-import android.content.Intent
-import android.net.Uri
import android.os.Build
-import androidx.appcompat.app.AppCompatDelegate
-import androidx.preference.PreferenceManager
import com.google.android.gms.common.GooglePlayServicesNotAvailableException
import com.google.android.gms.common.GooglePlayServicesRepairableException
import com.google.android.gms.security.ProviderInstaller
-import com.google.firebase.analytics.FirebaseAnalytics
-import com.google.firebase.analytics.ktx.analytics
-import com.google.firebase.crashlytics.FirebaseCrashlytics
-import com.google.firebase.ktx.Firebase
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import io.ktor.client.features.*
import io.ktor.client.features.cache.*
import io.ktor.client.features.json.*
import io.ktor.client.features.json.serializer.*
+import io.ktor.http.*
import okhttp3.Protocol
import org.kodein.di.*
import org.kodein.di.android.x.androidXModule
-import xyz.quaver.io.FileX
-import xyz.quaver.pupil.proto.settingsDataStore
import xyz.quaver.pupil.sources.core.NetworkCache
-import xyz.quaver.pupil.sources.sourceModule
-import xyz.quaver.pupil.util.*
-import java.util.*
+import xyz.quaver.pupil.sources.core.settingsDataStore
+import xyz.quaver.pupil.util.ApkDownloadManager
class Pupil : Application(), DIAware {
override val di: DI by DI.lazy {
import(androidXModule(this@Pupil))
- import(sourceModule(this@Pupil))
- bind { singleton { NetworkCache(applicationContext) } }
+ bind { singleton { NetworkCache(this@Pupil, instance()) } }
+ bindSingleton { ApkDownloadManager(this@Pupil, instance()) }
bindSingleton { settingsDataStore }
@@ -70,6 +60,7 @@ class Pupil : Application(), DIAware {
}
install(JsonFeature) {
serializer = KotlinxSerializer()
+ accept(ContentType("text", "plain"))
}
install(HttpTimeout) {
requestTimeoutMillis = HttpTimeout.INFINITE_TIMEOUT_MS
@@ -82,43 +73,8 @@ class Pupil : Application(), DIAware {
} }
}
- private lateinit var firebaseAnalytics: FirebaseAnalytics
-
override fun onCreate() {
- AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
-
- preferences = PreferenceManager.getDefaultSharedPreferences(this)
-
- val userID = Preferences["user_id", ""].let { userID ->
- if (userID.isEmpty()) UUID.randomUUID().toString().also { Preferences["user_id"] = it }
- else userID
- }
-
- firebaseAnalytics = Firebase.analytics
- FirebaseCrashlytics.getInstance().setUserId(userID)
-
- try {
- Preferences.get("download_folder").also {
- if (it.startsWith("content"))
- contentResolver.takePersistableUriPermission(
- Uri.parse(it),
- Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
- )
-
- if (!FileX(this, it).canWrite())
- throw Exception()
- }
- } catch (e: Exception) {
- Preferences.remove("download_folder")
- }
-
- if (!Preferences["reset_secure", false]) {
- Preferences["security_mode"] = false
- Preferences["reset_secure"] = true
- }
-
- if (BuildConfig.DEBUG)
- FirebaseAnalytics.getInstance(this).setAnalyticsCollectionEnabled(false)
+ super.onCreate()
try {
ProviderInstaller.installIfNeeded(this)
@@ -151,21 +107,7 @@ class Pupil : Application(), DIAware {
enableVibration(true)
lockscreenVisibility = Notification.VISIBILITY_SECRET
})
-
- manager.createNotificationChannel(NotificationChannel("import", getString(R.string.channel_update), NotificationManager.IMPORTANCE_LOW).apply {
- description = getString(R.string.channel_update_description)
- enableLights(false)
- enableVibration(false)
- lockscreenVisibility = Notification.VISIBILITY_SECRET
- })
}
-
- AppCompatDelegate.setDefaultNightMode(when (Preferences.get("dark_mode")) {
- true -> AppCompatDelegate.MODE_NIGHT_YES
- false -> AppCompatDelegate.MODE_NIGHT_NO
- })
-
- super.onCreate()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/proto/Settings.kt b/app/src/main/java/xyz/quaver/pupil/proto/Settings.kt
deleted file mode 100644
index 301437db..00000000
--- a/app/src/main/java/xyz/quaver/pupil/proto/Settings.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Pupil, Hitomi.la viewer for Android
- * Copyright (C) 2021 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
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package xyz.quaver.pupil.proto
-
-import android.app.Application
-import android.content.Context
-import androidx.datastore.core.CorruptionException
-import androidx.datastore.core.DataStore
-import androidx.datastore.core.Serializer
-import androidx.datastore.dataStore
-import com.google.protobuf.InvalidProtocolBufferException
-import java.io.InputStream
-import java.io.OutputStream
-
-object SettingsSerializer : Serializer {
- override val defaultValue: Settings = Settings.getDefaultInstance()
-
- override suspend fun readFrom(input: InputStream): Settings {
- try {
- return Settings.parseFrom(input)
- } catch (exception: InvalidProtocolBufferException) {
- throw CorruptionException("Cannot read proto.", exception)
- }
- }
-
- override suspend fun writeTo(t: Settings, output: OutputStream) = t.writeTo(output)
-}
-
-val Application.settingsDataStore: DataStore by dataStore(
- fileName = "settings.proto",
- serializer = SettingsSerializer
-)
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/receiver/UpdateBroadcastReceiver.kt b/app/src/main/java/xyz/quaver/pupil/receiver/UpdateBroadcastReceiver.kt
deleted file mode 100644
index badff65e..00000000
--- a/app/src/main/java/xyz/quaver/pupil/receiver/UpdateBroadcastReceiver.kt
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Pupil, Hitomi.la viewer for Android
- * Copyright (C) 2020 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
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package xyz.quaver.pupil.receiver
-
-import android.app.DownloadManager
-import android.app.PendingIntent
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.net.Uri
-import android.webkit.MimeTypeMap
-import androidx.core.app.NotificationCompat
-import androidx.core.app.NotificationManagerCompat
-import androidx.core.content.FileProvider
-import xyz.quaver.pupil.R
-import xyz.quaver.pupil.util.Preferences
-import java.io.File
-
-class UpdateBroadcastReceiver : BroadcastReceiver() {
-
- override fun onReceive(context: Context?, intent: Intent?) {
- context ?: return
-
- when (intent?.action) {
- DownloadManager.ACTION_DOWNLOAD_COMPLETE -> {
-
- // Validate download
- val downloadID: Long = Preferences["update_download_id"]
- val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
-
- if (intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -2) != downloadID)
- return
-
- // Get target uri
-
- val query = DownloadManager.Query()
- .setFilterById(downloadID)
-
- val uri = downloadManager.query(query).use { cursor ->
- if (cursor.moveToFirst()) {
- cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)).let {
- val uri = Uri.parse(it)
-
- when (uri.scheme) {
- "file" ->
- FileProvider.getUriForFile(context, context.applicationContext.packageName + ".provider", File(uri.path!!)
- )
- "content" -> uri
- else -> null
- }
- }
- } else
- null
- } ?: return
-
- // Build Notification
-
- val notificationManager = NotificationManagerCompat.from(context)
-
- val pendingIntent = PendingIntent.getActivity(context, 0, Intent(Intent.ACTION_VIEW).apply {
- flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK
- setDataAndType(uri, MimeTypeMap.getSingleton().getMimeTypeFromExtension("apk"))
- }, 0)
-
- val notification = NotificationCompat.Builder(context, "update")
- .setSmallIcon(android.R.drawable.stat_sys_download_done)
- .setContentTitle(context.getText(R.string.update_download_completed))
- .setContentText(context.getText(R.string.update_download_completed_description))
- .setContentIntent(pendingIntent)
- .build()
-
- notificationManager.notify(R.id.notification_id_update, notification)
- }
- }
- }
-
-}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/sources/SourceLoader.kt b/app/src/main/java/xyz/quaver/pupil/sources/SourceLoader.kt
index 973bff16..c9d1d718 100644
--- a/app/src/main/java/xyz/quaver/pupil/sources/SourceLoader.kt
+++ b/app/src/main/java/xyz/quaver/pupil/sources/SourceLoader.kt
@@ -22,69 +22,105 @@ import android.app.Application
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
+import androidx.compose.runtime.*
+import androidx.compose.runtime.snapshots.SnapshotStateList
+import androidx.compose.runtime.snapshots.SnapshotStateMap
import dalvik.system.PathClassLoader
-import org.kodein.di.*
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import org.kodein.di.DI
+import org.kodein.di.bindFactory
+import org.kodein.di.bindInstance
+import org.kodein.di.bindProvider
+import org.kodein.di.compose.rememberInstance
import xyz.quaver.pupil.sources.core.Source
-import java.util.*
+import java.util.concurrent.ConcurrentHashMap
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 name: String,
- val source: Source,
- val icon: Drawable
+ val packageName: String,
+ val packagePath: String,
+ val sourceName: String,
+ val sourcePath: String,
+ val sourceDir: String,
+ val icon: Drawable,
+ val version: String
)
typealias SourceEntries = Map
-private val sources = mutableMapOf()
-
val PackageInfo.isSourceFeatureEnabled
get() = this.reqFeatures.orEmpty().any { it.name == SOURCES_FEATURE }
-fun loadSource(app: Application, packageInfo: PackageInfo) {
+fun loadSource(app: Application, packageInfo: PackageInfo): List {
val packageManager = app.packageManager
+
val applicationInfo = packageInfo.applicationInfo
- val classLoader = PathClassLoader(applicationInfo.sourceDir, null, app.classLoader)
- val packageName = packageInfo.packageName
-
- val sourceName = packageManager.getApplicationLabel(applicationInfo).toString().substringAfter("[Pupil] ")
+ val packageName = packageManager.getApplicationLabel(applicationInfo).toString().substringAfter("[Pupil] ")
+ val packagePath = packageInfo.packageName
val icon = packageManager.getApplicationIcon(applicationInfo)
- packageInfo
+ val version = packageInfo.versionName
+
+ return packageInfo
.applicationInfo
.metaData
- .getString(SOURCES_PATH)
+ ?.getString(SOURCES_PATH)
?.split(';')
- .orEmpty()
- .forEach { sourcePath ->
- sources[sourceName] = SourceEntry(
+ ?.map { source ->
+ val (sourceName, sourcePath) = source.split(':', limit = 2)
+ SourceEntry(
+ packageName,
+ packagePath,
sourceName,
- Class.forName("$packageName$sourcePath", false, classLoader)
- .getConstructor(Application::class.java)
- .newInstance(app) as Source,
- icon
+ sourcePath,
+ applicationInfo.sourceDir,
+ icon,
+ version
)
- }
+ }.orEmpty()
}
-fun loadSources(app: Application) {
+fun loadSource(app: Application, sourceEntry: SourceEntry): Source {
+ val classLoader = PathClassLoader(sourceEntry.sourceDir, null, app.classLoader)
+
+ return Class.forName("${sourceEntry.packagePath}${sourceEntry.sourcePath}", false, classLoader)
+ .getConstructor(Application::class.java)
+ .newInstance(app) as Source
+}
+
+fun updateSources(app: Application): List {
val packageManager = app.packageManager
val packages = packageManager.getInstalledPackages(
- PackageManager.GET_CONFIGURATIONS or
- PackageManager.GET_META_DATA
+ PackageManager.GET_CONFIGURATIONS or PackageManager.GET_META_DATA
)
- val sources = packages.filter { it.isSourceFeatureEnabled }
-
- sources.forEach { loadSource(app, it) }
+ return packages.flatMap { packageInfo ->
+ if (packageInfo.isSourceFeatureEnabled)
+ loadSource(app, packageInfo)
+ else
+ emptyList()
+ }
}
-fun sourceModule(app: Application) = DI.Module(name = "source") {
- loadSources(app)
- bindInstance { Collections.unmodifiableMap(sources) }
+@Composable
+fun rememberSources(): State> {
+ val app: Application by rememberInstance()
+ val sources = remember { mutableStateOf>(emptyList()) }
+
+ LaunchedEffect(Unit) {
+ while (true) {
+ sources.value = updateSources(app)
+ delay(1000)
+ }
+ }
+
+ return sources
}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/sources/SourceSelectDialog.kt b/app/src/main/java/xyz/quaver/pupil/sources/SourceSelectDialog.kt
deleted file mode 100644
index 3d04c33c..00000000
--- a/app/src/main/java/xyz/quaver/pupil/sources/SourceSelectDialog.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Pupil, Hitomi.la viewer for Android
- * Copyright (C) 2021 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
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package xyz.quaver.pupil.sources
-
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.*
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Settings
-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.window.Dialog
-import androidx.navigation.NavController
-import com.google.accompanist.drawablepainter.rememberDrawablePainter
-import org.kodein.di.compose.rememberInstance
-import xyz.quaver.pupil.sources.core.Source
-
-@Composable
-fun SourceSelectDialog(navController: NavController, currentSource: String? = null, onDismissRequest: () -> Unit = { }) {
- SourceSelectDialog(currentSource = currentSource, onDismissRequest = onDismissRequest) {
- onDismissRequest()
- navController.navigate(it.name) {
- currentSource?.let { popUpTo("main") }
- }
- }
-}
-
-@Composable
-fun SourceSelectDialogItem(sourceEntry: SourceEntry, isSelected: Boolean, onSelected: (Source) -> Unit = { }) {
- Row(
- modifier = Modifier.padding(16.dp),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.spacedBy(16.dp)
- ) {
- Image(
- painter = rememberDrawablePainter(sourceEntry.icon),
- contentDescription = null,
- modifier = Modifier.size(24.dp)
- )
-
- Text(
- sourceEntry.name,
- modifier = Modifier.weight(1f)
- )
-
- Icon(
- Icons.Default.Settings,
- contentDescription = null,
- tint = MaterialTheme.colors.onSurface.copy(alpha = 0.5f)
- )
-
- Button(
- enabled = !isSelected,
- onClick = {
- onSelected(sourceEntry.source)
- }
- ) {
- Text("GO")
- }
-
- }
-}
-
-@Composable
-fun SourceSelectDialog(currentSource: String? = null, onDismissRequest: () -> Unit = { }, onSelected: (Source) -> Unit = { }) {
- val sourceEntries: SourceEntries by rememberInstance()
-
- Dialog(onDismissRequest = onDismissRequest) {
- Card(
- elevation = 8.dp,
- shape = RoundedCornerShape(12.dp)
- ) {
- Column() {
- sourceEntries.values.forEach { SourceSelectDialogItem(it, it.name == currentSource, onSelected) }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt
index 5068117b..c085626f 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt
@@ -20,39 +20,36 @@ package xyz.quaver.pupil.ui
import android.os.Bundle
import androidx.activity.ComponentActivity
+import androidx.activity.compose.BackHandler
import androidx.activity.compose.setContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.*
-import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.graphics.Color
import androidx.core.view.WindowCompat
-import androidx.datastore.core.DataStore
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
+import androidx.navigation.createGraph
import com.google.accompanist.insets.ProvideWindowInsets
import com.google.accompanist.systemuicontroller.rememberSystemUiController
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
-import org.kodein.di.compose.rememberInstance
import org.kodein.di.instance
import org.kodein.log.LoggerFactory
import org.kodein.log.newLogger
-import xyz.quaver.pupil.proto.Settings
import xyz.quaver.pupil.sources.SourceEntries
-import xyz.quaver.pupil.sources.SourceSelectDialog
+import xyz.quaver.pupil.sources.SourceEntry
+import xyz.quaver.pupil.sources.core.Source
+import xyz.quaver.pupil.sources.loadSource
import xyz.quaver.pupil.ui.theme.PupilTheme
@OptIn(ExperimentalAnimationApi::class, ExperimentalMaterialApi::class)
class MainActivity : ComponentActivity(), DIAware {
override val di by closestDI()
- private val sources: SourceEntries by instance()
-
private val logger = newLogger(LoggerFactory.default)
override fun onCreate(savedInstanceState: Bundle?) {
@@ -68,6 +65,8 @@ class MainActivity : ComponentActivity(), DIAware {
val systemUiController = rememberSystemUiController()
val useDarkIcons = MaterialTheme.colors.isLight
+ val coroutineScope = rememberCoroutineScope()
+
SideEffect {
systemUiController.setSystemBarsColor(
color = Color.Transparent,
@@ -75,42 +74,29 @@ class MainActivity : ComponentActivity(), DIAware {
)
}
- NavHost(navController, startDestination = "main") {
- composable("main") {
- var launched by rememberSaveable { mutableStateOf(false) }
- val settingsDataStore: DataStore by rememberInstance()
+ NavHost(navController, "source") {
+ composable("source") {
+ var source by remember { mutableStateOf(null) }
- var sourceSelectDialog by remember { mutableStateOf(false) }
+ BackHandler(
+ enabled = source != null
+ ) {
+ source = null
+ }
- if (sourceSelectDialog)
- SourceSelectDialog(navController, null)
-
- LaunchedEffect(Unit) {
- val recentSource =
- settingsDataStore.data.map { it.recentSource }
- .first()
-
- if (recentSource.isEmpty()) {
- sourceSelectDialog = true
- launched = true
- } else {
- if (!launched) {
- navController.navigate(recentSource)
- launched = true
- } else {
- onBackPressed()
+ if (source == null)
+ SourceSelector {
+ coroutineScope.launch {
+ source = loadSource(application, it)
}
}
+ else {
+ source!!.Entry()
}
}
composable("settings") {
}
- sources.values.forEach {
- it.source.run {
- navGraph(navController)
- }
- }
}
}
}
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/SourceSelector.kt b/app/src/main/java/xyz/quaver/pupil/ui/SourceSelector.kt
new file mode 100644
index 00000000..0260aa06
--- /dev/null
+++ b/app/src/main/java/xyz/quaver/pupil/ui/SourceSelector.kt
@@ -0,0 +1,295 @@
+/*
+ * Pupil, Hitomi.la viewer for Android
+ * Copyright (C) 2021 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package xyz.quaver.pupil.ui
+
+import android.content.Intent
+import android.net.Uri
+import android.provider.Settings
+import android.util.Log
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.*
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Download
+import androidx.compose.material.icons.filled.DownloadDone
+import androidx.compose.material.icons.filled.Explore
+import androidx.compose.material.icons.filled.Info
+import androidx.compose.material.icons.outlined.Info
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.text.capitalize
+import androidx.compose.ui.text.intl.Locale
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.navigation.NavController
+import androidx.navigation.NavDestination.Companion.hierarchy
+import androidx.navigation.NavGraph.Companion.findStartDestination
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.currentBackStackEntryAsState
+import androidx.navigation.compose.rememberNavController
+import coil.compose.rememberImagePainter
+import com.google.accompanist.drawablepainter.rememberDrawablePainter
+import com.google.accompanist.insets.LocalWindowInsets
+import com.google.accompanist.insets.rememberInsetsPaddingValues
+import com.google.accompanist.insets.ui.BottomNavigation
+import com.google.accompanist.insets.ui.Scaffold
+import com.google.accompanist.insets.ui.TopAppBar
+import io.ktor.client.*
+import io.ktor.client.request.*
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.onCompletion
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import kotlinx.serialization.Serializable
+import org.kodein.di.compose.rememberInstance
+import xyz.quaver.pupil.sources.SourceEntries
+import xyz.quaver.pupil.sources.SourceEntry
+import xyz.quaver.pupil.sources.rememberSources
+import xyz.quaver.pupil.util.ApkDownloadManager
+
+private sealed class SourceSelectorScreen(val route: String, val icon: ImageVector) {
+ object Local: SourceSelectorScreen("local", Icons.Default.DownloadDone)
+ object Explore: SourceSelectorScreen("explore", Icons.Default.Explore)
+}
+
+private val sourceSelectorScreens = listOf(
+ SourceSelectorScreen.Local,
+ SourceSelectorScreen.Explore
+)
+
+@Composable
+fun SourceListItem(icon: Painter, name: String, version: String, actions: @Composable () -> Unit = { }) {
+ Card(
+ modifier = Modifier.padding(8.dp),
+ elevation = 4.dp
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ Image(
+ icon,
+ contentDescription = "source icon",
+ modifier = Modifier.size(48.dp)
+ )
+
+ Column(
+ Modifier.weight(1f)
+ ) {
+ Text(
+ name.capitalize(Locale.current)
+ )
+
+ CompositionLocalProvider(LocalContentAlpha provides 0.5f) {
+ Text(
+ "v$version",
+ style = MaterialTheme.typography.caption
+ )
+ }
+ }
+
+ actions()
+ }
+ }
+}
+
+@Composable
+fun Local(onSource: (SourceEntry) -> Unit) {
+ val sources by rememberSources()
+
+ if (sources.isEmpty()) {
+ Box(Modifier.fillMaxSize()) {
+ Column(
+ Modifier.align(Alignment.Center),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ CompositionLocalProvider(LocalContentAlpha provides 0.5f) {
+ Text("(´∇`)", style = MaterialTheme.typography.h2)
+ }
+ Text("No sources found!\nLet's go download one.", textAlign = TextAlign.Center)
+ }
+ }
+ } else {
+ LazyColumn {
+ items(sources) { source ->
+ SourceListItem(
+ rememberDrawablePainter(source.icon),
+ source.sourceName,
+ source.version
+ ) {
+ TextButton(
+ onClick = { onSource(source) }
+ ) {
+ Text("GO")
+ }
+ }
+ }
+ }
+ }
+}
+
+@Serializable
+private data class RemoteSourceInfo(
+ val projectName: String,
+ val name: String,
+ val version: String
+)
+
+@Composable
+fun Explore() {
+ val sources by rememberSources()
+ val localSources by derivedStateOf {
+ sources.map {
+ it.packageName to it
+ }.toMap()
+ }
+
+ val client: HttpClient by rememberInstance()
+
+ val downloadManager: ApkDownloadManager by rememberInstance()
+ val progresses = remember { mutableStateMapOf() }
+
+ val context = LocalContext.current
+
+ val coroutineScope = rememberCoroutineScope()
+
+ val remoteSources by produceState