diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b25b0cf..730dcf6 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -51,7 +51,7 @@ android { dependencies { // core dependencies - implementation("androidx.appcompat:appcompat:1.3.1") + implementation("androidx.appcompat:appcompat:1.4.0") implementation("androidx.core:core-ktx:1.7.0") implementation("com.google.android.material:material:1.4.0") @@ -60,8 +60,8 @@ dependencies { implementation("androidx.compose.ui:ui:${composeVersion}") implementation("androidx.compose.material:material:${composeVersion}") implementation("androidx.compose.ui:ui-tooling:${composeVersion}") - implementation("androidx.constraintlayout:constraintlayout-compose:1.0.0-rc01") - implementation("androidx.navigation:navigation-compose:2.4.0-beta02") + implementation("androidx.constraintlayout:constraintlayout-compose:1.0.0-rc02") + implementation("androidx.navigation:navigation-compose:2.4.0-rc01") // accompanist dependencies implementation("com.google.accompanist:accompanist-systemuicontroller:${accompanistVersion}") @@ -69,4 +69,6 @@ dependencies { // other dependencies implementation("com.google.code.gson:gson:2.8.7") + implementation("io.coil-kt:coil-compose:1.4.0") + } diff --git a/app/src/main/java/com/aliucord/manager/models/GithubUser.kt b/app/src/main/java/com/aliucord/manager/models/GithubUser.kt new file mode 100644 index 0000000..ce925da --- /dev/null +++ b/app/src/main/java/com/aliucord/manager/models/GithubUser.kt @@ -0,0 +1,3 @@ +package com.aliucord.manager.models + +data class GithubUser(val login: String, val contributions: Int) \ No newline at end of file diff --git a/app/src/main/java/com/aliucord/manager/preferences/PreferencesHolder.kt b/app/src/main/java/com/aliucord/manager/preferences/PreferencesHolder.kt index bf14bba..f0e78a3 100644 --- a/app/src/main/java/com/aliucord/manager/preferences/PreferencesHolder.kt +++ b/app/src/main/java/com/aliucord/manager/preferences/PreferencesHolder.kt @@ -9,5 +9,7 @@ import android.os.Environment val themePreference = intPreference("theme") val replaceBgPreference = boolPreference("replace_bg", true) +val devModePreference = boolPreference("dev_mode", false) +val debuggablePreference = boolPreference("debuggable", false) val useDexFromStoragePreference = boolPreference("use_dex_from_storage") val dexLocationPreference = stringPreference("dex_location", Environment.getExternalStorageDirectory().absolutePath + "/Aliucord/Aliucord.dex") diff --git a/app/src/main/java/com/aliucord/manager/ui/MainActivity.kt b/app/src/main/java/com/aliucord/manager/ui/MainActivity.kt index e030e91..a79e48e 100644 --- a/app/src/main/java/com/aliucord/manager/ui/MainActivity.kt +++ b/app/src/main/java/com/aliucord/manager/ui/MainActivity.kt @@ -123,13 +123,21 @@ fun MainActivityLayout() { ) } DropdownMenu(expanded = isMenuExpanded, onDismissRequest = { isMenuExpanded = false }) { + DropdownMenuItem(onClick = { + isMenuExpanded = false + navController.navigate(Screen.About.route) { + popUpTo(Screen.Home.route) { saveState = true } + } + }) { + Text(stringResource(Screen.About.displayName)) + } DropdownMenuItem(onClick = { isMenuExpanded = false navController.navigate(Screen.Settings.route) { popUpTo(Screen.Home.route) { saveState = true } } }) { - Text(stringResource(id = Screen.Settings.displayName)) + Text(stringResource(Screen.Settings.displayName)) } } } diff --git a/app/src/main/java/com/aliucord/manager/ui/Screen.kt b/app/src/main/java/com/aliucord/manager/ui/Screen.kt index 51259bf..5f0f6b7 100644 --- a/app/src/main/java/com/aliucord/manager/ui/Screen.kt +++ b/app/src/main/java/com/aliucord/manager/ui/Screen.kt @@ -9,6 +9,7 @@ import androidx.annotation.StringRes import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.runtime.Composable import com.aliucord.manager.R +import com.aliucord.manager.ui.screens.AboutScreen import com.aliucord.manager.ui.screens.HomeScreen import com.aliucord.manager.ui.screens.PluginsScreen import com.aliucord.manager.ui.screens.SettingsScreen @@ -20,7 +21,7 @@ sealed class Screen( ) { companion object { // NOTE: without lazy it doesn't work, kotlin moment https://youtrack.jetbrains.com/issue/KT-8970 - val SCREENS by lazy { listOf(Home, Settings, Plugins) } + val SCREENS by lazy { listOf(Home, Settings, Plugins, About) } } object Home : Screen( @@ -29,6 +30,12 @@ sealed class Screen( { HomeScreen() } ) + object About : Screen( + "About", + R.string.about, + { AboutScreen() } + ) + object Settings : Screen( "settings", R.string.settings, diff --git a/app/src/main/java/com/aliucord/manager/ui/screens/AboutScreen.kt b/app/src/main/java/com/aliucord/manager/ui/screens/AboutScreen.kt new file mode 100644 index 0000000..72662b6 --- /dev/null +++ b/app/src/main/java/com/aliucord/manager/ui/screens/AboutScreen.kt @@ -0,0 +1,136 @@ +package com.aliucord.manager.ui.screens + +import android.util.Log +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.GridCells +import androidx.compose.foundation.lazy.LazyVerticalGrid +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.runtime.* +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import coil.compose.rememberImagePainter +import coil.transform.CircleCropTransformation +import com.aliucord.manager.BuildConfig +import com.aliucord.manager.R +import com.aliucord.manager.models.GithubUser +import com.aliucord.manager.utils.Github +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +@ExperimentalFoundationApi +@OptIn(ExperimentalMaterialApi::class) +@Composable +@Preview +fun AboutScreen() { + val coroutineScope = rememberCoroutineScope { Dispatchers.IO } + val modifier = Modifier.padding(8.dp) + val contributors = remember { mutableStateOf?>(null) } + + Column(modifier = Modifier.padding(16.dp)) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + stringResource(R.string.team), + style = MaterialTheme.typography.h4, + ) + + UserEntry("Juby210", "Fox") + UserEntry("torvalds", "Epic") + UserEntry("cyyynthia", "PSA: Uninstall Client mods by Cynthia") + } + + Column( + modifier = modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + stringResource(R.string.contributors), + style = MaterialTheme.typography.h4, + ) + + if (contributors.value == null) { + Text( + stringResource(R.string.loading) + ) + LaunchedEffect(null) { + coroutineScope.launch { + try { + contributors.value = Github.contributors + } catch (err: Throwable) { + Log.e(BuildConfig.TAG, "Failed to load contributors", err) + } + } + } + } else { + LazyVerticalGrid( + cells = GridCells.Adaptive(48.dp) + ) { + items(contributors.value!!) { + ContributorEntry(it.login) + } + } + } + } + } +} + +@Composable +fun UserEntry(name: String, roles: String) { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp, 8.dp), shape = RoundedCornerShape(15.dp) + ) { + Column(modifier = Modifier.padding(16.dp, 8.dp)) { + Row(verticalAlignment = Alignment.CenterVertically) { + Image( + painter = rememberImagePainter( + data = "https://github.com/$name.png", + builder = { + transformations(CircleCropTransformation()) + } + ), + contentDescription = null, + modifier = Modifier + .size(48.dp) + .padding(end = 8.dp) + ) + Column { + Text( + name, + style = MaterialTheme.typography.h6, + ) + Text(roles) + } + } + } + } +} + +@Composable +fun ContributorEntry(name: String) { + val handler = LocalUriHandler.current + Image( + painter = rememberImagePainter( + data = "https://github.com/$name.png", + ), + contentDescription = null, + modifier = Modifier + .size(48.dp) + .clickable { handler.openUri("https://github.com/$name") } + ) +} diff --git a/app/src/main/java/com/aliucord/manager/ui/screens/PluginsScreen.kt b/app/src/main/java/com/aliucord/manager/ui/screens/PluginsScreen.kt index 4984283..7e5b2d0 100644 --- a/app/src/main/java/com/aliucord/manager/ui/screens/PluginsScreen.kt +++ b/app/src/main/java/com/aliucord/manager/ui/screens/PluginsScreen.kt @@ -154,4 +154,4 @@ fun PluginsScreen() { } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/aliucord/manager/ui/screens/SettingsScreen.kt b/app/src/main/java/com/aliucord/manager/ui/screens/SettingsScreen.kt index be026e0..7dcdff5 100644 --- a/app/src/main/java/com/aliucord/manager/ui/screens/SettingsScreen.kt +++ b/app/src/main/java/com/aliucord/manager/ui/screens/SettingsScreen.kt @@ -17,10 +17,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.aliucord.manager.R -import com.aliucord.manager.preferences.dexLocationPreference -import com.aliucord.manager.preferences.replaceBgPreference -import com.aliucord.manager.preferences.themePreference -import com.aliucord.manager.preferences.useDexFromStoragePreference +import com.aliucord.manager.preferences.* import com.aliucord.manager.ui.components.ExposedDropdownMenu private val themes = arrayOf("System", "Light", "Dark", "Black") @@ -52,23 +49,49 @@ fun SettingsScreen() { }, modifier = modifier.clickable { replaceBgPreference.set(!replaceBgPreference.get()) } ) - val udfs = useDexFromStoragePreference.get() + + val devModeOn = devModePreference.get() ListItem( - text = { Text(stringResource(R.string.use_dex_from_storage)) }, + text = { Text(stringResource(R.string.developer_mode)) }, trailing = { Checkbox( - checked = udfs, - onCheckedChange = useDexFromStoragePreference::set + checked = devModeOn, + onCheckedChange = devModePreference::set ) }, - modifier = modifier.clickable { useDexFromStoragePreference.set(!useDexFromStoragePreference.get()) } - ) - val color = if (udfs) Color.Unspecified else MaterialTheme.colors.onBackground.copy(ContentAlpha.disabled) - ListItem( - text = { Text(stringResource(R.string.dex_location), color = color) }, - secondaryText = { Text(dexLocationPreference.get(), color = color) }, - modifier = if (udfs) modifier.clickable { /*TODO*/ } else modifier + modifier = modifier ) + if (devModeOn) { + ListItem( + text = { Text(stringResource(R.string.debuggable)) }, + trailing = { + Checkbox( + checked = debuggablePreference.get(), + onCheckedChange = debuggablePreference::set + ) + }, + modifier = modifier + ) + + val udfs = useDexFromStoragePreference.get() + ListItem( + text = { Text(stringResource(R.string.use_dex_from_storage)) }, + trailing = { + Checkbox( + checked = udfs, + onCheckedChange = useDexFromStoragePreference::set + ) + }, + modifier = modifier.clickable { useDexFromStoragePreference.set(!useDexFromStoragePreference.get()) } + ) + val color = + if (udfs) Color.Unspecified else MaterialTheme.colors.onBackground.copy(ContentAlpha.disabled) + ListItem( + text = { Text(stringResource(R.string.dex_location), color = color) }, + secondaryText = { Text(dexLocationPreference.get(), color = color) }, + modifier = if (udfs) modifier.clickable { /*TODO*/ } else modifier + ) + } Button( onClick = { /*TODO*/ }, Modifier diff --git a/app/src/main/java/com/aliucord/manager/utils/Downloader.kt b/app/src/main/java/com/aliucord/manager/utils/Downloader.kt new file mode 100644 index 0000000..06ef186 --- /dev/null +++ b/app/src/main/java/com/aliucord/manager/utils/Downloader.kt @@ -0,0 +1,39 @@ +package com.aliucord.manager.utils + +import android.app.DownloadManager +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.net.Uri +import java.io.File +import java.util.concurrent.CountDownLatch + +object Downloader { + fun download(ctx: Context, url: String, file: File) { + val dm = ctx.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager + val latch = CountDownLatch(1) + with (DownloadReceiver(ctx, latch)) { + ctx.registerReceiver(this, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) + downloadId = DownloadManager.Request(Uri.parse(url)).run { + setTitle("AliucordManager") + setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); + setDestinationUri(Uri.fromFile(file)) + dm.enqueue(this) + } + } + latch.await() + } +} + +class DownloadReceiver(private val ctx: Context, private val latch: CountDownLatch) : BroadcastReceiver() { + var downloadId = 0L + + override fun onReceive(context: Context, intent: Intent) { + val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) + if (downloadId == id) { + latch.countDown() + ctx.unregisterReceiver(this) + } + } +} diff --git a/app/src/main/java/com/aliucord/manager/utils/Github.kt b/app/src/main/java/com/aliucord/manager/utils/Github.kt index 6486990..ca6b681 100644 --- a/app/src/main/java/com/aliucord/manager/utils/Github.kt +++ b/app/src/main/java/com/aliucord/manager/utils/Github.kt @@ -6,6 +6,7 @@ package com.aliucord.manager.utils import com.aliucord.manager.models.Commit +import com.aliucord.manager.models.GithubUser import com.google.gson.Gson import com.google.gson.reflect.TypeToken import java.io.InputStreamReader @@ -18,6 +19,7 @@ object Github { private const val repo = "Aliucord" private const val commitsUrl = "https://api.github.com/repos/$org/$repo/commits" + private const val contributorsUrl = "https://api.github.com/repos/$org/$repo/contributors" private const val dataUrl = "https://raw.githubusercontent.com/$org/$repo/builds/data.json" fun checkForUpdates() { @@ -30,21 +32,35 @@ object Github { private val commitsCache = HashMap>() private val commitsType = object : TypeToken>() {}.type + private val contributorsType = object : TypeToken>() {}.type private val gson = Gson() fun getCommits(params: Map?): Array { // return emptyArray() val query = params?.map { it.key + "=" + it.value }?.joinToString("&", "?") ?: "" if (commitsCache.containsKey(query)) return commitsCache[query] ?: emptyArray() - val res = gson.fromJson>( - InputStreamReader(URL(commitsUrl + query).openStream()), - commitsType - ) + val res = InputStreamReader(URL(commitsUrl + query).openStream()).use { + gson.fromJson>( + it, + commitsType + ) + } commitsCache[query] = res return res } + val contributors: Array by lazy { + InputStreamReader(URL(contributorsUrl).openStream()).use { stream -> + gson.fromJson>( + stream, + contributorsType + ).apply { + sortByDescending { it.contributions } + } + } + } + fun getDownloadUrl(ref: String, file: String): String { - return "'https://raw.githubusercontent.com/${org}/${repo}/${ref}/${file}"; + return "https://raw.githubusercontent.com/${org}/${repo}/${ref}/${file}"; } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d5be4e7..7d248f6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -25,4 +25,10 @@ Install Reinstall Update + Enable Developer Mode + Make Aliucord debuggable + About + Team + Contributors + Loading...