From f0cd29c500a9990825933d1f22d6122dbb14f6d0 Mon Sep 17 00:00:00 2001 From: litleck Date: Thu, 17 Feb 2022 18:25:22 -0500 Subject: [PATCH] Overhauled UI and refactoring --- app/build.gradle.kts | 43 ++-- app/src/main/AndroidManifest.xml | 6 +- .../com/aliucord/manager/data/DataStore.kt | 20 ++ .../com/aliucord/manager/models/Commit.kt | 2 +- .../com/aliucord/manager/models/GithubUser.kt | 7 +- .../manager/preferences/Preferences.kt | 14 +- .../manager/preferences/PreferencesHolder.kt | 12 +- .../com/aliucord/manager/ui/MainActivity.kt | 186 ++++----------- .../java/com/aliucord/manager/ui/Screen.kt | 48 +--- .../aliucord/manager/ui/components/AppBar.kt | 110 +++++++++ .../manager/ui/components/CommitsList.kt | 132 ----------- .../ui/components/ExposedDropdownMenu.kt | 66 ------ .../manager/ui/components/GrantPermission.kt | 65 +++++ .../manager/ui/components/PluginsList.kt | 121 ++++++++++ .../aliucord/manager/ui/components/Switch.kt | 34 +++ .../ui/components/about/ContributorEntry.kt | 29 +++ .../ui/components/about/ContributorsCard.kt | 65 +++++ .../manager/ui/components/about/TeamCard.kt | 35 +++ .../manager/ui/components/about/UserEntry.kt | 40 ++++ .../components/installer/InstallerDialog.kt | 72 ++++++ .../ui/components/{ => plugins}/Changelog.kt | 103 ++++---- .../ui/components/plugins/PluginCard.kt | 112 +++++++++ .../ui/components/settings/SettingSwitch.kt | 16 ++ .../ui/components/settings/ThemeSetting.kt | 28 +++ .../manager/ui/screens/AboutScreen.kt | 142 ++--------- .../manager/ui/screens/CommitsScreen.kt | 95 ++++++++ .../aliucord/manager/ui/screens/HomeScreen.kt | 224 +++++++++++++++--- .../manager/ui/screens/InstallScreen.kt | 60 +++++ .../manager/ui/screens/PluginsScreen.kt | 157 ------------ .../manager/ui/screens/SettingsScreen.kt | 218 ++++++++++------- .../manager/ui/screens/StoreScreen.kt | 11 + .../com/aliucord/manager/ui/theme/Color.kt | 45 +++- .../com/aliucord/manager/ui/theme/Shape.kt | 16 -- .../com/aliucord/manager/ui/theme/Theme.kt | 86 +++---- .../com/aliucord/manager/ui/theme/Type.kt | 14 +- .../com/aliucord/manager/utils/Constants.kt | 5 +- .../aliucord/manager/utils/DownloadUtils.kt | 51 ++++ .../com/aliucord/manager/utils/Downloader.kt | 39 --- .../java/com/aliucord/manager/utils/Github.kt | 21 +- .../java/com/aliucord/manager/utils/Plugin.kt | 26 +- app/src/main/res/drawable/divider.xml | 5 - .../drawable/ic_account_github_white_24dp.xml | 4 + app/src/main/res/drawable/ic_discord.xml | 15 +- .../main/res/drawable/ic_download_24dp.xml | 4 +- .../res/drawable/ic_history_white_24dp.xml | 4 +- .../res/drawable/ic_launcher_foreground.xml | 33 +-- app/src/main/res/layout/commit.xml | 20 -- app/src/main/res/layout/recycler.xml | 8 - .../res/values/ic_launcher_background.xml | 2 +- app/src/main/res/values/strings.xml | 20 +- app/src/main/res/values/themes.xml | 1 - build.gradle.kts | 8 +- gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 0 settings.gradle.kts | 3 +- 55 files changed, 1642 insertions(+), 1065 deletions(-) create mode 100644 app/src/main/java/com/aliucord/manager/data/DataStore.kt create mode 100644 app/src/main/java/com/aliucord/manager/ui/components/AppBar.kt delete mode 100644 app/src/main/java/com/aliucord/manager/ui/components/CommitsList.kt delete mode 100644 app/src/main/java/com/aliucord/manager/ui/components/ExposedDropdownMenu.kt create mode 100644 app/src/main/java/com/aliucord/manager/ui/components/GrantPermission.kt create mode 100644 app/src/main/java/com/aliucord/manager/ui/components/PluginsList.kt create mode 100644 app/src/main/java/com/aliucord/manager/ui/components/Switch.kt create mode 100644 app/src/main/java/com/aliucord/manager/ui/components/about/ContributorEntry.kt create mode 100644 app/src/main/java/com/aliucord/manager/ui/components/about/ContributorsCard.kt create mode 100644 app/src/main/java/com/aliucord/manager/ui/components/about/TeamCard.kt create mode 100644 app/src/main/java/com/aliucord/manager/ui/components/about/UserEntry.kt create mode 100644 app/src/main/java/com/aliucord/manager/ui/components/installer/InstallerDialog.kt rename app/src/main/java/com/aliucord/manager/ui/components/{ => plugins}/Changelog.kt (64%) create mode 100644 app/src/main/java/com/aliucord/manager/ui/components/plugins/PluginCard.kt create mode 100644 app/src/main/java/com/aliucord/manager/ui/components/settings/SettingSwitch.kt create mode 100644 app/src/main/java/com/aliucord/manager/ui/components/settings/ThemeSetting.kt create mode 100644 app/src/main/java/com/aliucord/manager/ui/screens/CommitsScreen.kt create mode 100644 app/src/main/java/com/aliucord/manager/ui/screens/InstallScreen.kt delete mode 100644 app/src/main/java/com/aliucord/manager/ui/screens/PluginsScreen.kt create mode 100644 app/src/main/java/com/aliucord/manager/ui/screens/StoreScreen.kt delete mode 100644 app/src/main/java/com/aliucord/manager/ui/theme/Shape.kt create mode 100644 app/src/main/java/com/aliucord/manager/utils/DownloadUtils.kt delete mode 100644 app/src/main/java/com/aliucord/manager/utils/Downloader.kt delete mode 100644 app/src/main/res/drawable/divider.xml create mode 100644 app/src/main/res/drawable/ic_account_github_white_24dp.xml delete mode 100644 app/src/main/res/layout/commit.xml delete mode 100644 app/src/main/res/layout/recycler.xml mode change 100644 => 100755 gradlew diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 730dcf6..0e5dfcd 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -3,8 +3,9 @@ * Licensed under the Open Software License version 3.0 */ -val accompanistVersion = "0.14.0" -val composeVersion = "1.0.0-rc02" +val accompanistVersion = "0.23.0" +val composeVersion = "1.1.0" +val ktorVersion = "1.6.7" plugins { id("com.android.application") @@ -17,7 +18,7 @@ android { defaultConfig { applicationId = "com.aliucord.manager" minSdk = 24 - targetSdk = 29 + targetSdk = 31 versionCode = 1 versionName = "0.0.1" @@ -33,42 +34,46 @@ android { proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") } } + compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } + kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "11" freeCompilerArgs = freeCompilerArgs + "-Xopt-in=kotlin.RequiresOptIn" } - buildFeatures { - compose = true - } - composeOptions { - kotlinCompilerExtensionVersion = composeVersion - } + + buildFeatures.compose = true + composeOptions.kotlinCompilerExtensionVersion = composeVersion } dependencies { // core dependencies - implementation("androidx.appcompat:appcompat:1.4.0") + implementation("androidx.appcompat:appcompat:1.4.1") implementation("androidx.core:core-ktx:1.7.0") - implementation("com.google.android.material:material:1.4.0") + implementation("com.google.android.material:material:1.5.0") // compose dependencies implementation("androidx.activity:activity-compose:1.4.0") implementation("androidx.compose.ui:ui:${composeVersion}") + implementation("androidx.compose.material3:material3:1.0.0-alpha05") implementation("androidx.compose.material:material:${composeVersion}") - implementation("androidx.compose.ui:ui-tooling:${composeVersion}") - implementation("androidx.constraintlayout:constraintlayout-compose:1.0.0-rc02") - implementation("androidx.navigation:navigation-compose:2.4.0-rc01") + implementation("androidx.compose.ui:ui-tooling-preview:1.1.0") + implementation("androidx.navigation:navigation-compose:2.4.1") // accompanist dependencies implementation("com.google.accompanist:accompanist-systemuicontroller:${accompanistVersion}") implementation("com.google.accompanist:accompanist-permissions:${accompanistVersion}") // other dependencies - implementation("com.google.code.gson:gson:2.8.7") + implementation("com.google.code.gson:gson:2.8.9") implementation("io.coil-kt:coil-compose:1.4.0") + implementation("androidx.datastore:datastore-preferences:1.0.0") -} + // ktor + implementation("io.ktor:ktor-client-core:$ktorVersion") + implementation("io.ktor:ktor-client-cio:$ktorVersion") + implementation("io.ktor:ktor-client-android:$ktorVersion") +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1bb1282..e4d7799 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -10,6 +10,7 @@ + android:exported="true"> @@ -39,4 +39,4 @@ - + \ No newline at end of file diff --git a/app/src/main/java/com/aliucord/manager/data/DataStore.kt b/app/src/main/java/com/aliucord/manager/data/DataStore.kt new file mode 100644 index 0000000..cac5725 --- /dev/null +++ b/app/src/main/java/com/aliucord/manager/data/DataStore.kt @@ -0,0 +1,20 @@ +package com.aliucord.manager.data + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.intPreferencesKey +import androidx.datastore.preferences.preferencesDataStore + +class StorePreferences(private val context: Context) { + private val THEME = intPreferencesKey("theme") + + + companion object { + private val Context.dataStore: DataStore by preferencesDataStore("settings") + } + + init { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aliucord/manager/models/Commit.kt b/app/src/main/java/com/aliucord/manager/models/Commit.kt index 9e07eed..fef3c64 100644 --- a/app/src/main/java/com/aliucord/manager/models/Commit.kt +++ b/app/src/main/java/com/aliucord/manager/models/Commit.kt @@ -7,7 +7,7 @@ package com.aliucord.manager.models import com.google.gson.annotations.SerializedName -class Commit( +data class Commit( @SerializedName("html_url") val htmlUrl: String, val sha: String, val commit: Commit, diff --git a/app/src/main/java/com/aliucord/manager/models/GithubUser.kt b/app/src/main/java/com/aliucord/manager/models/GithubUser.kt index ce925da..1eb55ac 100644 --- a/app/src/main/java/com/aliucord/manager/models/GithubUser.kt +++ b/app/src/main/java/com/aliucord/manager/models/GithubUser.kt @@ -1,3 +1,8 @@ package com.aliucord.manager.models -data class GithubUser(val login: String, val contributions: Int) \ No newline at end of file +import com.google.gson.annotations.SerializedName + +data class GithubUser( + @SerializedName("login") val name: String, + val contributions: Int +) \ No newline at end of file diff --git a/app/src/main/java/com/aliucord/manager/preferences/Preferences.kt b/app/src/main/java/com/aliucord/manager/preferences/Preferences.kt index e02b538..2fd9832 100644 --- a/app/src/main/java/com/aliucord/manager/preferences/Preferences.kt +++ b/app/src/main/java/com/aliucord/manager/preferences/Preferences.kt @@ -7,6 +7,7 @@ package com.aliucord.manager.preferences import android.content.SharedPreferences import androidx.compose.runtime.mutableStateOf +import androidx.core.content.edit import kotlin.reflect.KProperty var sharedPreferences: SharedPreferences? = null @@ -26,12 +27,11 @@ class Preference( fun get() = value.value fun set(newValue: T) { - if (value.value != newValue) { - value.value = newValue - val editor = sharedPreferences!!.edit() - editor.setter(key, newValue) - editor.apply() - } + if (value.value == newValue) return + + value.value = newValue + + sharedPreferences!!.edit { setter(key, newValue) } } } @@ -63,4 +63,4 @@ fun stringPreference( defaultValue, SharedPreferences::getString, SharedPreferences.Editor::putString -) +) \ 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 f0e78a3..5c64556 100644 --- a/app/src/main/java/com/aliucord/manager/preferences/PreferencesHolder.kt +++ b/app/src/main/java/com/aliucord/manager/preferences/PreferencesHolder.kt @@ -7,9 +7,13 @@ package com.aliucord.manager.preferences import android.os.Environment -val themePreference = intPreference("theme") -val replaceBgPreference = boolPreference("replace_bg", true) +var themePref = intPreference("theme") +val replaceBg = boolPreference("replace_bg", true) val devModePreference = boolPreference("dev_mode", false) -val debuggablePreference = boolPreference("debuggable", false) +val debuggablePreference = boolPreference("debuggable") val useDexFromStoragePreference = boolPreference("use_dex_from_storage") -val dexLocationPreference = stringPreference("dex_location", Environment.getExternalStorageDirectory().absolutePath + "/Aliucord/Aliucord.dex") +val dexLocationPreference = stringPreference("dex_location", Environment.getExternalStorageDirectory().absolutePath + "/Aliucord/Injector.dex") + +object Preferences { + val useBlack = boolPreference("use_black") +} \ No newline at end of file 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 a79e48e..99a8cfd 100644 --- a/app/src/main/java/com/aliucord/manager/ui/MainActivity.kt +++ b/app/src/main/java/com/aliucord/manager/ui/MainActivity.kt @@ -7,188 +7,94 @@ package com.aliucord.manager.ui import android.Manifest import android.content.Context -import android.content.Intent -import android.net.Uri import android.os.Bundle -import android.provider.Settings import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.compose.foundation.layout.* -import androidx.compose.material.* -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack -import androidx.compose.material.icons.filled.MoreVert -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.layout.padding +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable -import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController -import com.aliucord.manager.BuildConfig -import com.aliucord.manager.R import com.aliucord.manager.preferences.sharedPreferences -import com.aliucord.manager.ui.theme.AliucordManagerTheme -import com.aliucord.manager.ui.theme.primaryColor +import com.aliucord.manager.ui.components.AppBar +import com.aliucord.manager.ui.components.GrantPermission +import com.aliucord.manager.ui.screens.* +import com.aliucord.manager.ui.theme.ManagerTheme import com.aliucord.manager.utils.Github import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.PermissionRequired -import com.google.accompanist.permissions.PermissionState import com.google.accompanist.permissions.rememberPermissionState -import com.google.accompanist.systemuicontroller.rememberSystemUiController +@ExperimentalMaterialApi +@ExperimentalFoundationApi class MainActivity : ComponentActivity() { + @OptIn(ExperimentalMaterial3Api::class, ExperimentalPermissionsApi::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - sharedPreferences = getSharedPreferences(BuildConfig.APPLICATION_ID + "_preferences", Context.MODE_PRIVATE) + sharedPreferences = getPreferences(Context.MODE_PRIVATE) + Github.checkForUpdates() + setContent { - AliucordManagerTheme { + ManagerTheme { MainActivityLayout() } } } } -@OptIn(ExperimentalPermissionsApi::class) +@ExperimentalMaterialApi +@ExperimentalMaterial3Api +@ExperimentalFoundationApi +@ExperimentalPermissionsApi @Composable -fun MainActivityLayout() { - val systemUiController = rememberSystemUiController() +private fun MainActivityLayout() { val storagePermissionState = rememberPermissionState(Manifest.permission.WRITE_EXTERNAL_STORAGE) + val navController = rememberNavController() - val colors = MaterialTheme.colors - SideEffect { - systemUiController.setNavigationBarColor(colors.background) - systemUiController.setStatusBarColor(colors.primary) - if (!storagePermissionState.hasPermission) storagePermissionState.launchPermissionRequest() + if (!storagePermissionState.hasPermission) SideEffect { + storagePermissionState.launchPermissionRequest() } - val navController = rememberNavController() - var isMenuExpanded by remember { mutableStateOf(false) } - Scaffold( - topBar = { - val route = navController.currentBackStackEntryAsState().value?.destination?.route ?: Screen.Home.route - TopAppBar( - title = { Text(stringResource(Screen.SCREENS.find { it.route == route }!!.displayName)) }, - backgroundColor = primaryColor, - contentColor = MaterialTheme.colors.onPrimary, - navigationIcon = if (route != Screen.Home.route) {{ - IconButton(onClick = { navController.popBackStack() }) { - Icon( - imageVector = Icons.Default.ArrowBack, - contentDescription = stringResource(R.string.back), - tint = MaterialTheme.colors.onPrimary - ) - } - }} else null, - actions = { - if (route != Screen.Home.route) return@TopAppBar - val context = LocalContext.current - IconButton( - onClick = { context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://discord.gg/${BuildConfig.SUPPORT_SERVER}"))) }, - ) { - Icon( - painter = painterResource(R.drawable.ic_discord), - contentDescription = stringResource(R.string.support_server), - tint = MaterialTheme.colors.onPrimary - ) - } - IconButton( - onClick = { - navController.navigate(Screen.Plugins.route) { - popUpTo(Screen.Home.route) { saveState = true } - } - }, - modifier = Modifier.padding(bottom = 2.dp, start = 8.dp) // Make up for icon being off center - ) { - Icon( - painter = painterResource(R.drawable.ic_plugin_24dp), - contentDescription = stringResource(id = R.string.launch_plugins), - tint = MaterialTheme.colors.onPrimary - ) - } - IconButton(onClick = { isMenuExpanded = !isMenuExpanded }) { - Icon( - imageVector = Icons.Default.MoreVert, - contentDescription = stringResource(R.string.show_menu), - tint = MaterialTheme.colors.onPrimary - ) - } - 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(Screen.Settings.displayName)) - } - } - } - ) - } - ) { + topBar = { AppBar(navController) } + ) { paddingValues -> PermissionRequired( permissionState = storagePermissionState, - permissionNotGrantedContent = { GrantPermission(permissionState = storagePermissionState) }, - permissionNotAvailableContent = { GrantPermission(permissionState = storagePermissionState) } + permissionNotGrantedContent = { GrantPermission(storagePermissionState) }, + permissionNotAvailableContent = { GrantPermission(storagePermissionState) } ) { NavHost( navController, + modifier = Modifier.padding(paddingValues).padding(12.dp), startDestination = Screen.Home.route ) { - for (screen in Screen.SCREENS) { - composable(screen.route) { screen.content() } + composable(Screen.Home.route) { HomeScreen(navController) } + composable(Screen.Commits.route) { CommitsScreen() } + composable(Screen.About.route) { AboutScreen() } + composable(Screen.Settings.route) { SettingsScreen() } + composable(Screen.Store.route) { StoreScreen() } + composable( + route = Screen.Installer.route, +// arguments = listOf( +// navArgument("method") { type = NavType.EnumType( type = InstallMethod::class.java ) } +// )) + ) { + InstallerScreen() } } } } } -@OptIn(ExperimentalPermissionsApi::class) -@Composable -fun GrantPermission(permissionState: PermissionState) { - Column( - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text(stringResource(R.string.permission_required), style = MaterialTheme.typography.h6, textAlign = TextAlign.Center) - Spacer(Modifier.height(8.dp)) - Row { - val context = LocalContext.current - - Button(onClick = { permissionState.launchPermissionRequest() }) { - Text(stringResource(R.string.permission_grant)) - } - Spacer(Modifier.width(8.dp)) - Button( - onClick = { - context.startActivity(Intent( - Settings.ACTION_APPLICATION_DETAILS_SETTINGS, - Uri.fromParts("package", BuildConfig.APPLICATION_ID, null) - )) - }, - colors = ButtonDefaults.buttonColors(backgroundColor = Color.LightGray, contentColor = Color.Black) - ) { - Text(stringResource(R.string.open_settings)) - } - } - } -} +enum class InstallMethod { + DOWNLOAD, INSTALLED, STORAGE +} \ No newline at end of file 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 5f0f6b7..c1b55c9 100644 --- a/app/src/main/java/com/aliucord/manager/ui/Screen.kt +++ b/app/src/main/java/com/aliucord/manager/ui/Screen.kt @@ -6,45 +6,19 @@ package com.aliucord.manager.ui import androidx.annotation.StringRes -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.runtime.Composable +import androidx.compose.foundation.ExperimentalFoundationApi 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 -sealed class Screen( - val route: String, - @StringRes val displayName: Int, - val content: @Composable () -> Unit -) { +@ExperimentalFoundationApi +sealed class Screen(val route: String, @StringRes val displayName: Int) { 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, About) } + val screens by lazy { listOf(Home, About, Settings, Commits, Installer) } } - object Home : Screen( - "home", - R.string.app_name, - { HomeScreen() } - ) - - object About : Screen( - "About", - R.string.about, - { AboutScreen() } - ) - - object Settings : Screen( - "settings", - R.string.settings, - { SettingsScreen() } - ) - - object Plugins : Screen( - "plugins", - R.string.plugins, - { PluginsScreen() } - ) -} + object Home : Screen("home", R.string.app_name) + object About : Screen("about", R.string.about) + object Settings : Screen("settings", R.string.settings) + object Commits : Screen("commits", R.string.commits) + object Installer : Screen("installer", R.string.installer) + object Store : Screen("store", R.string.store) +} \ No newline at end of file diff --git a/app/src/main/java/com/aliucord/manager/ui/components/AppBar.kt b/app/src/main/java/com/aliucord/manager/ui/components/AppBar.kt new file mode 100644 index 0000000..d8824a1 --- /dev/null +++ b/app/src/main/java/com/aliucord/manager/ui/components/AppBar.kt @@ -0,0 +1,110 @@ +package com.aliucord.manager.ui.components + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.navigation.NavController +import androidx.navigation.compose.currentBackStackEntryAsState +import com.aliucord.manager.BuildConfig +import com.aliucord.manager.R +import com.aliucord.manager.ui.Screen + +@ExperimentalFoundationApi +@Composable +fun AppBar(navController: NavController) { + val route = navController.currentBackStackEntryAsState().value?.destination?.route ?: Screen.Home.route + var isMenuExpanded by remember { mutableStateOf(false) } + + MediumTopAppBar( + navigationIcon = { + if (route != Screen.Home.route) { + IconButton(onClick = { navController.popBackStack() }) { + Icon(Icons.Default.ArrowBack, stringResource(R.string.back), tint = MaterialTheme.colorScheme.onBackground) + } + } + }, + title = { + Text(stringResource(Screen.screens.first { screen -> + screen.route == route + }.displayName)) + }, + actions = { + val localUriHandler = LocalUriHandler.current + + IconButton( + onClick = { + navController.navigate(Screen.Settings.route) + }, + ) { + Icon( + painter = painterResource(R.drawable.ic_plugin_24dp), + contentDescription = stringResource(R.string.plugins), + tint = MaterialTheme.colorScheme.onBackground + ) + } + + IconButton( + onClick = { + localUriHandler.openUri("https://discord.gg/${BuildConfig.SUPPORT_SERVER}") + }, + ) { + Icon( + painter = painterResource(R.drawable.ic_discord), + contentDescription = stringResource(R.string.support_server), + tint = MaterialTheme.colorScheme.onBackground + ) + } + + Box( + modifier = Modifier.wrapContentSize(Alignment.TopEnd) + ) { + IconButton( + onClick = { isMenuExpanded = !isMenuExpanded } + ) { + Icon( + imageVector = Icons.Default.MoreVert, + contentDescription = stringResource(R.string.show_menu), + tint = MaterialTheme.colorScheme.onBackground + ) + } + + DropdownMenu( + modifier = Modifier.wrapContentWidth(), + expanded = isMenuExpanded, + onDismissRequest = { isMenuExpanded = false } + ) { + DropdownMenuItem( + onClick = { + isMenuExpanded = false + navController.navigate(Screen.About.route) { + popUpTo(Screen.Home.route) { saveState = true } + } + }, + text = { Text(stringResource(Screen.About.displayName)) } + ) + + DropdownMenuItem( + onClick = { + isMenuExpanded = false + navController.navigate(Screen.Settings.route) { + popUpTo(Screen.Home.route) { saveState = true } + } + }, + text = { Text(stringResource(Screen.Settings.displayName)) } + ) + } + } + } + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/aliucord/manager/ui/components/CommitsList.kt b/app/src/main/java/com/aliucord/manager/ui/components/CommitsList.kt deleted file mode 100644 index 8bd569a..0000000 --- a/app/src/main/java/com/aliucord/manager/ui/components/CommitsList.kt +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (c) 2021 Juby210 - * Licensed under the Open Software License version 3.0 - */ - -package com.aliucord.manager.ui.components - -import android.annotation.SuppressLint -import android.content.Intent -import android.graphics.Color -import android.net.Uri -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.TextView -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.material.CircularProgressIndicator -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.runtime.* -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import androidx.compose.ui.viewinterop.AndroidView -import androidx.constraintlayout.compose.ConstraintLayout -import androidx.recyclerview.widget.DividerItemDecoration -import androidx.recyclerview.widget.RecyclerView -import com.aliucord.manager.BuildConfig -import com.aliucord.manager.R -import com.aliucord.manager.models.Commit -import com.aliucord.manager.ui.theme.isDark -import com.aliucord.manager.utils.Github -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch - -private class CommitData( - val commit: Commit, - val buildSha: String? -) - -@Composable -fun CommitsList(selectedCommit: MutableState, modifier: Modifier = Modifier) { - val coroutineScope = rememberCoroutineScope { Dispatchers.IO } - var isLoading by remember { mutableStateOf(true) } - val commits = remember { mutableStateListOf() } - - if (isLoading) { - ConstraintLayout(modifier = modifier - .fillMaxSize() - .padding(12.dp)) { - CommitsHeader() - CircularProgressIndicator(modifier = Modifier.constrainAs(createRef()) { - top.linkTo(parent.top) - bottom.linkTo(parent.bottom) - start.linkTo(parent.start) - end.linkTo(parent.end) - }) - } - - LaunchedEffect(null) { - coroutineScope.launch { - try { - val buildCommits = Github.getCommits(mapOf("sha" to "builds", "path" to "Aliucord.dex", "per_page" to "50")) - val commits2 = Github.getCommits(mapOf("per_page" to "50")) - commits.addAll(commits2.map { c -> CommitData(c, buildCommits.find { bc -> bc.commit.message.substring(6) == c.sha }?.sha) }) - isLoading = false - if (commits.size > 0) selectedCommit.value = commits.firstOrNull { it.buildSha != null }?.buildSha - } catch (e: Throwable) { - Log.e(BuildConfig.TAG, "Failed to get commits", e) - } - } - } - } else { - val dark = isDark() - - // LazyColumn causes too much lags, also you can't get scrollbar on it, so I used AndroidView with RecyclerView for now - Column( - modifier = modifier - .fillMaxSize() - .padding(12.dp) - ) { - CommitsHeader() - AndroidView({ context -> View.inflate(context, R.layout.recycler, null).apply { - val rv = this as RecyclerView - rv.adapter = CommitsAdapter(commits, dark) - rv.addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL)) - }}) - } - } -} - -@Composable -private fun CommitsHeader() { - Text( - stringResource(R.string.commits), - style = MaterialTheme.typography.subtitle1, - color = MaterialTheme.colors.primary, - modifier = Modifier.padding(bottom = 6.dp) - ) -} - -private class CommitsAdapter(private val commits: List, private val dark: Boolean) : RecyclerView.Adapter() { - class ViewHolder(view: View, dark: Boolean) : RecyclerView.ViewHolder(view) { - val sha: TextView = view.findViewById(R.id.commit_sha) - val message: TextView = view.findViewById(R.id.commit_message) - - init { - message.setTextColor(if (dark) 0xccffffff.toInt() else 0xcc000000.toInt()) - } - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = - ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.commit, parent, false), dark) - - @SuppressLint("SetTextI18n") - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val data = commits[position] - val commit = data.commit - val color = if (data.buildSha != null) holder.sha.context.getColor(R.color.primary) else Color.GRAY - - holder.sha.text = commit.sha.substring(0, 7) - holder.sha.setTextColor(color) - holder.sha.setOnClickListener { it.context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(commit.htmlUrl))) } - - holder.message.text = "${commit.commit.message.split("\n")[0]} - ${commit.author.name}" - } - - override fun getItemCount() = commits.size -} diff --git a/app/src/main/java/com/aliucord/manager/ui/components/ExposedDropdownMenu.kt b/app/src/main/java/com/aliucord/manager/ui/components/ExposedDropdownMenu.kt deleted file mode 100644 index 71065b5..0000000 --- a/app/src/main/java/com/aliucord/manager/ui/components/ExposedDropdownMenu.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2021 Juby210 - * Licensed under the Open Software License version 3.0 - */ - -package com.aliucord.manager.ui.components - -import androidx.compose.animation.core.FastOutSlowInEasing -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.tween -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.width -import androidx.compose.material.* -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowDropDown -import androidx.compose.runtime.* -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.rotate -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp - -@ExperimentalMaterialApi -@Composable -fun ExposedDropdownMenu( - items: Array, - selectedItem: Int, - selectItem: (item: Int) -> Unit = {}, - width: Dp = 200.dp -) { - var expanded by remember { mutableStateOf(false) } - val rotateAnimation by animateFloatAsState( - targetValue = if (expanded) 180F else 0F, - animationSpec = tween( - durationMillis = 250, - easing = FastOutSlowInEasing - ) - ) - - Column(modifier = Modifier.width(width)) { - ListItem( - text = { Text(items[selectedItem]) }, - trailing = { - Icon(imageVector = Icons.Filled.ArrowDropDown, contentDescription = null, modifier = Modifier.rotate(rotateAnimation)) - }, - modifier = Modifier.clickable { expanded = !expanded } - ) - if (expanded) Divider(color = MaterialTheme.colors.primary) else Divider() - DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false }, - modifier = Modifier.width(width) - ) { - items.forEachIndexed { i, item -> - DropdownMenuItem( - onClick = { - expanded = false - selectItem(i) - } - ) { - Text(item) - } - } - } - } -} diff --git a/app/src/main/java/com/aliucord/manager/ui/components/GrantPermission.kt b/app/src/main/java/com/aliucord/manager/ui/components/GrantPermission.kt new file mode 100644 index 0000000..e7d1d0a --- /dev/null +++ b/app/src/main/java/com/aliucord/manager/ui/components/GrantPermission.kt @@ -0,0 +1,65 @@ +package com.aliucord.manager.ui.components + +import android.content.Intent +import android.net.Uri +import android.provider.Settings +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.aliucord.manager.BuildConfig +import com.aliucord.manager.R +import com.google.accompanist.permissions.ExperimentalPermissionsApi +import com.google.accompanist.permissions.PermissionState + +@OptIn(ExperimentalPermissionsApi::class) +@Composable +fun GrantPermission(permissionState: PermissionState) = Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally +) { + Text( + stringResource(R.string.permission_required), + style = MaterialTheme.typography.headlineMedium, + textAlign = TextAlign.Center + ) + + Spacer(Modifier.height(8.dp)) + + Row { + val context = LocalContext.current + + Button(onClick = { permissionState.launchPermissionRequest() }) { + Text(stringResource(R.string.permission_grant)) + } + + Spacer(Modifier.width(8.dp)) + + Button( + onClick = { + context.startActivity( + Intent( + Settings.ACTION_APPLICATION_DETAILS_SETTINGS, + Uri.fromParts("package", BuildConfig.APPLICATION_ID, null) + ) + ) + }, + colors = ButtonDefaults.buttonColors( + containerColor = Color.LightGray, + contentColor = Color.Black + ), + ) { + Text(stringResource(R.string.open_settings)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aliucord/manager/ui/components/PluginsList.kt b/app/src/main/java/com/aliucord/manager/ui/components/PluginsList.kt new file mode 100644 index 0000000..e313295 --- /dev/null +++ b/app/src/main/java/com/aliucord/manager/ui/components/PluginsList.kt @@ -0,0 +1,121 @@ +package com.aliucord.manager.ui.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.TextFieldDefaults +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Search +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import com.aliucord.manager.R +import com.aliucord.manager.ui.components.plugins.Changelog +import com.aliucord.manager.ui.components.plugins.PluginCard +import com.aliucord.manager.utils.Plugin + +@Composable +fun PluginsList() = Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(8.dp) +) { + var plugins by remember { mutableStateOf(Plugin.loadAll()) } + val focusManager = LocalFocusManager.current + var search by remember { mutableStateOf("") } + + if (plugins.isNotEmpty()) { + OutlinedTextField( + modifier = Modifier.fillMaxWidth(), + singleLine = true, + value = search, + onValueChange = { search = it }, + placeholder = { Text(stringResource(R.string.search)) }, + trailingIcon = { Icon(Icons.Default.Search, stringResource(R.string.search_plugins)) }, + keyboardOptions = KeyboardOptions(autoCorrect = false, imeAction = ImeAction.Search), + keyboardActions = KeyboardActions { focusManager.clearFocus() }, + shape = RoundedCornerShape(20.dp), + colors = TextFieldDefaults.outlinedTextFieldColors( + textColor = MaterialTheme.colorScheme.onSecondaryContainer, + backgroundColor = MaterialTheme.colorScheme.secondaryContainer, + focusedBorderColor = MaterialTheme.colorScheme.outline, + cursorColor = MaterialTheme.colorScheme.primary + ) + ) + + LazyColumn( + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + items(plugins.filter { plugin -> + plugin.manifest.run { + name.contains(search, true) + || description.contains(search, true) + || authors.any { a -> a.name.contains(search, true) } + } + }) { plugin -> + // TODO - make this get pluginEnabled from Aliucord preferences somehow + var isChecked by remember { mutableStateOf(true) } + var showDeleteDialog by remember { mutableStateOf(false) } + var showChangelogDialog by remember { mutableStateOf(false) } + + if (showDeleteDialog) AlertDialog( + onDismissRequest = { showDeleteDialog = false }, + title = { Text(stringResource(R.string.delete_plugin, plugin.manifest.name)) }, + text = { Text(stringResource(R.string.delete_plugin_body)) }, + confirmButton = { + Button( + onClick = { + showDeleteDialog = false + plugin.file.delete() + plugins = plugins.filter { it != plugin } + }, + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primaryContainer, + contentColor = MaterialTheme.colorScheme.onPrimaryContainer + ) + ) { + Text(stringResource(android.R.string.ok).uppercase()) + } + }, + dismissButton = { + Button( + onClick = { + showDeleteDialog = false + }, + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.secondaryContainer, + contentColor = MaterialTheme.colorScheme.onSecondaryContainer + ) + ) { + Text(stringResource(android.R.string.cancel).uppercase()) + } + } + ) + + if (showChangelogDialog) Dialog( + onDismissRequest = { showChangelogDialog = false }, + content = { Changelog(plugin, onDismiss = { showChangelogDialog = false }) } + ) + + PluginCard( + plugin = plugin, + onDelete = { showDeleteDialog = true }, + onShowChangelog = { showChangelogDialog = true } + ) + } + } + } else { + Text(stringResource(R.string.no_plugins_installed)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aliucord/manager/ui/components/Switch.kt b/app/src/main/java/com/aliucord/manager/ui/components/Switch.kt new file mode 100644 index 0000000..e93fd2d --- /dev/null +++ b/app/src/main/java/com/aliucord/manager/ui/components/Switch.kt @@ -0,0 +1,34 @@ +package com.aliucord.manager.ui.components + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.material.SwitchDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.material.Switch as M2Switch + +@Composable +fun Switch( + checked: Boolean, + onCheckedChange: ((Boolean) -> Unit)?, + modifier: Modifier = Modifier, + enabled: Boolean = true, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, +) { + val colors = MaterialTheme.colorScheme + + M2Switch( + checked = checked, + onCheckedChange = onCheckedChange, + modifier = modifier, + enabled = enabled, + interactionSource = interactionSource, + colors = SwitchDefaults.colors( + checkedTrackColor = colors.surfaceVariant, + uncheckedTrackColor = colors.secondaryContainer, + checkedThumbColor = colors.primary, + uncheckedThumbColor = colors.onSecondaryContainer + ) + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/aliucord/manager/ui/components/about/ContributorEntry.kt b/app/src/main/java/com/aliucord/manager/ui/components/about/ContributorEntry.kt new file mode 100644 index 0000000..a031790 --- /dev/null +++ b/app/src/main/java/com/aliucord/manager/ui/components/about/ContributorEntry.kt @@ -0,0 +1,29 @@ +package com.aliucord.manager.ui.components.about + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.unit.dp +import coil.compose.rememberImagePainter +import coil.transform.CircleCropTransformation + +@Composable +fun ContributorEntry(name: String) { + val uriHandler = LocalUriHandler.current + + Image( + painter = rememberImagePainter( + data = "https://github.com/$name.png", + builder = { + transformations(CircleCropTransformation()) + } + ), + contentDescription = "Contributor", + modifier = Modifier + .size(48.dp) + .clickable { uriHandler.openUri("https://github.com/$name") } + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/aliucord/manager/ui/components/about/ContributorsCard.kt b/app/src/main/java/com/aliucord/manager/ui/components/about/ContributorsCard.kt new file mode 100644 index 0000000..44b4ec7 --- /dev/null +++ b/app/src/main/java/com/aliucord/manager/ui/components/about/ContributorsCard.kt @@ -0,0 +1,65 @@ +package com.aliucord.manager.ui.components.about + +import android.util.Log +import androidx.compose.foundation.ExperimentalFoundationApi +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.material3.ElevatedCard +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +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 + +@OptIn(ExperimentalMaterial3Api::class) +@ExperimentalFoundationApi +@Composable +fun ContributorsCard() = ElevatedCard( + modifier = Modifier.padding(bottom = 8.dp).wrapContentHeight().fillMaxWidth() +) { + val contributors = remember { mutableStateOf?>(null) } + + Column( + modifier = Modifier.padding(16.dp).fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text( + stringResource(R.string.contributors), + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.primary + ) + + if (contributors.value != null) LazyVerticalGrid( + cells = GridCells.Adaptive(48.dp), + verticalArrangement = Arrangement.spacedBy(4.dp), + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + items(contributors.value!!) { user -> ContributorEntry(user.name) } + } else { + Text(stringResource(R.string.loading)) + + LaunchedEffect(null) { + launch(Dispatchers.IO) { + try { + contributors.value = Github.contributors + } catch (err: Throwable) { + Log.e(BuildConfig.TAG, "Failed to load contributors", err) + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aliucord/manager/ui/components/about/TeamCard.kt b/app/src/main/java/com/aliucord/manager/ui/components/about/TeamCard.kt new file mode 100644 index 0000000..0c1189b --- /dev/null +++ b/app/src/main/java/com/aliucord/manager/ui/components/about/TeamCard.kt @@ -0,0 +1,35 @@ +package com.aliucord.manager.ui.components.about + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.aliucord.manager.R + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun TeamCard() = ElevatedCard( + modifier = Modifier.padding(bottom = 8.dp).wrapContentHeight().fillMaxWidth() +) { + Column( + modifier = Modifier.padding(16.dp).fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + stringResource(R.string.team), + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.primary + ) + + Column { + UserEntry("Juby210", "Fox") + UserEntry("Vendicated", "the ven") + UserEntry("mwittrien", "your memory gon") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aliucord/manager/ui/components/about/UserEntry.kt b/app/src/main/java/com/aliucord/manager/ui/components/about/UserEntry.kt new file mode 100644 index 0000000..38ea1ec --- /dev/null +++ b/app/src/main/java/com/aliucord/manager/ui/components/about/UserEntry.kt @@ -0,0 +1,40 @@ +package com.aliucord.manager.ui.components.about + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.* +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import coil.compose.rememberImagePainter +import coil.transform.CircleCropTransformation + +@Composable +fun UserEntry(name: String, roles: String) = Column( + modifier = Modifier.padding(vertical = 8.dp) +) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Image( + painter = rememberImagePainter( + data = "https://github.com/$name.png", + builder = { + transformations(CircleCropTransformation()) + } + ), + contentDescription = null, + modifier = Modifier.size(54.dp) + ) + Column { + Text( + name, + style = MaterialTheme.typography.titleLarge + ) + Text(roles) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aliucord/manager/ui/components/installer/InstallerDialog.kt b/app/src/main/java/com/aliucord/manager/ui/components/installer/InstallerDialog.kt new file mode 100644 index 0000000..e529925 --- /dev/null +++ b/app/src/main/java/com/aliucord/manager/ui/components/installer/InstallerDialog.kt @@ -0,0 +1,72 @@ +package com.aliucord.manager.ui.components.installer + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.layout.* +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import com.aliucord.manager.R +import com.aliucord.manager.ui.Screen + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun InstallerDialog( + visible: MutableState, + navController: NavController +) = AlertDialog( + title = { + Row ( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Icon(painterResource(R.drawable.ic_discord), "Discord") + Spacer(Modifier.width(12.dp)) + Text("Select Installation Method") + } + }, + confirmButton = { + Column( + modifier = Modifier.wrapContentWidth().padding(32.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + val modifier = Modifier.fillMaxWidth() + + Button( + modifier = modifier, + onClick = { + navController.currentBackStackEntry?.arguments + navController.navigate(Screen.Installer.route) + } + ) { + Text("Download") + } + + Button( + modifier = modifier, + onClick = { + navController.navigate(Screen.Installer.route) + } + ) { + Text("From installed app") + } + + Button( + modifier = modifier, + onClick = { + navController.navigate(Screen.Installer.route) + } + ) { + Text("From storage") + } + } + }, + onDismissRequest = { visible.value = false } +) \ No newline at end of file diff --git a/app/src/main/java/com/aliucord/manager/ui/components/Changelog.kt b/app/src/main/java/com/aliucord/manager/ui/components/plugins/Changelog.kt similarity index 64% rename from app/src/main/java/com/aliucord/manager/ui/components/Changelog.kt rename to app/src/main/java/com/aliucord/manager/ui/components/plugins/Changelog.kt index def3578..b6349a8 100644 --- a/app/src/main/java/com/aliucord/manager/ui/components/Changelog.kt +++ b/app/src/main/java/com/aliucord/manager/ui/components/plugins/Changelog.kt @@ -1,4 +1,4 @@ -package com.aliucord.manager.ui.components +package com.aliucord.manager.ui.components.plugins import android.annotation.SuppressLint import android.graphics.BitmapFactory @@ -9,8 +9,11 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.text.ClickableText -import androidx.compose.material.* -import androidx.compose.runtime.* +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ImageBitmap @@ -23,23 +26,23 @@ import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp import com.aliucord.manager.BuildConfig import com.aliucord.manager.R -import com.aliucord.manager.ui.theme.discordBrand -import com.aliucord.manager.ui.theme.discordGreen -import com.aliucord.manager.ui.theme.discordRed import com.aliucord.manager.utils.Plugin -import java.net.URL -import java.util.regex.Pattern import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import java.net.URL +import java.util.regex.Pattern private val hyperLinkPattern = Pattern.compile("\\[(.+?)]\\((.+?\\))") + @Suppress("RegExpRedundantEscape") // It is very much not redundant and causes a crash lol private val headerStylePattern = Pattern.compile("\\{(improved|added|fixed)( marginTop)?\\}") + @SuppressLint("ComposableNaming") // Can't use MaterialTheme without Composable, but this is not a component @Composable private fun AnnotatedString.Builder.hyperlink(content: String) { var idx = 0 - with (hyperLinkPattern.matcher(content)) { + + with(hyperLinkPattern.matcher(content)) { while (find()) { val start = start() val end = end() @@ -47,7 +50,12 @@ private fun AnnotatedString.Builder.hyperlink(content: String) { val url = group(2)!! append(content.substring(idx, start)) - withStyle(style = SpanStyle(color = MaterialTheme.colors.primary, textDecoration = TextDecoration.Underline)) { + withStyle( + style = SpanStyle( + color = MaterialTheme.colorScheme.primary, + textDecoration = TextDecoration.Underline + ) + ) { pushStringAnnotation(title, url) append(title) pop() @@ -55,45 +63,49 @@ private fun AnnotatedString.Builder.hyperlink(content: String) { idx = end } } + if (idx < content.length) append(content.substring(idx)) } -const val bulletPoint = "● " @Composable -fun Changelog(plugin: Plugin, showDialog: MutableState) { - val coroutineScope = rememberCoroutineScope { Dispatchers.IO } +fun Changelog(plugin: Plugin, onDismiss: () -> Unit) { val imageBitmap = remember { mutableStateOf(null) } if (imageBitmap.value == null && plugin.manifest.changelogMedia != null) { LaunchedEffect(null) { - coroutineScope.launch { + launch(Dispatchers.IO) { try { - imageBitmap.value = URL(plugin.manifest.changelogMedia).openStream().use { - BitmapFactory.decodeStream(it) + @Suppress("BlockingMethodInNonBlockingContext") + imageBitmap.value = URL(plugin.manifest.changelogMedia).openStream().use { stream -> + BitmapFactory.decodeStream(stream) }.asImageBitmap() } catch (err: Throwable) { - Log.e(BuildConfig.TAG, "Failed to load changelogMedia ${plugin.manifest.changelogMedia}", err) + Log.e( + BuildConfig.TAG, + "Failed to load changelogMedia ${plugin.manifest.changelogMedia}", + err + ) } } } } + Column( modifier = Modifier - .background(MaterialTheme.colors.surface) + .background(MaterialTheme.colorScheme.surface) .fillMaxHeight(0.9f) .padding(16.dp), verticalArrangement = Arrangement.SpaceBetween ) { - Text( - plugin.manifest.name, - style = MaterialTheme.typography.h5 - ) - Divider(color = MaterialTheme.colors.primary, modifier = Modifier.padding(vertical = 8.dp)) + Text(plugin.manifest.name, style = MaterialTheme.typography.headlineLarge) + Divider(color = MaterialTheme.colorScheme.primary, modifier = Modifier.padding(vertical = 8.dp)) + imageBitmap.value?.let { Image(it, "", modifier = Modifier.fillMaxWidth()) } + LazyColumn( modifier = Modifier .padding(bottom = 16.dp) @@ -105,17 +117,27 @@ fun Changelog(plugin: Plugin, showDialog: MutableState) { when (line[0]) { '#' -> { do { - line = line.substring(1); + line = line.substring(1) } while (line.startsWith("#")) - Text(line.trimStart(), fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 16.dp, bottom = 6.dp)) + Text( + line.trimStart(), + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(top = 16.dp, bottom = 6.dp) + ) } '*' -> { LinkText( buildAnnotatedString { - withStyle(style = SpanStyle(color = MaterialTheme.colors.primary, fontWeight = FontWeight.Bold)) { - append(bulletPoint) + withStyle( + style = SpanStyle( + color = MaterialTheme.colorScheme.primary, + fontWeight = FontWeight.Bold + ) + ) { + append("● ") } + hyperlink(line.substring(1)) }, Modifier.padding(bottom = 2.dp) @@ -124,19 +146,13 @@ fun Changelog(plugin: Plugin, showDialog: MutableState) { else -> { when { line.endsWith("marginTop}") -> { - val color = headerStylePattern.matcher(line).run { - val category = if (find()) { - line = this.replaceFirst("") - group(1) - } else null - when (category) { - "improved" -> discordBrand - "added" -> discordGreen - "fixed" -> discordRed - else -> MaterialTheme.colors.onSurface - } - } - Text(line, fontWeight = FontWeight.Bold, color = color, modifier = Modifier.padding(top = 16.dp, bottom = 6.dp)) + val color = MaterialTheme.colorScheme.onSurface + Text( + line, + fontWeight = FontWeight.Bold, + color = color, + modifier = Modifier.padding(top = 16.dp, bottom = 6.dp) + ) } line.all { c -> c == '=' } -> {} // Discord ignores ======= else -> { @@ -152,7 +168,11 @@ fun Changelog(plugin: Plugin, showDialog: MutableState) { } } } - Button(onClick = { showDialog.value = false }, modifier = Modifier.align(Alignment.End)) { + + Button( + onClick = onDismiss, + modifier = Modifier.align(Alignment.End).height(IntrinsicSize.Min) + ) { Text(stringResource(R.string.close)) } } @@ -162,6 +182,7 @@ fun Changelog(plugin: Plugin, showDialog: MutableState) { @Composable private fun LinkText(annotatedString: AnnotatedString, modifier: Modifier = Modifier) { val urlHandler = LocalUriHandler.current + ClickableText( text = annotatedString, style = TextStyle(color = LocalContentColor.current), diff --git a/app/src/main/java/com/aliucord/manager/ui/components/plugins/PluginCard.kt b/app/src/main/java/com/aliucord/manager/ui/components/plugins/PluginCard.kt new file mode 100644 index 0000000..0203414 --- /dev/null +++ b/app/src/main/java/com/aliucord/manager/ui/components/plugins/PluginCard.kt @@ -0,0 +1,112 @@ +package com.aliucord.manager.ui.components.plugins + +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.unit.dp +import com.aliucord.manager.R +import com.aliucord.manager.ui.components.Switch +import com.aliucord.manager.utils.Plugin + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun PluginCard( + plugin: Plugin, + onDelete: () -> Unit, + onShowChangelog: () -> Unit +) { + var isEnabled by remember { mutableStateOf(true) } + + ElevatedCard( + modifier = Modifier.fillMaxWidth() + ) { + Column( + modifier = Modifier.padding(12.dp) + ) { + // Header + Row { + Column { + // Name + Text( + buildAnnotatedString { + withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { + append(plugin.manifest.name) + } + + append(" v${plugin.manifest.version}") + } + ) + + // Author(s) + Text( + "By ${plugin.manifest.authors.joinToString { it.name }}", + style = MaterialTheme.typography.labelMedium + ) + } + + Spacer(Modifier.weight(1f, true)) + + // Toggle Switch + Switch( + checked = isEnabled, + onCheckedChange = { isEnabled = it } + ) + } + + // Description + Text( + text = plugin.manifest.description, + modifier = Modifier.heightIn(min = 50.dp), + style = MaterialTheme.typography.bodyMedium + ) + + // Buttons + Row(Modifier.fillMaxWidth()) { + val uriHandler = LocalUriHandler.current + + IconButton( + onClick = { + uriHandler.openUri( + plugin.manifest.updateUrl + .replace("raw.githubusercontent.com", "github.com") + .replaceFirst("/builds.*".toRegex(), "") + ) + } + ) { + Icon(painterResource(R.drawable.ic_account_github_white_24dp), "GitHub") + } + + if (plugin.manifest.changelog != null) { + IconButton( + onClick = onShowChangelog + ) { + Icon( + painter = painterResource(R.drawable.ic_history_white_24dp), + contentDescription = stringResource(R.string.view_plugin_changelog, plugin.manifest.name) + ) + } + } + + Spacer(Modifier.weight(1f, true)) + + IconButton(onClick = onDelete) { + Icon( + imageVector = Icons.Default.Delete, + contentDescription = stringResource(R.string.delete_plugin, plugin.manifest.name), + tint = MaterialTheme.colorScheme.error + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aliucord/manager/ui/components/settings/SettingSwitch.kt b/app/src/main/java/com/aliucord/manager/ui/components/settings/SettingSwitch.kt new file mode 100644 index 0000000..16d9d91 --- /dev/null +++ b/app/src/main/java/com/aliucord/manager/ui/components/settings/SettingSwitch.kt @@ -0,0 +1,16 @@ +package com.aliucord.manager.ui.components.settings + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.aliucord.manager.preferences.Preference +import com.aliucord.manager.ui.components.Switch + +@Composable +fun SettingSwitch( + modifier: Modifier = Modifier, + preference: Preference +) = Switch( + modifier = modifier, + checked = preference.get(), + onCheckedChange = preference::set +) \ No newline at end of file diff --git a/app/src/main/java/com/aliucord/manager/ui/components/settings/ThemeSetting.kt b/app/src/main/java/com/aliucord/manager/ui/components/settings/ThemeSetting.kt new file mode 100644 index 0000000..a23d67d --- /dev/null +++ b/app/src/main/java/com/aliucord/manager/ui/components/settings/ThemeSetting.kt @@ -0,0 +1,28 @@ +package com.aliucord.manager.ui.components.settings + +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.ListItem +import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.res.stringResource +import com.aliucord.manager.R +import com.aliucord.manager.preferences.themePref +import com.aliucord.manager.ui.theme.Theme + +@ExperimentalMaterialApi +@Composable +fun ThemeSetting(openDialog: MutableState) { + ListItem( + text = { Text(stringResource(R.string.theme)) }, + secondaryText = { Text(stringResource(R.string.theme_setting_description))}, + trailing = { + FilledTonalButton( + onClick = { openDialog.value = true } + ) { + Text(Theme.from(themePref.get()).displayName) + } + } + ) +} \ No newline at end of file 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 index 72662b6..6074550 100644 --- a/app/src/main/java/com/aliucord/manager/ui/screens/AboutScreen.kt +++ b/app/src/main/java/com/aliucord/manager/ui/screens/AboutScreen.kt @@ -1,136 +1,44 @@ 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.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.Text 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.painterResource 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 +import com.aliucord.manager.ui.components.about.ContributorsCard +import com.aliucord.manager.ui.components.about.TeamCard @ExperimentalFoundationApi -@OptIn(ExperimentalMaterialApi::class) @Composable -@Preview fun AboutScreen() { - val coroutineScope = rememberCoroutineScope { Dispatchers.IO } - val modifier = Modifier.padding(8.dp) - val contributors = remember { mutableStateOf?>(null) } + val uriHandler = LocalUriHandler.current - Column(modifier = Modifier.padding(16.dp)) { - Column( + Column { + TeamCard() + ContributorsCard() + + Spacer(Modifier.weight(1f, true)) + + Button( modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally + onClick = { uriHandler.openUri("https://github.com/Aliucord/Aliucord") } ) { - Text( - stringResource(R.string.team), - style = MaterialTheme.typography.h4, + Icon( + painter = painterResource(R.drawable.ic_account_github_white_24dp), + contentDescription = "GitHub", + modifier = Modifier.padding(8.dp) ) - - 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) - } - } - } + Text(stringResource(R.string.source_code)) } } -} - -@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") } - ) -} +} \ No newline at end of file diff --git a/app/src/main/java/com/aliucord/manager/ui/screens/CommitsScreen.kt b/app/src/main/java/com/aliucord/manager/ui/screens/CommitsScreen.kt new file mode 100644 index 0000000..bfe6e7e --- /dev/null +++ b/app/src/main/java/com/aliucord/manager/ui/screens/CommitsScreen.kt @@ -0,0 +1,95 @@ +package com.aliucord.manager.ui.screens + +import android.util.Log +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.ListItem +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.unit.dp +import com.aliucord.manager.BuildConfig +import com.aliucord.manager.models.Commit +import com.aliucord.manager.utils.Github +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +data class CommitData( + val commit: Commit, + val buildSha: String? +) + +@OptIn(ExperimentalMaterialApi::class) +@ExperimentalMaterialApi +@Composable +fun CommitsScreen() { + var isLoading by remember { mutableStateOf(true) } + val commits = remember { mutableStateListOf() } + + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + if (isLoading) { + Box( + modifier = Modifier.fillMaxSize().padding(12.dp), + contentAlignment = Alignment.Center, + content = { CircularProgressIndicator() } + ) + + LaunchedEffect(null) { + launch(Dispatchers.IO) { + try { + val buildCommits = Github.getCommits( + mapOf( + "sha" to "builds", + "path" to "Aliucord.dex", + "per_page" to "50" + ) + ) + val commits2 = Github.getCommits(mapOf("per_page" to "50")) + commits.addAll(commits2.map { c -> + CommitData( + c, + buildCommits.find { bc -> bc.commit.message.substring(6) == c.sha }?.sha + ) + }) + isLoading = false + } catch (e: Throwable) { + Log.e(BuildConfig.TAG, "Failed to get commits", e) + } + } + } + } else { + LazyColumn( + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + items(commits.toList()) { data -> + val commit = data.commit + val localUriHandler = LocalUriHandler.current + + ListItem( + modifier = Modifier.clickable { + localUriHandler.openUri(commit.htmlUrl) + }, + text = { + Text( + commit.sha.substring(0, 7), + color = if (data.buildSha != null) MaterialTheme.colorScheme.primary else Color.Unspecified + ) + }, + secondaryText = { Text("${commit.commit.message.split("\n").first()} - ${commit.author.name}") }, + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aliucord/manager/ui/screens/HomeScreen.kt b/app/src/main/java/com/aliucord/manager/ui/screens/HomeScreen.kt index d3d5cc9..8c2c170 100644 --- a/app/src/main/java/com/aliucord/manager/ui/screens/HomeScreen.kt +++ b/app/src/main/java/com/aliucord/manager/ui/screens/HomeScreen.kt @@ -5,12 +5,18 @@ package com.aliucord.manager.ui.screens +import android.Manifest import android.util.Log -import androidx.compose.foundation.background +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.* -import androidx.compose.runtime.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.ExitToApp +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -20,60 +26,84 @@ import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.withStyle -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.navigation.NavController import com.aliucord.manager.BuildConfig import com.aliucord.manager.R -import com.aliucord.manager.ui.components.CommitsList +import com.aliucord.manager.preferences.devModePreference +import com.aliucord.manager.ui.Screen +import com.aliucord.manager.ui.components.PluginsList +import com.aliucord.manager.ui.components.installer.InstallerDialog import com.aliucord.manager.utils.Github import com.aliucord.manager.utils.Versions +import com.google.accompanist.permissions.ExperimentalPermissionsApi +import com.google.accompanist.permissions.rememberPermissionState import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -@OptIn(ExperimentalMaterialApi::class) +@OptIn(ExperimentalMaterial3Api::class) +@ExperimentalPermissionsApi +@ExperimentalFoundationApi @Composable -@Preview -fun HomeScreen() { +fun HomeScreen(navController: NavController) { + val storagePermissionState = rememberPermissionState(Manifest.permission.WRITE_EXTERNAL_STORAGE) + val packageManager = LocalContext.current.packageManager - val coroutineScope = rememberCoroutineScope { Dispatchers.IO } - val selectedCommit = remember { mutableStateOf(null) } val versions = remember { mutableStateOf(null) } val installedVersion = remember { try { packageManager.getPackageInfo("com.aliucord", 0).versionName - } catch (th: Throwable) { "-" } + } catch (th: Throwable) { + "-" + } } - if (versions.value == null) { - LaunchedEffect(null) { - coroutineScope.launch { - try { - versions.value = Github.versions - } catch (th: Throwable) { - Log.e(BuildConfig.TAG, "Failed to get versions") - } + val showOptionsDialog = remember { mutableStateOf(false) } + + if (versions.value == null) LaunchedEffect(Unit) { + launch(Dispatchers.IO) { + try { + versions.value = Github.versions + } catch (th: Throwable) { + Log.e(BuildConfig.TAG, "Failed to get versions", th) } } } - Column(modifier = Modifier.padding(8.dp)) { - Card(modifier = Modifier.padding(bottom = 8.dp)) { - Row(modifier = Modifier.padding(16.dp).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) { + if (showOptionsDialog.value) InstallerDialog(showOptionsDialog, navController) + + Column( + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + ElevatedCard( + modifier = Modifier.wrapContentHeight() + ) { + Column( + modifier = Modifier.padding(16.dp).fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { Column { - Text("Aliucord", style = MaterialTheme.typography.h5, color = MaterialTheme.colors.primary) + Text( + "Aliucord", + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.primary + ) + Text(buildAnnotatedString { append("Supported version: ") + withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { - append( - versions.value?.versionName ?: "?" - ) + append(versions.value?.versionName ?: "?") } + append("\nInstalled version: ") + withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { append(installedVersion) } }) } + val (drawable, description) = when { installedVersion == "-" -> R.drawable.ic_download_24dp to R.string.install installedVersion.startsWith( @@ -81,19 +111,135 @@ fun HomeScreen() { ) -> R.drawable.ic_reinstall_24dp to R.string.reinstall else -> R.drawable.ic_update_24dp to R.string.update } - IconButton(onClick = { /* TODO */ }) { - Icon( - painter = painterResource(drawable), - contentDescription = stringResource(description), - tint = MaterialTheme.colors.onPrimary, - modifier = Modifier - .padding(end = 8.dp) - .background(MaterialTheme.colors.primary, CircleShape) - .padding(7.dp) - ) + + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { +// Row( +// modifier = Modifier.fillMaxWidth(), +// horizontalArrangement = Arrangement.spacedBy(12.dp) +// ) { +// +// +// Button( +// onClick = { +// navController.navigate(Screen.Commits.route) { +// popUpTo(Screen.Home.route) { saveState = true } +// } +// }, +// colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondaryContainer) +// ) { +// Icon( +// painter = painterResource(R.drawable.ic_update_24dp), +// contentDescription = "Commits", +// tint = MaterialTheme.colorScheme.onSecondaryContainer, +// modifier = Modifier.padding(8.dp) +// ) +// } +// } + + Button( + modifier = Modifier.fillMaxWidth(), + onClick = { + if (devModePreference.get()) { + showOptionsDialog.value = true + } else { + navController.navigate(Screen.Installer.route) { + popUpTo(Screen.Home.route) { saveState = true } + } + } + } + ) { + Icon( + painter = painterResource(drawable), + contentDescription = stringResource(description), + tint = MaterialTheme.colorScheme.onPrimary, + modifier = Modifier.padding(8.dp) + ) + + Text(stringResource(description)) + } + + BoxWithConstraints { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { +// if (installedVersion != "-") { + if (true) { + Button( + modifier = Modifier.weight(1f), + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondaryContainer), + onClick = { } + ) { + Icon( + modifier = Modifier.padding(8.dp), + imageVector = Icons.Default.Delete, + contentDescription = "Uninstall", + tint = MaterialTheme.colorScheme.onSecondaryContainer, + ) + } + + Button( + modifier = Modifier.weight(1f), + onClick = { }, + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondaryContainer) + ) { + Icon( + modifier = Modifier.padding(8.dp), + imageVector = Icons.Default.ExitToApp, + contentDescription = "Launch", + tint = MaterialTheme.colorScheme.onSecondaryContainer + ) + } + } + + Button( + modifier = Modifier.weight(1f), + onClick = { + navController.navigate(Screen.Commits.route) { + popUpTo(Screen.Home.route) { saveState = true } + } + }, + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondaryContainer) + ) { + Icon( + modifier = Modifier.padding(8.dp), + painter = painterResource(R.drawable.ic_update_24dp), + contentDescription = "Commits", + tint = MaterialTheme.colorScheme.onSecondaryContainer + ) + + if (this@BoxWithConstraints.minWidth > 20.dp) { + Text("Commits", color = MaterialTheme.colorScheme.onSecondaryContainer) + } + } + } + } } } } - Card { CommitsList(selectedCommit) } + + ElevatedCard(Modifier.fillMaxWidth().weight(1f)) { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Plugins", + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.primary + ) + + Spacer(Modifier.weight(1f, true)) + } + + PluginsList() + } + } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/aliucord/manager/ui/screens/InstallScreen.kt b/app/src/main/java/com/aliucord/manager/ui/screens/InstallScreen.kt new file mode 100644 index 0000000..2f884e3 --- /dev/null +++ b/app/src/main/java/com/aliucord/manager/ui/screens/InstallScreen.kt @@ -0,0 +1,60 @@ +package com.aliucord.manager.ui.screens + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import com.aliucord.manager.utils.DownloadUtils +import com.aliucord.manager.utils.Github +import com.aliucord.manager.utils.aliucordDir +import kotlinx.coroutines.launch +import java.io.File + +@Composable +fun InstallerScreen() { + var log by remember { mutableStateOf("") } + val context = LocalContext.current + + LaunchedEffect(Unit) { + launch { + val supportedVersion = Github.versions.versionCode + + log += "Checking for cached APK...\n" + + val apk = File(context.cacheDir, "discord-${supportedVersion}").let { file -> + val archiveInfo = context.packageManager.getPackageArchiveInfo(file.path, 0) + + if (file.exists() && archiveInfo?.versionCode.toString().startsWith(supportedVersion)) return@let file + + log += "Downloading APK...\n" + + DownloadUtils.downloadDiscordApk(context, supportedVersion) + + log += "Finished downloading APK\n" + } + + val manifest = File(aliucordDir, "AndroidManifest.xml").takeIf(File::exists) ?: run { + log += "Downloading manifest\n" + + DownloadUtils.download(context, Github.getDownloadUrl("builds", "AndroidManifest.xml")) + } + } + } + + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text(log) + + Spacer(Modifier.weight(1f, true)) + + LinearProgressIndicator( + modifier = Modifier.fillMaxWidth().height(8.dp), + progress = 0.5f + ) + } +} \ No newline at end of file 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 deleted file mode 100644 index 7e5b2d0..0000000 --- a/app/src/main/java/com/aliucord/manager/ui/screens/PluginsScreen.kt +++ /dev/null @@ -1,157 +0,0 @@ -package com.aliucord.manager.ui.screens - -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.Delete -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog -import com.aliucord.manager.R -import com.aliucord.manager.ui.components.Changelog -import com.aliucord.manager.utils.Plugin - -@OptIn(ExperimentalMaterialApi::class) -@Composable -@Preview -fun PluginsScreen() { - Column( - modifier = Modifier - .fillMaxSize() - .padding(12.dp) - ) { - var plugins by remember { mutableStateOf(Plugin.loadAll()) } - var search by remember { mutableStateOf("") } - - TextField( - value = search, - placeholder = { Text(stringResource(R.string.search)) }, - onValueChange = { search = it }, - modifier = Modifier - .fillMaxWidth() - .padding(16.dp) - ) - - LazyColumn { - items(plugins.filter { - it.manifest.run { - name.contains(search, true) - || description.contains(search, true) - || authors.any { a -> a.name.contains(search, true) } - } - }) { p -> - // TODO - make this get pluginEnabled from Aliucord preferences somehow - val isChecked = remember { mutableStateOf(true) } - var showDeleteDialog by remember { mutableStateOf(false) } - val showChangelogDialog = remember { mutableStateOf(false) } - - if (showDeleteDialog) { - AlertDialog( - onDismissRequest = { showDeleteDialog = false }, - title = { Text(stringResource(R.string.delete_plugin, p.manifest.name)) }, - text = { Text(stringResource(R.string.delete_plugin_body)) }, - confirmButton = { - Button( - onClick = { - showDeleteDialog = false - p.file.delete() - plugins = plugins.filter { it != p } - }, - colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colors.error, contentColor = MaterialTheme.colors.onError) - ) { - Text(stringResource(android.R.string.ok).uppercase()) - } - }, - dismissButton = { - Button( - onClick = { - showDeleteDialog = false - }, - colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colors.secondary) - ) { - Text(stringResource(android.R.string.cancel).uppercase()) - } - } - ) - } - - if (showChangelogDialog.value) { - Dialog( - onDismissRequest = { showChangelogDialog.value = false }, - content = { - Changelog(p, showChangelogDialog) - } - ) - } - - Card( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - elevation = 1.dp - ) { - Column { - ListItem( - text = { - Text( - stringResource( - R.string.plugin_title, - p.manifest.name, - p.manifest.version, - p.manifest.authors.joinToString( - " ", - transform = { it.name }) - ), - ) - }, - trailing = { - Switch( - checked = isChecked.value, - onCheckedChange = { isChecked.value = it }, - ) - }, - ) - Divider(color = MaterialTheme.colors.primary) - Text( - p.manifest.description, - modifier = Modifier - .heightIn(min = 50.dp) - .padding(start = 16.dp, top = 16.dp, end = 16.dp, bottom = 0.dp) - ) - - Row(modifier = Modifier.align(Alignment.End)) { - if (p.manifest.changelog != null) { - IconButton(onClick = { showChangelogDialog.value = true }) { - Icon( - painter = painterResource(R.drawable.ic_history_white_24dp), - contentDescription = stringResource( - R.string.view_plugin_changelog, - p.manifest.name - ) - ) - } - } - IconButton(onClick = { showDeleteDialog = true }) { - Icon( - imageVector = Icons.Default.Delete, - contentDescription = stringResource( - R.string.delete_plugin, - p.manifest.name - ), - tint = MaterialTheme.colors.error - ) - } - } - } - } - } - } - } -} 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 7dcdff5..31206e2 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 @@ -5,100 +5,152 @@ package com.aliucord.manager.ui.screens +import android.net.Uri +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material.* -import androidx.compose.runtime.Composable +import androidx.compose.foundation.layout.* +import androidx.compose.material.ContentAlpha +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.ListItem +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.core.net.toFile import com.aliucord.manager.R import com.aliucord.manager.preferences.* -import com.aliucord.manager.ui.components.ExposedDropdownMenu +import com.aliucord.manager.ui.components.Switch +import com.aliucord.manager.ui.components.settings.SettingSwitch +import com.aliucord.manager.ui.components.settings.ThemeSetting +import com.aliucord.manager.ui.theme.Theme -private val themes = arrayOf("System", "Light", "Dark", "Black") - -@OptIn(ExperimentalMaterialApi::class) +@OptIn(ExperimentalMaterial3Api::class) +@ExperimentalMaterialApi @Composable -fun SettingsScreen() { - Column { - val modifier = Modifier.padding(top = 8.dp) +fun SettingsScreen() = Column( + verticalArrangement = Arrangement.spacedBy(12.dp) +) { + val openDialog = remember { mutableStateOf(false) } - ListItem( - text = { Text(stringResource(R.string.theme)) }, - trailing = { - ExposedDropdownMenu( - items = themes, - selectedItem = themePreference.get(), - selectItem = themePreference::set - ) - }, - modifier = modifier - ) - ListItem( - text = { Text(stringResource(R.string.replace_bg)) }, - trailing = { - Checkbox( - checked = replaceBgPreference.get(), - onCheckedChange = replaceBgPreference::set - ) - }, - modifier = modifier.clickable { replaceBgPreference.set(!replaceBgPreference.get()) } - ) + if (openDialog.value) { + var selected by remember { mutableStateOf(Theme.from(themePref.get())) } - val devModeOn = devModePreference.get() - ListItem( - text = { Text(stringResource(R.string.developer_mode)) }, - trailing = { - Checkbox( - checked = devModeOn, - onCheckedChange = devModePreference::set - ) - }, - modifier = modifier - ) - if (devModeOn) { - ListItem( - text = { Text(stringResource(R.string.debuggable)) }, - trailing = { - Checkbox( - checked = debuggablePreference.get(), - onCheckedChange = debuggablePreference::set - ) - }, - modifier = modifier - ) + AlertDialog( + onDismissRequest = { openDialog.value = false }, + title = { Text("Theme") }, + text = { + Column { + Theme.values().forEach { theme -> + Row( + modifier = Modifier.clickable { + selected = theme + }, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + theme.displayName, + style = MaterialTheme.typography.labelLarge + ) - 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 - .fillMaxWidth() - .padding(8.dp) - ) { - Text(stringResource(R.string.clear_files_cache), textAlign = TextAlign.Center) - } + Spacer(Modifier.weight(1f, true)) + + RadioButton( + selected = theme == selected, + onClick = { selected = theme } + ) + } + } + } + }, + confirmButton = { + Button( + onClick = { + openDialog.value = false + themePref.set(selected.ordinal) + } + ) { + Text("Apply") + Icon(Icons.Default.Check, "Confirm") + } + } + ) } -} + + ThemeSetting(openDialog) + + ListItem( + text = { Text(stringResource(R.string.use_black)) }, + secondaryText = { Text(stringResource(R.string.use_black_description)) }, + trailing = { SettingSwitch(preference = Preferences.useBlack) } + ) + + Divider() + + ListItem( + text = { Text(stringResource(R.string.replace_bg)) }, + trailing = { SettingSwitch(preference = replaceBg) } + ) + + val devModeOn = devModePreference.get() + ListItem( + text = { Text(stringResource(R.string.developer_mode)) }, + trailing = { + Switch( + checked = devModeOn, + onCheckedChange = devModePreference::set + ) + } + ) + + if (devModeOn) { + ListItem( + text = { Text(stringResource(R.string.debuggable)) }, + trailing = { SettingSwitch(preference = debuggablePreference) } + ) + + val udfs = useDexFromStoragePreference + + ListItem( + text = { Text(stringResource(R.string.use_dex_from_storage)) }, + trailing = { SettingSwitch(preference = udfs) } + ) + + val color = if (udfs.get()) { + Color.Unspecified + } else { + MaterialTheme.colorScheme.onBackground.copy(ContentAlpha.disabled) + } + + val test = remember { mutableStateOf("") } + val result = remember { mutableStateOf(null) } + val context = LocalContext.current + val picker = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> + if (uri == null) return@rememberLauncherForActivityResult + + test.value = uri.toFile().absolutePath + } + + ListItem( + modifier = if (udfs.get()) Modifier.clickable { + picker.launch(arrayOf("application/octet-stream")) + } else Modifier, + text = { Text(stringResource(R.string.dex_location), color = color) }, + secondaryText = { Text(dexLocationPreference.get(), color = color) } + ) + } + + Button( + modifier = Modifier.fillMaxWidth().padding(8.dp), + onClick = { /*TODO*/ } + ) { + Text(stringResource(R.string.clear_files_cache), textAlign = TextAlign.Center) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aliucord/manager/ui/screens/StoreScreen.kt b/app/src/main/java/com/aliucord/manager/ui/screens/StoreScreen.kt new file mode 100644 index 0000000..101cadc --- /dev/null +++ b/app/src/main/java/com/aliucord/manager/ui/screens/StoreScreen.kt @@ -0,0 +1,11 @@ +package com.aliucord.manager.ui.screens + +import androidx.compose.foundation.layout.Column +import androidx.compose.runtime.Composable + +@Composable +fun StoreScreen() { + Column { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aliucord/manager/ui/theme/Color.kt b/app/src/main/java/com/aliucord/manager/ui/theme/Color.kt index 9cbe805..331bc6b 100644 --- a/app/src/main/java/com/aliucord/manager/ui/theme/Color.kt +++ b/app/src/main/java/com/aliucord/manager/ui/theme/Color.kt @@ -7,7 +7,7 @@ package com.aliucord.manager.ui.theme import androidx.compose.ui.graphics.Color -val darkBackground = Color(0xff2e2e2e) +val darkBackground = Color(0xff1a1a1a) val primaryColor = Color(0xff00c853) val primaryColorLight = Color(0xff5efc82) @@ -17,4 +17,45 @@ val discordBrand = Color(0xff5865F2) val discordGreen = Color(0xff57F287) val discordRed = Color(0xffED4245) -val errorColor = discordRed \ No newline at end of file +val errorColor = discordRed + +val Blue10 = Color(0xFF000F5E) +val Blue20 = Color(0xFF001E92) +val Blue30 = Color(0xFF002ECC) +val Blue40 = Color(0xFF1546F6) +val Blue80 = Color(0xFFB8C3FF) +val Blue90 = Color(0xFFDDE1FF) + +val DarkBlue10 = Color(0xFF00036B) +val DarkBlue20 = Color(0xFF000BA6) +val DarkBlue30 = Color(0xFF1026D3) +val DarkBlue40 = Color(0xFF3648EA) +val DarkBlue80 = Color(0xFFBBC2FF) +val DarkBlue90 = Color(0xFFDEE0FF) + +val Yellow10 = Color(0xFF261900) +val Yellow20 = Color(0xFF402D00) +val Yellow30 = Color(0xFF5C4200) +val Yellow40 = Color(0xFF7A5900) +val Yellow80 = Color(0xFFFABD1B) +val Yellow90 = Color(0xFFFFDE9C) + +val Red10 = Color(0xFF410001) +val Red20 = Color(0xFF680003) +val Red30 = Color(0xFF930006) +val Red40 = Color(0xFFBA1B1B) +val Red80 = Color(0xFFFFB4A9) +val Red90 = Color(0xFFFFDAD4) + +val Grey10 = Color(0xFF191C1D) +val Grey20 = Color(0xFF2D3132) +val Grey80 = Color(0xFFC4C7C7) +val Grey90 = Color(0xFFE0E3E3) +val Grey95 = Color(0xFFEFF1F1) +val Grey99 = Color(0xFFFBFDFD) + +val BlueGrey30 = Color(0xFF45464F) +val BlueGrey50 = Color(0xFF767680) +val BlueGrey60 = Color(0xFF90909A) +val BlueGrey80 = Color(0xFFC6C5D0) +val BlueGrey90 = Color(0xFFE2E1EC) \ No newline at end of file diff --git a/app/src/main/java/com/aliucord/manager/ui/theme/Shape.kt b/app/src/main/java/com/aliucord/manager/ui/theme/Shape.kt deleted file mode 100644 index de1a2bb..0000000 --- a/app/src/main/java/com/aliucord/manager/ui/theme/Shape.kt +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (c) 2021 Juby210 - * Licensed under the Open Software License version 3.0 - */ - -package com.aliucord.manager.ui.theme - -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Shapes -import androidx.compose.ui.unit.dp - -val Shapes = Shapes( - small = RoundedCornerShape(4.dp), - medium = RoundedCornerShape(4.dp), - large = RoundedCornerShape(0.dp) -) diff --git a/app/src/main/java/com/aliucord/manager/ui/theme/Theme.kt b/app/src/main/java/com/aliucord/manager/ui/theme/Theme.kt index 58d1867..7941fb3 100644 --- a/app/src/main/java/com/aliucord/manager/ui/theme/Theme.kt +++ b/app/src/main/java/com/aliucord/manager/ui/theme/Theme.kt @@ -5,64 +5,64 @@ package com.aliucord.manager.ui.theme +import android.os.Build import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material.MaterialTheme -import androidx.compose.material.darkColors -import androidx.compose.material.lightColors +import androidx.compose.material3.* import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect import androidx.compose.ui.graphics.Color -import com.aliucord.manager.preferences.themePreference +import androidx.compose.ui.platform.LocalContext +import com.google.accompanist.systemuicontroller.rememberSystemUiController + +private val DarkColorScheme = darkColorScheme( -private val LightColorPalette = lightColors( - primary = primaryColor, - primaryVariant = primaryColorDark, - secondary = primaryColorLight, - error = errorColor, ) -private val DarkColorPalette = darkColors( - primary = primaryColor, - primaryVariant = primaryColorDark, - secondary = primaryColorLight, - error = errorColor, +private val LightColorScheme = lightColorScheme( +) + +private val BlackColorPalette = darkColorScheme( + primary = Color.Black, onPrimary = Color.White, - background = darkBackground, - surface = Color(0xff424242), -) - -private val BlackColorPalette = darkColors( - primary = primaryColor, - primaryVariant = primaryColorDark, - secondary = primaryColorLight, - error = errorColor, - - onPrimary = Color.White, - background = Color(0xff000000), - surface = Color(0xff121212) ) @Composable -fun isDark() = when (themePreference.value.value) { - 0 -> isSystemInDarkTheme() - 1 -> false - else -> true -} +fun ManagerTheme( + isDarkTheme: Boolean = isSystemInDarkTheme(), + isDynamicColor: Boolean = true, + content: @Composable () -> Unit +) { + val dynamicColor = isDynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S + val colorScheme = when { + dynamicColor && isDarkTheme -> dynamicDarkColorScheme(LocalContext.current) + dynamicColor && !isDarkTheme -> dynamicLightColorScheme(LocalContext.current) + isDarkTheme -> DarkColorScheme + else -> LightColorScheme + } -@Composable -fun getTheme() = if (!isDark()) LightColorPalette else when (themePreference.value.value) { - 3 -> BlackColorPalette - else -> DarkColorPalette -} + val systemUiController = rememberSystemUiController() -@Composable -fun AliucordManagerTheme(content: @Composable () -> Unit) { - val colors = getTheme() + SideEffect { + systemUiController.setSystemBarsColor( + color = colorScheme.background, + darkIcons = !isDarkTheme + ) + } MaterialTheme( - colors, + colorScheme = colorScheme, typography = Typography, - shapes = Shapes, - content + content = content ) } + +enum class Theme(val displayName: String) { + SYSTEM("System"), + LIGHT("Light"), + DARK("Dark"); + + companion object { + fun from(index: Int) = values()[index] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aliucord/manager/ui/theme/Type.kt b/app/src/main/java/com/aliucord/manager/ui/theme/Type.kt index ee4418d..65b0c63 100644 --- a/app/src/main/java/com/aliucord/manager/ui/theme/Type.kt +++ b/app/src/main/java/com/aliucord/manager/ui/theme/Type.kt @@ -5,16 +5,6 @@ package com.aliucord.manager.ui.theme -import androidx.compose.material.Typography -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.sp +import androidx.compose.material3.Typography -val Typography = Typography( - body1 = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 16.sp - ) -) +val Typography = Typography() \ No newline at end of file diff --git a/app/src/main/java/com/aliucord/manager/utils/Constants.kt b/app/src/main/java/com/aliucord/manager/utils/Constants.kt index b0c0d31..c1ffe16 100644 --- a/app/src/main/java/com/aliucord/manager/utils/Constants.kt +++ b/app/src/main/java/com/aliucord/manager/utils/Constants.kt @@ -1,11 +1,12 @@ package com.aliucord.manager.utils -import android.content.Context import android.os.Environment import com.google.gson.Gson +import io.ktor.client.* +import io.ktor.client.engine.android.* import java.io.File +val httpClient = HttpClient(Android) val aliucordDir = File(Environment.getExternalStorageDirectory(), "Aliucord") val pluginsDir = File(aliucordDir, "plugins") - val gson = Gson() \ No newline at end of file diff --git a/app/src/main/java/com/aliucord/manager/utils/DownloadUtils.kt b/app/src/main/java/com/aliucord/manager/utils/DownloadUtils.kt new file mode 100644 index 0000000..dfdd773 --- /dev/null +++ b/app/src/main/java/com/aliucord/manager/utils/DownloadUtils.kt @@ -0,0 +1,51 @@ +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.widget.Toast +import androidx.core.net.toFile +import androidx.core.net.toUri +import java.io.File +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +object DownloadUtils { + private const val backendHost = "https://aliucord.com/" + + suspend fun downloadDiscordApk(ctx: Context, version: String) = download(ctx, "$backendHost/download/discord?v=$version") + + suspend fun download(ctx: Context, url: String): File { + val downloadManager = ctx.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager + + return suspendCoroutine { continuation -> + try { + val receiver = object : BroadcastReceiver() { + var downloadId = 0L + + override fun onReceive(context: Context, intent: Intent) { + val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) + + if (downloadId == id) { + Toast.makeText(ctx, "Finished downloading", Toast.LENGTH_SHORT).show() + ctx.unregisterReceiver(this) + continuation.resume(downloadManager.getUriForDownloadedFile(id).toFile()) + } + } + } + + ctx.registerReceiver(receiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) + + receiver.downloadId = DownloadManager.Request(url.toUri()).run { + setTitle("Downloading APK") + setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE) + downloadManager.enqueue(this) + } + } finally { + Toast.makeText(ctx, "Finished downloading", Toast.LENGTH_SHORT).show() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aliucord/manager/utils/Downloader.kt b/app/src/main/java/com/aliucord/manager/utils/Downloader.kt deleted file mode 100644 index 06ef186..0000000 --- a/app/src/main/java/com/aliucord/manager/utils/Downloader.kt +++ /dev/null @@ -1,39 +0,0 @@ -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 ca6b681..6500877 100644 --- a/app/src/main/java/com/aliucord/manager/utils/Github.kt +++ b/app/src/main/java/com/aliucord/manager/utils/Github.kt @@ -7,10 +7,10 @@ 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 import java.net.URL +import kotlin.collections.set data class Versions(val versionCode: String, val versionName: String, val aliucordHash: String) @@ -33,34 +33,25 @@ 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 = InputStreamReader(URL(commitsUrl + query).openStream()).use { - gson.fromJson>( - it, - commitsType - ) + gson.fromJson>(it, commitsType) } commitsCache[query] = res return res } - val contributors: Array by lazy { + val contributors: Array by lazy() { InputStreamReader(URL(contributorsUrl).openStream()).use { stream -> - gson.fromJson>( - stream, - contributorsType - ).apply { + gson.fromJson>(stream, contributorsType).apply { sortByDescending { it.contributions } } } } - fun getDownloadUrl(ref: String, file: String): String { - return "https://raw.githubusercontent.com/${org}/${repo}/${ref}/${file}"; - } -} + fun getDownloadUrl(ref: String, file: String) = "https://raw.githubusercontent.com/${org}/${repo}/${ref}/${file}" +} \ No newline at end of file diff --git a/app/src/main/java/com/aliucord/manager/utils/Plugin.kt b/app/src/main/java/com/aliucord/manager/utils/Plugin.kt index 128f80d..cedba47 100644 --- a/app/src/main/java/com/aliucord/manager/utils/Plugin.kt +++ b/app/src/main/java/com/aliucord/manager/utils/Plugin.kt @@ -9,9 +9,10 @@ import java.util.zip.ZipFile data class Manifest( val name: String, - val authors: Array, + val authors: ArrayList, val description: String, val version: String, + val updateUrl: String, val changelog: String?, val changelogMedia: String? ) { @@ -39,21 +40,18 @@ class Plugin(val file: File) { } val files = pluginsDir.listFiles() ?: return emptyList() - val plugins = ArrayList(files.size) - files.forEach { - if (!it.name.endsWith(".zip")) { - Log.w(BuildConfig.TAG, "Found non zip ${it.name} in plugins folder.") - } else { - try { - plugins.add(Plugin(it)) - } catch (th: Throwable) { - Log.e(BuildConfig.TAG, "Failed to load plugin ${it.nameWithoutExtension}", th) - } + return files.mapNotNull { file -> + if (!file.name.endsWith(".zip")) { + Log.w(BuildConfig.TAG, "Found non zip ${file.name} in plugins folder.") + null + } else try { + Plugin(file) + } catch (th: Throwable) { + Log.e(BuildConfig.TAG, "Failed to load plugin ${file.nameWithoutExtension}", th) + null } - } - - return plugins.apply { sortBy { it.manifest.name }} + }.sortedBy { it.manifest.name } } } } \ No newline at end of file diff --git a/app/src/main/res/drawable/divider.xml b/app/src/main/res/drawable/divider.xml deleted file mode 100644 index c646b35..0000000 --- a/app/src/main/res/drawable/divider.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/ic_account_github_white_24dp.xml b/app/src/main/res/drawable/ic_account_github_white_24dp.xml new file mode 100644 index 0000000..e4b5e91 --- /dev/null +++ b/app/src/main/res/drawable/ic_account_github_white_24dp.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_discord.xml b/app/src/main/res/drawable/ic_discord.xml index 7529205..0badb62 100644 --- a/app/src/main/res/drawable/ic_discord.xml +++ b/app/src/main/res/drawable/ic_discord.xml @@ -3,11 +3,10 @@ android:height="19dp" android:viewportWidth="71" android:viewportHeight="55"> - - - - - + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_download_24dp.xml b/app/src/main/res/drawable/ic_download_24dp.xml index f80cf84..1acdd9b 100644 --- a/app/src/main/res/drawable/ic_download_24dp.xml +++ b/app/src/main/res/drawable/ic_download_24dp.xml @@ -1,9 +1,9 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_history_white_24dp.xml b/app/src/main/res/drawable/ic_history_white_24dp.xml index 4bc8bde..b4a1e1d 100644 --- a/app/src/main/res/drawable/ic_history_white_24dp.xml +++ b/app/src/main/res/drawable/ic_history_white_24dp.xml @@ -1,9 +1,9 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml index f0a6991..2f0c97e 100644 --- a/app/src/main/res/drawable/ic_launcher_foreground.xml +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -3,19 +3,20 @@ android:height="108dp" android:viewportWidth="108" android:viewportHeight="108"> - - - - - - + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/commit.xml b/app/src/main/res/layout/commit.xml deleted file mode 100644 index 3ea4837..0000000 --- a/app/src/main/res/layout/commit.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - diff --git a/app/src/main/res/layout/recycler.xml b/app/src/main/res/layout/recycler.xml deleted file mode 100644 index 634b8cd..0000000 --- a/app/src/main/res/layout/recycler.xml +++ /dev/null @@ -1,8 +0,0 @@ - - diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml index c5d5899..a5b623a 100644 --- a/app/src/main/res/values/ic_launcher_background.xml +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -1,4 +1,4 @@ - #FFFFFF + #FFFFFFFF \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7d248f6..1c30b45 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,6 +1,6 @@ Aliucord Manager - Clear files cache (Aliucord.dex and patched manifest) + Clear files cache (Injector.dex and patched manifest) Commits Dex location Open settings @@ -13,15 +13,17 @@ Use Aliucord.dex from storage Plugins Launch Plugins Page - %1$s v%2$s by %3$s + %1$s v%2$s + By %1$s Delete %1$s Search View changelog for %1$s - Are you sure you want to delete this plugin? This action cannot be undone. + Are you sure you want to uninstall this plugin? This action cannot be undone. Delete Close Support Server Back + Home Install Reinstall Update @@ -30,5 +32,13 @@ About Team Contributors - Loading... - + Loading… + Search Plugins + Source Code + Installer + Use black + Use black colors for dark mode + Manager appearance + No plugins installed! + Store + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 8a3fc43..89d42b9 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -2,6 +2,5 @@ diff --git a/build.gradle.kts b/build.gradle.kts index 564619f..2107f69 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,14 +10,14 @@ buildscript { mavenCentral() } dependencies { - classpath("com.android.tools.build:gradle:7.1.0-alpha03") - @Suppress("GradleDependency") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10") + classpath("com.android.tools.build:gradle:7.1.1") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10") // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle.kts files } } -tasks.register("clean", Delete::class) { +tasks.register("clean") { delete(rootProject.buildDir) -} +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0c0b2dd..5500c88 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Jul 12 01:52:20 CEST 2021 +#Tue Jan 25 18:24:43 EST 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/settings.gradle.kts b/settings.gradle.kts index defd771..07c94aa 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -10,5 +10,6 @@ dependencyResolutionManagement { mavenCentral() } } + rootProject.name = "Aliucord Manager" -include(":app") +include(":app") \ No newline at end of file