diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d00cbd2..82fe8a5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -60,6 +60,7 @@ android { kotlinOptions { jvmTarget = "11" + freeCompilerArgs += "-Xcontext-receivers" } buildFeatures.compose = true @@ -67,7 +68,6 @@ android { } dependencies { - implementation(fileTree("./libs")) // AndroidX implementation("androidx.core:core-ktx:1.9.0") @@ -104,7 +104,6 @@ dependencies { implementation("com.github.X1nto:Taxi:1.2.0") implementation("org.bouncycastle:bcpkix-jdk15on:1.70") implementation("com.android:zipflinger:8.0.0-alpha01") -// implementation("com.github.Aliucord:libzip:1.0.0") implementation("com.aliucord:axml:1.0.1") implementation("com.android.tools.build:apksig:7.4.0-beta01") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.0") diff --git a/app/libs/libzip-release.aar b/app/libs/libzip-release.aar deleted file mode 100644 index 3627f30..0000000 Binary files a/app/libs/libzip-release.aar and /dev/null differ diff --git a/app/src/main/kotlin/com/aliucord/manager/MainActivity.kt b/app/src/main/kotlin/com/aliucord/manager/MainActivity.kt index 46bcfd5..083a5e3 100644 --- a/app/src/main/kotlin/com/aliucord/manager/MainActivity.kt +++ b/app/src/main/kotlin/com/aliucord/manager/MainActivity.kt @@ -17,11 +17,11 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import com.aliucord.manager.domain.manager.PreferencesManager import com.aliucord.manager.ui.dialog.StoragePermissionsDialog import com.aliucord.manager.ui.navigation.AppDestination +import com.aliucord.manager.ui.navigation.HomeDestination import com.aliucord.manager.ui.screen.* import com.aliucord.manager.ui.theme.ManagerTheme import com.aliucord.manager.ui.theme.Theme -import com.xinto.taxi.Taxi -import com.xinto.taxi.rememberBackstackNavigator +import com.xinto.taxi.* import org.koin.android.ext.android.inject val aliucordDir = Environment.getExternalStorageDirectory().resolve("Aliucord") @@ -41,9 +41,15 @@ class MainActivity : ComponentActivity() { isDynamicColor = preferences.dynamicColor ) { val navigator = rememberBackstackNavigator(AppDestination.Home) + val homeRootNavigator = rememberNavigator(HomeDestination.HOME) BackHandler { - if (!navigator.pop()) finish() + if (!navigator.pop()) { + if (homeRootNavigator.currentDestination == HomeDestination.HOME) + finish() + else + homeRootNavigator.replace(HomeDestination.HOME) + } } StoragePermissionsDialog() @@ -55,20 +61,21 @@ class MainActivity : ComponentActivity() { ) { destination -> when (destination) { is AppDestination.Home -> MainRootScreen( - onClickInstall = { navigator.push(AppDestination.Install(it)) }, - onClickAbout = { navigator.push(AppDestination.About) }, - onClickSettings = { navigator.push(AppDestination.Settings) } + mainRootNavigator = homeRootNavigator, + onInstallClick = { navigator.push(AppDestination.Install(it)) }, + onAboutClick = { navigator.push(AppDestination.About) } ) + is AppDestination.Install -> InstallerScreen( installData = destination.installData, - onClickBack = navigator::pop - ) - is AppDestination.Settings -> SettingsScreen( - onClickBack = navigator::pop + onBackClick = navigator::pop ) + is AppDestination.About -> AboutScreen( - onClickBack = navigator::pop + onBackClick = navigator::pop ) + + else -> {} } } } diff --git a/app/src/main/kotlin/com/aliucord/manager/di/HttpModule.kt b/app/src/main/kotlin/com/aliucord/manager/di/HttpModule.kt index 94e6b22..1f3fc8c 100644 --- a/app/src/main/kotlin/com/aliucord/manager/di/HttpModule.kt +++ b/app/src/main/kotlin/com/aliucord/manager/di/HttpModule.kt @@ -11,7 +11,7 @@ import org.koin.dsl.module import java.net.Inet4Address import java.net.InetAddress -val httpModule = module { +val httpModule = module { fun provideJson() = Json { ignoreUnknownKeys = true } diff --git a/app/src/main/kotlin/com/aliucord/manager/di/ViewModelModule.kt b/app/src/main/kotlin/com/aliucord/manager/di/ViewModelModule.kt index c344b6e..e383dd3 100644 --- a/app/src/main/kotlin/com/aliucord/manager/di/ViewModelModule.kt +++ b/app/src/main/kotlin/com/aliucord/manager/di/ViewModelModule.kt @@ -1,7 +1,6 @@ package com.aliucord.manager.di import com.aliucord.manager.ui.viewmodel.* -import org.koin.androidx.compose.get import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.androidx.viewmodel.dsl.viewModelOf import org.koin.dsl.module @@ -10,6 +9,6 @@ val viewModelModule = module { viewModelOf(::HomeViewModel) viewModelOf(::PluginsViewModel) viewModelOf(::AboutViewModel) - viewModel { params -> InstallViewModel(get(), get(), get(), get(), params.get())} + viewModel { params -> InstallViewModel(get(), get(), get(), get(), params.get()) } viewModelOf(::SettingsViewModel) } diff --git a/app/src/main/kotlin/com/aliucord/manager/domain/manager/DownloadManager.kt b/app/src/main/kotlin/com/aliucord/manager/domain/manager/DownloadManager.kt index 3790940..b71fe30 100644 --- a/app/src/main/kotlin/com/aliucord/manager/domain/manager/DownloadManager.kt +++ b/app/src/main/kotlin/com/aliucord/manager/domain/manager/DownloadManager.kt @@ -5,7 +5,6 @@ import android.content.* import androidx.core.content.getSystemService import androidx.core.net.toUri import java.io.File -import java.net.URL import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine diff --git a/app/src/main/kotlin/com/aliucord/manager/domain/repository/GithubRepository.kt b/app/src/main/kotlin/com/aliucord/manager/domain/repository/GithubRepository.kt index 0d91371..0bff85b 100644 --- a/app/src/main/kotlin/com/aliucord/manager/domain/repository/GithubRepository.kt +++ b/app/src/main/kotlin/com/aliucord/manager/domain/repository/GithubRepository.kt @@ -1,10 +1,7 @@ package com.aliucord.manager.domain.repository import com.aliucord.manager.network.dto.GithubUser -import com.aliucord.manager.network.dto.Version import com.aliucord.manager.network.service.GithubService -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json class GithubRepository( private val service: GithubService diff --git a/app/src/main/kotlin/com/aliucord/manager/installer/service/InstallService.kt b/app/src/main/kotlin/com/aliucord/manager/installer/service/InstallService.kt index ad99149..c3dd292 100644 --- a/app/src/main/kotlin/com/aliucord/manager/installer/service/InstallService.kt +++ b/app/src/main/kotlin/com/aliucord/manager/installer/service/InstallService.kt @@ -18,16 +18,19 @@ class InstallService : Service() { startActivity(confirmationIntent) } + PackageInstaller.STATUS_SUCCESS -> Toast.makeText( - this, - "Successfully installed Aliucord!", - Toast.LENGTH_LONG + this, + "Successfully installed Aliucord!", + Toast.LENGTH_LONG ).show() + PackageInstaller.STATUS_FAILURE_ABORTED -> Toast.makeText( this, "Aborted installation", Toast.LENGTH_LONG ).show() + else -> { Log.i(BuildConfig.TAG, "Installation failed: error code $statusCode") Toast.makeText(this, "Failed: Error $statusCode", Toast.LENGTH_LONG).show() diff --git a/app/src/main/kotlin/com/aliucord/manager/installer/util/ManifestPatcher.kt b/app/src/main/kotlin/com/aliucord/manager/installer/util/ManifestPatcher.kt index bb79f23..59c5034 100644 --- a/app/src/main/kotlin/com/aliucord/manager/installer/util/ManifestPatcher.kt +++ b/app/src/main/kotlin/com/aliucord/manager/installer/util/ManifestPatcher.kt @@ -100,6 +100,7 @@ object ManifestPatcher { ) } } + else -> visitor } } diff --git a/app/src/main/kotlin/com/aliucord/manager/installer/util/ZipUtils.kt b/app/src/main/kotlin/com/aliucord/manager/installer/util/ZipUtils.kt deleted file mode 100644 index 961f5b8..0000000 --- a/app/src/main/kotlin/com/aliucord/manager/installer/util/ZipUtils.kt +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (c) 2022 Juby210 & zt - * Licensed under the Open Software License version 3.0 - */ - -package com.aliucord.manager.installer.util - -import com.aliucord.libzip.Zip -import java.io.InputStream - -fun Zip.writeEntry(entryName: String, stream: InputStream) = writeEntry(entryName, stream.readBytes()) -fun Zip.writeEntry(entryName: String, bytes: ByteArray) { - openEntry(entryName) - writeEntry(bytes, bytes.size.toLong()) - closeEntry() -} diff --git a/app/src/main/kotlin/com/aliucord/manager/network/dto/Commit.kt b/app/src/main/kotlin/com/aliucord/manager/network/dto/Commit.kt index 2a6797c..600495e 100644 --- a/app/src/main/kotlin/com/aliucord/manager/network/dto/Commit.kt +++ b/app/src/main/kotlin/com/aliucord/manager/network/dto/Commit.kt @@ -18,6 +18,7 @@ data class Commit( ) { @Serializable data class Author(@SerialName("login") val name: String) + @Serializable data class Commit(val message: String) } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/Contributors.kt b/app/src/main/kotlin/com/aliucord/manager/ui/component/Contributors.kt new file mode 100644 index 0000000..e1a8039 --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/ui/component/Contributors.kt @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2022 Juby210 & zt + * Licensed under the Open Software License version 3.0 + */ + +package com.aliucord.manager.ui.component + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import com.aliucord.manager.R +import com.aliucord.manager.network.dto.GithubUser + +fun LazyListScope.contributors(contributors: List) { + + item { + Text( + text = stringResource(R.string.contributors), + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier + .padding(start = 16.dp, bottom = 16.dp) + ) + } + + if (contributors.isNotEmpty()) { + items(contributors) { user -> + ContributorEntry(user = user) + } + } else { + item { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + contentAlignment = Alignment.Center, + content = { CircularProgressIndicator() } + ) + } + } +} + +@Composable +fun ContributorEntry(user: GithubUser) { + val uriHandler = LocalUriHandler.current + + Row( + modifier = Modifier + .clickable { + uriHandler.openUri("https://github.com/${user.name}") + } + .padding(horizontal = 16.dp, vertical = 10.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + AsyncImage( + model = "https://github.com/${user.name}.png", + contentDescription = user.name, + Modifier + .size(45.dp) + .clip(CircleShape) + ) + + Column { + Text( + text = user.name, + style = MaterialTheme.typography.bodyLarge + ) + Text( + text = stringResource(R.string.contributions, user.contributions), + style = MaterialTheme.typography.bodyMedium.copy( + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) + ) + ) + } + + } +} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/ContributorsCard.kt b/app/src/main/kotlin/com/aliucord/manager/ui/component/ContributorsCard.kt deleted file mode 100644 index f61a604..0000000 --- a/app/src/main/kotlin/com/aliucord/manager/ui/component/ContributorsCard.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2022 Juby210 & zt - * Licensed under the Open Software License version 3.0 - */ - -package com.aliucord.manager.ui.component - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.grid.* -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.* -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.platform.LocalUriHandler -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import coil.compose.AsyncImage -import com.aliucord.manager.R -import com.aliucord.manager.network.dto.GithubUser - -@Composable -fun ContributorsCard(contributors: List) { - ElevatedCard { - Column( - modifier = Modifier - .padding(16.dp) - .fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - Text( - text = stringResource(R.string.contributors), - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.primary - ) - - if (contributors.isNotEmpty()) { - LazyVerticalGrid( - columns = GridCells.Adaptive(48.dp), - verticalArrangement = Arrangement.spacedBy(4.dp), - horizontalArrangement = Arrangement.spacedBy(4.dp), - userScrollEnabled = false - ) { - items(contributors) { user -> ContributorEntry(user.name) } - } - } else { - Box( - modifier = Modifier - .fillMaxWidth() - .padding(12.dp), - contentAlignment = Alignment.Center, - content = { CircularProgressIndicator() } - ) - } - } - } -} - -@Composable -fun ContributorEntry(name: String) { - val uriHandler = LocalUriHandler.current - - AsyncImage( - modifier = Modifier - .fillMaxSize() - .clip(CircleShape) - .clickable { uriHandler.openUri("https://github.com/$name") }, - model = "https://github.com/$name.png", - contentDescription = name - ) -} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/home/CommitList.kt b/app/src/main/kotlin/com/aliucord/manager/ui/component/home/CommitList.kt new file mode 100644 index 0000000..9111b3f --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/ui/component/home/CommitList.kt @@ -0,0 +1,152 @@ +package com.aliucord.manager.ui.component.home + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.* +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.paging.LoadState +import androidx.paging.PagingData +import androidx.paging.compose.collectAsLazyPagingItems +import androidx.paging.compose.items +import coil.compose.AsyncImage +import com.aliucord.manager.R +import com.aliucord.manager.network.dto.Commit +import kotlinx.coroutines.flow.Flow + +@Composable +fun CommitList( + commits: Flow> +) { + ElevatedCard( + modifier = Modifier.padding(bottom = 16.dp) + ) { + Column( + modifier = Modifier + .weight(1f, true) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + val lazyPagingItems = commits.collectAsLazyPagingItems() + + Text( + modifier = Modifier.padding(20.dp, 20.dp, 20.dp), + text = stringResource(R.string.commits), + style = MaterialTheme.typography.titleLarge.copy(fontSize = 23.sp), + color = MaterialTheme.colorScheme.primary + ) + + LazyColumn( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + if (lazyPagingItems.loadState.refresh == LoadState.Loading) { + item { + Text( + modifier = Modifier + .fillMaxWidth() + .wrapContentWidth(Alignment.CenterHorizontally), + text = stringResource(R.string.paging_initial_load) + ) + } + } + + items(lazyPagingItems) { commitData -> + if (commitData == null) return@items + + CommitItem(commitData) + } + + if (lazyPagingItems.loadState.append == LoadState.Loading) { + item { + CircularProgressIndicator( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + .wrapContentWidth(Alignment.CenterHorizontally) + ) + } + } + } + } + } +} + +@Composable +fun CommitItem( + commit: Commit +) { + val uriHandler = LocalUriHandler.current + + Column( + modifier = Modifier + .fillMaxWidth() + .clickable { + uriHandler.openUri(commit.htmlUrl) + } + .padding(horizontal = 20.dp, vertical = 5.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + + Text( + commit.commit.message.split("\n").first(), + style = MaterialTheme.typography.bodyMedium + ) + + if (commit.author != null) Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + AsyncImage( + modifier = Modifier + .size(20.dp) + .clip(CircleShape), + model = "https://github.com/${commit.author.name}.png", + contentDescription = commit.author.name + ) + Text( + buildAnnotatedString { + withStyle( + SpanStyle( + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface + ) + ) { + append(commit.author.name) + } + + withStyle( + SpanStyle( + color = MaterialTheme.colorScheme.onSurface.copy( + alpha = 0.6f + ) + ) + ) { + append(" authored ") + } + + withStyle( + SpanStyle( + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.primary + ) + ) { + append(commit.sha.substring(0, 7)) + } + + }, + style = MaterialTheme.typography.bodySmall + ) + } + } +} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/home/InfoCard.kt b/app/src/main/kotlin/com/aliucord/manager/ui/component/home/InfoCard.kt new file mode 100644 index 0000000..d671656 --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/ui/component/home/InfoCard.kt @@ -0,0 +1,121 @@ +package com.aliucord.manager.ui.component.home + +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.* +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.aliucord.manager.R + +@Composable +fun InfoCard( + packageName: String, + supportedVersion: String, + currentVersion: String, + onDownloadClick: () -> Unit, + onUninstallClick: () -> Unit, + onLaunchClick: () -> Unit +) { + ElevatedCard { + Column( + modifier = Modifier + .padding(20.dp) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + text = "Aliucord${packageName.let { if (it != "com.aliucord") " ($it)" else "" }}", + style = MaterialTheme.typography.titleLarge.copy(fontSize = 23.sp), + color = MaterialTheme.colorScheme.primary + ) + + Text( + buildAnnotatedString { + append("Supported version: ") + + withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { + append(supportedVersion) + } + + append("\nInstalled version: ") + + withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { + append(currentVersion) + } + }, + style = MaterialTheme.typography.bodyMedium + ) + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 10.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + + val (icon, description) = when (currentVersion) { + "-" -> Icons.Default.Download to R.string.install + supportedVersion -> Icons.Default.Refresh to R.string.reinstall + else -> Icons.Default.Update to R.string.update + } + + FilledTonalIconButton( + modifier = Modifier + .weight(1f) + .heightIn(min = 50.dp), + onClick = onDownloadClick, + shape = ShapeDefaults.Large + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(5.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = icon, + contentDescription = stringResource(description) + ) + if (currentVersion == "-") Text( + stringResource(description), + style = MaterialTheme.typography.labelLarge + ) + } + } + + if (currentVersion != "-") { + FilledTonalIconButton( + modifier = Modifier + .weight(1f) + .heightIn(min = 50.dp), + onClick = onUninstallClick, + shape = ShapeDefaults.Large + ) { + Icon( + imageVector = Icons.Default.Delete, + contentDescription = stringResource(R.string.uninstall) + ) + } + + FilledTonalIconButton( + modifier = Modifier + .weight(1f) + .heightIn(min = 50.dp), + onClick = onLaunchClick, + shape = ShapeDefaults.Large + ) { + Icon( + imageVector = Icons.Default.Launch, + contentDescription = stringResource(R.string.launch) + ) + } + } + } + } + } +} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/plugins/Changelog.kt b/app/src/main/kotlin/com/aliucord/manager/ui/component/plugins/Changelog.kt index 57c3cce..a6a7821 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/component/plugins/Changelog.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/component/plugins/Changelog.kt @@ -106,6 +106,7 @@ fun Changelog( modifier = Modifier.padding(top = 16.dp, bottom = 6.dp) ) } + '*' -> { LinkText( modifier = Modifier.padding(bottom = 2.dp), @@ -123,6 +124,7 @@ fun Changelog( } ) } + else -> { when { line.endsWith("marginTop}") -> { @@ -135,7 +137,8 @@ fun Changelog( modifier = Modifier.padding(top = 16.dp, bottom = 6.dp) ) } - line.all { c -> c == '=' } -> { } // Discord ignores ======= + + line.all { c -> c == '=' } -> {} // Discord ignores ======= else -> { LinkText( annotatedString = buildAnnotatedString { diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/plugins/PluginCard.kt b/app/src/main/kotlin/com/aliucord/manager/ui/component/plugins/PluginCard.kt index 21c5f29..5aaf7f0 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/component/plugins/PluginCard.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/component/plugins/PluginCard.kt @@ -14,6 +14,7 @@ import androidx.compose.material.icons.filled.History import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.platform.LocalUriHandler @@ -21,9 +22,11 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp import com.aliucord.manager.R import com.aliucord.manager.domain.model.Plugin +import com.aliucord.manager.ui.util.joinToAnnotatedString @Composable fun PluginCard( @@ -31,81 +34,86 @@ fun PluginCard( onClickDelete: () -> Unit, onClickShowChangelog: () -> Unit ) { + val uriHandler = LocalUriHandler.current + var isEnabled by rememberSaveable { mutableStateOf(true) } + ElevatedCard { + // Header + Row( + modifier = Modifier + .clickable { isEnabled = !isEnabled } + .padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 14.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Column { + // Name + Text( + buildAnnotatedString { + withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { + append(plugin.manifest.name) + } + + append(" v${plugin.manifest.version}") + } + ) + + // Authors + val authors = buildAnnotatedString { + withStyle(SpanStyle(color = MaterialTheme.colorScheme.primary)) { + plugin.manifest.authors.joinToAnnotatedString( + prefix = { + withStyle(SpanStyle(color = MaterialTheme.colorScheme.secondary)) { + append("By ") + } + } + ) { author -> + val start = this@joinToAnnotatedString.length + withStyle( + SpanStyle( + textDecoration = TextDecoration.Underline + ) + ) { + append(author.name) + } + addStringAnnotation("authorId", author.id.toString(), start, start + author.name.length) + } + } + } + + ClickableText( + text = authors, + style = MaterialTheme.typography.labelMedium, + onClick = { offset -> + authors.getStringAnnotations("authorId", offset, offset) + .firstOrNull() + ?.let { (id) -> uriHandler.openUri("https://discord.com/users/$id") } + } + ) + } + + Spacer(Modifier.weight(1f, true)) + + // Toggle Switch + Switch( + checked = isEnabled, + onCheckedChange = { isEnabled = it } + ) + } + + Divider(modifier = Modifier + .alpha(0.3f) + .padding(horizontal = 16.dp)) + Column( modifier = Modifier .fillMaxWidth() .padding(16.dp) ) { - val uriHandler = LocalUriHandler.current - var isEnabled by rememberSaveable { mutableStateOf(true) } - - // Header - Row( - modifier = Modifier.clickable { isEnabled = !isEnabled } - ) { - Column { - // Name - Text( - buildAnnotatedString { - withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { - append(plugin.manifest.name) - } - - append(" v${plugin.manifest.version}") - } - ) - - // Authors - val authors = buildAnnotatedString { - withStyle(SpanStyle(color = MaterialTheme.colorScheme.secondary)) { - append("By ") - } - - val iterator = plugin.manifest.authors.iterator() - - iterator.forEach { author -> - withStyle(SpanStyle(color = MaterialTheme.colorScheme.primary)) { - val start = this@buildAnnotatedString.length - append(author.name) - addStringAnnotation("authorId", author.id.toString(), start, start + author.name.length) - } - - if (iterator.hasNext()) { - withStyle(SpanStyle(color = MaterialTheme.colorScheme.secondary)) { - append(", ") - } - } - } - } - - ClickableText( - text = authors, - style = MaterialTheme.typography.labelMedium, - onClick = { offset -> - authors.getStringAnnotations("authorId", offset, offset) - .firstOrNull() - ?.let { (id) -> uriHandler.openUri("https://discord.com/users/$id") } - } - ) - } - - Spacer(Modifier.weight(1f, true)) - - // Toggle Switch - Switch( - checked = isEnabled, - onCheckedChange = { isEnabled = it } - ) - } - - Divider(modifier = Modifier.alpha(0.3f)) - // Description Text( modifier = Modifier .heightIn(max = 150.dp, min = 40.dp) - .padding(top = 10.dp, bottom = 20.dp), + .padding(bottom = 20.dp), text = plugin.manifest.description, style = MaterialTheme.typography.bodyMedium ) diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/settings/SettingsHeader.kt b/app/src/main/kotlin/com/aliucord/manager/ui/component/settings/SettingsHeader.kt new file mode 100644 index 0000000..918309c --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/ui/component/settings/SettingsHeader.kt @@ -0,0 +1,19 @@ +package com.aliucord.manager.ui.component.settings + +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +fun SettingsHeader( + text: String +) { + Text( + text = text, + style = MaterialTheme.typography.titleSmall, + modifier = Modifier.padding(18.dp, 24.dp, 18.dp, 10.dp) + ) +} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/settings/SettingsItem.kt b/app/src/main/kotlin/com/aliucord/manager/ui/component/settings/SettingsItem.kt new file mode 100644 index 0000000..ec56a7f --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/ui/component/settings/SettingsItem.kt @@ -0,0 +1,60 @@ +package com.aliucord.manager.ui.component.settings + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ProvideTextStyle +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +@Composable +fun SettingsItem( + modifier: Modifier = Modifier, + icon: @Composable (() -> Unit) = { }, + text: @Composable () -> Unit, + secondaryText: @Composable (() -> Unit) = { }, + trailing: @Composable (() -> Unit) = { }, +) { + Row( + modifier = modifier + .heightIn(min = 64.dp) + .fillMaxWidth() + .padding(horizontal = 18.dp, vertical = 4.dp), + horizontalArrangement = Arrangement.spacedBy(14.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier.padding(end = 5.dp) + ) { + icon() + } + + Column( + verticalArrangement = Arrangement.spacedBy(2.dp), + modifier = Modifier.weight(0.95f, true) + ) { + ProvideTextStyle( + MaterialTheme.typography.titleLarge.copy( + fontWeight = FontWeight.Normal, + fontSize = 18.sp + ) + ) { + text() + } + ProvideTextStyle( + MaterialTheme.typography.bodyMedium.copy( + color = MaterialTheme.colorScheme.onSurface.copy(0.6f) + ) + ) { + secondaryText() + } + } + + Spacer(Modifier.weight(0.05f, true)) + + trailing() + } +} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/settings/SettingsSwitch.kt b/app/src/main/kotlin/com/aliucord/manager/ui/component/settings/SettingsSwitch.kt new file mode 100644 index 0000000..ab2b777 --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/ui/component/settings/SettingsSwitch.kt @@ -0,0 +1,34 @@ +package com.aliucord.manager.ui.component.settings + +import androidx.compose.foundation.clickable +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +fun SettingsSwitch( + label: String, + secondaryLabel: String? = null, + disabled: Boolean = false, + icon: @Composable () -> Unit = {}, + pref: Boolean, + onPrefChange: (Boolean) -> Unit, +) { + SettingsItem( + modifier = Modifier.clickable(enabled = !disabled) { onPrefChange(!pref) }, + text = { Text(text = label, softWrap = true) }, + icon = icon, + secondaryText = { + secondaryLabel?.let { + Text(text = it) + } + } + ) { + Switch( + checked = pref, + enabled = !disabled, + onCheckedChange = { onPrefChange(!pref) } + ) + } +} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/settings/SettingsTextField.kt b/app/src/main/kotlin/com/aliucord/manager/ui/component/settings/SettingsTextField.kt new file mode 100644 index 0000000..4a10d4d --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/ui/component/settings/SettingsTextField.kt @@ -0,0 +1,27 @@ +package com.aliucord.manager.ui.component.settings + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SettingsTextField( + label: String, + disabled: Boolean = false, + pref: String, + onPrefChange: (String) -> Unit, +) { + Box(modifier = Modifier.padding(horizontal = 18.dp, vertical = 10.dp)) { + OutlinedTextField( + modifier = Modifier.fillMaxWidth(), + value = pref, + onValueChange = onPrefChange, + enabled = !disabled, + label = { Text(label) }, + singleLine = true + ) + } +} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/navigation/AppDestination.kt b/app/src/main/kotlin/com/aliucord/manager/ui/navigation/AppDestination.kt index 30a2de8..d559693 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/navigation/AppDestination.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/navigation/AppDestination.kt @@ -2,10 +2,8 @@ package com.aliucord.manager.ui.navigation import androidx.annotation.StringRes import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Extension -import androidx.compose.material.icons.filled.Home -import androidx.compose.material.icons.outlined.Extension -import androidx.compose.material.icons.outlined.Home +import androidx.compose.material.icons.filled.* +import androidx.compose.material.icons.outlined.* import androidx.compose.ui.graphics.vector.ImageVector import com.aliucord.manager.R import com.aliucord.manager.ui.screen.InstallData @@ -35,5 +33,6 @@ enum class HomeDestination( @StringRes val label: Int ) : Destination { HOME(Icons.Default.Home, Icons.Outlined.Home, R.string.home), - PLUGINS(Icons.Default.Extension, Icons.Outlined.Extension, R.string.plugins) + PLUGINS(Icons.Default.Extension, Icons.Outlined.Extension, R.string.plugins), + SETTINGS(Icons.Default.Settings, Icons.Outlined.Settings, R.string.settings) } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screen/AboutScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screen/AboutScreen.kt index 9f719db..71a1c50 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screen/AboutScreen.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screen/AboutScreen.kt @@ -5,12 +5,17 @@ package com.aliucord.manager.ui.screen +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.NavigateBefore +import androidx.compose.material.icons.filled.* +import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.* import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -18,9 +23,10 @@ import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import coil.compose.AsyncImage import com.aliucord.manager.R -import com.aliucord.manager.ui.component.ContributorsCard +import com.aliucord.manager.ui.component.contributors import com.aliucord.manager.ui.viewmodel.AboutViewModel import org.koin.androidx.compose.getViewModel @@ -28,16 +34,18 @@ import org.koin.androidx.compose.getViewModel @Composable fun AboutScreen( viewModel: AboutViewModel = getViewModel(), - onClickBack: () -> Unit + onBackClick: () -> Unit ) { + val uriHandler = LocalUriHandler.current + Scaffold( topBar = { TopAppBar( title = { Text(stringResource(R.string.about)) }, navigationIcon = { - IconButton(onClick = onClickBack) { + IconButton(onClick = onBackClick) { Icon( - imageVector = Icons.Default.NavigateBefore, + imageVector = Icons.Default.ArrowBack, contentDescription = stringResource(R.string.back) ) } @@ -45,80 +53,120 @@ fun AboutScreen( ) } ) { paddingValues -> - Column( + LazyColumn( modifier = Modifier .padding(paddingValues) - .padding(horizontal = 12.dp, vertical = 14.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) ) { - val uriHandler = LocalUriHandler.current - - ElevatedCard { + item { Column( + horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier - .padding(16.dp) - .fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(8.dp) + .fillMaxWidth() + .padding(vertical = 16.dp) ) { - Text( - text = stringResource(R.string.team), - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.primary + AsyncImage( + model = "https://github.com/Aliucord.png", + contentDescription = "Aliucord", + modifier = Modifier.size(71.dp) ) - Column( - verticalArrangement = Arrangement.spacedBy(16.dp) + Text( + text = "Aliucord", + style = MaterialTheme.typography.titleMedium.copy( + fontSize = 26.sp + ) + ) + + Text( + text = stringResource(R.string.aliucord_description), + style = MaterialTheme.typography.titleSmall.copy( + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) + ) + ) + + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth() ) { - UserEntry("Juby210", "Fox") - UserEntry("Vendicated", "the ven") + TextButton(onClick = { uriHandler.openUri("https://github.com/Aliucord") }) { + Icon( + painter = painterResource(R.drawable.ic_account_github_white_24dp), + contentDescription = stringResource(R.string.github) + ) + Spacer(modifier = Modifier.width(ButtonDefaults.IconSpacing)) + Text(text = stringResource(id = R.string.github)) + } + + TextButton(onClick = { uriHandler.openUri("https://aliucord.com") }) { + Icon( + imageVector = Icons.Filled.Link, + contentDescription = stringResource(R.string.website) + ) + Spacer(modifier = Modifier.width(ButtonDefaults.IconSpacing)) + Text(text = stringResource(id = R.string.website)) + } } + } } - ContributorsCard(viewModel.contributors) - - Spacer(Modifier.weight(1f, true)) - - Button( - modifier = Modifier.fillMaxWidth(), - onClick = { uriHandler.openUri("https://github.com/Aliucord/Aliucord") } - ) { - Icon( - painter = painterResource(R.drawable.ic_account_github_white_24dp), - contentDescription = stringResource(R.string.github) - ) - - Spacer(Modifier.width(ButtonDefaults.IconSpacing)) - - Text(stringResource(R.string.github)) + item { + Row( + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp) + ) { + UserEntry("Vendicated", "the ven") + UserEntry("Juby210", "Fox") + UserEntry("rushii", "explod", "DiamondMiner88") + } } + + contributors(viewModel.contributors) } } } @Composable -private fun UserEntry(name: String, roles: String) { - Column { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) +private fun UserEntry(name: String, roles: String, username: String = name) { + val uriHandler = LocalUriHandler.current + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp), + modifier = Modifier + .clickable( + onClick = { uriHandler.openUri("https://github.com/$username") }, + indication = rememberRipple(bounded = false, radius = 90.dp), + interactionSource = remember { MutableInteractionSource() } + ) + .widthIn(min = 100.dp) + ) { + AsyncImage( + modifier = Modifier + .size(71.dp) + .clip(CircleShape), + model = "https://github.com/$username.png", + contentDescription = username + ) + + Column( + horizontalAlignment = Alignment.CenterHorizontally ) { - AsyncImage( - modifier = Modifier - .size(54.dp) - .clip(CircleShape), - model = "https://github.com/$name.png", - contentDescription = name + Text( + text = name, + style = MaterialTheme.typography.titleMedium.copy( + fontSize = 18.sp + ) ) - Column { - Text( - text = name, - style = MaterialTheme.typography.titleLarge + Text( + text = roles, + style = MaterialTheme.typography.titleSmall.copy( + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) ) - - Text(roles) - } + ) } } } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screen/HomeRootScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screen/HomeRootScreen.kt index b418d3c..01bcc5e 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screen/HomeRootScreen.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screen/HomeRootScreen.kt @@ -1,41 +1,33 @@ package com.aliucord.manager.ui.screen import androidx.compose.animation.* +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Info -import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.* import androidx.compose.runtime.Composable 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.unit.dp import com.aliucord.manager.BuildConfig import com.aliucord.manager.R import com.aliucord.manager.ui.navigation.HomeDestination +import com.xinto.taxi.RegularNavigator import com.xinto.taxi.Taxi -import com.xinto.taxi.rememberNavigator @OptIn(ExperimentalMaterial3Api::class, ExperimentalAnimationApi::class) @Composable fun MainRootScreen( - onClickInstall: (InstallData) -> Unit, - onClickAbout: () -> Unit, - onClickSettings: () -> Unit + mainRootNavigator: RegularNavigator, + onInstallClick: (InstallData) -> Unit, + onAboutClick: () -> Unit ) { - val mainRootNavigator = rememberNavigator(HomeDestination.HOME) - Scaffold( topBar = { TopAppBar( - title = { - Text( - stringResource(mainRootNavigator.currentDestination.label), - modifier = Modifier.padding(horizontal = 4.dp) - ) - }, + title = { Text(stringResource(mainRootNavigator.currentDestination.label)) }, actions = { val localUriHandler = LocalUriHandler.current @@ -50,19 +42,12 @@ fun MainRootScreen( ) } - IconButton(onClick = onClickAbout) { + IconButton(onClick = onAboutClick) { Icon( imageVector = Icons.Default.Info, contentDescription = stringResource(R.string.about) ) } - - IconButton(onClick = onClickSettings) { - Icon( - imageVector = Icons.Default.Settings, - contentDescription = stringResource(R.string.settings) - ) - } } ) }, @@ -94,15 +79,19 @@ fun MainRootScreen( } ) { paddingValues -> Taxi( - modifier = Modifier.padding(paddingValues), + modifier = Modifier + .padding(paddingValues) + .fillMaxSize(), navigator = mainRootNavigator, transitionSpec = { fadeIn() with fadeOut() } ) { destination -> when (destination) { HomeDestination.HOME -> HomeScreen( - onClickInstall = onClickInstall + onClickInstall = onInstallClick ) + HomeDestination.PLUGINS -> PluginsScreen() + HomeDestination.SETTINGS -> SettingsScreen() } } } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screen/HomeScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screen/HomeScreen.kt index 79bd680..1aee229 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screen/HomeScreen.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screen/HomeScreen.kt @@ -8,30 +8,17 @@ package com.aliucord.manager.ui.screen import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.* -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.stringResource -import androidx.compose.ui.text.* -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import androidx.paging.LoadState -import androidx.paging.compose.collectAsLazyPagingItems -import androidx.paging.compose.items -import com.aliucord.manager.R +import com.aliucord.manager.ui.component.home.CommitList +import com.aliucord.manager.ui.component.home.InfoCard import com.aliucord.manager.ui.dialog.DownloadMethod import com.aliucord.manager.ui.dialog.InstallerDialog import com.aliucord.manager.ui.viewmodel.HomeViewModel import org.koin.androidx.compose.getViewModel -@OptIn(ExperimentalMaterial3Api::class) @Composable fun HomeScreen( viewModel: HomeViewModel = getViewModel(), @@ -65,167 +52,29 @@ fun HomeScreen( modifier = Modifier .padding(horizontal = 16.dp) .padding(top = 8.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) + verticalArrangement = Arrangement.spacedBy(16.dp) ) { - ElevatedCard { - Column( - modifier = Modifier - .padding(16.dp) - .fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - Column { - Text( - text = "Aliucord${viewModel.preferences.packageName.let { if (it != "com.aliucord") " ($it)" else "" }}", - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.primary - ) - - Text( - buildAnnotatedString { - append("Supported version: ") - - withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { - append(viewModel.supportedVersion) - } - - append("\nInstalled version: ") - - withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { - append(viewModel.installedVersion) - } - } - ) - } - - Column( - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - val (icon, description) = when (viewModel.installedVersion) { - "-" -> Icons.Default.Download to R.string.install - viewModel.supportedVersion -> Icons.Default.Update to R.string.reinstall - else -> Icons.Default.Update to R.string.update - } - - Row( - horizontalArrangement = Arrangement.spacedBy(12.dp) - ) { - Button( - modifier = Modifier.weight(1f, true), - onClick = { - if (viewModel.preferences.devMode) { - showOptionsDialog = true - } else { - onClickInstall( - InstallData( - DownloadMethod.DOWNLOAD, - ) - ) - } - } - ) { - Icon( - modifier = Modifier.padding(8.dp), - imageVector = icon, - contentDescription = stringResource(description), - tint = MaterialTheme.colorScheme.onPrimary - ) - - Text(stringResource(description)) - } - } - - if (viewModel.installedVersion != "-") { - Row( - horizontalArrangement = Arrangement.spacedBy(12.dp) - ) { - CompositionLocalProvider( - LocalContentColor provides MaterialTheme.colorScheme.secondaryContainer - ) { - FilledTonalButton( - modifier = Modifier.weight(1f), - onClick = viewModel::uninstallAliucord - ) { - Icon( - modifier = Modifier.padding(8.dp), - imageVector = Icons.Default.Delete, - contentDescription = stringResource(R.string.uninstall) - ) - } - - FilledTonalButton( - modifier = Modifier.weight(1f), - onClick = viewModel::launchAliucord - ) { - Icon( - modifier = Modifier.padding(8.dp), - imageVector = Icons.Default.Launch, - contentDescription = stringResource(R.string.launch) - ) - } - } - } - } - } - } - } - - ElevatedCard( - modifier = Modifier.padding(bottom = 16.dp) - ) { - Column( - modifier = Modifier - .weight(1f, true) - .fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - val lazyPagingItems = viewModel.commits.collectAsLazyPagingItems() - - Text( - modifier = Modifier.padding(16.dp, 16.dp, 16.dp), - text = stringResource(R.string.commits), - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.primary - ) - - LazyColumn( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(4.dp) - ) { - if (lazyPagingItems.loadState.refresh == LoadState.Loading) { - item { - Text( - modifier = Modifier - .fillMaxWidth() - .wrapContentWidth(Alignment.CenterHorizontally), - text = stringResource(R.string.paging_initial_load) - ) - } - } - - items(lazyPagingItems) { commitData -> - if (commitData == null) return@items - - val uriHandler = LocalUriHandler.current - - ListItem( - modifier = Modifier.clickable { uriHandler.openUri(commitData.htmlUrl) }, - overlineText = { Text(commitData.sha.substring(0, 7)) }, - headlineText = { Text("${commitData.commit.message.split("\n").first()} - ${commitData.author?.name ?: "UNKNOWN"}") } + InfoCard( + packageName = viewModel.preferences.packageName, + supportedVersion = viewModel.supportedVersion, + currentVersion = viewModel.installedVersion, + onDownloadClick = { + if (viewModel.preferences.devMode) { + showOptionsDialog = true + } else { + onClickInstall( + InstallData( + DownloadMethod.DOWNLOAD, ) - } - - if (lazyPagingItems.loadState.append == LoadState.Loading) { - item { - CircularProgressIndicator( - modifier = Modifier - .fillMaxWidth() - .wrapContentWidth(Alignment.CenterHorizontally) - ) - } - } + ) } - } - } + }, + onLaunchClick = viewModel::launchAliucord, + onUninstallClick = viewModel::uninstallAliucord + ) + + CommitList( + commits = viewModel.commits + ) } } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screen/InstallScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screen/InstallScreen.kt index 677f949..de76683 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screen/InstallScreen.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screen/InstallScreen.kt @@ -35,19 +35,19 @@ data class InstallData( @Composable fun InstallerScreen( installData: InstallData, - onClickBack: () -> Unit, + onBackClick: () -> Unit, viewModel: InstallViewModel = getViewModel(parameters = { parametersOf(installData) }) ) { val navigateMain by viewModel.returnToHome.collectAsState(initial = false) - if (navigateMain) onClickBack() + if (navigateMain) onBackClick() Scaffold( topBar = { TopAppBar( title = { Text("Installer") }, navigationIcon = { - IconButton(onClick = onClickBack) { + IconButton(onClick = onBackClick) { Icon( imageVector = Icons.Default.NavigateBefore, contentDescription = stringResource(R.string.back) diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screen/PluginsScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screen/PluginsScreen.kt index c883dbf..9302b31 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screen/PluginsScreen.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screen/PluginsScreen.kt @@ -84,7 +84,7 @@ fun PluginsScreen( modifier = Modifier .fillMaxSize() .padding(horizontal = 16.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) + verticalArrangement = Arrangement.spacedBy(14.dp) ) { val search = viewModel.search @@ -119,8 +119,8 @@ fun PluginsScreen( ) LazyColumn( - contentPadding = PaddingValues(bottom = 8.dp, top = 6.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) + contentPadding = PaddingValues(bottom = 15.dp, top = 6.dp), + verticalArrangement = Arrangement.spacedBy(14.dp) ) { items( viewModel.plugins.filter { plugin -> diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screen/SettingsScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screen/SettingsScreen.kt index 1b22757..86920a9 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screen/SettingsScreen.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screen/SettingsScreen.kt @@ -15,186 +15,126 @@ import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.aliucord.manager.R +import com.aliucord.manager.ui.component.settings.* import com.aliucord.manager.ui.theme.Theme import com.aliucord.manager.ui.viewmodel.SettingsViewModel import org.koin.androidx.compose.getViewModel -@OptIn(ExperimentalMaterial3Api::class) @Composable fun SettingsScreen( - viewModel: SettingsViewModel = getViewModel(), - onClickBack: () -> Unit + viewModel: SettingsViewModel = getViewModel() ) { - val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + Column( + modifier = Modifier + .verticalScroll(state = rememberScrollState()) + ) { + val preferences = viewModel.preferences - Scaffold( - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), - topBar = { - TopAppBar( - title = { Text(stringResource(R.string.settings)) }, - navigationIcon = { - IconButton(onClick = onClickBack) { - Icon( - imageVector = Icons.Default.NavigateBefore, - contentDescription = stringResource(R.string.back) - ) - } - }, - scrollBehavior = scrollBehavior + if (viewModel.showThemeDialog) { + ThemeDialog( + currentTheme = preferences.theme, + onDismissRequest = viewModel::hideThemeDialog, + onConfirm = viewModel::setTheme ) } - ) { paddingValues -> - Column( - modifier = Modifier - .padding(paddingValues) - .padding(horizontal = 12.dp, vertical = 14.dp) - .verticalScroll(state = rememberScrollState()), - verticalArrangement = Arrangement.spacedBy(12.dp) + + SettingsHeader(stringResource(R.string.appearance)) + + SettingsItem( + modifier = Modifier.clickable(onClick = viewModel::showThemeDialog), + icon = { Icon(Icons.Default.Style, null) }, + text = { Text(stringResource(R.string.theme)) } ) { - val preferences = viewModel.preferences - - if (viewModel.showThemeDialog) { - ThemeDialog( - currentTheme = preferences.theme, - onDismissRequest = viewModel::hideThemeDialog, - onConfirm = viewModel::setTheme - ) + FilledTonalButton(onClick = viewModel::showThemeDialog) { + Text(preferences.theme.displayName) } + } - GroupHeader(stringResource(R.string.appearance)) + SettingsSwitch( + label = stringResource(R.string.dynamic_color), + pref = preferences.dynamicColor, + icon = { Icon(Icons.Default.Palette, null) } + ) { + preferences.dynamicColor = it + } - ListItem( - modifier = Modifier.clickable(onClick = viewModel::showThemeDialog), - headlineText = { Text(stringResource(R.string.theme)) }, - supportingText = { Text(stringResource(R.string.theme_setting_description)) }, - leadingContent = { Icon(Icons.Default.Style, null) }, - trailingContent = { - FilledTonalButton(onClick = viewModel::showThemeDialog) { - Text(preferences.theme.displayName) - } - } - ) + SettingsHeader(stringResource(R.string.advanced)) - SwitchSetting( - checked = preferences.dynamicColor, - title = { Text(stringResource(R.string.dynamic_color)) }, - onCheckedChange = { preferences.dynamicColor = it }, - icon = { Icon(Icons.Default.Palette, null) } - ) + SettingsSwitch( + label = stringResource(R.string.replace_bg), + secondaryLabel = stringResource(R.string.replace_bg_description), + pref = preferences.replaceIcon, + icon = { Icon(Icons.Default.AppShortcut, null) } + ) { + preferences.replaceIcon = it + } - GroupHeader(stringResource(R.string.advanced)) + Spacer(modifier = Modifier.height(8.dp)) - SwitchSetting( - checked = preferences.replaceIcon, - title = { Text(stringResource(R.string.replace_bg)) }, - onCheckedChange = { preferences.replaceIcon = it }, - icon = { Icon(Icons.Default.AppShortcut, null) } - ) + SettingsTextField( + label = stringResource(R.string.app_name_setting), + pref = preferences.appName, + onPrefChange = viewModel::setAppName + ) - OutlinedTextField( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 10.dp), - value = preferences.appName, - onValueChange = viewModel::setAppName, - label = { Text(stringResource(R.string.app_name_setting)) }, - placeholder = { Text("Aliucord") }, - singleLine = true - ) + SettingsSwitch( + label = stringResource(R.string.developer_options), + pref = preferences.devMode, + icon = { Icon(Icons.Default.Code, null) } + ) { + preferences.devMode = it + } - SwitchSetting( - checked = preferences.devMode, - title = { Text(stringResource(R.string.developer_options)) }, - onCheckedChange = { preferences.devMode = it }, - icon = { Icon(Icons.Default.Code, null) } - ) - - AnimatedVisibility( - visible = preferences.devMode, - enter = expandVertically(), - exit = shrinkVertically() + AnimatedVisibility( + visible = preferences.devMode, + enter = expandVertically(), + exit = shrinkVertically() + ) { + Column( + verticalArrangement = Arrangement.spacedBy(12.dp) ) { - Column( - verticalArrangement = Arrangement.spacedBy(12.dp) + SettingsTextField( + label = stringResource(R.string.package_name), + pref = preferences.packageName, + onPrefChange = viewModel::setPackageName + ) + + SettingsTextField( + label = stringResource(R.string.version), + pref = preferences.version, + onPrefChange = viewModel::setVersion + ) + + SettingsSwitch( + label = stringResource(R.string.debuggable), + secondaryLabel = stringResource(R.string.debuggable_description), + pref = preferences.debuggable, + icon = { Icon(Icons.Default.BugReport, null) } ) { - OutlinedTextField( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 10.dp), - value = preferences.packageName, - onValueChange = viewModel::setPackageName, - label = { Text(stringResource(R.string.package_name)) }, - placeholder = { Text("com.aliucord") }, - singleLine = true - ) - - OutlinedTextField( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 10.dp), - value = preferences.version, - onValueChange = viewModel::setVersion, - label = { Text(stringResource(R.string.version)) }, - singleLine = true - ) - - SwitchSetting( - checked = preferences.debuggable, - title = { Text(stringResource(R.string.debuggable)) }, - description = { Text(stringResource(R.string.debuggable_description)) }, - onCheckedChange = { preferences.debuggable = it }, - icon = { Icon(Icons.Default.BugReport, null) } - ) + preferences.debuggable = it } } + } - Button( - modifier = Modifier - .fillMaxWidth() - .padding(8.dp), - onClick = viewModel::clearCacheDir - ) { - Text( - text = stringResource(R.string.clear_files_cache), - textAlign = TextAlign.Center - ) - } + Button( + modifier = Modifier + .fillMaxWidth() + .padding(18.dp), + shape = ShapeDefaults.Large, + onClick = viewModel::clearCacheDir + ) { + Text( + text = stringResource(R.string.clear_cache), + textAlign = TextAlign.Center + ) } } } -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun SwitchSetting( - enabled: Boolean = true, - checked: Boolean, - icon: @Composable (() -> Unit)? = null, - title: @Composable () -> Unit, - description: @Composable (() -> Unit)? = null, - onCheckedChange: (Boolean) -> Unit -) { - ListItem( - modifier = Modifier.clickable { onCheckedChange(!checked) }, - leadingContent = icon, - headlineText = title, - supportingText = description, - trailingContent = { - Switch( - enabled = enabled, - checked = checked, - onCheckedChange = onCheckedChange - ) - } - ) -} - @Composable fun ThemeDialog( currentTheme: Theme, @@ -248,23 +188,3 @@ fun ThemeDialog( } ) } - -@Composable -fun GroupHeader( - title: String, - color: Color = MaterialTheme.colorScheme.primary -) { - Box( - Modifier - .padding(start = 12.dp) - .fillMaxWidth(), - contentAlignment = Alignment.CenterStart - ) { - Text( - text = title, - color = color, - fontSize = LocalTextStyle.current.fontSize.times(0.95f), - fontWeight = FontWeight.SemiBold - ) - } -} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/util/Utils.kt b/app/src/main/kotlin/com/aliucord/manager/ui/util/Utils.kt new file mode 100644 index 0000000..46c4158 --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/ui/util/Utils.kt @@ -0,0 +1,23 @@ +package com.aliucord.manager.ui.util + +import androidx.compose.ui.text.AnnotatedString + +context(AnnotatedString.Builder) + inline fun Iterable.joinToAnnotatedString( + separator: AnnotatedString.Builder.() -> Unit = { append(", ") }, + prefix: AnnotatedString.Builder.() -> Unit = {}, + postfix: AnnotatedString.Builder.() -> Unit = {}, + transform: (AnnotatedString.Builder.(T) -> Unit) = { + when (it) { + is Char -> append(it) + else -> append(it.toString()) + } + } +) { + prefix(this@Builder) + for ((count, element) in this@joinToAnnotatedString.withIndex()) { + if (count + 1 > 1) separator(this@Builder) + transform(this@Builder, element) + } + postfix(this@Builder) +} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/AboutViewModel.kt b/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/AboutViewModel.kt index a75241f..5c4f405 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/AboutViewModel.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/viewmodel/AboutViewModel.kt @@ -3,8 +3,8 @@ package com.aliucord.manager.ui.viewmodel import androidx.compose.runtime.mutableStateListOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.aliucord.manager.network.dto.GithubUser import com.aliucord.manager.domain.repository.GithubRepository +import com.aliucord.manager.network.dto.GithubUser import kotlinx.coroutines.launch class AboutViewModel( diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 570a0e5..c9571fb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,10 +1,11 @@ Aliucord Manager - Clear files cache + Clear cache Commits Dex location Open settings - Replace app icon with Aliucord variant + Replace app icon + Uses the Aliucord variant as opposed to Discord\'s own icon Settings Show menu Theme @@ -28,7 +29,9 @@ Contributors Search Plugins GitHub + Website Installer + %o contributions Manager appearance No plugins installed! Loading… @@ -51,4 +54,5 @@ Advanced Grant Permissions In order for Aliucord Manager to work properly, you need to grant file permissions. Since shared data in stored in ~/Aliucord, permissions are required in order for manager to function. + A modification for the Discord Android App