From 12b9a45b5f10af3a3b1233300f26cdc1ad3bcf8d Mon Sep 17 00:00:00 2001 From: Der_Googler <54764558+DerGoogler@users.noreply.github.com> Date: Fri, 9 Oct 2026 14:18:09 +0200 Subject: [PATCH 01/24] update actions --- .github/workflows/github.yml | 1 + .github/workflows/release-ci.yml | 2 -- .github/workflows/telegram.yml | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index c90a05549..2d8f81f04 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -80,6 +80,7 @@ jobs: id: create_release uses: actions/create-release@v1 with: + prerelease: ${{ github.event.inputs.track == 'beta' }} tag_name: v${{ env.VERSION }} release_name: v${{ env.VERSION }} body: | diff --git a/.github/workflows/release-ci.yml b/.github/workflows/release-ci.yml index c65853621..439db9c5f 100644 --- a/.github/workflows/release-ci.yml +++ b/.github/workflows/release-ci.yml @@ -30,8 +30,6 @@ on: options: - production - beta - - alpha - - qa jobs: # Build step for both official & spoofed flavors diff --git a/.github/workflows/telegram.yml b/.github/workflows/telegram.yml index 13709fe20..19b57abaf 100644 --- a/.github/workflows/telegram.yml +++ b/.github/workflows/telegram.yml @@ -70,6 +70,7 @@ jobs: ${{ env.MESSAGE }} Branch: ${{ env.BRANCH_NAME }} + Track: ${{ github.event.inputs.track }} [Workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) files: | ${{ steps.find_apks.outputs.OFFICIAL_APK_PATH }} From 0f885ed8ff102a8dbe49e0649fdd73a7cf09f42e Mon Sep 17 00:00:00 2001 From: Der_Googler <54764558+DerGoogler@users.noreply.github.com> Date: Fri, 9 Oct 2026 14:49:24 +0200 Subject: [PATCH 02/24] Update mmrlx libs and local Maven repo Bump mmrlx to 1.0.223, replace the old WebUI-X Portable helper dependencies with the new mmrlx nio and utilities modules, and add `mavenLocal()` to dependency and plugin resolution so locally published artifacts can be resolved. --- app/build.gradle.kts | 5 ++--- gradle/libs.versions.toml | 7 +++---- settings.gradle.kts | 2 ++ 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2ae18e4ef..e4f3ebb3c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -197,9 +197,6 @@ dependencies { implementation(projects.compat) implementation(projects.datastore) - implementation(libs.webuix.hwui) - implementation(libs.webuix.helper) - implementation(libs.kotlin.stdlib) implementation(libs.hiddenApiBypass) @@ -272,6 +269,8 @@ dependencies { ksp(libs.square.moshi.kotlin) implementation(libs.mmrlx.terminal) + implementation(libs.mmrlx.nio) + implementation(libs.mmrlx.utilities) implementation(libs.mmrlx.webui.core) implementation("dev.chrisbanes.haze:haze:1.6.10") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 095d5a087..117ba55ba 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -41,8 +41,7 @@ squareRetrofit = "2.11.0" squareOkhttp = "4.12.0" squareMoshi = "1.15.1" runtimeAndroid = "1.7.6" -mmrlx = "1.0.152" -webuiXPortable = "c92f5f6c3c" +mmrlx = "1.0.223" workRuntimeKtx = "2.10.0" lifecycleProcess = "2.8.7" apacheCommons = "1.28.0" @@ -139,6 +138,8 @@ markwon-core = "io.noties.markwon:core:4.6.2" hiddenApiBypass = "org.lsposed.hiddenapibypass:hiddenapibypass:6.1" mmrlx-terminal = { module = "dev.mmrlx:terminal", version.ref = "mmrlx" } mmrlx-webui-core = { module = "dev.mmrlx:webui.core", version.ref = "mmrlx" } +mmrlx-nio = { module = "dev.mmrlx:nio", version.ref = "mmrlx" } +mmrlx-utilities = { module = "dev.mmrlx:utilities", version.ref = "mmrlx" } timber = "com.jakewharton.timber:timber:5.0.1" # Dependencies of the included build-logic @@ -155,8 +156,6 @@ material = { group = "com.google.android.material", name = "material", version.r androidx-material3-android = { group = "androidx.compose.material3", name = "material3-android", version.ref = "material3Android" } runtime-android = { group = "androidx.compose.runtime", name = "runtime-android", version.ref = "runtimeAndroidVersion" } androidx-swiperefreshlayout = { group = "androidx.swiperefreshlayout", name = "swiperefreshlayout", version.ref = "swiperefreshlayout" } -webuix-hwui = { module = "com.github.MMRLApp.WebUI-X-Portable:hwui", version.ref = "webuiXPortable" } -webuix-helper = { module = "com.github.MMRLApp.WebUI-X-Portable:helper", version.ref = "webuiXPortable" } [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 7910b760c..58879b97a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -11,6 +11,7 @@ dependencyResolutionManagement drm@{ repositories { google() mavenCentral() + mavenLocal() maven("https://jitpack.io") if (user != null && pass != null) { @@ -31,6 +32,7 @@ pluginManagement { repositories { google() mavenCentral() + mavenLocal() gradlePluginPortal() } } From a8b99affd365baabd09839c1d5ae59fc9b6564db Mon Sep 17 00:00:00 2001 From: Der_Googler <54764558+DerGoogler@users.noreply.github.com> Date: Fri, 9 Oct 2026 16:23:34 +0200 Subject: [PATCH 03/24] Remove SuperUser and app profile screens Deletes the SuperUser feature stack, including its view model, screens, profile editing UI, and composition local wiring. Navigation is updated to remove the SuperUser destination and simplify access checks to root-only gating, and activity-level providers are cleaned up to drop the removed dependency. --- .../mmrl/ui/activity/MMRLComponentActivity.kt | 3 - .../mmrl/ui/navigation/MainDestination.kt | 16 +- .../ui/providable/LocalSuperUserViewModel.kt | 9 - .../ui/screens/appProfile/AppProfileScreen.kt | 232 ------------ .../appProfile/items/RootProfileConfig.kt | 344 ------------------ .../remember/rememberProfileChange.kt | 142 -------- .../ui/screens/superuser/SuperUserItem.kt | 83 ----- .../ui/screens/superuser/SuperUserList.kt | 59 --- .../ui/screens/superuser/SuperUserMenu.kt | 94 ----- .../ui/screens/superuser/SuperUserScreen.kt | 167 --------- .../mmrl/viewmodel/SuperUserViewModel.kt | 249 ------------- 11 files changed, 1 insertion(+), 1397 deletions(-) delete mode 100644 app/src/main/kotlin/com/dergoogler/mmrl/ui/providable/LocalSuperUserViewModel.kt delete mode 100644 app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/appProfile/AppProfileScreen.kt delete mode 100644 app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/appProfile/items/RootProfileConfig.kt delete mode 100644 app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/appProfile/remember/rememberProfileChange.kt delete mode 100644 app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/superuser/SuperUserItem.kt delete mode 100644 app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/superuser/SuperUserList.kt delete mode 100644 app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/superuser/SuperUserMenu.kt delete mode 100644 app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/superuser/SuperUserScreen.kt delete mode 100644 app/src/main/kotlin/com/dergoogler/mmrl/viewmodel/SuperUserViewModel.kt diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/activity/MMRLComponentActivity.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/activity/MMRLComponentActivity.kt index 82d20f0f0..2ce4467da 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/activity/MMRLComponentActivity.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/activity/MMRLComponentActivity.kt @@ -34,13 +34,11 @@ import com.dergoogler.mmrl.ui.providable.LocalLifecycleScope import com.dergoogler.mmrl.ui.providable.LocalMainNavController import com.dergoogler.mmrl.ui.providable.LocalNavController import com.dergoogler.mmrl.ui.providable.LocalSettings -import com.dergoogler.mmrl.ui.providable.LocalSuperUserViewModel import com.dergoogler.mmrl.ui.providable.LocalUserPreferences import com.dergoogler.mmrl.ui.providable.LocalWindowSizeClass import com.dergoogler.mmrl.ui.theme.Colors import com.dergoogler.mmrl.ui.theme.MMRLAppTheme import com.dergoogler.mmrl.viewmodel.SettingsViewModel -import com.dergoogler.mmrl.viewmodel.SuperUserViewModel import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator import dagger.hilt.android.AndroidEntryPoint import dev.dergoogler.mmrl.compat.BuildCompat @@ -225,7 +223,6 @@ fun BaseContent( LocalWindowSizeClass provides windowSizeClass, LocalDestinationsNavigator provides navigator, LocalActivity provides activity, - LocalSuperUserViewModel provides hiltViewModel(activity), LocalSettings provides hiltViewModel(activity), LocalUserPreferences provides preferences, dev.dergoogler.mmrl.compat.core.LocalUriHandler provides diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/navigation/MainDestination.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/navigation/MainDestination.kt index 0962e3c0b..f52c009ff 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/navigation/MainDestination.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/navigation/MainDestination.kt @@ -4,14 +4,11 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.compose.runtime.Composable import com.dergoogler.mmrl.R -import com.dergoogler.mmrl.platform.ksu.KsuNative import com.dergoogler.mmrl.ui.remember.rememberIsRoot -import com.dergoogler.mmrl.utils.isManager import com.ramcosta.composedestinations.generated.destinations.HomeScreenDestination import com.ramcosta.composedestinations.generated.destinations.ModulesScreenDestination import com.ramcosta.composedestinations.generated.destinations.RepositoriesScreenDestination import com.ramcosta.composedestinations.generated.destinations.SettingsScreenDestination -import com.ramcosta.composedestinations.generated.destinations.SuperUserScreenDestination import com.ramcosta.composedestinations.spec.DirectionDestinationSpec // @NavHostGraph @@ -34,15 +31,6 @@ enum class MainDestination( requiresKernel = false, ), - SuperUser( - direction = SuperUserScreenDestination, - label = R.string.page_superuser, - icon = R.drawable.user, - iconFilled = R.drawable.user_filled, - requiresRoot = true, - requiresKernel = true, - ), - Repository( direction = RepositoriesScreenDestination, label = R.string.page_repositorys, @@ -75,7 +63,5 @@ enum class MainDestination( val MainDestination.isAccessible: Boolean get() { val isRoot = rememberIsRoot() - if (this.requiresRoot && !isRoot) return false - if (this.requiresKernel && !(KsuNative.isManager && isRoot)) return false - return true + return !(this.requiresRoot && !isRoot) } diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/providable/LocalSuperUserViewModel.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/providable/LocalSuperUserViewModel.kt deleted file mode 100644 index c58ef3c51..000000000 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/providable/LocalSuperUserViewModel.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.dergoogler.mmrl.ui.providable - -import androidx.compose.runtime.staticCompositionLocalOf -import com.dergoogler.mmrl.viewmodel.SuperUserViewModel - -val LocalSuperUserViewModel = - staticCompositionLocalOf { - error("CompositionLocal LocalSuperUserViewModel not present") - } diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/appProfile/AppProfileScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/appProfile/AppProfileScreen.kt deleted file mode 100644 index 96a9c88dc..000000000 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/appProfile/AppProfileScreen.kt +++ /dev/null @@ -1,232 +0,0 @@ -package com.dergoogler.mmrl.ui.screens.appProfile - -import androidx.annotation.StringRes -import androidx.compose.animation.Crossfade -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.only -import androidx.compose.foundation.layout.safeDrawing -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Scaffold -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import coil3.compose.AsyncImage -import com.dergoogler.mmrl.R -import com.dergoogler.mmrl.ext.nullable -import com.dergoogler.mmrl.platform.ksu.KsuNative -import com.dergoogler.mmrl.ui.component.LocalScreen -import com.dergoogler.mmrl.ui.component.LocalScreenProvider -import com.dergoogler.mmrl.ui.component.listItem.dsl.List -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.Item -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.RadioDialogItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.SwitchItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Description -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Icon -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Start -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Title -import com.dergoogler.mmrl.ui.component.toolbar.BlurNavigateUpToolbar -import com.dergoogler.mmrl.ui.providable.LocalDestinationsNavigator -import com.dergoogler.mmrl.ui.providable.LocalHazeState -import com.dergoogler.mmrl.ui.providable.LocalMainScreenInnerPaddings -import com.dergoogler.mmrl.ui.providable.LocalSnackbarHost -import com.dergoogler.mmrl.ui.screens.appProfile.items.RootProfileConfig -import com.dergoogler.mmrl.ui.screens.appProfile.remember.LocalProfileChange -import com.dergoogler.mmrl.ui.screens.appProfile.remember.rememberProfileChange -import com.dergoogler.mmrl.viewmodel.SuperUserViewModel -import com.dergoogler.mmrl.viewmodel.SuperUserViewModel.AppInfo.Companion.loadIcon -import com.ramcosta.composedestinations.annotation.Destination -import com.ramcosta.composedestinations.annotation.RootGraph -import dev.chrisbanes.haze.hazeSource - -@OptIn(ExperimentalMaterial3Api::class) -@Destination -@Composable -fun AppProfileScreen(appInfo: SuperUserViewModel.AppInfo) = - LocalScreenProvider { - val context = LocalContext.current - val snackBarHost = LocalSnackbarHost.current - val bottomBarPaddingValues = LocalMainScreenInnerPaddings.current - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() - - val (profile, onProfileChange) = rememberProfileChange(appInfo) - - val icon = - remember { - appInfo.loadIcon(context) - } - - CompositionLocalProvider( - LocalProfileChange provides (profile to onProfileChange), - ) { - Scaffold( - modifier = - Modifier - .nestedScroll(scrollBehavior.nestedScrollConnection), - topBar = { - BlurNavigateUpToolbar( - fade = true, - fadeDistance = 50f, - navigator = LocalDestinationsNavigator.current, - title = stringResource(R.string.profile), - scrollBehavior = scrollBehavior, - ) - }, - snackbarHost = { - LocalScreen.SnackbarHost() - }, - contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal), - ) { paddingValues -> - val isRootGranted = profile.allowSu - - var mode by rememberSaveable(profile, isRootGranted) { - val initialMode = - if (isRootGranted) { - if (profile.rootUseDefault) Mode.Default else Mode.Custom - } else { - if (profile.nonRootUseDefault) Mode.Default else Mode.Custom - } - mutableStateOf(initialMode) - } - - List( - modifier = - Modifier - .hazeSource(LocalHazeState.current) - .verticalScroll(rememberScrollState()), - ) { - Spacer(Modifier.height(paddingValues.calculateTopPadding())) - - Item { - Title(appInfo.label) - Description(appInfo.packageName) - Start { - AsyncImage( - model = icon, - contentDescription = appInfo.label, - modifier = Modifier.size(48.dp), - ) - } - } - - SwitchItem( - checked = isRootGranted, - onChange = { - onProfileChange(profile.copy(allowSu = it)) - }, - ) { - Icon( - painter = painterResource(R.drawable.square_root_2), - ) - Title(R.string.allow_root_access) - Description(R.string.allow_root_access_desc) - } - - RadioDialogItem( - selection = mode, - options = mode.dialogOptions, - onConfirm = { - if (isRootGranted) { - if (it.value == Mode.Default || it.value == Mode.Custom) { - onProfileChange(profile.copy(rootUseDefault = it.value == Mode.Default)) - } - - mode = it.value - - return@RadioDialogItem - } - - onProfileChange(profile.copy(nonRootUseDefault = (it.value == Mode.Default))) - }, - ) { - Icon(painter = painterResource(R.drawable.disabled)) - Title(R.string.profile) - it.title.nullable { t -> - Description(t) - } - } - - Crossfade( - targetState = (isRootGranted to mode), - label = "", - ) { (granted, currentMode) -> - List { - if (granted) { - if (currentMode == Mode.Custom) { - RootProfileConfig( - fixedName = true, - profile = profile, - onProfileChange = onProfileChange, - ) - } - - return@List - } - - val modifyEnabled = currentMode == Mode.Custom - - SwitchItem( - checked = - if (modifyEnabled) { - profile.umountModules - } else { - KsuNative.isDefaultUmountModules() - }, - enabled = modifyEnabled, - onChange = { - onProfileChange( - profile.copy( - umountModules = it, - nonRootUseDefault = false, - ), - ) - }, - ) { - Title(R.string.settings_umount_modules_default) - Description(R.string.settings_umount_modules_default_desc) - } - } - } - - Spacer(Modifier.height(bottomBarPaddingValues.calculateBottomPadding())) - } - } - } - } - -private enum class Mode( - @StringRes private val res: Int, -) { - Default(R.string.profile_default), - Custom(R.string.profile_custom), - ; - - @get:Composable - val dialogOptions: List> - get() = - entries.map { - RadioDialogItem( - value = it, - title = it.text, - ) - } - - @get:Composable - val text: String get() = stringResource(res) -} diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/appProfile/items/RootProfileConfig.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/appProfile/items/RootProfileConfig.kt deleted file mode 100644 index 4252ab975..000000000 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/appProfile/items/RootProfileConfig.kt +++ /dev/null @@ -1,344 +0,0 @@ -package com.dergoogler.mmrl.ui.screens.appProfile.items - -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.KeyboardType -import androidx.core.text.isDigitsOnly -import com.dergoogler.mmrl.R -import com.dergoogler.mmrl.ext.nullable -import com.dergoogler.mmrl.platform.SePolicy.isSepolicyValid -import com.dergoogler.mmrl.platform.ksu.Capabilities -import com.dergoogler.mmrl.platform.ksu.Groups -import com.dergoogler.mmrl.platform.ksu.Profile -import com.dergoogler.mmrl.platform.ksu.Profile.Namespace -import com.dergoogler.mmrl.ui.component.LabelItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.ListScope -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.CheckboxDialogItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.CheckboxItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.RadioDialogItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.Section -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.TextEditDialogItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Description -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.DialogSupportingText -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Labels -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Title - -@Composable -fun ListScope.RootProfileConfig( - modifier: Modifier = Modifier, - fixedName: Boolean, - profile: Profile, - onProfileChange: (Profile) -> Unit, -) { - Section( - title = stringResource(R.string.profile_custom), - ) { - RadioDialogItem( - selection = profile.namespace, - options = NamespaceOptions, - onConfirm = { - onProfileChange(profile.copy(namespace = it.value)) - }, - ) { - Title(R.string.profile_namespace) - it.title.nullable { t -> - Description(t) - } - } - - UidPanel( - uid = profile.uid, - label = "User Identifier (UID)", - onUidChange = { - onProfileChange( - profile.copy( - uid = it, - rootUseDefault = false, - ), - ) - }, - ) - - UidPanel( - uid = profile.gid, - label = "Group Identifier (GID)", - onUidChange = { - onProfileChange( - profile.copy( - gid = it, - rootUseDefault = false, - ), - ) - }, - ) - - val selectedGroups = - profile.groups.ifEmpty { listOf(0) }.let { e -> - e.mapNotNull { g -> - Groups.entries.find { it.gid == g } - } - } - - GroupsPanel(selectedGroups) { - onProfileChange( - profile.copy( - groups = it.map { group -> group.gid }.ifEmpty { listOf(0) }, - rootUseDefault = false, - ), - ) - } - - val selectedCaps = - profile.capabilities.mapNotNull { e -> - Capabilities.entries.find { it.cap == e } - } - - CapsPanel(selectedCaps) { - onProfileChange( - profile.copy( - capabilities = it.map { cap -> cap.cap }, - rootUseDefault = false, - ), - ) - } - } - - Section( - stringResource(R.string.profile_selinux), - ) { - SELinuxPanel( - profile = profile, - onSELinuxChange = { domain, rules -> - onProfileChange( - profile.copy( - context = domain, - rules = rules, - rootUseDefault = false, - ), - ) - }, - ) - } -} - -@Composable -fun ListScope.GroupsPanel( - selected: List, - closeSelection: (selection: List) -> Unit, -) { - val groups = - remember(selected) { - Groups.entries.toTypedArray().sortedWith( - compareBy { if (selected.contains(it)) 0 else 1 } - .then( - compareBy { - when (it) { - Groups.ROOT -> 0 - Groups.SYSTEM -> 1 - Groups.SHELL -> 2 - else -> Int.MAX_VALUE - } - }, - ).then(compareBy { it.name }), - ) - } - - val options = - remember(groups) { - groups.map { value -> - CheckboxItem( - value = value, - title = value.display, - desc = value.desc, - checked = selected.contains(value), - ) - } - } - - CheckboxDialogItem( - strict = false, - multiple = true, - maxChoices = 32, - options = options, - onConfirm = { - closeSelection(it.map { item -> item.value }) - }, - ) { - Title(R.string.profile_groups) - Labels { - selected.forEach { - LabelItem(it.display) - } - } - } -} - -@Composable -fun ListScope.CapsPanel( - selected: Collection, - closeSelection: (selection: List) -> Unit, -) { - val caps = - remember(selected) { - Capabilities.entries.toTypedArray().sortedWith( - compareBy { if (selected.contains(it)) 0 else 1 } - .then(compareBy { it.name }), - ) - } - - val options = - remember(caps) { - caps.map { value -> - CheckboxItem( - value = value, - title = value.display, - desc = value.desc, - checked = selected.contains(value), - ) - } - } - - CheckboxDialogItem( - strict = false, - multiple = true, - options = options, - onConfirm = { - closeSelection(it.map { item -> item.value }) - }, - ) { - Title(R.string.profile_capabilities) - Labels { - selected.forEach { - LabelItem(it.display) - } - } - } -} - -@Composable -private fun ListScope.UidPanel( - uid: Int, - label: String, - onUidChange: (Int) -> Unit, -) { - val keyboardController = LocalSoftwareKeyboardController.current - - var lastValidUid by remember { - mutableIntStateOf(uid) - } - - TextEditDialogItem( - value = uid.toString(), - onValid = { - isTextValidUid(it) - }, - keyboardOptions = - KeyboardOptions( - keyboardType = KeyboardType.Number, - imeAction = ImeAction.Done, - ), - keyboardActions = - KeyboardActions(onDone = { - keyboardController?.hide() - }), - onConfirm = { - if (it.isEmpty()) { - onUidChange(0) - return@TextEditDialogItem - } - val valid = isTextValidUid(it) - - val targetUid = if (valid) it.toInt() else lastValidUid - if (valid) { - lastValidUid = it.toInt() - } - - onUidChange(targetUid) - }, - ) { - Title(label) - Description(it.value) - if (it.isError) { - DialogSupportingText("Invalid Input!") - } - } -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -private fun ListScope.SELinuxPanel( - profile: Profile, - onSELinuxChange: (domain: String, rules: String) -> Unit, -) { - TextEditDialogItem( - value = profile.context, - strict = false, - onValid = { - it.matches(Regex("^[a-z_]+:[a-z0-9_]+:[a-z0-9_]+(:[a-z0-9_]+)?$")) - }, - onConfirm = { - onSELinuxChange(it, profile.rules) - }, - ) { - if (it.isError) { - DialogSupportingText(stringResource(R.string.profile_selinux_context_invalid)) - } - - Title(stringResource(R.string.profile_selinux_context)) - Description(it.value) - } - - TextEditDialogItem( - value = profile.rules, - strict = false, - onValid = { - isSepolicyValid(it) - }, - onConfirm = { - onSELinuxChange(profile.context, it) - }, - ) { - Title(stringResource(R.string.profile_selinux_rules)) - - if (it.isError) { - DialogSupportingText(stringResource(R.string.profile_selinux_rules_invalid)) - } - - if (it.value.isBlank()) { - Description(stringResource(R.string.empty)) - } else { - Description(it.value) - } - } -} - -@get:Composable -private val NamespaceOptions: List> - get() { - return enumValues().map { - val title = - when (it) { - Namespace.INHERITED -> stringResource(R.string.profile_namespace_inherited) - Namespace.GLOBAL -> stringResource(R.string.profile_namespace_global) - Namespace.INDIVIDUAL -> stringResource(R.string.profile_namespace_individual) - } - - RadioDialogItem( - value = it.ordinal, - title = title, - ) - } - } - -private fun isTextValidUid(text: String): Boolean = - text.isNotEmpty() && text.isDigitsOnly() && text.toInt() >= 0 && text.toInt() <= Int.MAX_VALUE diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/appProfile/remember/rememberProfileChange.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/appProfile/remember/rememberProfileChange.kt deleted file mode 100644 index aececf5c7..000000000 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/appProfile/remember/rememberProfileChange.kt +++ /dev/null @@ -1,142 +0,0 @@ -package com.dergoogler.mmrl.ui.screens.appProfile.remember - -import android.util.Log -import androidx.compose.material3.SnackbarHostState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.runtime.staticCompositionLocalOf -import com.dergoogler.mmrl.R -import com.dergoogler.mmrl.platform.SePolicy -import com.dergoogler.mmrl.platform.SePolicy.getSepolicy -import com.dergoogler.mmrl.platform.ksu.KsuNative -import com.dergoogler.mmrl.platform.ksu.Profile -import com.dergoogler.mmrl.ui.providable.LocalSnackbarHost -import com.dergoogler.mmrl.ui.providable.LocalSuperUserViewModel -import com.dergoogler.mmrl.viewmodel.SuperUserViewModel -import kotlinx.coroutines.launch - -@Composable -fun rememberProfileChange(info: SuperUserViewModel.AppInfo): Pair Unit> { - val snackBarHost = LocalSnackbarHost.current - val scope = rememberCoroutineScope() - val viewModel = LocalSuperUserViewModel.current - - val initialProfile = - remember(info.packageName, info.uid) { - val profile = KsuNative.getAppProfile(info.packageName, info.uid) - if (profile.allowSu) { - profile.rules = getSepolicy(info.packageName) - } - profile - } - - var profile by rememberSaveable(info.packageName, info.uid) { - mutableStateOf(initialProfile) - } - - // Cache string resources to avoid recomposition - val errorMessages = - remember { - ErrorMessages( - failToUpdateAppProfile = R.string.failed_to_update_app_profile, - failToUpdateSepolicy = R.string.failed_to_update_sepolicy, - suNotAllowed = R.string.su_not_allowed, - ) - } - - val updateProfile: (Profile) -> Unit = - remember(info, snackBarHost, scope, viewModel) { - { newProfile -> - scope.launch { - try { - val result = - updateProfileSafely( - newProfile, - info, - errorMessages, - snackBarHost, - ) - if (result.isSuccess) { - profile = newProfile - viewModel.updateAppProfile(info.packageName, newProfile) - } - } catch (e: Exception) { - Log.e("ProfileChange", "Error updating profile", e) - snackBarHost.showSnackbar("An unexpected error occurred") - } - } - } - } - - return profile to updateProfile -} - -private data class ErrorMessages( - val failToUpdateAppProfile: Int, - val failToUpdateSepolicy: Int, - val suNotAllowed: Int, -) - -private sealed class UpdateResult { - object Success : UpdateResult() - - data class Error( - val messageRes: Int, - val args: List, - ) : UpdateResult() - - val isSuccess: Boolean get() = this is Success -} - -private suspend fun updateProfileSafely( - profile: Profile, - info: SuperUserViewModel.AppInfo, - errorMessages: ErrorMessages, - snackBarHost: SnackbarHostState, -): UpdateResult { - if (profile.allowSu) { - // Validate system UID restrictions - if (isSystemUidForbidden(info.uid)) { - snackBarHost.showSnackbar( - // You'll need to resolve string resource here or pass context - "SU not allowed for system UID ${info.uid}", - ) - return UpdateResult.Error(errorMessages.suNotAllowed, listOf(info.label)) - } - - // Handle SELinux policy updates - if (shouldUpdateSepolicy(profile)) { - val sepolicyResult = SePolicy.setSePolicy(profile.name, profile.rules) - if (!sepolicyResult) { - snackBarHost.showSnackbar("Failed to update SEPolicy for ${info.label}") - return UpdateResult.Error(errorMessages.failToUpdateSepolicy, listOf(info.label)) - } - } - } - - // Update app profile - val profileUpdateSuccess = KsuNative.setAppProfile(profile) - if (!profileUpdateSuccess) { - snackBarHost.showSnackbar("Failed to update app profile for ${info.label}") - return UpdateResult.Error(errorMessages.failToUpdateAppProfile, listOf(info.uid)) - } - - return UpdateResult.Success -} - -private fun isSystemUidForbidden(uid: Int): Boolean { - // sync with allowlist.c - forbid_system_uid - return uid < 2000 && uid != 1000 -} - -private fun shouldUpdateSepolicy(profile: Profile): Boolean = !profile.rootUseDefault && profile.rules.isNotEmpty() - -val LocalProfileChange = - staticCompositionLocalOf Unit>> { - error("CompositionLocal LocalProfileChange not present") - } diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/superuser/SuperUserItem.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/superuser/SuperUserItem.kt deleted file mode 100644 index 26affa14b..000000000 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/superuser/SuperUserItem.kt +++ /dev/null @@ -1,83 +0,0 @@ -package com.dergoogler.mmrl.ui.screens.superuser - -import androidx.compose.foundation.layout.ExperimentalLayoutApi -import androidx.compose.foundation.layout.size -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.unit.dp -import coil3.compose.AsyncImage -import com.dergoogler.mmrl.platform.ksu.KsuNative -import com.dergoogler.mmrl.ui.component.LabelItem -import com.dergoogler.mmrl.ui.component.LabelItemDefaults -import com.dergoogler.mmrl.ui.component.listItem.dsl.ListScope -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.ButtonItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Description -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Labels -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Start -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Title -import com.dergoogler.mmrl.viewmodel.SuperUserViewModel -import com.dergoogler.mmrl.viewmodel.SuperUserViewModel.AppInfo.Companion.loadIcon - -@OptIn(ExperimentalLayoutApi::class) -@Composable -fun ListScope.SuperUserItem( - app: SuperUserViewModel.AppInfo, - onClick: () -> Unit, -) { - val context = LocalContext.current - - ButtonItem( - onClick = onClick, - ) { - Title(app.label) - Description(app.packageName) - - Start { - AsyncImage( - model = app.loadIcon(context), - contentDescription = app.label, - modifier = Modifier.size(48.dp), - ) - } - - Labels { - if (app.allowSu) { - LabelItem( - text = "ROOT", - ) - } else { - if (KsuNative.uidShouldUmount(app.uid)) { - LabelItem( - text = "UMOUNT", - style = - LabelItemDefaults.style.copy( - containerColor = MaterialTheme.colorScheme.secondaryContainer, - contentColor = MaterialTheme.colorScheme.onSecondaryContainer, - ), - ) - } - } - if (app.hasCustomProfile) { - LabelItem( - text = "CUSTOM", - style = - LabelItemDefaults.style.copy( - containerColor = MaterialTheme.colorScheme.tertiaryContainer, - contentColor = MaterialTheme.colorScheme.onTertiaryContainer, - ), - ) - } else if (!app.allowSu && !KsuNative.uidShouldUmount(app.uid)) { - LabelItem( - text = "DEFAULT", - style = - LabelItemDefaults.style.copy( - containerColor = MaterialTheme.colorScheme.primaryContainer, - contentColor = MaterialTheme.colorScheme.onPrimaryContainer, - ), - ) - } - } - } -} diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/superuser/SuperUserList.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/superuser/SuperUserList.kt deleted file mode 100644 index 76ddbdcee..000000000 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/superuser/SuperUserList.kt +++ /dev/null @@ -1,59 +0,0 @@ -package com.dergoogler.mmrl.ui.screens.superuser - -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.calculateStartPadding -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.foundation.lazy.items -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.unit.dp -import com.dergoogler.mmrl.ui.component.listItem.dsl.List -import com.dergoogler.mmrl.ui.component.scaffold.ScaffoldScope -import com.dergoogler.mmrl.ui.providable.LocalDestinationsNavigator -import com.dergoogler.mmrl.ui.providable.LocalHazeState -import com.dergoogler.mmrl.ui.providable.LocalMainScreenInnerPaddings -import com.dergoogler.mmrl.viewmodel.SuperUserViewModel -import com.ramcosta.composedestinations.generated.destinations.AppProfileScreenDestination -import dev.chrisbanes.haze.hazeSource - -@Composable -fun ScaffoldScope.SuperUserList( - innerPadding: PaddingValues, - list: List, - state: LazyListState, -) { - val navigator = LocalDestinationsNavigator.current - val paddingValues = LocalMainScreenInnerPaddings.current - val layoutDirection = LocalLayoutDirection.current - - this@SuperUserList.ResponsiveContent { - List { - LazyColumn( - state = state, - modifier = - Modifier - .fillMaxSize() - .hazeSource(state = LocalHazeState.current), - contentPadding = - PaddingValues( - top = innerPadding.calculateTopPadding(), - start = innerPadding.calculateStartPadding(layoutDirection), - bottom = paddingValues.calculateBottomPadding(), - end = 0.dp, - ), - ) { - items( - items = list, - key = { it.packageName + it.uid }, - ) { app -> - SuperUserItem(app) { - navigator.navigate(AppProfileScreenDestination(app)) - } - } - } - } - } -} diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/superuser/SuperUserMenu.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/superuser/SuperUserMenu.kt deleted file mode 100644 index 22d71a528..000000000 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/superuser/SuperUserMenu.kt +++ /dev/null @@ -1,94 +0,0 @@ -package com.dergoogler.mmrl.ui.screens.superuser - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.FlowRow -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -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.unit.dp -import com.dergoogler.mmrl.R -import com.dergoogler.mmrl.datastore.model.SuperUserMenu -import com.dergoogler.mmrl.ui.component.BottomSheet -import com.dergoogler.mmrl.ui.component.MenuChip -import com.dergoogler.mmrl.ui.providable.LocalUserPreferences - -@Composable -fun SuperUserMenu(setMenu: (SuperUserMenu) -> Unit) { - val userPreferences = LocalUserPreferences.current - var open by rememberSaveable { mutableStateOf(false) } - - IconButton( - onClick = { open = true }, - ) { - Icon( - painter = painterResource(id = R.drawable.filter_outlined), - contentDescription = null, - ) - - if (open) { - MenuBottomSheet( - onClose = { open = false }, - menu = userPreferences.superUserMenu, - setMenu = setMenu, - ) - } - } -} - -@Composable -private fun MenuBottomSheet( - onClose: () -> Unit, - menu: SuperUserMenu, - setMenu: (SuperUserMenu) -> Unit, -) = BottomSheet(onDismissRequest = onClose) { - Text( - text = stringResource(id = R.string.menu_advanced_menu), - style = MaterialTheme.typography.headlineSmall, - modifier = Modifier.align(Alignment.CenterHorizontally), - ) - - Column( - modifier = Modifier.padding(all = 18.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), - ) { - Text( - text = stringResource(id = R.string.menu_sort_mode), - style = MaterialTheme.typography.titleSmall, - ) - - FlowRow( - modifier = - Modifier - .fillMaxWidth(1f) - .wrapContentHeight(align = Alignment.Top), - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), - ) { - /*MenuChip( - selected = menu.pinHasRoot, - onClick = { setMenu(menu.copy(pinHasRoot = !menu.pinHasRoot)) }, - label = { Text(text = stringResource(id = R.string.menu_pin_installed)) } - )*/ - - MenuChip( - selected = menu.showSystemApps, - onClick = { setMenu(menu.copy(showSystemApps = !menu.showSystemApps)) }, - label = { Text(text = stringResource(id = R.string.menu_show_system_apps)) }, - ) - } - } -} diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/superuser/SuperUserScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/superuser/SuperUserScreen.kt deleted file mode 100644 index 2d1f727d3..000000000 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/superuser/SuperUserScreen.kt +++ /dev/null @@ -1,167 +0,0 @@ -package com.dergoogler.mmrl.ui.screens.superuser - -import androidx.activity.compose.BackHandler -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.TopAppBarScrollBehavior -import androidx.compose.material3.pulltorefresh.PullToRefreshBox -import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults.Indicator -import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.res.painterResource -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.dergoogler.mmrl.R -import com.dergoogler.mmrl.datastore.model.SuperUserMenu -import com.dergoogler.mmrl.ext.currentScreenWidth -import com.dergoogler.mmrl.ext.none -import com.dergoogler.mmrl.ui.component.Loading -import com.dergoogler.mmrl.ui.component.LocalScreenProvider -import com.dergoogler.mmrl.ui.component.PageIndicator -import com.dergoogler.mmrl.ui.component.TopAppBarEventIcon -import com.dergoogler.mmrl.ui.component.scaffold.Scaffold -import com.dergoogler.mmrl.ui.component.toolbar.BlurSearchToolbar -import com.dergoogler.mmrl.ui.providable.LocalSuperUserViewModel -import com.dergoogler.mmrl.ui.providable.LocalUserPreferences -import com.ramcosta.composedestinations.annotation.Destination -import com.ramcosta.composedestinations.annotation.RootGraph - -@Destination -@Composable -fun SuperUserScreen() = - LocalScreenProvider { - val userPrefs = LocalUserPreferences.current - val viewModel = LocalSuperUserViewModel.current - - val list by viewModel.local.collectAsStateWithLifecycle() - val query by viewModel.query.collectAsStateWithLifecycle() - val state by viewModel.screenState.collectAsStateWithLifecycle() - val isLoading by viewModel.isLoading.collectAsStateWithLifecycle() - - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() - - val pullToRefreshState = rememberPullToRefreshState() - - BackHandler( - enabled = viewModel.isSearch, - onBack = viewModel::closeSearch, - ) - - Scaffold( - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), - topBar = { - TopBar( - isSearch = viewModel.isSearch, - query = query, - onQueryChange = viewModel::search, - onOpenSearch = viewModel::openSearch, - onCloseSearch = viewModel::closeSearch, - setMenu = viewModel::setSuperUserMenu, - scrollBehavior = scrollBehavior, - ) - }, - contentWindowInsets = WindowInsets.none, - ) { innerPadding -> - if (isLoading) { - Loading() - } - - if (list.isEmpty() && !isLoading) { - PageIndicator( - icon = R.drawable.keyframes, - text = if (viewModel.isSearch) R.string.search_empty else R.string.modules_empty, - ) - } - - PullToRefreshBox( - state = pullToRefreshState, - isRefreshing = state.isRefreshing, - onRefresh = viewModel::fetchAppList, - indicator = { - Indicator( - modifier = - Modifier.align(Alignment.TopCenter).let { - if (!userPrefs.enableBlur) { - it.padding(top = innerPadding.calculateTopPadding()) - } else { - it - } - }, - isRefreshing = state.isRefreshing, - state = pullToRefreshState, - ) - }, - ) { - this@Scaffold.SuperUserList( - innerPadding = innerPadding, - list = list, - state = viewModel.listState, - ) - } - } - } - -@Composable -private fun TopBar( - isSearch: Boolean, - query: String, - onQueryChange: (String) -> Unit, - onOpenSearch: () -> Unit, - onCloseSearch: () -> Unit, - setMenu: (SuperUserMenu) -> Unit, - scrollBehavior: TopAppBarScrollBehavior, -) { - val width = currentScreenWidth() - - var currentQuery by remember { mutableStateOf(query) } - DisposableEffect(isSearch) { - onDispose { currentQuery = "" } - } - - BlurSearchToolbar( - scrollBehavior = scrollBehavior, - isSearch = isSearch, - query = currentQuery, - fade = true, - fadeDistance = 50f, - onQueryChange = { - onQueryChange(it) - currentQuery = it - }, - onClose = { - onCloseSearch() - currentQuery = "" - }, - title = { - if (!width.isSmall) return@BlurSearchToolbar - - TopAppBarEventIcon() - }, - actions = { - if (!isSearch) { - IconButton( - onClick = onOpenSearch, - ) { - Icon( - painter = painterResource(id = R.drawable.search), - contentDescription = null, - ) - } - } - - SuperUserMenu( - setMenu = setMenu, - ) - }, - ) -} diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/viewmodel/SuperUserViewModel.kt b/app/src/main/kotlin/com/dergoogler/mmrl/viewmodel/SuperUserViewModel.kt deleted file mode 100644 index 25dd5f0eb..000000000 --- a/app/src/main/kotlin/com/dergoogler/mmrl/viewmodel/SuperUserViewModel.kt +++ /dev/null @@ -1,249 +0,0 @@ -package com.dergoogler.mmrl.viewmodel - -import android.app.Application -import android.content.Context -import android.content.pm.ApplicationInfo -import android.content.pm.PackageInfo -import android.graphics.Bitmap -import android.graphics.drawable.Drawable -import android.os.Parcelable -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateMapOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import androidx.compose.ui.unit.Density -import androidx.core.graphics.drawable.toBitmap -import androidx.lifecycle.viewModelScope -import coil3.request.ImageRequest -import coil3.request.crossfade -import com.dergoogler.mmrl.datastore.UserPreferencesRepository -import com.dergoogler.mmrl.datastore.model.SuperUserMenu -import com.dergoogler.mmrl.platform.PlatformManager -import com.dergoogler.mmrl.platform.ksu.KsuNative -import com.dergoogler.mmrl.platform.ksu.Profile -import com.dergoogler.mmrl.repository.LocalRepository -import com.dergoogler.mmrl.repository.ModulesRepository -import com.dergoogler.mmrl.utils.packages -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import kotlinx.parcelize.Parcelize -import java.text.Collator -import java.util.Locale -import javax.inject.Inject - -@HiltViewModel -class SuperUserViewModel - @Inject - constructor( - application: Application, - localRepository: LocalRepository, - modulesRepository: ModulesRepository, - userPreferencesRepository: UserPreferencesRepository, - ) : MMRLViewModel(application, localRepository, modulesRepository, userPreferencesRepository) { - private val superUserMenu - get() = userPreferencesRepository.data.map { it.superUserMenu } - - var isSearch by mutableStateOf(false) - private set - private val keyFlow = MutableStateFlow("") - val query get() = keyFlow.asStateFlow() - - val listState: LazyListState = LazyListState() - - private val cacheFlow = MutableStateFlow(listOf()) - private val localFlow = MutableStateFlow(listOf()) - val local get() = localFlow.asStateFlow() - - private var isLoadingFlow = MutableStateFlow(false) - val isLoading get() = isLoadingFlow.asStateFlow() - - private val profileOverrides = mutableStateMapOf() - - val screenState: StateFlow = - localFlow - .combine(isLoadingFlow) { items, isRefreshing -> - SuperUserScreenState(items = items, isRefreshing = isRefreshing) - }.stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5000), - initialValue = SuperUserScreenState(), - ) - - init { - providerObserver() - dataObserver() - keyObserver() - } - - private fun providerObserver() { - PlatformManager.isAliveFlow - .onEach { if (it) fetchAppList() } - .launchIn(viewModelScope) - } - - private val comparator get() = - compareBy { - when { - it.profile != null && it.profile.allowSu -> 0 - it.profile != null && - ( - if (it.profile.allowSu) !it.profile.rootUseDefault else !it.profile.nonRootUseDefault - ) -> 1 - - else -> 2 - } - }.then(compareBy(Collator.getInstance(Locale.getDefault()), AppInfo::label)) - - private fun dataObserver() { - combine(cacheFlow, superUserMenu) { list, menu -> - if (list.isEmpty()) return@combine - - val comparator = - compareBy { - when { - it.allowSu -> 0 - it.hasCustomProfile -> 1 - else -> 2 - } - }.then(compareBy(Collator.getInstance(Locale.getDefault()), AppInfo::label)) - - localFlow.value = - list - .filter { - menu.showSystemApps || - (it.uid == 2000 || (it.packageInfo.applicationInfo!!.flags and ApplicationInfo.FLAG_SYSTEM) == 0) - }.sortedWith(comparator) - }.launchIn(viewModelScope) - } - - private fun keyObserver() { - combine(keyFlow, cacheFlow, superUserMenu) { key, source, menu -> - val newKey = key.trim() - localFlow.value = - source - .filter { - if (newKey.isNotBlank()) { - it.label.contains(newKey, true) || - it.packageName.contains(newKey, true) - } else { - true - } - }.map { app -> - profileOverrides[app.packageName]?.let { app.copy(profile = it) } ?: app - }.filter { - menu.showSystemApps || - (it.uid == 2000 || (it.packageInfo.applicationInfo!!.flags and ApplicationInfo.FLAG_SYSTEM) == 0) - }.sortedWith(comparator) - }.launchIn(viewModelScope) - } - - fun search(key: String) { - keyFlow.value = key - } - - fun openSearch() { - isSearch = true - } - - fun closeSearch() { - isSearch = false - keyFlow.value = "" - } - - fun setSuperUserMenu(value: SuperUserMenu) = - viewModelScope.launch { - userPreferencesRepository.setSuperUserMenu(value) - } - - private inline fun T.refreshing(block: T.() -> Unit) { - isLoadingFlow.update { true } - block() - isLoadingFlow.update { false } - } - - fun fetchAppList() = - viewModelScope.launch { - refreshing { - cacheFlow.value = - withContext(Dispatchers.IO) { - getAppList() - } - profileOverrides.clear() - } - } - - private fun getAppList(): List { - val pm = context.packageManager - val packages = context.packages - return packages - .map { pkg -> - val appInfo = pkg.applicationInfo!! - val uid = appInfo.uid - val profile = KsuNative.getAppProfile(pkg.packageName, uid) - AppInfo( - label = appInfo.loadLabel(pm).toString(), - packageInfo = pkg, - profile = profile, - ) - }.filter { it.packageName != context.packageName } - } - - fun updateAppProfile( - packageName: String, - newProfile: Profile, - ) { - profileOverrides[packageName] = newProfile - } - - @Parcelize - data class AppInfo( - val label: String, - val packageInfo: PackageInfo, - val profile: Profile? = null, - ) : Parcelable { - val packageName get() = packageInfo.packageName - val uid get() = packageInfo.applicationInfo!!.uid - val allowSu get() = profile?.allowSu == true - val hasCustomProfile - get() = - profile?.let { - if (it.allowSu) !it.rootUseDefault else !it.nonRootUseDefault - } ?: false - - companion object { - fun AppInfo.loadIcon(context: Context): ImageRequest { - val drawable: Drawable? = - packageInfo.applicationInfo?.loadIcon(context.packageManager) - val density = Density(context) - val icon: Bitmap? = - with(density) { - drawable?.toBitmap() - } - - return ImageRequest - .Builder(context) - .data(icon) - .crossfade(true) - .build() - } - } - } - - data class SuperUserScreenState( - val items: List = emptyList(), - val isRefreshing: Boolean = false, - ) - } From e7b7265ab46707999a0b9f22fc612db36c12b87e Mon Sep 17 00:00:00 2001 From: Der_Googler <54764558+DerGoogler@users.noreply.github.com> Date: Fri, 9 Oct 2026 16:24:26 +0200 Subject: [PATCH 04/24] Refactor module description WebUI routing Replace the custom path handler classes with route-based WebUI registration inside `ViewDescriptionScreen`, including internal asset, readme, inset, and color routes. Add a shared `MimeUtil` for asset MIME detection, and trim unrelated WebUI launcher code from `Extensions.kt` as part of the cleanup. --- .../mmrl/pathHandler/AssetsPathHandler.kt | 28 ---- .../mmrl/pathHandler/InternalPathHandler.kt | 64 --------- .../moduleView/ViewDescriptionScreen.kt | 69 ++++++--- .../mmrl/ui/screens/modules/ModuleItem.kt | 78 ++++++++-- .../com/dergoogler/mmrl/utils/Extensions.kt | 135 ------------------ .../com/dergoogler/mmrl/utils/MimeUtil.kt | 63 ++++++++ 6 files changed, 174 insertions(+), 263 deletions(-) delete mode 100644 app/src/main/kotlin/com/dergoogler/mmrl/pathHandler/AssetsPathHandler.kt delete mode 100644 app/src/main/kotlin/com/dergoogler/mmrl/pathHandler/InternalPathHandler.kt create mode 100644 app/src/main/kotlin/com/dergoogler/mmrl/utils/MimeUtil.kt diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/pathHandler/AssetsPathHandler.kt b/app/src/main/kotlin/com/dergoogler/mmrl/pathHandler/AssetsPathHandler.kt deleted file mode 100644 index b8255883f..000000000 --- a/app/src/main/kotlin/com/dergoogler/mmrl/pathHandler/AssetsPathHandler.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.dergoogler.mmrl.pathHandler - -import android.util.Log -import android.webkit.WebResourceResponse -import com.dergoogler.mmrl.hybridwebui.HybridWebUI -import dev.mmrlx.webui.PathHandler -import dev.mmrlx.webui.WebUI -import dev.mmrlx.webui.WebUIResourceRequest -import java.io.IOException - -class AssetsPathHandler( - webui: WebUI, -) : PathHandler(webui) { - private val assetHelper get() = kontext.assets - - override fun handle(request: WebUIResourceRequest): WebResourceResponse { - val path = request.path - - try { - val inputStream = assetHelper.open(path.removePrefix("/")) - val mimeType = HybridWebUI.MimeType.getMimeFromFileName(path) - return WebResourceResponse(mimeType, null, inputStream) - } catch (e: IOException) { - Log.e("assetsPathHandler", "Error opening asset path: $path", e) - return notFoundResponse - } - } -} diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/pathHandler/InternalPathHandler.kt b/app/src/main/kotlin/com/dergoogler/mmrl/pathHandler/InternalPathHandler.kt deleted file mode 100644 index c1e90cad5..000000000 --- a/app/src/main/kotlin/com/dergoogler/mmrl/pathHandler/InternalPathHandler.kt +++ /dev/null @@ -1,64 +0,0 @@ -package com.dergoogler.mmrl.pathHandler - - -import android.util.Log -import android.webkit.WebResourceResponse -import androidx.compose.material3.ColorScheme -import com.dergoogler.mmrl.model.WebColors -import dev.mmrlx.webui.PathHandler -import dev.mmrlx.webui.WebUI -import dev.mmrlx.webui.WebUIResourceRequest -import java.io.IOException -import java.net.HttpURLConnection -import java.net.URL - -class InternalPathHandler( - webui: WebUI, - private val readmeUrl: String, - private val colorScheme: ColorScheme, -) : PathHandler(webui) { - override val id = "/internal/" - val webColors get() = WebColors(colorScheme) - val assetsPathHandler = AssetsPathHandler(this) - - override fun handle(request: WebUIResourceRequest): WebResourceResponse { - val path = request.path - - try { - if (path.matches(Regex("^assets(/.*)?$"))) { - return assetsPathHandler.handle( - WebUIResourceRequest( - method = request.method, - isForMainFrame = request.isForMainFrame, - url = request.url, - path = path.removePrefix("assets/"), - requestHeaders = request.requestHeaders, - isRedirect = request.isRedirect, - hasGesture = request.hasGesture() - ) - ) - } - - - - if (path.matches(Regex("readme\\.md"))) { - val connection = URL(readmeUrl).openConnection() as HttpURLConnection - connection.connect() - return htmlResponse(connection.getInputStream()) - } - - if (path.matches(Regex("insets\\.css"))) { - return insets.css.asStyleResponse() - } - - if (path.matches(Regex("colors\\.css"))) { - return webColors.allCssColors.asStyleResponse() - } - - return notFoundResponse - } catch (e: IOException) { - Log.e("InternalPathHandler", "Error opening mmrl asset path: $path", e) - return notFoundResponse - } - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/ViewDescriptionScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/ViewDescriptionScreen.kt index c9d93c30b..244c3dc76 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/ViewDescriptionScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/ViewDescriptionScreen.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.ColorScheme import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -17,13 +16,12 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import com.dergoogler.mmrl.BuildConfig import com.dergoogler.mmrl.R import com.dergoogler.mmrl.ext.none -import com.dergoogler.mmrl.pathHandler.InternalPathHandler +import com.dergoogler.mmrl.model.WebColors import com.dergoogler.mmrl.ui.component.LocalScreenProvider import com.dergoogler.mmrl.ui.component.scaffold.Scaffold import com.dergoogler.mmrl.ui.component.toolbar.BlurToolbar @@ -31,6 +29,7 @@ import com.dergoogler.mmrl.ui.providable.LocalDestinationsNavigator import com.dergoogler.mmrl.ui.providable.LocalHazeState import com.dergoogler.mmrl.ui.providable.LocalMainScreenInnerPaddings import com.dergoogler.mmrl.ui.providable.LocalUserPreferences +import com.dergoogler.mmrl.utils.MimeUtil import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.navigation.DestinationsNavigator @@ -38,27 +37,30 @@ import dev.chrisbanes.haze.hazeSource import dev.mmrlx.compose.webui.WebUIView import dev.mmrlx.compose.webui.insets import dev.mmrlx.compose.webui.rememberWebUIState - -const val launchUrl = "https://mui.kernelsu.org/internal/assets/markdown.html" +import dev.mmrlx.webui.RouteRegistry +import dev.mmrlx.webui.RouteScope +import dev.mmrlx.webui.WebResourceResponse +import dev.mmrlx.webui.WebUIOptions +import java.net.HttpURLConnection +import java.net.URL @SuppressLint("SetJavaScriptEnabled", "JavascriptInterface") @Composable @Destination fun ViewDescriptionScreen(readmeUrl: String) = LocalScreenProvider { - val density = LocalDensity.current val navigator = LocalDestinationsNavigator.current val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() val userPrefs = LocalUserPreferences.current val scheme = MaterialTheme.colorScheme - @Suppress("KotlinConstantConditions") val wstate = - rememberWebUIState("https://desc.mmrl.dev", "internal/assets/markdown.html") { + val wstate = + rememberWebUIState( + "https://desc.mmrl.dev", + WebUIOptions("internal/assets/markdown.html") + ) { it .settings { - schemeWhitelist += "ksu" - useDefaultApplicationInterface = false - useDefaultFileSystem = false debug = BuildConfig.DEBUG forceKillProcess = false useConsoleInterceptor = false @@ -69,15 +71,35 @@ fun ViewDescriptionScreen(readmeUrl: String) = it.webview.setBackgroundColor(scheme.background.toArgb()) } .chromeClient { } - .registerPathHandler( - InternalPathHandler::class.java - ) { - add( - String::class.java to readmeUrl - ) - add( - ColorScheme::class.java to scheme - ) + .registerRoutes { + val webColors = WebColors(scheme) + + internalRoute("readme.md") { + val connection = URL(readmeUrl).openConnection() as HttpURLConnection + connection.connect() + htmlResponse(connection.getInputStream()) + } + + internalRoute("/internal/insets.css") { + insets.css.asStyleResponse() + } + + internalRoute("/internal/colors.css") { + webColors.allCssColors.asStyleResponse() + } + + internalRoute("/internal/assets/*asset") { + val filePath = request.params["asset"] ?: return@internalRoute notFoundResponse + + try { + val inputStream = kontext.assets.open(filePath) + val mimeType = MimeUtil.getMimeFromFileName(filePath) + response(mimeType = mimeType, data = inputStream) + } catch (e: Exception) { + console.error("Failed to open $filePath from the assets: $e") + notFoundResponse + } + } } } @@ -133,4 +155,9 @@ private fun TopBar( }, title = { Text(text = stringResource(id = R.string.view_module_about_this_module)) }, scrollBehavior = scrollBehavior, -) \ No newline at end of file +) + +private fun RouteRegistry.internalRoute( + name: String, + content: RouteScope.() -> WebResourceResponse, +) = route("internal/$name", content) \ No newline at end of file diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/modules/ModuleItem.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/modules/ModuleItem.kt index 51251cc81..fbc372d0e 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/modules/ModuleItem.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/modules/ModuleItem.kt @@ -1,5 +1,11 @@ package com.dergoogler.mmrl.ui.screens.modules +import android.content.ComponentName +import android.content.Intent +import android.util.Log +import android.widget.Toast +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.DrawableRes import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image @@ -71,7 +77,6 @@ import com.dergoogler.mmrl.ui.component.text.BBCodeText import com.dergoogler.mmrl.ui.providable.LocalModule import com.dergoogler.mmrl.ui.providable.LocalUserPreferences import com.dergoogler.mmrl.utils.toFormattedDateSafely -import com.dergoogler.mmrl.utils.webUILauncher import dev.dergoogler.mmrl.compat.core.LocalUriHandler import kotlinx.coroutines.launch @@ -82,17 +87,17 @@ fun ModuleItem( alpha: Float = 1f, decoration: TextDecoration = TextDecoration.None, switch: - @Composable() - (() -> Unit?)? = null, + @Composable() + (() -> Unit?)? = null, indicator: - @Composable() - (CardScope.() -> Unit?)? = null, + @Composable() + (CardScope.() -> Unit?)? = null, startTrailingButton: - @Composable() - (LiteRowScope.() -> Unit)? = null, + @Composable() + (LiteRowScope.() -> Unit)? = null, trailingButton: - @Composable() - (LiteRowScope.() -> Unit), + @Composable() + (LiteRowScope.() -> Unit), isBlacklisted: Boolean = false, isProviderAlive: Boolean, ) { @@ -115,7 +120,36 @@ fun ModuleItem( !context.isPackageInstalled(userPreferences.webuixPackageName) } - val launch = userPreferences.webUILauncher(context, module) + val webui = + rememberLauncherForActivityResult( + contract = ActivityResultContracts.RequestMultiplePermissions(), + ) { result -> + if (!result.allGranted { perm -> + Toast + .makeText(context, "Permission denied! Requires $perm", Toast.LENGTH_SHORT) + .show() + } + ) { + return@rememberLauncherForActivityResult + } + + try { + val intent = Intent().apply { + component = ComponentName( + userPreferences.webuixPackageName, + "com.dergoogler.mmrl.wx.ui.webui.WebUIActivity" + ) + + putExtra("MODULE_ID", module.id.toString()) + } + + context.startActivity(intent) + } catch (e: Exception) { + e.printStackTrace() + Log.e("webUILauncher", "Failed to launch WebUI", e) + } + } + val clicker: (() -> Unit)? = remember(canWenUIAccessed) { @@ -125,7 +159,11 @@ fun ModuleItem( return@jump } - launch() + webui.launch( + arrayOf( + "com.dergoogler.mmrl.permission.WEBUI_X", + ), + ) } } @@ -138,10 +176,10 @@ fun ModuleItem( Card( border = isBlacklisted nullable - BorderStroke( - width = 1.dp, - color = MaterialTheme.colorScheme.errorContainer, - ), + BorderStroke( + width = 1.dp, + color = MaterialTheme.colorScheme.errorContainer, + ), onClick = clicker, ) { indicator.nullable { @@ -439,3 +477,13 @@ fun StateIndicator( alpha = 0.1f, colorFilter = ColorFilter.tint(color), ) + +private fun Map.allGranted(onDenied: (String) -> Unit): Boolean { + for ((perm, granted) in this) { + if (!granted) { + onDenied(perm) + return false + } + } + return true +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/utils/Extensions.kt b/app/src/main/kotlin/com/dergoogler/mmrl/utils/Extensions.kt index 7d027432a..9e29630b8 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/utils/Extensions.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/utils/Extensions.kt @@ -1,27 +1,8 @@ package com.dergoogler.mmrl.utils -import android.annotation.SuppressLint -import android.content.Context -import android.widget.Toast -import androidx.activity.ComponentActivity -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.runtime.Composable -import com.dergoogler.mmrl.App -import com.dergoogler.mmrl.BuildConfig -import com.dergoogler.mmrl.datastore.model.UserPreferences -import com.dergoogler.mmrl.datastore.model.WebUIEngine -import com.dergoogler.mmrl.ext.findActivity import com.dergoogler.mmrl.ext.toFormattedDateSafely -import com.dergoogler.mmrl.modconf.helper.ModConfLauncher -import com.dergoogler.mmrl.model.local.LocalModule -import com.dergoogler.mmrl.platform.PlatformManager -import com.dergoogler.mmrl.platform.content.LocalModule.Companion.hasModConf -import com.dergoogler.mmrl.platform.content.LocalModule.Companion.hasWebUI -import com.dergoogler.mmrl.platform.ksu.KsuNative -import com.dergoogler.mmrl.platform.model.toModuleConfig import com.dergoogler.mmrl.ui.providable.LocalUserPreferences -import com.dergoogler.mmrl.webui.helper.WebUILauncher import com.topjohnwu.superuser.NoShellException import com.topjohnwu.superuser.Shell @@ -60,119 +41,3 @@ fun createRootShell( } return builder.build(*commands) } - -@SuppressLint("MissingPermission") -@Composable -fun UserPreferences.webUILauncher( - context: Context, - module: LocalModule, -): () -> Unit { - val modId = module.id - val config = modId.toModuleConfig() - - val activity = - context.findActivity() as? ComponentActivity ?: run { - Toast.makeText(context, "No activity found", Toast.LENGTH_SHORT).show() - return {} - } - - val modconfLauncher = - ModConfLauncher( - debug = BuildConfig.DEBUG, - packageName = webuixPackageName, - ) - - val webuiLauncher = - WebUILauncher( - debug = BuildConfig.DEBUG, - packageName = webuixPackageName, - ) - - fun Map.allGranted(onDenied: (String) -> Unit): Boolean { - for ((perm, granted) in this) { - if (!granted) { - onDenied(perm) - return false - } - } - return true - } - - val modconf = - rememberLauncherForActivityResult( - contract = ActivityResultContracts.RequestMultiplePermissions(), - ) { result -> - if (!result.allGranted { perm -> - Toast - .makeText(context, "Permission denied! Requires $perm", Toast.LENGTH_SHORT) - .show() - } - ) { - return@rememberLauncherForActivityResult - } - - modconfLauncher.launch( - context = context, - modId = modId, - platform = workingMode.toPlatform(), - ) - } - - val webui = - rememberLauncherForActivityResult( - contract = ActivityResultContracts.RequestMultiplePermissions(), - ) { result -> - if (!result.allGranted { perm -> - Toast - .makeText(context, "Permission denied! Requires $perm", Toast.LENGTH_SHORT) - .show() - } - ) { - return@rememberLauncherForActivityResult - } - - val effectiveEngine = - when (webuiEngine) { - WebUIEngine.PREFER_MODULE -> - config.getWebuiEngine(context)?.let { - when (it) { - "wx" -> WebUIEngine.WX - "ksu" -> WebUIEngine.KSU - else -> { - Toast.makeText(context, "Unknown WebUI engine", Toast.LENGTH_SHORT).show() - return@rememberLauncherForActivityResult - } - } - } ?: WebUIEngine.WX - - else -> webuiEngine - } - - when (effectiveEngine) { - WebUIEngine.WX -> webuiLauncher.launchWX(context, modId, workingMode.toPlatform()) - WebUIEngine.KSU -> webuiLauncher.launchLegacy(context, modId, workingMode.toPlatform()) - else -> Toast.makeText(context, "Unsupported WebUI engine", Toast.LENGTH_SHORT).show() - } - } - - return { - when { - module.hasModConf -> modconf.launch(arrayOf(modconfLauncher.permissions.MODCONF)) - module.hasWebUI -> - webui.launch( - arrayOf( - webuiLauncher.permissions.WEBUI_X, - webuiLauncher.permissions.WEBUI_LEGACY, - ), - ) - - else -> Toast.makeText(context, "Unsupported module", Toast.LENGTH_SHORT).show() - } - } -} - -val KsuNative.isManager: Boolean - get() { - val pkg = App.context.packageName - return PlatformManager.platform.isKernelSuVariant && becomeManager(pkg) - } diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/utils/MimeUtil.kt b/app/src/main/kotlin/com/dergoogler/mmrl/utils/MimeUtil.kt new file mode 100644 index 000000000..9ca504ed0 --- /dev/null +++ b/app/src/main/kotlin/com/dergoogler/mmrl/utils/MimeUtil.kt @@ -0,0 +1,63 @@ +package com.dergoogler.mmrl.utils + +import java.net.URLConnection +import java.util.Locale + +object MimeUtil { + fun getMimeFromFileName(fileName: String?): String? { + if (fileName == null) { + return null + } + + val mimeType = URLConnection.guessContentTypeFromName(fileName) + + if (mimeType != null) { + return mimeType + } + + return guessHardcodedMime(fileName) + } + + private fun guessHardcodedMime(fileName: String): String? { + val finalFullStop = fileName.lastIndexOf('.') + if (finalFullStop == -1) { + return null + } + + val extension = fileName.substring(finalFullStop + 1).lowercase(Locale.getDefault()) + + return when (extension) { + "webm" -> "video/webm" + "mpeg", "mpg" -> "video/mpeg" + "mp3" -> "audio/mpeg" + "wasm" -> "application/wasm" + "xhtml", "xht", "xhtm" -> "application/xhtml+xml" + "flac" -> "audio/flac" + "ogg", "oga", "opus" -> "audio/ogg" + "wav" -> "audio/wav" + "m4a" -> "audio/x-m4a" + "gif" -> "image/gif" + "jpeg", "jpg", "jfif", "pjpeg", "pjp" -> "image/jpeg" + "png" -> "image/png" + "apng" -> "image/apng" + "svg", "svgz" -> "image/svg+xml" + "webp" -> "image/webp" + "mht", "mhtml" -> "multipart/related" + "css" -> "text/css" + "html", "htm", "shtml", "shtm", "ehtml" -> "text/html" + "js", "mjs" -> "application/javascript" + "xml" -> "text/xml" + "mp4", "m4v" -> "video/mp4" + "ogv", "ogm" -> "video/ogg" + "ico" -> "image/x-icon" + "woff" -> "application/font-woff" + "gz", "tgz" -> "application/gzip" + "json" -> "application/json" + "pdf" -> "application/pdf" + "zip" -> "application/zip" + "bmp" -> "image/bmp" + "tiff", "tif" -> "image/tiff" + else -> null + } + } +} \ No newline at end of file From 16943bffe3a2c7ad15d31017c6ede07a55a55fce Mon Sep 17 00:00:00 2001 From: Der_Googler <54764558+DerGoogler@users.noreply.github.com> Date: Fri, 9 Oct 2026 17:35:06 +0200 Subject: [PATCH 05/24] [skip ci] fuck this shit --- app/build.gradle.kts | 4 +- gradle/libs.versions.toml | 23 ++++++----- ui/build.gradle.kts | 1 + .../mmrl/ui/component/BottomSheet.kt | 16 ++++---- .../mmrl/ui/component/text/BBCodeText.kt | 40 ++++++++++++------- 5 files changed, 49 insertions(+), 35 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e4f3ebb3c..44e83d86c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -269,8 +269,8 @@ dependencies { ksp(libs.square.moshi.kotlin) implementation(libs.mmrlx.terminal) - implementation(libs.mmrlx.nio) - implementation(libs.mmrlx.utilities) +// implementation(libs.mmrlx.nio) +// implementation(libs.mmrlx.utilities) implementation(libs.mmrlx.webui.core) implementation("dev.chrisbanes.haze:haze:1.6.10") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 117ba55ba..1dd8cfaae 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,8 +3,8 @@ androidGradlePlugin = "9.0.0" androidxActivity = "1.10.1" androidxAnnotation = "1.9.1" androidxAppCompat = "1.7.1" -androidxCompose = "1.8.3" -androidxComposeMaterial3 = "1.3.2" +androidxCompose = "1.11.0" +androidxComposeMaterial3 = "1.4.0" androidxCore = "1.16.0" androidxCoreSplashscreen = "1.0.1" androidxDataStore = "1.1.7" @@ -13,7 +13,7 @@ androidxHiltNavigationCompose = "1.2.0" androidxLifecycle = "2.9.1" androidxNavigation = "2.9.0" arbor = "2.0.103" -foundation = "1.8.3" +foundation = "1.11.0" kotlinxHtmlJvm = "0.9.1" multiplatformMarkdownRenderer = "0.27.0" processPhoenix = "3.0.0" @@ -25,13 +25,13 @@ browser = "1.8.0" coilCompose = "2.7.0" composeMarkdown = "0.5.4" hiddenApiRefine = "4.4.0" -hilt = "2.59.2" -kotlin = "2.2.0" +hilt = "2.60.1" +kotlin = "2.4.10" kotlinReflect = "1.9.24" kotlinxCoroutines = "1.8.1" kotlinxDatetime = "0.6.0" kotlinxSerialization = "1.7.1" -ksp = "2.2.0-2.0.2" +ksp = "2.3.11" libsu = "6.0.0" protobuf = "4.34.1" protobufPlugin = "0.9.6" @@ -40,8 +40,8 @@ shizuku = "13.1.5" squareRetrofit = "2.11.0" squareOkhttp = "4.12.0" squareMoshi = "1.15.1" -runtimeAndroid = "1.7.6" -mmrlx = "1.0.223" +runtimeAndroid = "1.11.0" +mmrlx = "1.0.224" workRuntimeKtx = "2.10.0" lifecycleProcess = "2.8.7" apacheCommons = "1.28.0" @@ -52,8 +52,9 @@ junit = "4.13.2" junitVersion = "1.2.1" espressoCore = "3.6.1" material = "1.12.0" -material3Android = "1.3.2" -runtimeAndroidVersion = "1.7.8" +materialIcons = "1.7.8" +material3Android = "1.4.0" +runtimeAndroidVersion = "1.11.0" swiperefreshlayout = "1.1.0" composedestinations = "2.2.0" @@ -83,6 +84,7 @@ androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lif androidx-lifecycle-service = { group = "androidx.lifecycle", name = "lifecycle-service", version.ref = "androidxLifecycle" } androidx-lifecycle-viewModel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "androidxLifecycle" } androidx-material = { module = "androidx.compose.material:material" } +androidx-material-icons-core = { module = "androidx.compose.material:material-icons-core", version.ref = "materialIcons" } androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" } androidx-multidex = { module = "androidx.multidex:multidex", version.ref = "multidex" } androidx-navigation-runtime-ktx = { group = "androidx.navigation", name = "navigation-runtime-ktx", version.ref = "navigationRuntimeKtx" } @@ -139,6 +141,7 @@ hiddenApiBypass = "org.lsposed.hiddenapibypass:hiddenapibypass:6.1" mmrlx-terminal = { module = "dev.mmrlx:terminal", version.ref = "mmrlx" } mmrlx-webui-core = { module = "dev.mmrlx:webui.core", version.ref = "mmrlx" } mmrlx-nio = { module = "dev.mmrlx:nio", version.ref = "mmrlx" } +mmrlx-ui = { module = "dev.mmrlx:ui", version.ref = "mmrlx" } mmrlx-utilities = { module = "dev.mmrlx:utilities", version.ref = "mmrlx" } timber = "com.jakewharton.timber:timber:5.0.1" diff --git a/ui/build.gradle.kts b/ui/build.gradle.kts index a7cc481db..426d1f744 100644 --- a/ui/build.gradle.kts +++ b/ui/build.gradle.kts @@ -73,4 +73,5 @@ dependencies { implementation(libs.androidx.material3.android) implementation(libs.androidx.compose.ui.tooling) implementation(libs.androidx.compose.ui.tooling.preview) + implementation("androidx.compose.material:material-icons-extended:1.7.8") } \ No newline at end of file diff --git a/ui/src/main/kotlin/com/dergoogler/mmrl/ui/component/BottomSheet.kt b/ui/src/main/kotlin/com/dergoogler/mmrl/ui/component/BottomSheet.kt index 17a5194dd..0e8239648 100644 --- a/ui/src/main/kotlin/com/dergoogler/mmrl/ui/component/BottomSheet.kt +++ b/ui/src/main/kotlin/com/dergoogler/mmrl/ui/component/BottomSheet.kt @@ -50,14 +50,14 @@ fun BottomSheet( modifier, sheetState, sheetMaxWidth, - shape, - containerColor, - contentColor, - tonalElevation, - scrimColor, - dragHandle, - { windowInsets }, - properties, +// shape, +// containerColor, +// contentColor, +// tonalElevation, +// scrimColor, +// dragHandle, +// { windowInsets }, +// properties, ) { val statusBarHeight = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + 28.dp diff --git a/ui/src/main/kotlin/com/dergoogler/mmrl/ui/component/text/BBCodeText.kt b/ui/src/main/kotlin/com/dergoogler/mmrl/ui/component/text/BBCodeText.kt index 5f436f227..1d6fd5c8e 100644 --- a/ui/src/main/kotlin/com/dergoogler/mmrl/ui/component/text/BBCodeText.kt +++ b/ui/src/main/kotlin/com/dergoogler/mmrl/ui/component/text/BBCodeText.kt @@ -55,11 +55,13 @@ enum class BBCodeTag { ; companion object { - fun disableAllExcept(vararg tags: BBCodeTag): Set = entries.filterNot { it in tags }.toSet() + fun disableAllExcept(vararg tags: BBCodeTag): Set = + entries.filterNot { it in tags }.toSet() fun disableAll(): Set = entries.toSet() - fun enableAllExcept(vararg tags: BBCodeTag): Set = entries.filterNot { it in tags }.toSet() + fun enableAllExcept(vararg tags: BBCodeTag): Set = + entries.filterNot { it in tags }.toSet() } } @@ -204,7 +206,11 @@ fun BBCodeText( val ann = string - .getStringAnnotations(tag = "URL", start = position, end = position) + .getStringAnnotations( + tag = "URL", + start = position, + end = position + ) .firstOrNull() ann?.let { @@ -327,13 +333,11 @@ private fun String.toStyleMarkup( placeholderVerticalAlign = PlaceholderVerticalAlign.Center, ), ) { - runCatching { - ProvideContentColorTextStyle( - contentColor = color, - textStyle = textStyle, - ) { - iconContent(value) - } + ProvideContentColorTextStyle( + contentColor = color, + textStyle = textStyle, + ) { + iconContent(value) } } } @@ -350,9 +354,7 @@ private fun String.toStyleMarkup( placeholderVerticalAlign = PlaceholderVerticalAlign.Center, ), ) { - runCatching { - imageContent(value) - } + imageContent(value) } } @@ -360,8 +362,16 @@ private fun String.toStyleMarkup( val current = styleStack.peek() val newStyle = when (tagType) { - BBCodeTag.COLOR -> current.copy(tag = tagType, color = colorFromName(value)) - BBCodeTag.BACKGROUND -> current.copy(tag = tagType, bg = colorFromName(value)) + BBCodeTag.COLOR -> current.copy( + tag = tagType, + color = colorFromName(value) + ) + + BBCodeTag.BACKGROUND -> current.copy( + tag = tagType, + bg = colorFromName(value) + ) + BBCodeTag.BOLD -> current.copy(tag = tagType, bold = true) BBCodeTag.ITALIC -> current.copy(tag = tagType, italic = true) BBCodeTag.UNDERLINE -> current.copy(tag = tagType, underline = true) From 740673c53c707b825e63446bd346e44d3721353a Mon Sep 17 00:00:00 2001 From: Der_Googler <54764558+DerGoogler@users.noreply.github.com> Date: Sun, 11 Oct 2026 15:46:57 +0200 Subject: [PATCH 06/24] . --- .../mmrl/ui/screens/main/MainScreen.kt | 22 +------ build-logic/src/main/kotlin/ProjectExt.kt | 2 +- gradle/libs.versions.toml | 64 +++++++++---------- gradle/wrapper/gradle-wrapper.properties | 5 +- 4 files changed, 37 insertions(+), 56 deletions(-) diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/main/MainScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/main/MainScreen.kt index fc8cd65ac..631fa3220 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/main/MainScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/main/MainScreen.kt @@ -6,24 +6,13 @@ import androidx.compose.animation.ExitTransition import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.imePadding -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items import androidx.compose.material3.Badge import androidx.compose.material3.BadgedBox import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.NavigationBarItem -import androidx.compose.material3.NavigationDrawerItem -import androidx.compose.material3.NavigationRail -import androidx.compose.material3.NavigationRailItem -import androidx.compose.material3.PermanentDrawerSheet -import androidx.compose.material3.PermanentNavigationDrawer import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -35,15 +24,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavBackStackEntry -import com.dergoogler.mmrl.ext.currentScreenWidth import com.dergoogler.mmrl.ext.none -import com.dergoogler.mmrl.ui.component.TopAppBar -import com.dergoogler.mmrl.ui.component.TopAppBarEventIcon import com.dergoogler.mmrl.ui.component.scaffold.ResponsiveScaffold -import com.dergoogler.mmrl.ui.component.scaffold.Scaffold import com.dergoogler.mmrl.ui.component.toolbar.BlurBottomToolbar import com.dergoogler.mmrl.ui.navigation.MainDestination import com.dergoogler.mmrl.ui.navigation.isAccessible @@ -210,10 +194,10 @@ private fun BottomNav(updates: Int) { Modifier .imePadding(), ) { - MainDestination.entries.forEach { screen -> - val isSelected by navController.isRouteOnBackStackAsState(screen.direction) + for (screen in MainDestination.entries) { + if (!screen.isAccessible) continue - if (!screen.isAccessible) return@forEach + val isSelected by navController.isRouteOnBackStackAsState(screen.direction) NavigationBarItem( icon = { diff --git a/build-logic/src/main/kotlin/ProjectExt.kt b/build-logic/src/main/kotlin/ProjectExt.kt index b25057701..0ddbb72aa 100644 --- a/build-logic/src/main/kotlin/ProjectExt.kt +++ b/build-logic/src/main/kotlin/ProjectExt.kt @@ -8,7 +8,7 @@ import java.util.Properties // #### CONFIG START #### -const val COMPILE_SDK = 36 +const val COMPILE_SDK = 37 const val BUILD_TOOLS_VERSION = "$COMPILE_SDK.0.0" const val MIN_SDK = 26 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1dd8cfaae..c633516a5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,60 +1,60 @@ [versions] -androidGradlePlugin = "9.0.0" -androidxActivity = "1.10.1" -androidxAnnotation = "1.9.1" -androidxAppCompat = "1.7.1" -androidxCompose = "1.11.0" +androidGradlePlugin = "9.4.1" +androidxActivity = "1.13.0" +androidxAnnotation = "1.11.0" +androidxAppCompat = "1.8.0" +androidxCompose = "1.12.1" androidxComposeMaterial3 = "1.4.0" -androidxCore = "1.16.0" -androidxCoreSplashscreen = "1.0.1" -androidxDataStore = "1.1.7" +androidxCore = "1.19.1" +androidxCoreSplashscreen = "1.2.0" +androidxDataStore = "1.2.1" androidxDocumentFile = "1.1.0" -androidxHiltNavigationCompose = "1.2.0" -androidxLifecycle = "2.9.1" -androidxNavigation = "2.9.0" +androidxHiltNavigationCompose = "1.4.0" +androidxLifecycle = "2.11.0" +androidxNavigation = "2.10.2" arbor = "2.0.103" -foundation = "1.11.0" +foundation = "1.10.6" +cashLicensee = "1.13.0" kotlinxHtmlJvm = "0.9.1" multiplatformMarkdownRenderer = "0.27.0" processPhoenix = "3.0.0" -webkit = "1.14.0" +webkit = "1.17.1" multidex = "2.0.1" -navigationRuntimeKtx = "2.9.0" -androidxRoom = "2.7.2" +navigationRuntimeKtx = "2.8.5" +androidxRoom = "2.8.5" browser = "1.8.0" -coilCompose = "2.7.0" +coilCompose = "2.1.0" +coilSvg = "3.6.3" composeMarkdown = "0.5.4" hiddenApiRefine = "4.4.0" hilt = "2.60.1" -kotlin = "2.4.10" +kotlin = "2.4.20" kotlinReflect = "1.9.24" kotlinxCoroutines = "1.8.1" kotlinxDatetime = "0.6.0" kotlinxSerialization = "1.7.1" ksp = "2.3.11" libsu = "6.0.0" -protobuf = "4.34.1" -protobufPlugin = "0.9.6" +protobuf = "4.28.2" +protobufPlugin = "0.9.4" semver = "2.0.0" shizuku = "13.1.5" squareRetrofit = "2.11.0" squareOkhttp = "4.12.0" squareMoshi = "1.15.1" -runtimeAndroid = "1.11.0" +runtimeAndroid = "1.12.1" mmrlx = "1.0.224" workRuntimeKtx = "2.10.0" lifecycleProcess = "2.8.7" -apacheCommons = "1.28.0" +apacheCommons = "1.27.1" parcelablelist = "2.0.1" -zoomableVersion = "2.2.0" -kotlinVersion = "2.2.0" +zoomableVersion = "2.15.0" junit = "4.13.2" junitVersion = "1.2.1" espressoCore = "3.6.1" material = "1.12.0" materialIcons = "1.7.8" material3Android = "1.4.0" -runtimeAndroidVersion = "1.11.0" swiperefreshlayout = "1.1.0" composedestinations = "2.2.0" @@ -85,8 +85,8 @@ androidx-lifecycle-service = { group = "androidx.lifecycle", name = "lifecycle-s androidx-lifecycle-viewModel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "androidxLifecycle" } androidx-material = { module = "androidx.compose.material:material" } androidx-material-icons-core = { module = "androidx.compose.material:material-icons-core", version.ref = "materialIcons" } -androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" } -androidx-multidex = { module = "androidx.multidex:multidex", version.ref = "multidex" } +androidx-webkit = { group = "androidx.webkit", name = "webkit", version.ref = "webkit" } +androidx-multidex = { group = "androidx.multidex", name = "multidex", version.ref = "multidex" } androidx-navigation-runtime-ktx = { group = "androidx.navigation", name = "navigation-runtime-ktx", version.ref = "navigationRuntimeKtx" } androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "androidxNavigation" } androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "androidxRoom" } @@ -112,7 +112,7 @@ kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", lib-zoomable = { module = "net.engawapg.lib:zoomable", version.ref = "zoomableVersion" } libsu-core = { group = "com.github.topjohnwu.libsu", name = "core", version.ref = "libsu" } libsu-service = { group = "com.github.topjohnwu.libsu", name = "service", version.ref = "libsu" } -libsu-io = { module = "com.github.topjohnwu.libsu:io", version.ref = "libsu" } +libsu-io = { module = "com.github.topjohnwu.libsu:io", name = "io", version.ref = "libsu" } multiplatform-markdown-renderer-android = { module = "com.mikepenz:multiplatform-markdown-renderer-android", version.ref = "multiplatformMarkdownRenderer" } multiplatform-markdown-renderer-coil3 = { module = "com.mikepenz:multiplatform-markdown-renderer-coil3", version.ref = "multiplatformMarkdownRenderer" } multiplatform-markdown-renderer-m3 = { module = "com.mikepenz:multiplatform-markdown-renderer-m3", version.ref = "multiplatformMarkdownRenderer" } @@ -132,10 +132,8 @@ square-okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = square-okhttp-dnsoverhttps = { group = "com.squareup.okhttp3", name = "okhttp-dnsoverhttps", version.ref = "squareOkhttp" } square-logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "squareOkhttp" } square-moshi = { group = "com.squareup.moshi", name = "moshi", version.ref = "squareMoshi" } -square-moshi-kotlin = { group = "com.squareup.moshi", name = "moshi-kotlin-codegen", version.ref = "squareMoshi" } - +square-moshi-kotlin = { module = "com.squareup.moshi:moshi-kotlin-codegen", version.ref = "squareMoshi" } dev-rikka-rikkax-parcelablelist = { module = "dev.rikka.rikkax.parcelablelist:parcelablelist", version.ref = "parcelablelist" } - markwon-core = "io.noties.markwon:core:4.6.2" hiddenApiBypass = "org.lsposed.hiddenapibypass:hiddenapibypass:6.1" mmrlx-terminal = { module = "dev.mmrlx:terminal", version.ref = "mmrlx" } @@ -147,7 +145,7 @@ timber = "com.jakewharton.timber:timber:5.0.1" # Dependencies of the included build-logic android-gradle = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" } -compose-gradle= { module = "org.jetbrains.kotlin:compose-compiler-gradle-plugin", version.ref = "kotlin" } +compose-gradle = { module = "org.jetbrains.kotlin:compose-compiler-gradle-plugin", version.ref = "kotlin" } kotlin-gradle = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } ksp-gradle = { group = "com.google.devtools.ksp", name = "com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" } androidx-runtime-android = { group = "androidx.compose.runtime", name = "runtime-android", version.ref = "runtimeAndroid" } @@ -157,7 +155,7 @@ androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "j androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } material = { group = "com.google.android.material", name = "material", version.ref = "material" } androidx-material3-android = { group = "androidx.compose.material3", name = "material3-android", version.ref = "material3Android" } -runtime-android = { group = "androidx.compose.runtime", name = "runtime-android", version.ref = "runtimeAndroidVersion" } +runtime-android = { group = "androidx.compose.runtime", name = "runtime-android", version.ref = "runtimeAndroid" } androidx-swiperefreshlayout = { group = "androidx.swiperefreshlayout", name = "swiperefreshlayout", version.ref = "swiperefreshlayout" } [plugins] @@ -178,4 +176,4 @@ self-library = { id = "self.library", version = "unspecified" } self-compose = { id = "self.compose", version = "unspecified" } self-hilt = { id = "self.hilt", version = "unspecified" } self-room = { id = "self.room", version = "unspecified" } -jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlinVersion" } +jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6510bd8ba..809449e78 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,6 @@ -#Sat Mar 28 13:59:40 CET 2026 +#Fri Oct 09 20:46:13 CEST 2026 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=a17ddd85a26b6a7f5ddb71ff8b05fc5104c0202c6e64782429790c933686c806 -distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 8dfcfb4731227292807068921c034245bfe526f4 Mon Sep 17 00:00:00 2001 From: Der_Googler <54764558+DerGoogler@users.noreply.github.com> Date: Sun, 11 Oct 2026 15:52:37 +0200 Subject: [PATCH 07/24] Handle back press in module description view Add a Compose `BackHandler` so the description WebView first navigates backward in its own history, and only pops the app navigation stack when no web history remains. Also align internal route registrations (`insets.css`, `colors.css`, `assets/*asset`) with relative paths expected by the local router. --- .../moduleView/ViewDescriptionScreen.kt | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/ViewDescriptionScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/ViewDescriptionScreen.kt index 244c3dc76..1f58c4d60 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/ViewDescriptionScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/ViewDescriptionScreen.kt @@ -1,6 +1,7 @@ package com.dergoogler.mmrl.ui.screens.moduleView import android.annotation.SuppressLint +import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets @@ -80,16 +81,17 @@ fun ViewDescriptionScreen(readmeUrl: String) = htmlResponse(connection.getInputStream()) } - internalRoute("/internal/insets.css") { + internalRoute("insets.css") { insets.css.asStyleResponse() } - internalRoute("/internal/colors.css") { + internalRoute("colors.css") { webColors.allCssColors.asStyleResponse() } - internalRoute("/internal/assets/*asset") { - val filePath = request.params["asset"] ?: return@internalRoute notFoundResponse + internalRoute("assets/*asset") { + val filePath = + request.params["asset"] ?: return@internalRoute notFoundResponse try { val inputStream = kontext.assets.open(filePath) @@ -109,6 +111,16 @@ fun ViewDescriptionScreen(readmeUrl: String) = } } + // We have to override otherwise the WebUI will close the entire app + BackHandler { + if (wstate.canGoBack()) { + wstate.goBack() + return@BackHandler + } + + navigator.popBackStack() + } + Scaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { From c41b2079cc1595753e20ce67fa57dbaf70361673 Mon Sep 17 00:00:00 2001 From: Der_Googler <54764558+DerGoogler@users.noreply.github.com> Date: Sun, 11 Oct 2026 17:18:54 +0200 Subject: [PATCH 08/24] Refactor repository screen and toolbar state Introduces a stable `BlurToolbarState` with `rememberBlurToolbarState` to centralize fade/blur alpha behavior and reuse it from search toolbars. The repository UI is consolidated by renaming `TypedModulesList` to `ModulesList`, adding injectable pre-list content, and rebuilding `RepositoryScreen` around that shared list + `BlurSearchToolbar`. Obsolete typed-module/top-picks screens and components were removed, and module author/category links to the deleted typed screen were disabled. --- .../ui/component/toolbar/BlurSearchToolbar.kt | 6 + .../mmrl/ui/component/toolbar/BlurToolbar.kt | 209 +++++--- .../moduleView/items/OtherSourcesItem.kt | 9 +- .../moduleView/sections/AboutModule.kt | 18 +- .../ui/screens/moduleView/sections/Header.kt | 29 +- .../mmrl/ui/screens/modules/ModulesScreen.kt | 6 +- .../TypedModulesList.kt => ModulesList.kt} | 54 +- .../ui/screens/repository/RepositoryScreen.kt | 480 ++++++++---------- .../ui/screens/repository/TopPickModule.kt | 143 ------ .../mmrl/ui/screens/repository/TopPicks.kt | 105 ---- .../repository/modules/TypedModulesScreen.kt | 208 -------- .../mmrl/ui/screens/search/SearchScreen.kt | 2 +- 12 files changed, 410 insertions(+), 859 deletions(-) rename app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/{modules/TypedModulesList.kt => ModulesList.kt} (70%) delete mode 100644 app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/TopPickModule.kt delete mode 100644 app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/TopPicks.kt delete mode 100644 app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/modules/TypedModulesScreen.kt diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/toolbar/BlurSearchToolbar.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/toolbar/BlurSearchToolbar.kt index 454e81dd2..7a5764934 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/toolbar/BlurSearchToolbar.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/toolbar/BlurSearchToolbar.kt @@ -46,7 +46,13 @@ fun BlurSearchToolbar( fadeBackgroundIfNoBlur: Boolean = false, fadeDistance: Float = 200f, fade: Boolean = false, + state: BlurToolbarState = rememberBlurToolbarState( + scrollBehavior = scrollBehavior, + fade = fade, + fadeDistance = fadeDistance, + ), ) = BlurToolbar( + state = state, modifier = modifier, actions = actions, windowInsets = windowInsets, diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/toolbar/BlurToolbar.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/toolbar/BlurToolbar.kt index aa9238b6f..c485133df 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/toolbar/BlurToolbar.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/toolbar/BlurToolbar.kt @@ -10,8 +10,9 @@ import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind @@ -25,106 +26,160 @@ import dev.chrisbanes.haze.hazeEffect import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import dev.chrisbanes.haze.materials.HazeMaterials -@OptIn(ExperimentalHazeMaterialsApi::class) +private const val VISIBILITY_THRESHOLD = 0.01f + +/** + * Read-only view of the toolbar's current visual state. + * All properties are snapshot-backed, so reading them in composition + * (or in a draw/layout lambda) will trigger the right invalidation. + */ +@Stable +class BlurToolbarState internal constructor( + private val animatedAlpha: State, + private val targetAlphaState: State, + private val blurEnabled: State, + private val scrollBehavior: TopAppBarScrollBehavior?, +) { + /** Current (animated) alpha, 0f..1f. */ + val alpha: Float get() = animatedAlpha.value + + /** Un-animated alpha derived from scroll position. */ + val targetAlpha: Float get() = targetAlphaState.value + + /** Blur is enabled in prefs AND supported on this device. */ + val isBlurEnabled: Boolean get() = blurEnabled.value + + /** True if the toolbar is visible enough to draw anything. */ + val isVisible: Boolean get() = alpha > VISIBILITY_THRESHOLD + + /** True if the haze blur effect is actually being applied. */ + val isBlurActive: Boolean get() = isBlurEnabled && isVisible + + val isFullyVisible: Boolean get() = alpha >= 1f - VISIBILITY_THRESHOLD + + /** True while the alpha animation is still catching up with its target. */ + val isAnimating: Boolean get() = alpha != targetAlpha + + /** Raw scroll content offset (<= 0 when scrolled), or 0f without a scroll behavior. */ + val contentOffset: Float get() = scrollBehavior?.state?.contentOffset ?: 0f + + /** 0f = expanded, 1f = fully collapsed. */ + val collapsedFraction: Float get() = scrollBehavior?.state?.collapsedFraction ?: 0f + + /** 0f..1f, how much content overlaps the toolbar. */ + val overlappedFraction: Float get() = scrollBehavior?.state?.overlappedFraction ?: 0f +} + @Composable -fun BlurToolbar( - title: @Composable (Float) -> Unit, - modifier: Modifier = Modifier, - navigationIcon: @Composable () -> Unit = {}, - actions: @Composable RowScope.() -> Unit = {}, - expandedHeight: Dp = TopAppBarDefaults.TopAppBarExpandedHeight, - windowInsets: WindowInsets = TopAppBarDefaults.windowInsets, - fadeBackgroundIfNoBlur: Boolean = false, - fadeDistance: Float = 200f, - fade: Boolean = false, +fun rememberBlurToolbarState( scrollBehavior: TopAppBarScrollBehavior? = null, -) { + fade: Boolean = false, + fadeDistance: Float = 200f, +): BlurToolbarState { val prefs = LocalUserPreferences.current - val state = scrollBehavior?.state + val scrollState = scrollBehavior?.state - val isBlurEnabled = - remember(prefs) { - prefs.enableBlur && BlurUtil.isBlurSupported() - } + val blurEnabled = remember(prefs) { + derivedStateOf { prefs.enableBlur && BlurUtil.isBlurSupported() } + } - val targetAlpha by remember { + val targetAlpha = remember(scrollState, fade, fadeDistance) { derivedStateOf { - if (!fade || state == null) { + if (!fade || scrollState == null || fadeDistance <= 0f) { 1f } else { - ((-state.contentOffset) / fadeDistance).coerceIn(0f, 1f) + (-scrollState.contentOffset / fadeDistance).coerceIn(0f, 1f) } } } - val animatedAlpha by animateFloatAsState( - targetValue = targetAlpha, - animationSpec = - tween( - durationMillis = 10, - easing = FastOutSlowInEasing, - ), + val animatedAlpha = animateFloatAsState( + targetValue = targetAlpha.value, + animationSpec = tween(durationMillis = 150, easing = FastOutSlowInEasing), label = "toolbar_alpha", ) + return remember(scrollBehavior, blurEnabled, targetAlpha, animatedAlpha) { + BlurToolbarState( + animatedAlpha = animatedAlpha, + targetAlphaState = targetAlpha, + blurEnabled = blurEnabled, + scrollBehavior = scrollBehavior, + ) + } +} + +@OptIn(ExperimentalHazeMaterialsApi::class) +@Composable +fun BlurToolbar( + title: @Composable (Float) -> Unit, + modifier: Modifier = Modifier, + navigationIcon: @Composable () -> Unit = {}, + actions: @Composable RowScope.() -> Unit = {}, + expandedHeight: Dp = TopAppBarDefaults.TopAppBarExpandedHeight, + windowInsets: WindowInsets = TopAppBarDefaults.windowInsets, + fadeBackgroundIfNoBlur: Boolean = false, + fadeDistance: Float = 200f, + fade: Boolean = false, + scrollBehavior: TopAppBarScrollBehavior? = null, + state: BlurToolbarState = rememberBlurToolbarState( + scrollBehavior = scrollBehavior, + fade = fade, + fadeDistance = fadeDistance, + ), +) { val borderColor = MaterialTheme.colorScheme.outline val backgroundColor = MaterialTheme.colorScheme.background - val topAppBarColors = TopAppBarDefaults.topAppBarColors() - val colors = - if (isBlurEnabled) { - topAppBarColors.copy( - scrolledContainerColor = Color.Transparent, - containerColor = Color.Transparent, - ) + val colors = if (state.isBlurEnabled) { + topAppBarColors.copy( + scrolledContainerColor = Color.Transparent, + containerColor = Color.Transparent, + ) + } else { + val container = if (fadeBackgroundIfNoBlur) { + backgroundColor.copy(alpha = state.alpha) } else { - val backgroundColor = - MaterialTheme.colorScheme.background - .let { - if (fadeBackgroundIfNoBlur) { - it.copy(alpha = animatedAlpha) - } else { - it - } - } - - topAppBarColors.copy( - scrolledContainerColor = backgroundColor, - containerColor = backgroundColor, - ) + backgroundColor } + topAppBarColors.copy( + scrolledContainerColor = container, + containerColor = container, + ) + } - val blur = - if (isBlurEnabled && animatedAlpha > 0.01f) { - Modifier.hazeEffect( - state = LocalHazeState.current, - style = HazeMaterials.ultraThin(), - ) { - this@hazeEffect.backgroundColor = backgroundColor - this@hazeEffect.alpha = animatedAlpha - } - } else { - Modifier + val blur = if (state.isBlurActive) { + Modifier.hazeEffect( + state = LocalHazeState.current, + style = HazeMaterials.ultraThin(), + ) { + this.backgroundColor = backgroundColor + this.alpha = state.alpha } + } else { + Modifier + } TopAppBar( - title = { title(animatedAlpha) }, - modifier = - Modifier - .drawBehind { - if (animatedAlpha > 0.01f) { - val borderSize = Dp.Hairline - val y = size.height - borderSize.value - drawLine( - color = borderColor.copy(alpha = animatedAlpha), - start = Offset(0f, y), - end = Offset(size.width, y), - strokeWidth = borderSize.value, - ) - } - }.then(blur) - .then(modifier), + title = { title(state.alpha) }, + modifier = Modifier + .drawBehind { + // Read inside the draw scope so alpha changes only redraw + val alpha = state.alpha + if (alpha > VISIBILITY_THRESHOLD) { + val borderSize = Dp.Hairline + val y = size.height - borderSize.value + drawLine( + color = borderColor.copy(alpha = alpha), + start = Offset(0f, y), + end = Offset(size.width, y), + strokeWidth = borderSize.value, + ) + } + } + .then(blur) + .then(modifier), navigationIcon = navigationIcon, actions = actions, colors = colors, @@ -132,4 +187,4 @@ fun BlurToolbar( windowInsets = windowInsets, expandedHeight = expandedHeight, ) -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/items/OtherSourcesItem.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/items/OtherSourcesItem.kt index 0d861b49c..e32115c14 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/items/OtherSourcesItem.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/items/OtherSourcesItem.kt @@ -13,12 +13,15 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.dergoogler.mmrl.model.online.OtherSources +import com.dergoogler.mmrl.ui.providable.LocalDestinationsNavigator import com.dergoogler.mmrl.ui.providable.LocalOnlineModule import com.dergoogler.mmrl.ui.providable.LocalOnlineModuleState import com.dergoogler.mmrl.ui.screens.repository.ModuleItemCompact +import com.ramcosta.composedestinations.generated.destinations.NewViewScreenDestination @Composable fun OtherSourcesItem(items: List) { + val navigator = LocalDestinationsNavigator.current val pagerState = rememberPagerState(pageCount = { (items.size + 2) / 3 }) HorizontalPager( @@ -49,7 +52,11 @@ fun OtherSourcesItem(items: List) { sourceProvider = item.repo.name, showLabels = false, showLastUpdated = false, - onClick = {}, + onClick = { + navigator.navigate( + NewViewScreenDestination(item.repo, item.online) + ) + }, ) } } diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/AboutModule.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/AboutModule.kt index f5ecbc3b4..49aa14686 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/AboutModule.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/AboutModule.kt @@ -29,8 +29,6 @@ import com.dergoogler.mmrl.ui.providable.LocalDestinationsNavigator import com.dergoogler.mmrl.ui.providable.LocalOnlineModule import com.dergoogler.mmrl.ui.providable.LocalRepo import com.dergoogler.mmrl.ui.screens.moduleView.listItemContentPaddingValues -import com.dergoogler.mmrl.ui.screens.repository.modules.ModulesFilter -import com.ramcosta.composedestinations.generated.destinations.TypedModulesScreenDestination import com.ramcosta.composedestinations.generated.destinations.ViewDescriptionScreenDestination @Composable @@ -93,14 +91,14 @@ internal fun AboutModule() { items(it.size) { category -> AssistChip( onClick = { - navigator.navigate( - TypedModulesScreenDestination( - type = ModulesFilter.CATEGORY, - title = it[category], - query = it[category], - repo = repo, - ), - ) +// navigator.navigate( +// TypedModulesScreenDestination( +// type = ModulesFilter.CATEGORY, +// title = it[category], +// query = it[category], +// repo = repo, +// ), +// ) }, label = { Text(it[category]) }, ) diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Header.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Header.kt index f4739fe84..dea019632 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Header.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Header.kt @@ -1,6 +1,5 @@ package com.dergoogler.mmrl.ui.screens.moduleView.sections -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -44,8 +43,6 @@ import com.dergoogler.mmrl.ui.providable.LocalRepo import com.dergoogler.mmrl.ui.providable.LocalUserPreferences import com.dergoogler.mmrl.ui.providable.LocalVersionItem import com.dergoogler.mmrl.ui.screens.moduleView.providable.LocalModuleViewModel -import com.dergoogler.mmrl.ui.screens.repository.modules.ModulesFilter -import com.ramcosta.composedestinations.generated.destinations.TypedModulesScreenDestination @Composable internal fun Header() { @@ -104,19 +101,19 @@ internal fun Header() { Spacer(modifier = Modifier.height(4.dp)) Text( - modifier = - Modifier.clickable( - onClick = { - navigator.navigate( - TypedModulesScreenDestination( - type = ModulesFilter.AUTHOR, - title = module.author, - query = module.author, - repo = repo, - ), - ) - }, - ), +// modifier = +// Modifier.clickable( +// onClick = { +// navigator.navigate( +// TypedModulesScreenDestination( +// type = ModulesFilter.AUTHOR, +// title = module.author, +// query = module.author, +// repo = repo, +// ), +// ) +// }, +// ), text = module.author, style = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.surfaceTint), maxLines = 2, diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/modules/ModulesScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/modules/ModulesScreen.kt index f2957e11f..c7a0fd250 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/modules/ModulesScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/modules/ModulesScreen.kt @@ -188,11 +188,8 @@ private fun TopBar( } BlurSearchToolbar( - scrollBehavior = scrollBehavior, isSearch = isSearch, query = currentQuery, - fade = true, - fadeDistance = 50f, onQueryChange = { onQueryChange(it) currentQuery = it @@ -222,6 +219,9 @@ private fun TopBar( setMenu = setMenu, ) }, + scrollBehavior = scrollBehavior, + fadeDistance = 50f, + fade = true, ) } diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/modules/TypedModulesList.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/ModulesList.kt similarity index 70% rename from app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/modules/TypedModulesList.kt rename to app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/ModulesList.kt index c0d5b53f9..de0b73074 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/modules/TypedModulesList.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/ModulesList.kt @@ -1,4 +1,4 @@ -package com.dergoogler.mmrl.ui.screens.repository.modules +package com.dergoogler.mmrl.ui.screens.repository import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -9,12 +9,12 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.dp @@ -23,15 +23,12 @@ import com.dergoogler.mmrl.datastore.model.RepoListMode import com.dergoogler.mmrl.model.online.OnlineModule import com.dergoogler.mmrl.model.state.OnlineState import com.dergoogler.mmrl.ui.component.scaffold.ScaffoldScope -import com.dergoogler.mmrl.ui.component.scrollbar.VerticalFastScrollbar import com.dergoogler.mmrl.ui.providable.LocalDestinationsNavigator import com.dergoogler.mmrl.ui.providable.LocalHazeState import com.dergoogler.mmrl.ui.providable.LocalMainScreenInnerPaddings import com.dergoogler.mmrl.ui.providable.LocalOnlineModule import com.dergoogler.mmrl.ui.providable.LocalOnlineModuleState import com.dergoogler.mmrl.ui.providable.LocalUserPreferences -import com.dergoogler.mmrl.ui.screens.repository.ModuleItemCompact -import com.dergoogler.mmrl.ui.screens.repository.ModuleItemDetailed import com.ramcosta.composedestinations.generated.destinations.NewViewScreenDestination import dev.chrisbanes.haze.hazeSource @@ -41,6 +38,7 @@ fun ScaffoldScope.TypedModulesList( repo: Repo, list: List>, state: LazyListState, + beforeContent: LazyListScope.() -> Unit = {}, ) { val navigator = LocalDestinationsNavigator.current val userPreferences = LocalUserPreferences.current @@ -57,16 +55,18 @@ fun ScaffoldScope.TypedModulesList( this@TypedModulesList.ResponsiveContent { LazyColumn( state = state, - modifier = Modifier.fillMaxSize().hazeSource(LocalHazeState.current), + modifier = Modifier + .fillMaxSize() + .hazeSource(LocalHazeState.current), contentPadding = PaddingValues( - top = innerPadding.calculateTopPadding() + pad, + // top = innerPadding.calculateTopPadding(), bottom = pad, - start = innerPadding.calculateStartPadding(layoutDirection) + pad, - end = pad, ), verticalArrangement = Arrangement.spacedBy(16.dp), ) { + beforeContent() + items( items = list, key = { it.second.id }, @@ -81,16 +81,23 @@ fun ScaffoldScope.TypedModulesList( ) } - when (menu.repoListMode) { - RepoListMode.Compact -> - ModuleItemCompact( - onClick = click, - ) + Box( + modifier = Modifier.padding( + start = innerPadding.calculateStartPadding(layoutDirection) + pad, + end = pad, + ) + ) { + when (menu.repoListMode) { + RepoListMode.Compact -> + ModuleItemCompact( + onClick = click, + ) - RepoListMode.Detailed -> - ModuleItemDetailed( - onClick = click, - ) + RepoListMode.Detailed -> + ModuleItemDetailed( + onClick = click, + ) + } } } } @@ -100,16 +107,5 @@ fun ScaffoldScope.TypedModulesList( } } } - - VerticalFastScrollbar( - state = state, - modifier = - Modifier - .align(Alignment.CenterEnd) - .padding( - top = innerPadding.calculateTopPadding(), - bottom = paddingValues.calculateBottomPadding(), - ), - ) } } diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryScreen.kt index 4903fa61a..ed31c32a7 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryScreen.kt @@ -9,32 +9,31 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalDensity @@ -50,318 +49,267 @@ import androidx.core.net.toUri import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.dergoogler.mmrl.R import com.dergoogler.mmrl.database.entity.Repo +import com.dergoogler.mmrl.datastore.model.RepositoryMenu import com.dergoogler.mmrl.ext.fadingEdge import com.dergoogler.mmrl.ext.isNotNullOrBlank import com.dergoogler.mmrl.ext.none import com.dergoogler.mmrl.ext.nullply import com.dergoogler.mmrl.ext.onClick -import com.dergoogler.mmrl.ext.rememberSaveableLazyListState import com.dergoogler.mmrl.ext.stripLinks -import com.dergoogler.mmrl.model.ui.TopCategory import com.dergoogler.mmrl.ui.component.Cover import com.dergoogler.mmrl.ui.component.LabelItem import com.dergoogler.mmrl.ui.component.LabelItemDefaults -import com.dergoogler.mmrl.ui.component.Loading import com.dergoogler.mmrl.ui.component.LocalScreenProvider import com.dergoogler.mmrl.ui.component.PageIndicator import com.dergoogler.mmrl.ui.component.card.Card import com.dergoogler.mmrl.ui.component.scaffold.Scaffold import com.dergoogler.mmrl.ui.component.text.BBCodeText -import com.dergoogler.mmrl.ui.component.toolbar.BlurToolbar +import com.dergoogler.mmrl.ui.component.toolbar.BlurSearchToolbar import com.dergoogler.mmrl.ui.component.toolbar.ToolbarTitle -import com.dergoogler.mmrl.ui.providable.LocalDestinationsNavigator -import com.dergoogler.mmrl.ui.providable.LocalHazeState -import com.dergoogler.mmrl.ui.providable.LocalMainScreenInnerPaddings -import com.dergoogler.mmrl.ui.providable.LocalRepo -import com.dergoogler.mmrl.ui.screens.repository.modules.ModulesFilter +import com.dergoogler.mmrl.ui.component.toolbar.rememberBlurToolbarState import com.dergoogler.mmrl.viewmodel.RepositoryViewModel import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph -import com.ramcosta.composedestinations.generated.destinations.TypedModulesScreenDestination -import dev.chrisbanes.haze.hazeSource import dev.dergoogler.mmrl.compat.core.LocalUriHandler -@Destination() +@Destination @Composable -fun RepositoryScreen(repo: Repo) = - LocalScreenProvider { - val viewModel = RepositoryViewModel.build(repo) - val list by viewModel.online.collectAsStateWithLifecycle() +fun RepositoryScreen( + repo: Repo, +) = LocalScreenProvider { + val browser = LocalUriHandler.current + val density = LocalDensity.current - val browser = LocalUriHandler.current - val density = LocalDensity.current - val navigator = LocalDestinationsNavigator.current - val hazeState = LocalHazeState.current + val viewModel = RepositoryViewModel.build(repo) + val modules by viewModel.online.collectAsStateWithLifecycle() - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() + val query by viewModel.query.collectAsStateWithLifecycle() + val openSearch = viewModel::openSearch + val closeSearch = viewModel::closeSearch + val search = viewModel::search + val isSearch by viewModel::isSearch + val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() - // Use rememberSaveable to persist scroll position across navigation - val listState = rememberSaveableLazyListState(key = "repository_${repo.url}") + var coverHeight by remember { mutableIntStateOf(0) } + var cardHeight by remember { mutableIntStateOf(0) } - BackHandler( - enabled = viewModel.isSearch, - onBack = viewModel::closeSearch, - ) + BackHandler(enabled = viewModel.isSearch, onBack = viewModel::closeSearch) - val modules = remember(list) { list.map { it.second } } - val topCategories = remember(modules) { TopCategory.fromModuleList(modules) } - val metaModules = remember(modules) { - modules.filter { - it.permissions?.contains("kernelsu.permission.METAMODULE") == true - } - } + Scaffold( + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + topBar = { + TopBar( + repo = repo, + isSearch = isSearch, + query = query, + onQueryChange = search, + onOpenSearch = openSearch, + onCloseSearch = closeSearch, + scrollBehavior = scrollBehavior, + setMenu = viewModel::setRepositoryMenu, + ) + }, + contentWindowInsets = WindowInsets.none, + ) { innerPadding -> + if (modules.isEmpty()) { + PageIndicator( + icon = R.drawable.cloud, + text = if (isSearch) R.string.search_empty else R.string.repository_empty, + ) - var coverHeight by remember { mutableIntStateOf(0) } - var cardHeight by remember { mutableIntStateOf(0) } + return@Scaffold + } - CompositionLocalProvider( - LocalRepo provides repo, + this@Scaffold.TypedModulesList( + innerPadding = innerPadding, + repo = repo, + list = modules, + state = viewModel.listState, ) { - Scaffold( - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), - topBar = { - BlurToolbar( - fadeBackgroundIfNoBlur = true, - navigationIcon = { - IconButton(onClick = { navigator.popBackStack() }) { - Icon( - painter = painterResource(id = R.drawable.arrow_left), - contentDescription = null, + item { + Box { + Cover( + modifier = + Modifier + .fadingEdge( + brush = + Brush.verticalGradient( + colors = listOf( + Color.Transparent, + Color.Black + ), + startY = Float.POSITIVE_INFINITY, + endY = 0f, + ), ) - } - }, - title = { - ToolbarTitle( - modifier = Modifier.alpha(it), - title = repo.name, - ) - }, - fade = true, - scrollBehavior = scrollBehavior, + .onGloballyPositioned { coordinates -> + coverHeight = coordinates.size.height + }, + url = repo.cover ?: "ERROR ME", + errorIcon = R.drawable.box, ) - }, - contentWindowInsets = WindowInsets.none, - ) { _ -> - ResponsiveContent { - if (viewModel.isLoading) { - Loading() - - return@ResponsiveContent - } - - if (list.isEmpty() && !viewModel.isLoading) { - PageIndicator( - icon = R.drawable.cloud, - text = if (viewModel.isSearch) R.string.search_empty else R.string.repository_empty, - ) - return@ResponsiveContent - } - - LazyColumn( - state = listState, + Box( modifier = Modifier - .fillMaxSize() - .hazeSource(state = hazeState), - verticalArrangement = Arrangement.spacedBy(16.dp), + .offset(y = 164.4.dp) + .align(Alignment.TopCenter) + .fillMaxWidth(0.85f) + .onGloballyPositioned { coordinates -> + cardHeight = coordinates.size.height + }, ) { - item { - Box { - Cover( - modifier = - Modifier - .fadingEdge( - brush = - Brush.verticalGradient( - colors = listOf( - Color.Transparent, - Color.Black + Card( + modifier = + Modifier + .align(Alignment.BottomCenter) + .background( + brush = + Brush.linearGradient( + colors = + listOf( + MaterialTheme.colorScheme.surfaceColorAtElevation( + 1.dp, ), - startY = Float.POSITIVE_INFINITY, - endY = 0f, + MaterialTheme.colorScheme.background, ), - ) - .onGloballyPositioned { coordinates -> - coverHeight = coordinates.size.height - }, - url = repo.cover ?: "ERROR ME", - errorIcon = R.drawable.box, + start = Offset(0f, 0f), + end = + Offset( + Float.POSITIVE_INFINITY, + Float.POSITIVE_INFINITY, + ), + ), + RoundedCornerShape(20.dp), + ) + .border( + Dp.Hairline, + MaterialTheme.colorScheme.outlineVariant, + RoundedCornerShape(20.dp), + ), + ) { + Column( + modifier = + Modifier + .relative() + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + Text( + text = repo.name, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.headlineMedium, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onBackground, ) - Box( - modifier = - Modifier - .offset(y = 164.4.dp) - .align(Alignment.TopCenter) - .fillMaxWidth(0.85f) - .onGloballyPositioned { coordinates -> - cardHeight = coordinates.size.height - }, - ) { - Card( - modifier = - Modifier - .align(Alignment.BottomCenter) - .background( - brush = - Brush.linearGradient( - colors = - listOf( - MaterialTheme.colorScheme.surfaceColorAtElevation( - 1.dp, - ), - MaterialTheme.colorScheme.background, - ), - start = Offset(0f, 0f), - end = - Offset( - Float.POSITIVE_INFINITY, - Float.POSITIVE_INFINITY, - ), - ), - RoundedCornerShape(20.dp), - ) - .border( - Dp.Hairline, - MaterialTheme.colorScheme.outlineVariant, - RoundedCornerShape(20.dp), - ), - ) { - Column( - modifier = - Modifier - .relative() - .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(16.dp), - ) { - Text( - text = repo.name, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - textAlign = TextAlign.Center, - style = MaterialTheme.typography.headlineMedium, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.onBackground, - ) + BBCodeText( + text = + ( + repo.description + ?: stringResource(R.string.view_module_no_description) + ).stripLinks( + "", + ), + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.outlineVariant, + lineHeight = 20.sp, + ) - BBCodeText( - text = - ( - repo.description - ?: stringResource(R.string.view_module_no_description) - ).stripLinks( - "", - ), - style = MaterialTheme.typography.bodyMedium, - textAlign = TextAlign.Center, - color = MaterialTheme.colorScheme.outlineVariant, - lineHeight = 20.sp, + FlowRow { + repo.nullply { + if (submission.isNotNullOrBlank()) { + LabelItem( + style = + LabelItemDefaults.style.copy( + containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, + contentColor = MaterialTheme.colorScheme.onSurfaceVariant, + ), + modifier = + Modifier.onClick { + browser.openUri(submission) + }, + icon = getDomainIcon(submission), + text = stringResource(R.string.repo_options_submission), ) - - FlowRow { - repo.nullply { - if (submission.isNotNullOrBlank()) { - LabelItem( - style = - LabelItemDefaults.style.copy( - containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, - contentColor = MaterialTheme.colorScheme.onSurfaceVariant, - ), - modifier = - Modifier.onClick { - browser.openUri(submission) - }, - icon = getDomainIcon(submission), - text = stringResource(R.string.repo_options_submission), - ) - } - } - } } } } - - with(density) { - Spacer(modifier = Modifier.height(((coverHeight / 1.2f) + cardHeight).toDp())) - } - } - } - - item { - val takenModules = remember { modules.take(9) } - val title = stringResource(R.string.page_modules) - - TopPicks( - label = title, - list = takenModules, - onMoreClick = { - navigator.navigate( - TypedModulesScreenDestination( - type = ModulesFilter.ALL, - title = title, - repo = repo, - ), - ) - }, - ) - } - - if (!metaModules.isEmpty() && viewModel.platform.isKernelSuVariant) { - item { - val title = stringResource(R.string.meta_modules) - - TopPicks( - label = title, - icon = com.dergoogler.mmrl.ui.R.drawable.kernelsu_logo, - list = metaModules, - onMoreClick = { - navigator.navigate( - TypedModulesScreenDestination( - type = ModulesFilter.ALL, - title = title, - repo = repo, - ), - ) - }, - ) } } + } - items( - items = topCategories, - key = { it.label }, - ) { - TopPicks( - label = it.label, - list = it.modules, - onMoreClick = { - navigator.navigate( - TypedModulesScreenDestination( - type = ModulesFilter.CATEGORY, - title = it.label, - repo = repo, - query = it.label, - ), - ) - }, - ) - } - - item { - Spacer( - Modifier.height(16.dp), - ) - - val paddingValues = LocalMainScreenInnerPaddings.current - Spacer(modifier = Modifier.height(paddingValues.calculateBottomPadding())) - } + with(density) { + Spacer(modifier = Modifier.height((((coverHeight / 1.05f) + cardHeight)).toDp())) } } } } } +} + +@Composable +private fun TopBar( + repo: Repo, + isSearch: Boolean, + query: String, + onQueryChange: (String) -> Unit, + onOpenSearch: () -> Unit, + onCloseSearch: () -> Unit, + scrollBehavior: TopAppBarScrollBehavior, + setMenu: (RepositoryMenu) -> Unit, +) { + val state = rememberBlurToolbarState(scrollBehavior, fade = true) + + var currentQuery by remember { mutableStateOf(query) } + DisposableEffect(isSearch) { + onDispose { currentQuery = "" } + } + + BlurSearchToolbar( + state = state, + fadeBackgroundIfNoBlur = true, + isSearch = isSearch, + query = currentQuery, + scrollBehavior = scrollBehavior, + fade = true, + fadeDistance = 200f, + onQueryChange = { + onQueryChange(it) + currentQuery = it + }, + onClose = { + onCloseSearch() + currentQuery = "" + }, + title = { + ToolbarTitle( + title = repo.name, + modifier = Modifier.graphicsLayer { alpha = state.alpha }, + ) + }, + actions = { + if (!isSearch) { + IconButton( + onClick = onOpenSearch, + ) { + Icon( + painter = painterResource(id = R.drawable.search), + contentDescription = null, + ) + } + } + + RepositoryMenu( + setMenu = setMenu, + ) + }, + ) +} @Composable fun getDomainIcon(url: String) = @@ -378,4 +326,4 @@ fun getDomain(url: String): String = if (host.startsWith("www.")) host.substring(4) else host } catch (_: Exception) { "" - } + } \ No newline at end of file diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/TopPickModule.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/TopPickModule.kt deleted file mode 100644 index 0171099bd..000000000 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/TopPickModule.kt +++ /dev/null @@ -1,143 +0,0 @@ -package com.dergoogler.mmrl.ui.screens.repository - -import androidx.compose.foundation.border -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Icon -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.layout.ContentScale -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import coil.compose.AsyncImage -import com.dergoogler.mmrl.R -import com.dergoogler.mmrl.ext.iconSize -import com.dergoogler.mmrl.ui.component.Logo -import com.dergoogler.mmrl.ui.component.card.Card -import com.dergoogler.mmrl.ui.providable.LocalDestinationsNavigator -import com.dergoogler.mmrl.ui.providable.LocalOnlineModule -import com.dergoogler.mmrl.ui.providable.LocalRepo -import com.dergoogler.mmrl.ui.providable.LocalUserPreferences -import com.ramcosta.composedestinations.generated.destinations.NewViewScreenDestination - -@Composable -fun TopPickModule( - modifier: Modifier = Modifier, - enabled: Boolean = true, -) { - val navigator = LocalDestinationsNavigator.current - val module = LocalOnlineModule.current - val repo = LocalRepo.current - val userPreferences = LocalUserPreferences.current - val menu = userPreferences.repositoryMenu - val isVerified = module.isVerified && menu.showVerified - - Card( - onClick = { - navigator.navigate(NewViewScreenDestination(repo, module)) - }, - modifier = - modifier - .fillMaxWidth() - .border( - width = Dp.Hairline, - color = MaterialTheme.colorScheme.outlineVariant, - shape = RoundedCornerShape(20.dp), - ), - enabled = enabled, - shape = RoundedCornerShape(20.dp), - ) { - Row( - modifier = - Modifier - .padding(all = 16.dp) - .relative(), - verticalAlignment = Alignment.CenterVertically, - ) { - Logo( - contentColor = MaterialTheme.colorScheme.onSecondaryContainer, - containerColor = MaterialTheme.colorScheme.secondaryContainer, - modifier = Modifier.size(64.dp), - shape = RoundedCornerShape(20), - icon = { - if (module.icon != null) { - AsyncImage( - model = module.icon, - contentScale = ContentScale.Crop, - contentDescription = null, - ) - } else { - Icon( - modifier = it, - painter = painterResource(id = R.drawable.box), - contentDescription = null, - ) - } - }, - ) - - Spacer(modifier = Modifier.width(10.dp)) - - Column( - modifier = Modifier.weight(1f), - ) { - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(4.dp), - ) { - val style = - MaterialTheme.typography.titleSmall.copy( - fontWeight = FontWeight.Bold, - ) - - Text( - text = module.name, - style = style, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - - if (isVerified) { - Icon( - modifier = Modifier.iconSize(LocalDensity.current, style, 1.0f), - painter = painterResource(id = R.drawable.rosette_discount_check), - contentDescription = null, - tint = MaterialTheme.colorScheme.surfaceTint, - ) - } - } - - Spacer(modifier = Modifier.height(2.dp)) - - Text( - text = module.author, - style = MaterialTheme.typography.bodyMedium.copy(), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - Text( - maxLines = 1, - text = module.versionDisplay, - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.outline, - ) - } - } - } -} diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/TopPicks.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/TopPicks.kt deleted file mode 100644 index b8daa2e20..000000000 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/TopPicks.kt +++ /dev/null @@ -1,105 +0,0 @@ -package com.dergoogler.mmrl.ui.screens.repository - -import androidx.annotation.DrawableRes -import androidx.compose.animation.animateContentSize -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.pager.HorizontalPager -import androidx.compose.foundation.pager.rememberPagerState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.unit.dp -import com.dergoogler.mmrl.R -import com.dergoogler.mmrl.ext.nullable -import com.dergoogler.mmrl.model.online.OnlineModule -import com.dergoogler.mmrl.ui.component.listItem.dsl.List -import com.dergoogler.mmrl.ui.component.listItem.dsl.ListItemSlot -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.ButtonItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Icon -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Title -import com.dergoogler.mmrl.ui.providable.LocalOnlineModule - -@Composable -fun TopPicks( - label: String, - @DrawableRes icon: Int? = null, - onMoreClick: () -> Unit, - list: List, -) { - val randomModules = - remember(list) { - list - .shuffled() - .sortedBy { if (it == OnlineModule.example()) 1 else 0 } - } - - val pagerState = - rememberPagerState(pageCount = { (randomModules.size + 2) / 3 }) - - List { - ButtonItem( - onClick = onMoreClick, - ) { - icon.nullable { - Icon( - slot = ListItemSlot.Start, - painter = painterResource(it), - ) - } - Title(label) - Icon( - slot = ListItemSlot.End, - painter = painterResource(R.drawable.arrow_right), - ) - } - - Spacer(Modifier.height(8.dp)) - - HorizontalPager( - state = pagerState, - modifier = - Modifier - .fillMaxWidth() - .animateContentSize(), - contentPadding = PaddingValues(horizontal = 16.dp), - ) { page -> - Column( - modifier = - Modifier - .fillMaxWidth() - .padding(horizontal = 6.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - for (i in 0 until 3) { - val itemIndex = page * 3 + i - if (itemIndex < randomModules.size) { - val item = randomModules[itemIndex] - - CompositionLocalProvider( - LocalOnlineModule provides item, - ) { - if (item.id == "##==online_example==##") { - Spacer(Modifier.height(96.dp)) - return@CompositionLocalProvider - } - - TopPickModule( - modifier = Modifier.height(96.dp), - ) - } - } - } - } - } - } -} diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/modules/TypedModulesScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/modules/TypedModulesScreen.kt deleted file mode 100644 index 03b9d0972..000000000 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/modules/TypedModulesScreen.kt +++ /dev/null @@ -1,208 +0,0 @@ -package com.dergoogler.mmrl.ui.screens.repository.modules - -import androidx.activity.compose.BackHandler -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.res.painterResource -import com.dergoogler.mmrl.R -import com.dergoogler.mmrl.database.entity.Repo -import com.dergoogler.mmrl.datastore.model.RepositoryMenu -import com.dergoogler.mmrl.ext.isNotNullOrBlank -import com.dergoogler.mmrl.ext.none -import com.dergoogler.mmrl.ext.rememberSaveableLazyListState -import com.dergoogler.mmrl.ui.component.LocalScreenProvider -import com.dergoogler.mmrl.ui.component.PageIndicator -import com.dergoogler.mmrl.ui.component.scaffold.Scaffold -import com.dergoogler.mmrl.ui.component.toolbar.BlurSearchToolbar -import com.dergoogler.mmrl.ui.component.toolbar.ToolbarTitle -import com.dergoogler.mmrl.ui.remember.rememberOnlineModules -import com.dergoogler.mmrl.ui.remember.rememberUserPreferencesRepository -import com.dergoogler.mmrl.ui.screens.repository.RepositoryMenu -import com.ramcosta.composedestinations.annotation.Destination -import com.ramcosta.composedestinations.annotation.RootGraph -import kotlinx.coroutines.launch -import kotlinx.serialization.Serializable - -@Serializable -enum class ModulesFilter { - ALL, - AUTHOR, - CATEGORY, - NAME, -} - -@Destination -@Composable -fun TypedModulesScreen( - title: String? = null, - type: ModulesFilter = ModulesFilter.ALL, - query: String = "", - repo: Repo, -) = LocalScreenProvider { - var searchQuery by remember { mutableStateOf("") } - val modules by rememberOnlineModules(repo, searchQuery) - val userPreferencesRepository = rememberUserPreferencesRepository() - val scope = rememberCoroutineScope() - var isSearch by remember { mutableStateOf(false) } - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() - // Use rememberSaveable to persist scroll position across navigation - val listState = rememberSaveableLazyListState(key = "typed_modules_${repo.url}_${type}_${query}") - - val filterQuery = remember { query } - - val filteredModules = - remember(modules, filterQuery, type, searchQuery) { - modules.filter { (_, m) -> - val typeMatches = - when (type) { - ModulesFilter.ALL -> true - ModulesFilter.AUTHOR -> m.author.equals(filterQuery, ignoreCase = true) - ModulesFilter.CATEGORY -> - m.categories?.any { - it.equals( - filterQuery, - ignoreCase = true, - ) - } ?: false - - ModulesFilter.NAME -> m.name.equals(filterQuery, ignoreCase = true) - } - - val searchMatches = - if (searchQuery.isBlank()) { - true - } else { - m.name.contains(searchQuery, ignoreCase = true) || - m.author.contains(searchQuery, ignoreCase = true) || - (m.categories?.any { it.contains(searchQuery, ignoreCase = true) } ?: false) - } - - typeMatches && searchMatches - } - } - - val openSearch = fun() { - isSearch = true - } - - val closeSearch = fun() { - isSearch = false - searchQuery = "" - } - - val search = fun(data: String) { - searchQuery = data - } - - val setRepositoryMenu = fun(value: RepositoryMenu) { - scope.launch { - userPreferencesRepository.setRepositoryMenu(value) - } - } - - BackHandler(enabled = isSearch, onBack = closeSearch) - - Scaffold( - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), - topBar = { - TopBar( - title = title, - repo = repo, - isSearch = isSearch, - query = searchQuery, - onQueryChange = search, - onOpenSearch = openSearch, - onCloseSearch = closeSearch, - setMenu = setRepositoryMenu, - ) - }, - contentWindowInsets = WindowInsets.none, - ) { innerPadding -> - if (filteredModules.isEmpty()) { - PageIndicator( - icon = R.drawable.cloud, - text = if (isSearch) R.string.search_empty else R.string.repository_empty, - ) - } - - this@Scaffold.TypedModulesList( - innerPadding = innerPadding, - repo = repo, - list = filteredModules, - state = listState, - ) - } -} - -@Composable -private fun TopBar( - title: String?, - repo: Repo, - isSearch: Boolean, - query: String, - onQueryChange: (String) -> Unit, - onOpenSearch: () -> Unit, - onCloseSearch: () -> Unit, - setMenu: (RepositoryMenu) -> Unit, -) { - var currentQuery by remember { mutableStateOf(query) } - DisposableEffect(isSearch) { - onDispose { currentQuery = "" } - } - - BlurSearchToolbar( - isSearch = isSearch, - query = currentQuery, - fade = true, - fadeDistance = 50f, - onQueryChange = { - onQueryChange(it) - currentQuery = it - }, - onClose = { - onCloseSearch() - currentQuery = "" - }, - title = { - if (title.isNotNullOrBlank()) { - ToolbarTitle( - title = title, - subtitle = repo.name, - ) - - return@BlurSearchToolbar - } - - ToolbarTitle( - title = repo.name, - ) - }, - actions = { - if (!isSearch) { - IconButton( - onClick = onOpenSearch, - ) { - Icon( - painter = painterResource(id = R.drawable.search), - contentDescription = null, - ) - } - } - - RepositoryMenu( - setMenu = setMenu, - ) - }, - ) -} diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/search/SearchScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/search/SearchScreen.kt index e58f029d1..88c19d370 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/search/SearchScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/search/SearchScreen.kt @@ -35,8 +35,8 @@ fun SearchScreen() = topBar = { BlurSearchToolbar( isSearch = true, - query = query, autoFocus = false, + query = query, onQueryChange = viewModel::search, title = {}, ) From f48c02ae8fb1b802cf0313eb47314da344b8a66c Mon Sep 17 00:00:00 2001 From: Der_Googler <54764558+DerGoogler@users.noreply.github.com> Date: Sun, 11 Oct 2026 17:26:56 +0200 Subject: [PATCH 09/24] Add WebUI X branding for config permissions Replace the MMRL WebUI config label with a dedicated WebUI X label in the permission UI, update the related user-facing strings, and add a new WebUI X vector icon asset for the badge. --- .../mmrl/ui/component/PermissionItem.kt | 6 +-- app/src/main/res/values/strings.xml | 4 +- .../dergoogler/mmrl/ui/component/LabelItem.kt | 8 ++++ ui/src/main/res/drawable/wxp_logo.xml | 41 +++++++++++++++++++ 4 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 ui/src/main/res/drawable/wxp_logo.xml diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/PermissionItem.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/PermissionItem.kt index fa0502746..785cb31cd 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/PermissionItem.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/PermissionItem.kt @@ -16,7 +16,7 @@ import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Title fun ListScope.PermissionItem( permissions: List, contentPadding: PaddingValues, -) = permissions.forEach { +) = permissions.forEach { it -> val permission = getPermissionDetails(it) permission.nullable { p -> Item( @@ -80,7 +80,7 @@ private fun getPermissionDetails(id: String): Permission? { { KernelSuLabel() APatchLabel() - MMRLLabel() + WebUIXLabel() }, ), "kernelsu.permission.POST_MOUNT" to @@ -111,7 +111,7 @@ private fun getPermissionDetails(id: String): Permission? { Permission( R.string.view_module_features_mmrl_webui_config, R.string.view_module_features_mmrl_webui_config_sub, - { MMRLLabel() }, + { WebUIXLabel() }, ), "mmrl.permission.APKS" to Permission( diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 91b494309..279f9b1c2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -372,8 +372,8 @@ Failed to close the action execution MMRL WebUI This module includes a MMRL only WebUI. - MMRL WebUI Config - This module uses a config file for WebUI\'s in MMRL + WebUI X Config + This module uses a configuration file for WebUI\'s in WebUI X FileSystem API This module maybe used the FileSystem API provided by MMRL. Eruda Console diff --git a/ui/src/main/kotlin/com/dergoogler/mmrl/ui/component/LabelItem.kt b/ui/src/main/kotlin/com/dergoogler/mmrl/ui/component/LabelItem.kt index b5f9f85c8..d83bca037 100644 --- a/ui/src/main/kotlin/com/dergoogler/mmrl/ui/component/LabelItem.kt +++ b/ui/src/main/kotlin/com/dergoogler/mmrl/ui/component/LabelItem.kt @@ -201,6 +201,14 @@ fun MMRLLabel() { ) } +@Composable +fun WebUIXLabel() { + LabelItem( + icon = R.drawable.wxp_logo, + text = "WebUI X", + ) +} + @Composable fun SukiSU() { LabelItem( diff --git a/ui/src/main/res/drawable/wxp_logo.xml b/ui/src/main/res/drawable/wxp_logo.xml new file mode 100644 index 000000000..3720d1718 --- /dev/null +++ b/ui/src/main/res/drawable/wxp_logo.xml @@ -0,0 +1,41 @@ + + + + + + + From dad70c40a1907000ac91e779e9b955800bcd1628 Mon Sep 17 00:00:00 2001 From: Der_Googler <54764558+DerGoogler@users.noreply.github.com> Date: Sun, 11 Oct 2026 18:05:20 +0200 Subject: [PATCH 10/24] Hide repo bulk install for invalid platforms Gate the repositories bulk-install FAB behind `platform.isValid` so it only appears on supported root platforms. Update platform validity to treat `Unknown` as invalid (alongside `NonRoot`), bump `mmrlx` to `1.0.229`, and clean up redundant self-imports in `PlatformManager`. --- .../repositories/RepositoriesScreen.kt | 41 ++++++++++--------- gradle/libs.versions.toml | 2 +- .../com/dergoogler/mmrl/platform/Platform.kt | 2 +- .../mmrl/platform/PlatformManager.kt | 7 ---- 4 files changed, 24 insertions(+), 28 deletions(-) diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repositories/RepositoriesScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repositories/RepositoriesScreen.kt index a9da830cd..3e173d8ab 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repositories/RepositoriesScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repositories/RepositoriesScreen.kt @@ -1,5 +1,6 @@ package com.dergoogler.mmrl.ui.screens.repositories +import android.util.Log import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.tween import androidx.compose.animation.scaleIn @@ -87,7 +88,7 @@ fun RepositoriesScreen() = val progress by viewModel.progress.collectAsStateWithLifecycle() val pullToRefreshState = rememberPullToRefreshState() - + // Use rememberSaveable to persist scroll position across navigation val listState = rememberSaveableLazyListState(key = "repositories_list") @@ -166,24 +167,26 @@ fun RepositoriesScreen() = }, contentWindowInsets = WindowInsets.none, floatingActionButton = { - AnimatedVisibility( - visible = showFab, - enter = - scaleIn( - animationSpec = tween(100), - initialScale = 0.8f, - ), - exit = - scaleOut( - animationSpec = tween(100), - targetScale = 0.8f, - ), - ) { - FloatingButton( - onClick = { - bulkInstallBottomSheet = true - }, - ) + if (viewModel.platform.isValid) { + AnimatedVisibility( + visible = showFab, + enter = + scaleIn( + animationSpec = tween(100), + initialScale = 0.8f, + ), + exit = + scaleOut( + animationSpec = tween(100), + targetScale = 0.8f, + ), + ) { + FloatingButton( + onClick = { + bulkInstallBottomSheet = true + }, + ) + } } }, ) { innerPadding -> diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c633516a5..3593d02c5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -43,7 +43,7 @@ squareRetrofit = "2.11.0" squareOkhttp = "4.12.0" squareMoshi = "1.15.1" runtimeAndroid = "1.12.1" -mmrlx = "1.0.224" +mmrlx = "1.0.229" workRuntimeKtx = "2.10.0" lifecycleProcess = "2.8.7" apacheCommons = "1.27.1" diff --git a/platform/src/main/kotlin/com/dergoogler/mmrl/platform/Platform.kt b/platform/src/main/kotlin/com/dergoogler/mmrl/platform/Platform.kt index 7fd1bc033..81b243847 100644 --- a/platform/src/main/kotlin/com/dergoogler/mmrl/platform/Platform.kt +++ b/platform/src/main/kotlin/com/dergoogler/mmrl/platform/Platform.kt @@ -172,7 +172,7 @@ enum class Platform( // Category checks val isKernelSuVariant: Boolean get() = type is PlatformType.KernelSU val isKernelSuOrNext: Boolean get() = this == KernelSU || this == KsuNext - val isValid: Boolean get() = this != NonRoot + val isValid: Boolean get() = this != NonRoot && this != Unknown // Negation checks (computed properties for consistency) val isNotMagisk: Boolean get() = !isMagisk diff --git a/platform/src/main/kotlin/com/dergoogler/mmrl/platform/PlatformManager.kt b/platform/src/main/kotlin/com/dergoogler/mmrl/platform/PlatformManager.kt index 86377b170..074b99921 100644 --- a/platform/src/main/kotlin/com/dergoogler/mmrl/platform/PlatformManager.kt +++ b/platform/src/main/kotlin/com/dergoogler/mmrl/platform/PlatformManager.kt @@ -14,13 +14,6 @@ import androidx.compose.runtime.DisallowComposableCalls import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue -import com.dergoogler.mmrl.platform.PlatformManager.isAlive -import com.dergoogler.mmrl.platform.PlatformManager.isAliveDeferred -import com.dergoogler.mmrl.platform.PlatformManager.isAliveFlow -import com.dergoogler.mmrl.platform.PlatformManager.mService -import com.dergoogler.mmrl.platform.PlatformManager.proxyBy -import com.dergoogler.mmrl.platform.PlatformManager.serviceOrNull -import com.dergoogler.mmrl.platform.PlatformManager.state import com.dergoogler.mmrl.platform.content.IService import com.dergoogler.mmrl.platform.content.Service import com.dergoogler.mmrl.platform.hiddenApi.HiddenPackageManager From 457acbd4bc9b7a0fb84227e2905c3ecccab3c3d0 Mon Sep 17 00:00:00 2001 From: Der_Googler <54764558+DerGoogler@users.noreply.github.com> Date: Sun, 11 Oct 2026 18:31:38 +0200 Subject: [PATCH 11/24] Bump library modules to compileSdk 37 Updates compileSdk from 36 to 37 across compat, datastore, ext, platform, and ui modules to align Android build targets. Also adds trailing commas in module include lists in compat and datastore settings for consistent Kotlin DSL formatting. --- compat/build.gradle.kts | 2 +- compat/settings.gradle.kts | 2 +- datastore/build.gradle.kts | 2 +- datastore/settings.gradle.kts | 2 +- ext/build.gradle.kts | 2 +- platform/build.gradle.kts | 2 +- ui/build.gradle.kts | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/compat/build.gradle.kts b/compat/build.gradle.kts index b9f919f45..b13c7915a 100644 --- a/compat/build.gradle.kts +++ b/compat/build.gradle.kts @@ -8,7 +8,7 @@ plugins { android { namespace = "com.dergoogler.mmrl.compat" - compileSdk = 36 + compileSdk = 37 defaultConfig { minSdk = 26 diff --git a/compat/settings.gradle.kts b/compat/settings.gradle.kts index 10a989ad5..c799adea2 100644 --- a/compat/settings.gradle.kts +++ b/compat/settings.gradle.kts @@ -13,6 +13,6 @@ dependencyResolutionManagement { include( ":platform", - ":ui" + ":ui", ":ext" ) diff --git a/datastore/build.gradle.kts b/datastore/build.gradle.kts index 59eaa7051..a2e294e62 100644 --- a/datastore/build.gradle.kts +++ b/datastore/build.gradle.kts @@ -7,7 +7,7 @@ plugins { android { namespace = "com.dergoogler.mmrl.datastore" - compileSdk = 36 + compileSdk = 37 defaultConfig { minSdk = 26 diff --git a/datastore/settings.gradle.kts b/datastore/settings.gradle.kts index 10a989ad5..c799adea2 100644 --- a/datastore/settings.gradle.kts +++ b/datastore/settings.gradle.kts @@ -13,6 +13,6 @@ dependencyResolutionManagement { include( ":platform", - ":ui" + ":ui", ":ext" ) diff --git a/ext/build.gradle.kts b/ext/build.gradle.kts index 72b3575cd..65f8d45f3 100644 --- a/ext/build.gradle.kts +++ b/ext/build.gradle.kts @@ -8,7 +8,7 @@ plugins { android { namespace = "com.dergoogler.mmrl.ext" - compileSdk = 36 + compileSdk = 37 defaultConfig { minSdk = 26 diff --git a/platform/build.gradle.kts b/platform/build.gradle.kts index da888f8bf..7b5860312 100644 --- a/platform/build.gradle.kts +++ b/platform/build.gradle.kts @@ -10,7 +10,7 @@ plugins { android { namespace = "com.dergoogler.mmrl.platform" - compileSdk = 36 + compileSdk = 37 publishing { singleVariant("release") diff --git a/ui/build.gradle.kts b/ui/build.gradle.kts index 426d1f744..d89502abe 100644 --- a/ui/build.gradle.kts +++ b/ui/build.gradle.kts @@ -7,7 +7,7 @@ plugins { android { namespace = "com.dergoogler.mmrl.ui" - compileSdk = 36 + compileSdk = 37 publishing { singleVariant("release") From edb82f19ef1748dc92ac8ef91222e734b9e55df1 Mon Sep 17 00:00:00 2001 From: Der_Googler <54764558+DerGoogler@users.noreply.github.com> Date: Sun, 11 Oct 2026 18:32:02 +0200 Subject: [PATCH 12/24] Refine repository search and menu state Refactors `RepositoryViewModel` to derive the repository module list from combined flows, including sorting and prefixed search filtering, instead of managing separate cache/search observers. It also fixes the category toggle in the repository menu and hides the repository cover/header content while search is active. --- .../repositories/RepositoriesScreen.kt | 1 - .../ui/screens/repository/RepositoryMenu.kt | 2 +- .../ui/screens/repository/RepositoryScreen.kt | 210 ++++++------ .../mmrl/viewmodel/RepositoryViewModel.kt | 324 +++++++++--------- 4 files changed, 268 insertions(+), 269 deletions(-) diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repositories/RepositoriesScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repositories/RepositoriesScreen.kt index 3e173d8ab..1e78e2dd0 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repositories/RepositoriesScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repositories/RepositoriesScreen.kt @@ -1,6 +1,5 @@ package com.dergoogler.mmrl.ui.screens.repositories -import android.util.Log import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.tween import androidx.compose.animation.scaleIn diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryMenu.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryMenu.kt index 853d31547..1c706f7b8 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryMenu.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryMenu.kt @@ -190,7 +190,7 @@ private fun MenuBottomSheet( MenuChip( selected = menu.showCategory, - onClick = { setMenu(menu.copy(showIcon = !menu.showCategory)) }, + onClick = { setMenu(menu.copy(showCategory = !menu.showCategory)) }, label = { Text(text = stringResource(id = R.string.menu_show_category)) }, ) diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryScreen.kt index ed31c32a7..57ded4d76 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryScreen.kt @@ -98,7 +98,7 @@ fun RepositoryScreen( Scaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { - TopBar( + TopBar0( repo = repo, isSearch = isSearch, query = query, @@ -126,125 +126,127 @@ fun RepositoryScreen( list = modules, state = viewModel.listState, ) { - item { - Box { - Cover( - modifier = - Modifier - .fadingEdge( - brush = - Brush.verticalGradient( - colors = listOf( - Color.Transparent, - Color.Black - ), - startY = Float.POSITIVE_INFINITY, - endY = 0f, - ), - ) - .onGloballyPositioned { coordinates -> - coverHeight = coordinates.size.height - }, - url = repo.cover ?: "ERROR ME", - errorIcon = R.drawable.box, - ) - - Box( - modifier = - Modifier - .offset(y = 164.4.dp) - .align(Alignment.TopCenter) - .fillMaxWidth(0.85f) - .onGloballyPositioned { coordinates -> - cardHeight = coordinates.size.height - }, - ) { - Card( + if (!isSearch) { + item { + Box { + Cover( modifier = Modifier - .align(Alignment.BottomCenter) - .background( + .fadingEdge( brush = - Brush.linearGradient( - colors = - listOf( - MaterialTheme.colorScheme.surfaceColorAtElevation( - 1.dp, - ), - MaterialTheme.colorScheme.background, - ), - start = Offset(0f, 0f), - end = - Offset( - Float.POSITIVE_INFINITY, - Float.POSITIVE_INFINITY, - ), + Brush.verticalGradient( + colors = listOf( + Color.Transparent, + Color.Black + ), + startY = Float.POSITIVE_INFINITY, + endY = 0f, ), - RoundedCornerShape(20.dp), ) - .border( - Dp.Hairline, - MaterialTheme.colorScheme.outlineVariant, - RoundedCornerShape(20.dp), - ), + .onGloballyPositioned { coordinates -> + coverHeight = coordinates.size.height + }, + url = repo.cover ?: "ERROR ME", + errorIcon = R.drawable.box, + ) + + Box( + modifier = + Modifier + .offset(y = 164.4.dp) + .align(Alignment.TopCenter) + .fillMaxWidth(0.85f) + .onGloballyPositioned { coordinates -> + cardHeight = coordinates.size.height + }, ) { - Column( + Card( modifier = Modifier - .relative() - .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(16.dp), + .align(Alignment.BottomCenter) + .background( + brush = + Brush.linearGradient( + colors = + listOf( + MaterialTheme.colorScheme.surfaceColorAtElevation( + 1.dp, + ), + MaterialTheme.colorScheme.background, + ), + start = Offset(0f, 0f), + end = + Offset( + Float.POSITIVE_INFINITY, + Float.POSITIVE_INFINITY, + ), + ), + RoundedCornerShape(20.dp), + ) + .border( + Dp.Hairline, + MaterialTheme.colorScheme.outlineVariant, + RoundedCornerShape(20.dp), + ), ) { - Text( - text = repo.name, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - textAlign = TextAlign.Center, - style = MaterialTheme.typography.headlineMedium, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.onBackground, - ) + Column( + modifier = + Modifier + .relative() + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + Text( + text = repo.name, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.headlineMedium, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onBackground, + ) - BBCodeText( - text = - ( - repo.description - ?: stringResource(R.string.view_module_no_description) - ).stripLinks( - "", - ), - style = MaterialTheme.typography.bodyMedium, - textAlign = TextAlign.Center, - color = MaterialTheme.colorScheme.outlineVariant, - lineHeight = 20.sp, - ) + BBCodeText( + text = + ( + repo.description + ?: stringResource(R.string.view_module_no_description) + ).stripLinks( + "", + ), + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.outlineVariant, + lineHeight = 20.sp, + ) - FlowRow { - repo.nullply { - if (submission.isNotNullOrBlank()) { - LabelItem( - style = - LabelItemDefaults.style.copy( - containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, - contentColor = MaterialTheme.colorScheme.onSurfaceVariant, - ), - modifier = - Modifier.onClick { - browser.openUri(submission) - }, - icon = getDomainIcon(submission), - text = stringResource(R.string.repo_options_submission), - ) + FlowRow { + repo.nullply { + if (submission.isNotNullOrBlank()) { + LabelItem( + style = + LabelItemDefaults.style.copy( + containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, + contentColor = MaterialTheme.colorScheme.onSurfaceVariant, + ), + modifier = + Modifier.onClick { + browser.openUri(submission) + }, + icon = getDomainIcon(submission), + text = stringResource(R.string.repo_options_submission), + ) + } } } } } } - } - with(density) { - Spacer(modifier = Modifier.height((((coverHeight / 1.05f) + cardHeight)).toDp())) + with(density) { + Spacer(modifier = Modifier.height((((coverHeight / 1.05f) + cardHeight)).toDp())) + } } } } @@ -253,7 +255,7 @@ fun RepositoryScreen( } @Composable -private fun TopBar( +private fun TopBar0( repo: Repo, isSearch: Boolean, query: String, diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/viewmodel/RepositoryViewModel.kt b/app/src/main/kotlin/com/dergoogler/mmrl/viewmodel/RepositoryViewModel.kt index c8371ba8a..371cb1c53 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/viewmodel/RepositoryViewModel.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/viewmodel/RepositoryViewModel.kt @@ -23,192 +23,190 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import timber.log.Timber @HiltViewModel(assistedFactory = RepositoryViewModel.Factory::class) class RepositoryViewModel - @AssistedInject - constructor( - @Assisted val repo: Repo, - application: Application, - localRepository: LocalRepository, - modulesRepository: ModulesRepository, - userPreferencesRepository: UserPreferencesRepository, - ) : MMRLViewModel(application, localRepository, modulesRepository, userPreferencesRepository) { - private val repositoryMenu - get() = - userPreferencesRepository.data - .map { it.repositoryMenu } - - var isSearch by mutableStateOf(false) - private set - private val keyFlow = MutableStateFlow("") - val query get() = keyFlow.asStateFlow() - - val listState: LazyListState = LazyListState() - - private val cacheFlow = MutableStateFlow(listOf>()) - private val onlineFlow = MutableStateFlow(listOf>()) - val online get() = onlineFlow.asStateFlow() - - var isLoading by mutableStateOf(true) - private set - - init { - Timber.d("RepositoryViewModel init") - dataObserver() - keyObserver() +@AssistedInject +constructor( + @Assisted val repo: Repo, + application: Application, + localRepository: LocalRepository, + modulesRepository: ModulesRepository, + userPreferencesRepository: UserPreferencesRepository, +) : MMRLViewModel( + application, + localRepository, + modulesRepository, + userPreferencesRepository, +) { + private val keyFlow = MutableStateFlow("") + val query: StateFlow = keyFlow.asStateFlow() + + private val repositoryMenu = + userPreferencesRepository.data.map { it.repositoryMenu } + + val listState: LazyListState = LazyListState() + + var isSearch by mutableStateOf(false) + private set + + var isLoading by mutableStateOf(true) + private set + + private val onlineModules = + if (repo.url.isNotBlank()) { + localRepository.getOnlineAllByUrlAsFlow(repo.url) + } else { + localRepository.getOnlineAllAsFlow() } - private fun dataObserver() { - val onlineModules = - if (repo.url.isNotBlank()) { - localRepository.getOnlineAllByUrlAsFlow(repo.url) - } else { - localRepository.getOnlineAllAsFlow() - } + val online: StateFlow>> = + combine( + onlineModules, + repositoryMenu, + keyFlow, + ) { modules, menu, key -> + val source = modules.map { module -> + val local = localRepository.getLocalByIdOrNull(module.id) - combine( - onlineModules, - repositoryMenu, - ) { list, menu -> - cacheFlow.value = - list - .map { - val local = localRepository.getLocalByIdOrNull(it.id) - val versionsList = it.versions.toMutableList() - - if (local != null) { - UpdateJson.loadToVersionItem(local.updateJson)?.let { es -> - // no need to define here a repo name since we only need to for the last updated - versionsList.add(0, es) - } - } - - it.copy(versions = versionsList).createState( - local = local, - hasUpdatableTag = localRepository.hasUpdatableTag(it.id), - ) to it.copy(versions = versionsList) - }.sortedWith( - comparator(menu.option, menu.descending), - ).let { v -> - val a = - if (menu.pinInstalled) { - v.sortedByDescending { it.first.installed } - } else { - v - } - - if (menu.pinUpdatable) { - a.sortedByDescending { it.first.updatable } - } else { - a - } - } - - isLoading = false - }.launchIn(viewModelScope) - } + val versions = module.versions.toMutableList() - private fun keyObserver() { - combine( - keyFlow, - cacheFlow, - ) { key, source -> - val newKey = - when { - key.startsWith("id:", ignoreCase = true) -> key.removePrefix("id:") - key.startsWith("name:", ignoreCase = true) -> key.removePrefix("name:") - key.startsWith("author:", ignoreCase = true) -> key.removePrefix("author:") - key.startsWith("category:", ignoreCase = true) -> key.removePrefix("category:") - else -> key - }.trim() - - onlineFlow.value = - source.filter { (_, m) -> - if (key.isNotBlank() || newKey.isNotBlank()) { - when { - key.startsWith("id:", ignoreCase = true) -> - m.id.equals(newKey, ignoreCase = true) - - key.startsWith("name:", ignoreCase = true) -> - m.name.equals(newKey, ignoreCase = true) - - key.startsWith("author:", ignoreCase = true) -> - m.author.equals(newKey, ignoreCase = true) - - key.startsWith("category:", ignoreCase = true) -> - m.categories?.any { - it.equals( - newKey, - ignoreCase = true, - ) - } ?: false - - else -> - m.name.contains(key, ignoreCase = true) || - m.author.contains(key, ignoreCase = true) || - m.description?.contains(key, ignoreCase = true) == true - } - } else { - true - } + if (local != null) { + UpdateJson.loadToVersionItem(local.updateJson)?.let { + versions.add(0, it) } - }.launchIn(viewModelScope) - } + } - private fun comparator( - option: Option, - descending: Boolean, - ): Comparator> = - if (descending) { - when (option) { - Option.Name -> compareByDescending { it.second.name.lowercase() } - Option.UpdatedTime -> compareBy { it.first.lastUpdated } - Option.Size -> compareByDescending { 0 } + val updatedModule = module.copy(versions = versions) + + updatedModule.createState( + local = local, + hasUpdatableTag = localRepository.hasUpdatableTag(module.id), + ) to updatedModule + } + + val sorted = source + .sortedWith(comparator(menu.option, menu.descending)) + .let { list -> + if (menu.pinInstalled) { + list.sortedByDescending { it.first.installed } + } else { + list + } } + .let { list -> + if (menu.pinUpdatable) { + list.sortedByDescending { it.first.updatable } + } else { + list + } + } + + isLoading = false + + if (key.isBlank()) { + sorted } else { - when (option) { - Option.Name -> compareBy { it.second.name.lowercase() } - Option.UpdatedTime -> compareByDescending { it.first.lastUpdated } - Option.Size -> compareBy { 0 } + val (searchKey, prefix) = parseSearchKey(key) + + sorted.filter { (_, module) -> + when (prefix) { + "id" -> + module.id.equals(searchKey, ignoreCase = true) + + "name" -> + module.name.equals(searchKey, ignoreCase = true) + + "author" -> + module.author.equals(searchKey, ignoreCase = true) + + "category" -> + module.categories?.any { + it.equals(searchKey, ignoreCase = true) + } == true + + else -> + module.name.contains(searchKey, ignoreCase = true) || + module.author.contains(searchKey, ignoreCase = true) || + module.description?.contains( + searchKey, + ignoreCase = true, + ) == true + } } } - - fun search(key: String) { - keyFlow.value = key + }.stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = emptyList(), + ) + + private fun parseSearchKey(raw: String): Pair { + val prefix = listOf("id", "name", "author", "category") + .firstOrNull { raw.startsWith("$it:", ignoreCase = true) } + + return if (prefix != null) { + raw.substringAfter(":").trim() to prefix + } else { + raw.trim() to null } + } - fun openSearch() { - isSearch = true - } + private fun comparator( + option: Option, + descending: Boolean, + ): Comparator> { + val base: Comparator> = + when (option) { + Option.Name -> + compareBy { it.second.name.lowercase() } - fun closeSearch() { - isSearch = false - keyFlow.value = "" - } + Option.UpdatedTime -> + compareByDescending { it.first.lastUpdated } - fun setRepositoryMenu(value: RepositoryMenu) { - viewModelScope.launch { - userPreferencesRepository.setRepositoryMenu(value) + Option.Size -> + compareByDescending { 0 } } - } - @AssistedFactory - interface Factory { - fun create(repo: Repo): RepositoryViewModel - } + return if (descending) base.reversed() else base + } - companion object { - @Composable - fun build(repo: Repo): RepositoryViewModel = - hiltViewModel { factory -> - factory.create(repo) - } + fun search(key: String) { + keyFlow.value = key + } + + fun openSearch() { + isSearch = true + } + + fun closeSearch() { + isSearch = false + keyFlow.value = "" + } + + fun setRepositoryMenu(value: RepositoryMenu) { + viewModelScope.launch { + userPreferencesRepository.setRepositoryMenu(value) } } + + @AssistedFactory + interface Factory { + fun create(repo: Repo): RepositoryViewModel + } + + companion object { + @Composable + fun build(repo: Repo): RepositoryViewModel = + hiltViewModel { factory -> + factory.create(repo) + } + } +} \ No newline at end of file From 22394ec05605470128e45bf21650091b964ace04 Mon Sep 17 00:00:00 2001 From: Der_Googler <54764558+DerGoogler@users.noreply.github.com> Date: Sun, 11 Oct 2026 18:33:10 +0200 Subject: [PATCH 13/24] Remove repositories fast scrollbar Drop the `VerticalFastScrollbar` from the repositories list screen and clean up the now-unused Compose imports. This simplifies the layout after the scrollbar was no longer needed. --- .../ui/screens/repositories/RepositoriesList.kt | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repositories/RepositoriesList.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repositories/RepositoriesList.kt index c224ec578..3418716e4 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repositories/RepositoriesList.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repositories/RepositoriesList.kt @@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredHeightIn import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState @@ -23,7 +22,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource @@ -31,7 +29,6 @@ import androidx.compose.ui.unit.dp import com.dergoogler.mmrl.R import com.dergoogler.mmrl.model.state.RepoState import com.dergoogler.mmrl.ui.component.scaffold.ScaffoldScope -import com.dergoogler.mmrl.ui.component.scrollbar.VerticalFastScrollbar import com.dergoogler.mmrl.ui.providable.LocalDestinationsNavigator import com.dergoogler.mmrl.ui.providable.LocalHazeState import com.dergoogler.mmrl.ui.providable.LocalMainScreenInnerPaddings @@ -92,17 +89,6 @@ fun ScaffoldScope.RepositoriesList( } } } - - VerticalFastScrollbar( - state = state, - modifier = - Modifier - .align(Alignment.CenterEnd) - .padding( - top = innerPadding.calculateTopPadding(), - bottom = paddingValues.calculateBottomPadding(), - ), - ) } @Composable From 0931788460de11cc032861373fccab2f7260c350 Mon Sep 17 00:00:00 2001 From: Der_Googler <54764558+DerGoogler@users.noreply.github.com> Date: Sun, 11 Oct 2026 18:44:59 +0200 Subject: [PATCH 14/24] Animate repository list header transitions Refactors `TypedModulesList` to `ModulesList` and adds `animateContentSize()` to smooth list layout updates. In `RepositoryScreen`, it now reads `repositoryMenu` preferences to adjust top padding for compact mode and uses `AnimatedVisibility` to transition between search and non-search header states instead of abruptly adding/removing the cover item. --- .../mmrl/ui/screens/repository/ModulesList.kt | 6 ++++-- .../ui/screens/repository/RepositoryScreen.kt | 19 +++++++++++++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/ModulesList.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/ModulesList.kt index de0b73074..bfbae90f2 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/ModulesList.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/ModulesList.kt @@ -1,5 +1,6 @@ package com.dergoogler.mmrl.ui.screens.repository +import androidx.compose.animation.animateContentSize import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues @@ -33,7 +34,7 @@ import com.ramcosta.composedestinations.generated.destinations.NewViewScreenDest import dev.chrisbanes.haze.hazeSource @Composable -fun ScaffoldScope.TypedModulesList( +fun ScaffoldScope.ModulesList( innerPadding: PaddingValues, repo: Repo, list: List>, @@ -52,11 +53,12 @@ fun ScaffoldScope.TypedModulesList( Box( modifier = Modifier.fillMaxSize(), ) { - this@TypedModulesList.ResponsiveContent { + this@ModulesList.ResponsiveContent { LazyColumn( state = state, modifier = Modifier .fillMaxSize() + .animateContentSize() .hazeSource(LocalHazeState.current), contentPadding = PaddingValues( diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryScreen.kt index 57ded4d76..86be545ce 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryScreen.kt @@ -1,6 +1,7 @@ package com.dergoogler.mmrl.ui.screens.repository import androidx.activity.compose.BackHandler +import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement @@ -49,6 +50,7 @@ import androidx.core.net.toUri import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.dergoogler.mmrl.R import com.dergoogler.mmrl.database.entity.Repo +import com.dergoogler.mmrl.datastore.model.RepoListMode import com.dergoogler.mmrl.datastore.model.RepositoryMenu import com.dergoogler.mmrl.ext.fadingEdge import com.dergoogler.mmrl.ext.isNotNullOrBlank @@ -67,6 +69,7 @@ import com.dergoogler.mmrl.ui.component.text.BBCodeText import com.dergoogler.mmrl.ui.component.toolbar.BlurSearchToolbar import com.dergoogler.mmrl.ui.component.toolbar.ToolbarTitle import com.dergoogler.mmrl.ui.component.toolbar.rememberBlurToolbarState +import com.dergoogler.mmrl.ui.providable.LocalUserPreferences import com.dergoogler.mmrl.viewmodel.RepositoryViewModel import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph @@ -77,6 +80,7 @@ import dev.dergoogler.mmrl.compat.core.LocalUriHandler fun RepositoryScreen( repo: Repo, ) = LocalScreenProvider { + val userPreferences = LocalUserPreferences.current val browser = LocalUriHandler.current val density = LocalDensity.current @@ -93,6 +97,9 @@ fun RepositoryScreen( var coverHeight by remember { mutableIntStateOf(0) } var cardHeight by remember { mutableIntStateOf(0) } + val menu = userPreferences.repositoryMenu + val pad = remember(menu) { if (menu.repoListMode == RepoListMode.Compact) 0.dp else 16.dp } + BackHandler(enabled = viewModel.isSearch, onBack = viewModel::closeSearch) Scaffold( @@ -120,15 +127,19 @@ fun RepositoryScreen( return@Scaffold } - this@Scaffold.TypedModulesList( + this@Scaffold.ModulesList( innerPadding = innerPadding, repo = repo, list = modules, state = viewModel.listState, ) { - if (!isSearch) { - item { - Box { + item { + AnimatedVisibility(isSearch) { + Spacer(Modifier.height(innerPadding.calculateTopPadding() + pad)) + } + + AnimatedVisibility(!isSearch) { + Box(Modifier.animateEnterExit()) { Cover( modifier = Modifier From 1904fe775d97fc247d7e840aaaa7ad7fdeb03a9b Mon Sep 17 00:00:00 2001 From: Der_Googler <54764558+DerGoogler@users.noreply.github.com> Date: Sun, 11 Oct 2026 18:58:42 +0200 Subject: [PATCH 15/24] [skip ci] update workflows --- .github/actions/build-apk/action.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/actions/build-apk/action.yml b/.github/actions/build-apk/action.yml index cea5e54cb..f15a65591 100644 --- a/.github/actions/build-apk/action.yml +++ b/.github/actions/build-apk/action.yml @@ -93,19 +93,10 @@ runs: shell: bash run: | apk_path=$(find app/build/outputs/apk/${{ env.FLAVOR_LOWERCASE }}/${{ env.BUILD_TYPE_LOWERCASE }} -name "*.apk" -print -quit) - mapping_path=$(find app/build/outputs/mapping/${{ env.FLAVOR_LOWERCASE }}${{ env.BUILD_TYPE_CAPITALIZED }} -name "mapping.txt" -print -quit) name=$(basename "$apk_path" .apk) echo "name=$name" >> $GITHUB_OUTPUT echo "path=$apk_path" >> $GITHUB_OUTPUT - echo "mapping=$mapping_path" >> $GITHUB_OUTPUT - - - name: Upload mapping as artifact - if: ${{ steps.apk.outputs.mapping != '' }} - uses: actions/upload-artifact@v4 - with: - name: ${{ inputs.flavor }}-${{ inputs.buildType }}-mapping - path: ${{ steps.apk.outputs.mapping }} - name: Upload APK as artifact uses: actions/upload-artifact@v4 From 0734e469f5b64c8ad2e95c4768a4d2d1dea8db81 Mon Sep 17 00:00:00 2001 From: Der_Googler <54764558+DerGoogler@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:14:22 +0200 Subject: [PATCH 16/24] . --- .github/workflows/github.yml | 2 +- .github/workflows/release-ci.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index 2d8f81f04..9886cfefd 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -112,4 +112,4 @@ jobs: asset_name: ${{ steps.find_apks.outputs.SPOOFED_NAME }} asset_content_type: application/vnd.android.package-archive env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/release-ci.yml b/.github/workflows/release-ci.yml index 439db9c5f..6dbf0ae36 100644 --- a/.github/workflows/release-ci.yml +++ b/.github/workflows/release-ci.yml @@ -102,8 +102,8 @@ jobs: run: | [ -f gradle.properties ] && [ "$(tail -c1 gradle.properties)" != "" ] && echo >> gradle.properties { - echo "gpr.user=${{ inputs.username }}" - echo "gpr.key=${{ inputs.token }}" + echo "gpr.user=${{ secrets.ACTOR }}" + echo "gpr.key=${{ secrets.GH_TOKEN }}" } >> gradle.properties - name: Set up JDK From ecc350bbafe399728d6102658c279c90861d1909 Mon Sep 17 00:00:00 2001 From: Der_Googler <54764558+DerGoogler@users.noreply.github.com> Date: Sun, 11 Oct 2026 20:01:49 +0200 Subject: [PATCH 17/24] [skip ci] Update build.gradle.kts --- app/build.gradle.kts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 44e83d86c..1bb506d85 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -174,10 +174,14 @@ android { androidComponents { onVariants { variant -> + val distributionFlavor = variant.productFlavors + .firstOrNull { it.first == "distribution" } + ?.second + variant.outputs.filterIsInstance().forEach { output -> output.outputFileName.set( output.versionName.map { vName -> - "MMRL-${vName}-${variant.buildType ?: variant.name}.apk" + "MMRL-$vName-$distributionFlavor.apk" } ) } From 9d6770512dce5eb94861798feb523085b85595d1 Mon Sep 17 00:00:00 2001 From: Der_Googler <54764558+DerGoogler@users.noreply.github.com> Date: Sun, 18 Oct 2026 18:51:23 +0200 Subject: [PATCH 18/24] Migrate UI to MMRLX toolbar, theme and scaffold Add the mmrlx ui and utilities dependencies and bump mmrlx to 1.0.230. Wrap the app in MMRLXTheme. Replace Material3 TopAppBarScrollBehavior/TopAppBarDefaults with the MMRLX ToolbarScrollBehavior/ToolbarDefaults across screens. BlurToolbar now uses the MMRLX Toolbar with Oklch colors, and the haze blur effect is dropped from it. Move the bottom navigation into a new BottomBar component built on the MMRLX NavigationBar. HomeScreen, RepositoriesScreen, RepositoryScreen and the new DescriptionScreen render it themselves, and MainScreen uses the MMRLX Scaffold. Rewrite HomeScreen and the Root/NonRoot items with MMRLX list and card components. Add a WebUI-based DescriptionScreen for module readmes and point AboutModule at it. Move ToolbarIcon and ToolbarEventIcon into TopAppBarEventIcon.kt. --- app/build.gradle.kts | 4 +- .../mmrl/ui/activity/MMRLComponentActivity.kt | 55 +- .../activity/terminal/action/ActionScreen.kt | 6 +- .../terminal/install/InstallScreen.kt | 6 +- .../dergoogler/mmrl/ui/component/BottomBar.kt | 157 +++++ .../mmrl/ui/component/SELinuxStatus.kt | 11 +- .../dergoogler/mmrl/ui/component/Scaffold.kt | 3 +- .../mmrl/ui/component/TopAppBarEventIcon.kt | 73 +- .../toolbar/BlurNavigateUpToolbar.kt | 15 +- .../ui/component/toolbar/BlurSearchToolbar.kt | 8 +- .../mmrl/ui/component/toolbar/BlurToolbar.kt | 47 +- .../mmrl/ui/providable/LocalScrollBehavior.kt | 4 +- .../ExploreRepositoryScreen.kt | 3 +- .../mmrl/ui/screens/home/HomeScreen.kt | 645 +++++++++--------- .../mmrl/ui/screens/home/items/NonRootItem.kt | 40 +- .../mmrl/ui/screens/home/items/RootItem.kt | 87 ++- .../mmrl/ui/screens/main/MainScreen.kt | 161 +---- .../moduleDetails/DescriptionScreen.kt | 177 +++++ .../ui/screens/moduleView/NewViewScreen.kt | 4 +- .../moduleView/ViewDescriptionScreen.kt | 8 +- .../moduleView/sections/AboutModule.kt | 4 +- .../mmrl/ui/screens/modules/ModulesScreen.kt | 8 +- .../repositories/RepositoriesScreen.kt | 12 +- .../ui/screens/repository/RepositoryScreen.kt | 12 +- .../screens/settings/logviewer/LogScreen.kt | 8 +- gradle/libs.versions.toml | 2 +- 26 files changed, 894 insertions(+), 666 deletions(-) create mode 100644 app/src/main/kotlin/com/dergoogler/mmrl/ui/component/BottomBar.kt create mode 100644 app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleDetails/DescriptionScreen.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1bb506d85..4ebd6c12f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -273,8 +273,8 @@ dependencies { ksp(libs.square.moshi.kotlin) implementation(libs.mmrlx.terminal) -// implementation(libs.mmrlx.nio) -// implementation(libs.mmrlx.utilities) + implementation(libs.mmrlx.ui) + implementation(libs.mmrlx.utilities) implementation(libs.mmrlx.webui.core) implementation("dev.chrisbanes.haze:haze:1.6.10") diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/activity/MMRLComponentActivity.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/activity/MMRLComponentActivity.kt index 2ce4467da..ad77ea39e 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/activity/MMRLComponentActivity.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/activity/MMRLComponentActivity.kt @@ -44,6 +44,7 @@ import dagger.hilt.android.AndroidEntryPoint import dev.dergoogler.mmrl.compat.BuildCompat import dev.dergoogler.mmrl.compat.core.BrickException import dev.dergoogler.mmrl.compat.core.MMRLUriHandlerImpl +import dev.mmrlx.compose.ui.theme.MMRLXTheme import timber.log.Timber import javax.inject.Inject import kotlin.system.exitProcess @@ -214,30 +215,36 @@ fun BaseContent( val currentTheme = Colors.getColor(preferences.themeColor, preferences.isDarkMode()) val toolbarColor = currentTheme.surface - MMRLAppTheme( - darkMode = preferences.isDarkMode(), - navController = navController, - themeColor = preferences.themeColor, - providerValues = - arrayOf( - LocalWindowSizeClass provides windowSizeClass, - LocalDestinationsNavigator provides navigator, - LocalActivity provides activity, - LocalSettings provides hiltViewModel(activity), - LocalUserPreferences provides preferences, - dev.dergoogler.mmrl.compat.core.LocalUriHandler provides - MMRLUriHandlerImpl( - context, - toolbarColor, - ), - LocalLifecycleScope provides activity.lifecycleScope, - LocalLifecycle provides activity.lifecycle, - LocalUriHandler provides MMRLUriHandlerImpl(context, toolbarColor), - LocalNavController provides navController, - LocalMainNavController provides mainNavController, - ), - content = content, - ) + val darkMode = preferences.isDarkMode() + + MMRLXTheme( + darkTheme = darkMode, + ) { + MMRLAppTheme( + darkMode = darkMode, + navController = navController, + themeColor = preferences.themeColor, + providerValues = + arrayOf( + LocalWindowSizeClass provides windowSizeClass, + LocalDestinationsNavigator provides navigator, + LocalActivity provides activity, + LocalSettings provides hiltViewModel(activity), + LocalUserPreferences provides preferences, + dev.dergoogler.mmrl.compat.core.LocalUriHandler provides + MMRLUriHandlerImpl( + context, + toolbarColor, + ), + LocalLifecycleScope provides activity.lifecycleScope, + LocalLifecycle provides activity.lifecycle, + LocalUriHandler provides MMRLUriHandlerImpl(context, toolbarColor), + LocalNavController provides navController, + LocalMainNavController provides mainNavController, + ), + content = content, + ) + } } fun MMRLComponentActivity.setBaseContent( diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/activity/terminal/action/ActionScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/activity/terminal/action/ActionScreen.kt index 1aea464bc..4214e0de1 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/activity/terminal/action/ActionScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/activity/terminal/action/ActionScreen.kt @@ -46,6 +46,8 @@ import com.dergoogler.mmrl.ui.providable.LocalUserPreferences import com.dergoogler.mmrl.viewmodel.ActionViewModel import dev.chrisbanes.haze.hazeSource import dev.mmrlx.compose.terminal.Terminal +import dev.mmrlx.compose.ui.toolbar.ToolbarDefaults +import dev.mmrlx.compose.ui.toolbar.ToolbarScrollBehavior import dev.mmrlx.terminal.TerminalEmulator.Companion.TERMINAL_CURSOR_STYLE_NONE import kotlinx.coroutines.launch @@ -54,7 +56,7 @@ fun ActionScreen(viewModel: ActionViewModel) = LocalScreenProvider { val scope = rememberCoroutineScope() val snackbarHostState = remember { SnackbarHostState() } - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() + val scrollBehavior = ToolbarDefaults.pinnedScrollBehavior() val userPreferences = LocalUserPreferences.current val context = LocalContext.current @@ -174,7 +176,7 @@ private fun TopBar( exportLog: () -> Unit, event: Event, onBack: () -> Unit, - scrollBehavior: TopAppBarScrollBehavior, + scrollBehavior: ToolbarScrollBehavior, enable: Boolean, ) = BlurNavigateUpToolbar( title = stringResource(id = R.string.action_activity), diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/activity/terminal/install/InstallScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/activity/terminal/install/InstallScreen.kt index 852553a1a..c10393df8 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/activity/terminal/install/InstallScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/activity/terminal/install/InstallScreen.kt @@ -54,6 +54,8 @@ import com.dergoogler.mmrl.ui.providable.LocalUserPreferences import com.dergoogler.mmrl.viewmodel.InstallViewModel import dev.chrisbanes.haze.hazeSource import dev.mmrlx.compose.terminal.Terminal +import dev.mmrlx.compose.ui.toolbar.ToolbarDefaults +import dev.mmrlx.compose.ui.toolbar.ToolbarScrollBehavior import dev.mmrlx.terminal.TerminalEmulator.Companion.TERMINAL_CURSOR_STYLE_NONE import kotlinx.coroutines.launch @@ -62,7 +64,7 @@ fun InstallScreen(viewModel: InstallViewModel) = LocalScreenProvider { val scope = rememberCoroutineScope() val snackbarHostState = remember { SnackbarHostState() } - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() + val scrollBehavior = ToolbarDefaults.pinnedScrollBehavior() val userPreferences = LocalUserPreferences.current val context = LocalContext.current @@ -226,7 +228,7 @@ private fun TopBar( exportLog: () -> Unit, event: Event, onBack: () -> Unit, - scrollBehavior: TopAppBarScrollBehavior, + scrollBehavior: ToolbarScrollBehavior, enable: Boolean, ) = BlurNavigateUpToolbar( title = stringResource(id = R.string.install_screen_title), diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/BottomBar.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/BottomBar.kt new file mode 100644 index 000000000..9cf34f3ad --- /dev/null +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/BottomBar.kt @@ -0,0 +1,157 @@ +package com.dergoogler.mmrl.ui.component + +import androidx.compose.foundation.layout.imePadding +import androidx.compose.material3.Badge +import androidx.compose.material3.BadgedBox +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import com.dergoogler.mmrl.ui.navigation.MainDestination +import com.dergoogler.mmrl.ui.navigation.isAccessible +import com.dergoogler.mmrl.ui.providable.LocalDestinationsNavigator +import com.dergoogler.mmrl.ui.providable.LocalNavController +import com.dergoogler.mmrl.ui.providable.LocalUserPreferences +import com.ramcosta.composedestinations.generated.NavGraphs +import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState +import dev.mmrlx.compose.ui.Text +import dev.mmrlx.compose.ui.icon.Icon +import dev.mmrlx.compose.ui.navigationbar.NavigationBar +import dev.mmrlx.compose.ui.navigationbar.NavigationBarItem +import dev.mmrlx.compose.ui.theme.MMRLXTheme + +@Composable +fun BottomBar(updates: Int = 0) { + val prefs = LocalUserPreferences.current + val navigator = LocalDestinationsNavigator.current + val navController = LocalNavController.current + + NavigationBar( + modifier = + Modifier + .imePadding() + ) { + for (screen in MainDestination.entries) { + if (!screen.isAccessible) continue + + val isSelected by navController.isRouteOnBackStackAsState(screen.direction) + + NavigationBarItem( + icon = { + BaseNavIcon(screen, isSelected, updates) + }, + label = { + Text( + text = stringResource(id = screen.label), + style = MMRLXTheme.typography.labelLarge, + ) + }, + alwaysShowLabel = !prefs.hideBottomBarLabels, + selected = isSelected, + onClick = { + if (isSelected) { + navigator.popBackStack(screen.direction, false) + } + navigator.navigate(screen.direction) { + popUpTo(NavGraphs.root) { + saveState = true + } + launchSingleTop = true + restoreState = true + } + }, + ) + } + } +} +/* +@Composable +private fun RailNav(updates: Int) { + val prefs = LocalUserPreferences.current + val navigator = LocalDestinationsNavigator.current + val navController = LocalNavController.current + + NavigationRail( + header = { + TopAppBarEventIcon() + }, + ) { + MainDestination.entries.forEach { screen -> + val isSelected by navController.isRouteOnBackStackAsState(screen.direction) + + if (!screen.isAccessible) return@forEach + + NavigationRailItem( + icon = { + BaseNavIcon(screen, isSelected, updates) + }, + label = { + Text( + text = stringResource(id = screen.label), + style = MaterialTheme.typography.labelLarge, + ) + }, + alwaysShowLabel = !prefs.hideBottomBarLabels, + selected = isSelected, + onClick = { + if (isSelected) { + navigator.popBackStack(screen.direction, false) + } + navigator.navigate(screen.direction) { + popUpTo(NavGraphs.root) { + saveState = true + } + launchSingleTop = true + restoreState = true + } + }, + ) + } + } +}*/ + +@Composable +private fun BaseNavIcon( + screen: MainDestination, + selected: Boolean, + updates: Int, +) { + if (screen == MainDestination.Modules && updates > 0) { + BadgedBox( + badge = { + Badge { + Text(text = updates.toString()) + } + }, + ) { + Icon( + painter = + painterResource( + id = + if (selected) { + screen.iconFilled + } else { + screen.icon + }, + ), + contentDescription = null, + ) + } + + return + } + + Icon( + painter = + painterResource( + id = + if (selected) { + screen.iconFilled + } else { + screen.icon + }, + ), + contentDescription = null, + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/SELinuxStatus.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/SELinuxStatus.kt index b7f89725d..e1cc68d67 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/SELinuxStatus.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/SELinuxStatus.kt @@ -3,15 +3,14 @@ package com.dergoogler.mmrl.ui.component import android.util.Log import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.compose.ui.res.painterResource import com.dergoogler.mmrl.R import com.dergoogler.mmrl.platform.PlatformManager -import com.dergoogler.mmrl.ui.component.listItem.dsl.ListScope -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.Item -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Description -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Icon -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Title +import dev.mmrlx.compose.ui.list.ListScope +import dev.mmrlx.compose.ui.list.component.Item +import dev.mmrlx.compose.ui.list.component.item.Description +import dev.mmrlx.compose.ui.list.component.item.Icon +import dev.mmrlx.compose.ui.list.component.item.Title import com.dergoogler.mmrl.ui.remember.remember @Composable diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/Scaffold.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/Scaffold.kt index 42f585c19..effb1ecbb 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/Scaffold.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/Scaffold.kt @@ -29,6 +29,7 @@ import com.dergoogler.mmrl.ui.providable.LocalHazeState import com.dergoogler.mmrl.ui.providable.LocalMainScreenInnerPaddings import com.dergoogler.mmrl.ui.providable.LocalNavController import dev.chrisbanes.haze.hazeSource +import dev.mmrlx.compose.ui.toolbar.ToolbarDefaults @Composable fun SettingsScaffold( @@ -60,7 +61,7 @@ fun SettingsScaffold( relative: @Composable (ListScope.() -> Unit), ) = LocalScreenProvider { val navController = LocalNavController.current - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() + val scrollBehavior = ToolbarDefaults.pinnedScrollBehavior() ResponsiveScaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/TopAppBarEventIcon.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/TopAppBarEventIcon.kt index 31d37e462..349771beb 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/TopAppBarEventIcon.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/TopAppBarEventIcon.kt @@ -1,11 +1,22 @@ package com.dergoogler.mmrl.ui.component +import androidx.annotation.DrawableRes +import androidx.compose.foundation.layout.size +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp import com.dergoogler.mmrl.R import com.dergoogler.mmrl.datastore.providable.LocalUserPreferences -import com.dergoogler.mmrl.ui.component.toolbar.ToolbarEventIcon import com.dergoogler.mmrl.ui.model.toolbar.ToolbarEvent +import dev.mmrlx.compose.ui.Text +import dev.mmrlx.compose.ui.icon.Icon +import dev.mmrlx.compose.ui.theme.LocalContentColor +import dev.mmrlx.compose.ui.toolbar.ToolbarTitle @Composable fun TopAppBarEventIcon() { @@ -52,3 +63,63 @@ fun TopAppBarEventIcon() { events = events, ) } + +@Composable +fun ToolbarIcon( + modifier: Modifier = Modifier, + @DrawableRes icon: Int = com.dergoogler.mmrl.ui.R.drawable.mmrl_logo, + subtitle: String? = null, + tint: Color = MaterialTheme.colorScheme.surfaceTint, +) = ToolbarTitle( + modifier = modifier, + titleContent = { + Icon( + modifier = Modifier.size(30.dp), + painter = painterResource(id = icon), + contentDescription = null, + tint = tint, + ) + }, + subtitleContent = { + subtitle?.let { + Text( + text = it, + style = MaterialTheme.typography.bodySmall, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = LocalContentColor.current.copy(alpha = 0.75f), + ) + } + } +) + +@Composable +fun ToolbarEventIcon( + events: List, + modifier: Modifier = Modifier, + enabled: Boolean = true, + @DrawableRes nonEventIcon: Int = com.dergoogler.mmrl.ui.R.drawable.mmrl_logo, +) { + if (!enabled) { + return ToolbarIcon( + modifier = modifier, + icon = nonEventIcon, + ) + } + + val event = remember(events) { events.firstOrNull { it.isActive } } + + if (event != null) { + ToolbarIcon( + modifier = modifier, + icon = event.icon, + subtitle = event.getTitle(), + ) + } else { + ToolbarIcon( + modifier = modifier, + icon = nonEventIcon, + ) + } +} + diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/toolbar/BlurNavigateUpToolbar.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/toolbar/BlurNavigateUpToolbar.kt index 820d1b258..055f6fee9 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/toolbar/BlurNavigateUpToolbar.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/toolbar/BlurNavigateUpToolbar.kt @@ -8,7 +8,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -17,6 +16,8 @@ import androidx.navigation.NavController import com.dergoogler.mmrl.ext.takeTrue import com.dergoogler.mmrl.ui.R import com.ramcosta.composedestinations.navigation.DestinationsNavigator +import dev.mmrlx.compose.ui.toolbar.ToolbarDefaults +import dev.mmrlx.compose.ui.toolbar.ToolbarScrollBehavior @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -28,7 +29,7 @@ fun BlurNavigateUpToolbar( enable: Boolean = true, actions: @Composable RowScope.() -> Unit = {}, windowInsets: WindowInsets = TopAppBarDefaults.windowInsets, - scrollBehavior: TopAppBarScrollBehavior? = null, + scrollBehavior: ToolbarScrollBehavior? = null, fade: Boolean = false, fadeDistance: Float = 200f, fadeBackgroundIfNoBlur: Boolean = false, @@ -56,7 +57,7 @@ fun BlurNavigateUpToolbar( enable: Boolean = true, actions: @Composable RowScope.() -> Unit = {}, windowInsets: WindowInsets = TopAppBarDefaults.windowInsets, - scrollBehavior: TopAppBarScrollBehavior? = null, + scrollBehavior: ToolbarScrollBehavior? = null, fade: Boolean = false, fadeDistance: Float = 200f, fadeBackgroundIfNoBlur: Boolean = false, @@ -84,7 +85,7 @@ fun BlurNavigateUpToolbar( enable: Boolean = true, actions: @Composable RowScope.() -> Unit = {}, windowInsets: WindowInsets = TopAppBarDefaults.windowInsets, - scrollBehavior: TopAppBarScrollBehavior? = null, + scrollBehavior: ToolbarScrollBehavior? = null, fade: Boolean = false, fadeDistance: Float = 200f, fadeBackgroundIfNoBlur: Boolean = false, @@ -112,7 +113,7 @@ fun BlurNavigateUpToolbar( enable: Boolean = true, actions: @Composable RowScope.() -> Unit = {}, windowInsets: WindowInsets = TopAppBarDefaults.windowInsets, - scrollBehavior: TopAppBarScrollBehavior? = null, + scrollBehavior: ToolbarScrollBehavior? = null, fade: Boolean = false, fadeDistance: Float = 200f, fadeBackgroundIfNoBlur: Boolean = false, @@ -139,8 +140,8 @@ fun BlurNavigateUpToolbar( modifier: Modifier = Modifier, enable: Boolean = true, actions: @Composable RowScope.() -> Unit = {}, - windowInsets: WindowInsets = TopAppBarDefaults.windowInsets, - scrollBehavior: TopAppBarScrollBehavior? = null, + windowInsets: WindowInsets = ToolbarDefaults.windowInsets, + scrollBehavior: ToolbarScrollBehavior? = null, fade: Boolean = false, fadeDistance: Float = 200f, fadeBackgroundIfNoBlur: Boolean = false, diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/toolbar/BlurSearchToolbar.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/toolbar/BlurSearchToolbar.kt index 7a5764934..9606dece8 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/toolbar/BlurSearchToolbar.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/toolbar/BlurSearchToolbar.kt @@ -12,8 +12,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember @@ -28,6 +26,8 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import com.dergoogler.mmrl.ui.R +import dev.mmrlx.compose.ui.toolbar.ToolbarDefaults +import dev.mmrlx.compose.ui.toolbar.ToolbarScrollBehavior @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -41,8 +41,8 @@ fun BlurSearchToolbar( title: @Composable () -> Unit, navigationIcon: @Composable () -> Unit = {}, actions: @Composable RowScope.() -> Unit = {}, - windowInsets: WindowInsets = TopAppBarDefaults.windowInsets, - scrollBehavior: TopAppBarScrollBehavior? = null, + windowInsets: WindowInsets = ToolbarDefaults.windowInsets, + scrollBehavior: ToolbarScrollBehavior? = null, fadeBackgroundIfNoBlur: Boolean = false, fadeDistance: Float = 200f, fade: Boolean = false, diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/toolbar/BlurToolbar.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/toolbar/BlurToolbar.kt index c485133df..e446e90f6 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/toolbar/BlurToolbar.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/toolbar/BlurToolbar.kt @@ -5,10 +5,7 @@ import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.State @@ -17,14 +14,14 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp -import com.dergoogler.mmrl.ui.providable.LocalHazeState import com.dergoogler.mmrl.ui.providable.LocalUserPreferences import com.dergoogler.mmrl.utils.BlurUtil -import dev.chrisbanes.haze.hazeEffect import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi -import dev.chrisbanes.haze.materials.HazeMaterials +import dev.mmrlx.compose.ui.theme.MMRLXTheme +import dev.mmrlx.compose.ui.theme.Oklch +import dev.mmrlx.compose.ui.toolbar.ToolbarDefaults +import dev.mmrlx.compose.ui.toolbar.ToolbarScrollBehavior private const val VISIBILITY_THRESHOLD = 0.01f @@ -38,7 +35,7 @@ class BlurToolbarState internal constructor( private val animatedAlpha: State, private val targetAlphaState: State, private val blurEnabled: State, - private val scrollBehavior: TopAppBarScrollBehavior?, + private val scrollBehavior: ToolbarScrollBehavior?, ) { /** Current (animated) alpha, 0f..1f. */ val alpha: Float get() = animatedAlpha.value @@ -72,7 +69,7 @@ class BlurToolbarState internal constructor( @Composable fun rememberBlurToolbarState( - scrollBehavior: TopAppBarScrollBehavior? = null, + scrollBehavior: ToolbarScrollBehavior? = null, fade: Boolean = false, fadeDistance: Float = 200f, ): BlurToolbarState { @@ -121,25 +118,25 @@ fun BlurToolbar( fadeBackgroundIfNoBlur: Boolean = false, fadeDistance: Float = 200f, fade: Boolean = false, - scrollBehavior: TopAppBarScrollBehavior? = null, + scrollBehavior: ToolbarScrollBehavior? = null, state: BlurToolbarState = rememberBlurToolbarState( scrollBehavior = scrollBehavior, fade = fade, fadeDistance = fadeDistance, ), ) { - val borderColor = MaterialTheme.colorScheme.outline - val backgroundColor = MaterialTheme.colorScheme.background - val topAppBarColors = TopAppBarDefaults.topAppBarColors() + val borderColor = MMRLXTheme.border.okLchColor + val backgroundColor = MMRLXTheme.oklchColors.background + val topAppBarColors = ToolbarDefaults.colors() val colors = if (state.isBlurEnabled) { topAppBarColors.copy( - scrolledContainerColor = Color.Transparent, - containerColor = Color.Transparent, + scrolledContainerColor = Oklch(0.0,0.0, 0.0,0.0), + containerColor = Oklch(0.0,0.0, 0.0,0.0), ) } else { val container = if (fadeBackgroundIfNoBlur) { - backgroundColor.copy(alpha = state.alpha) + backgroundColor.copy(opacity = state.alpha.toDouble()) } else { backgroundColor } @@ -149,19 +146,7 @@ fun BlurToolbar( ) } - val blur = if (state.isBlurActive) { - Modifier.hazeEffect( - state = LocalHazeState.current, - style = HazeMaterials.ultraThin(), - ) { - this.backgroundColor = backgroundColor - this.alpha = state.alpha - } - } else { - Modifier - } - - TopAppBar( + dev.mmrlx.compose.ui.toolbar.Toolbar( title = { title(state.alpha) }, modifier = Modifier .drawBehind { @@ -171,20 +156,18 @@ fun BlurToolbar( val borderSize = Dp.Hairline val y = size.height - borderSize.value drawLine( - color = borderColor.copy(alpha = alpha), + color = borderColor.copy(opacity = alpha.toDouble()).toColor(), start = Offset(0f, y), end = Offset(size.width, y), strokeWidth = borderSize.value, ) } } - .then(blur) .then(modifier), navigationIcon = navigationIcon, actions = actions, colors = colors, scrollBehavior = scrollBehavior, windowInsets = windowInsets, - expandedHeight = expandedHeight, ) } \ No newline at end of file diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/providable/LocalScrollBehavior.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/providable/LocalScrollBehavior.kt index 426ddc75a..528e62131 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/providable/LocalScrollBehavior.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/providable/LocalScrollBehavior.kt @@ -1,9 +1,9 @@ package com.dergoogler.mmrl.ui.providable -import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.staticCompositionLocalOf +import dev.mmrlx.compose.ui.toolbar.ToolbarScrollBehavior val LocalScrollBehavior = - staticCompositionLocalOf { + staticCompositionLocalOf { error("CompositionLocal LocalScrollBehavior not present") } diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/exploreRepositories/ExploreRepositoryScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/exploreRepositories/ExploreRepositoryScreen.kt index 3018a18d5..4e3946f94 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/exploreRepositories/ExploreRepositoryScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/exploreRepositories/ExploreRepositoryScreen.kt @@ -70,6 +70,7 @@ import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import dev.chrisbanes.haze.hazeSource import dev.dergoogler.mmrl.compat.core.LocalUriHandler +import dev.mmrlx.compose.ui.toolbar.ToolbarDefaults import kotlinx.coroutines.launch @Destination @@ -81,7 +82,7 @@ fun ExploreRepositoryScreen(repo: ExploreRepository) = val scope = rememberCoroutineScope() val snackbarHostState = LocalSnackbarHost.current val browser = LocalUriHandler.current - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() + val scrollBehavior = ToolbarDefaults.pinnedScrollBehavior() val userPreferences = LocalUserPreferences.current val repositoriesMenu = userPreferences.repositoriesMenu diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/HomeScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/HomeScreen.kt index 35945cbf8..7408f2401 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/HomeScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/HomeScreen.kt @@ -7,26 +7,19 @@ import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable -import androidx.compose.runtime.ExperimentalComposeApi import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -44,7 +37,6 @@ import androidx.compose.ui.unit.dp import com.dergoogler.mmrl.R import com.dergoogler.mmrl.datastore.model.WorkingMode.Companion.isNonRoot import com.dergoogler.mmrl.datastore.model.WorkingMode.Companion.isRoot -import com.dergoogler.mmrl.ext.currentScreenWidth import com.dergoogler.mmrl.ext.managerVersion import com.dergoogler.mmrl.ext.none import com.dergoogler.mmrl.ext.nullable @@ -56,21 +48,10 @@ import com.dergoogler.mmrl.platform.file.SuFile.Companion.toFormattedFileSize import com.dergoogler.mmrl.platform.ksu.KsuNative import com.dergoogler.mmrl.stub.IMMRLApiManager import com.dergoogler.mmrl.ui.component.Alert -import com.dergoogler.mmrl.ui.component.LocalScreenProvider +import com.dergoogler.mmrl.ui.component.BottomBar import com.dergoogler.mmrl.ui.component.SELinuxStatus import com.dergoogler.mmrl.ui.component.TopAppBarEventIcon -import com.dergoogler.mmrl.ui.component.card.Card -import com.dergoogler.mmrl.ui.component.card.component.Relative -import com.dergoogler.mmrl.ui.component.listItem.dsl.List -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.Item -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Description -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Icon -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Title -import com.dergoogler.mmrl.ui.component.scaffold.Scaffold -import com.dergoogler.mmrl.ui.component.toolbar.BlurToolbar import com.dergoogler.mmrl.ui.providable.LocalDestinationsNavigator -import com.dergoogler.mmrl.ui.providable.LocalHazeState -import com.dergoogler.mmrl.ui.providable.LocalMainScreenInnerPaddings import com.dergoogler.mmrl.ui.providable.LocalUserPreferences import com.dergoogler.mmrl.ui.remember.rememberLocalAnalytics import com.dergoogler.mmrl.ui.remember.seLinuxContext @@ -84,7 +65,20 @@ import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.generated.destinations.AboutScreenDestination import com.ramcosta.composedestinations.generated.destinations.ThankYouScreenDestination -import dev.chrisbanes.haze.hazeSource +import dev.mmrlx.compose.layout.outlinedCard +import dev.mmrlx.compose.ui.LinearProgressIndicator +import dev.mmrlx.compose.ui.Text +import dev.mmrlx.compose.ui.icon.Icon +import dev.mmrlx.compose.ui.icon.IconButton +import dev.mmrlx.compose.ui.list.List +import dev.mmrlx.compose.ui.list.component.Item +import dev.mmrlx.compose.ui.list.component.item.Description +import dev.mmrlx.compose.ui.list.component.item.Icon +import dev.mmrlx.compose.ui.list.component.item.Title +import dev.mmrlx.compose.ui.scaffold.Scaffold +import dev.mmrlx.compose.ui.toolbar.Toolbar +import dev.mmrlx.compose.ui.toolbar.ToolbarDefaults +import dev.mmrlx.compose.ui.toolbar.ToolbarScrollBehavior import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import timber.log.Timber @@ -92,383 +86,356 @@ import timber.log.Timber val listItemContentPaddingValues: PaddingValues = PaddingValues(vertical = 8.dp, horizontal = 25.dp) @Destination(start = true) -@OptIn(ExperimentalComposeApi::class) @Composable -fun HomeScreen() = - LocalScreenProvider { - val navigator = LocalDestinationsNavigator.current - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() - val context = LocalContext.current - val userPreferences = LocalUserPreferences.current - val browser = LocalUriHandler.current - - val analytics by rememberLocalAnalytics() - - var openRebootSheet by remember { mutableStateOf(false) } - if (openRebootSheet) { - RebootBottomSheet( - onClose = { openRebootSheet = false }, - ) - } +fun HomeScreen() { + val navigator = LocalDestinationsNavigator.current + val scrollBehavior = ToolbarDefaults.fadePinnedScrollBehavior() + val context = LocalContext.current + val userPreferences = LocalUserPreferences.current + val browser = LocalUriHandler.current + + val analytics by rememberLocalAnalytics() + + var openRebootSheet by remember { mutableStateOf(false) } + if (openRebootSheet) { + RebootBottomSheet( + onClose = { openRebootSheet = false }, + ) + } - Scaffold( - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), - topBar = { - TopBar( - onInfoClick = { - navigator.navigate(AboutScreenDestination) - }, - onHeartClick = { - navigator.navigate(ThankYouScreenDestination) - }, - onRebootClick = { - openRebootSheet = true - }, - scrollBehavior = scrollBehavior, - ) - }, - contentWindowInsets = WindowInsets.none, - ) { innerPadding -> - ResponsiveContent { - Column( - modifier = - Modifier - .hazeSource(state = LocalHazeState.current) - .fillMaxWidth() - .verticalScroll(rememberScrollState()) - .padding(16.dp) - .padding(top = innerPadding.calculateTopPadding()), - verticalArrangement = Arrangement.spacedBy(16.dp), - ) { - when { - userPreferences.workingMode.isRoot -> - RootItem( - developerMode = userPreferences.developerMode, - ) - - userPreferences.workingMode.isNonRoot -> - NonRootItem( - developerMode = userPreferences.developerMode, - ) - } + Scaffold( + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + toolbar = { + TopBar0( + onInfoClick = { + navigator.navigate(AboutScreenDestination) + }, + onHeartClick = { + navigator.navigate(ThankYouScreenDestination) + }, + onRebootClick = { + openRebootSheet = true + }, + scrollBehavior = scrollBehavior, + ) + }, + bottomBar = { + BottomBar() + }, + contentWindowInsets = WindowInsets.none, + ) { + Column( + modifier = + Modifier + .scaffoldHazeSource() + .fillMaxWidth() + .verticalScroll(rememberScrollState()) + .scaffoldPadding() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + when { + userPreferences.workingMode.isRoot -> + RootItem() + + userPreferences.workingMode.isNonRoot -> + NonRootItem() + } - if (userPreferences.checkAppUpdates) { - var changelog by remember { mutableStateOf?>(null) } - LaunchedEffect(Unit) { - runRequest { - withContext(Dispatchers.IO) { - val api = IMMRLApiManager.build() - return@withContext api.changelog.execute() - } - }.onSuccess { list -> - changelog = list - }.onFailure { - Timber.e(it, "unable to get changelog") - } + if (userPreferences.checkAppUpdates) { + var changelog by remember { mutableStateOf?>(null) } + LaunchedEffect(Unit) { + runRequest { + withContext(Dispatchers.IO) { + val api = IMMRLApiManager.build() + return@withContext api.changelog.execute() } + }.onSuccess { list -> + changelog = list + }.onFailure { + Timber.e(it, "unable to get changelog") + } + } - changelog?.let { - val latest = it.first() + changelog?.let { + val latest = it.first() - var changelogSheet by remember { mutableStateOf(false) } - if (changelogSheet) { - ChangelogBottomSheet( - changelog = latest, - onClose = { changelogSheet = false }, - ) - } + var changelogSheet by remember { mutableStateOf(false) } + if (changelogSheet) { + ChangelogBottomSheet( + changelog = latest, + onClose = { changelogSheet = false }, + ) + } - AnimatedVisibility( - visible = - userPreferences.developerMode { devAlwaysShowUpdateAlert } || - ( + AnimatedVisibility( + visible = + userPreferences.developerMode { devAlwaysShowUpdateAlert } || + ( if (latest.preRelease) { userPreferences.checkAppUpdatesPreReleases && - latest.versionCode > context.managerVersion.second + latest.versionCode > context.managerVersion.second } else { latest.versionCode > context.managerVersion.second } - ), - enter = fadeIn() + expandVertically(), - exit = shrinkVertically() + fadeOut(), - ) { - Alert( - onClick = { - changelogSheet = true - }, - backgroundColor = MaterialTheme.colorScheme.tertiaryContainer, - title = stringResource(R.string.update_available), - message = - stringResource( - R.string.new_version_available, - latest.versionName, - ), - icon = R.drawable.cloud_download, - ) - } - } + ), + enter = fadeIn() + expandVertically(), + exit = shrinkVertically() + fadeOut(), + ) { + Alert( + onClick = { + changelogSheet = true + }, + backgroundColor = MaterialTheme.colorScheme.tertiaryContainer, + title = stringResource(R.string.update_available), + message = + stringResource( + R.string.new_version_available, + latest.versionName, + ), + icon = R.drawable.cloud_download, + ) } + } + } - List( - verticalArrangement = Arrangement.spacedBy(16.dp), - contentPadding = listItemContentPaddingValues, - ) { - val scope = this + List( + verticalArrangement = Arrangement.spacedBy(16.dp), + contentPadding = listItemContentPaddingValues, + ) { + val scope = this - Card( - modifier = Modifier.padding(vertical = 16.dp), - ) { - val uname = Os.uname() - Column( - modifier = Modifier.relative(), - ) { - scope.Item { - Icon(painter = painterResource(R.drawable.cookie_man)) - Title(R.string.kernel) - Description(uname.release) - } + Column( + modifier = Modifier + .outlinedCard() + .padding(vertical = 16.dp) + .fillMaxWidth() + ) { + val uname = Os.uname() + scope.Item { + Icon(painter = painterResource(R.drawable.cookie_man)) + Title(R.string.kernel) + Description(uname.release) + } - scope.Item { - Icon(painter = painterResource(R.drawable.launcher_outline)) - Title(R.string.manager_version) - Description("${context.managerVersion.first} (${context.managerVersion.second})") - } + scope.Item { + Icon(painter = painterResource(R.drawable.launcher_outline)) + Title(R.string.manager_version) + Description("${context.managerVersion.first} (${context.managerVersion.second})") + } - scope.Item { - Icon(painter = painterResource(R.drawable.fingerprint)) - Title(R.string.fingerprint) - Description( - if (userPreferences.hideFingerprintInHome) { - stringResource(id = R.string.hidden) - } else { - Build.FINGERPRINT - }, - ) - } + scope.Item { + Icon(painter = painterResource(R.drawable.fingerprint)) + Title(R.string.fingerprint) + Description( + if (userPreferences.hideFingerprintInHome) { + stringResource(id = R.string.hidden) + } else { + Build.FINGERPRINT + }, + ) + } - scope.Item { - Icon(painter = painterResource(R.drawable.cpu_2)) - Title(R.string.architecture) - Description(uname.machine) - } + scope.Item { + Icon(painter = painterResource(R.drawable.cpu_2)) + Title(R.string.architecture) + Description(uname.machine) + } - scope.SELinuxStatus() + scope.SELinuxStatus() - PlatformManager.platform.isKernelSuOrNext.takeTrue { + PlatformManager.platform.isKernelSuOrNext.takeTrue { + scope.Item { + Icon(painter = painterResource(R.drawable.user_outlined)) + Title(R.string.super_user_apps) + Description(superUserCount.toString()) + } + } + } + + PlatformManager.isAlive.takeTrue { + userPreferences.developerMode.takeTrue { + Column( + modifier = Modifier + .outlinedCard() + .padding(vertical = 16.dp) + .fillMaxWidth() + ) { + scope.Item { + Title(R.string.home_root_provider_version_name) + Description(versionName) + } + + scope.Item { + Title(R.string.home_root_provider_se_linux_context) + Description(seLinuxContext) + } + + if (PlatformManager.platform.isKernelSuNext) { + KsuNative.getHookMode().nullable { scope.Item { - Icon(painter = painterResource(R.drawable.user_outlined)) - Title(R.string.super_user_apps) - Description(superUserCount.toString()) + Title(R.string.hook_mode) + Description(it) } } } - } - - PlatformManager.isAlive.takeTrue { - userPreferences.developerMode.takeTrue { - Card( - modifier = Modifier.padding(vertical = 16.dp), - ) { - Column( - modifier = Modifier.relative(), - ) { - scope.Item { - Title(R.string.home_root_provider_version_name) - Description(versionName) - } - - scope.Item { - Title(R.string.home_root_provider_se_linux_context) - Description(seLinuxContext) - } - - if (PlatformManager.platform.isKernelSuNext) { - KsuNative.getHookMode().nullable { - scope.Item { - Title(R.string.hook_mode) - Description(it) - } - } - } - - if (PlatformManager.platform.isSukiSU) { - KsuNative.getHookType().nullable { - scope.Item { - Title(R.string.hook_mode) - Description(it) - } - } - scope.Item { - Title(R.string.kpm_support) - Description(if (KsuNative.isKPMEnabled()) R.string.yes else R.string.no) - } - } + if (PlatformManager.platform.isSukiSU) { + KsuNative.getHookType().nullable { + scope.Item { + Title(R.string.hook_mode) + Description(it) } } + + scope.Item { + Title(R.string.kpm_support) + Description(if (KsuNative.isKPMEnabled()) R.string.yes else R.string.no) + } } } + } + } - if (PlatformManager.platform.isValid) { - analytics.nullable { - Row( - horizontalArrangement = Arrangement.spacedBy(16.dp), - ) { - Card( - modifier = - Modifier - .padding(vertical = 16.dp) - .weight(1f), - ) { - Relative { - scope.Item { - Title(R.string.home_installed_modules) - Description(it.totalModules.toString()) - } - } - } - - Card( - modifier = - Modifier - .padding(vertical = 16.dp) - .weight(1f), - ) { - Relative { - scope.Item { - Title(R.string.home_updated_modules) - Description(it.totalUpdated.toString()) - } - } - } + if (PlatformManager.platform.isValid) { + analytics.nullable { + Row( + horizontalArrangement = Arrangement.spacedBy(16.dp), + ) { + Column( + modifier = Modifier + .outlinedCard() + .padding(vertical = 16.dp) + .weight(1f) + ) { + scope.Item { + Title(R.string.home_installed_modules) + Description(it.totalModules.toString()) } + } - Card( - modifier = - Modifier - .padding(vertical = 16.dp), - ) { - Relative { - scope.Item { - Title(R.string.home_storage_usage) - - Description { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp), - ) { - Text( - text = it.totalModulesUsageBytes.toFormattedFileSize(), - ) - - LinearProgressIndicator( - progress = { - it.totalStorageUsage - }, - modifier = - Modifier - .height(10.dp) - .weight(1f), - drawStopIndicator = {}, - ) - - Text( - text = it.totalDeviceStorageBytes.toFormattedFileSize(), - ) - } - } - } - } + Column( + modifier = Modifier + .outlinedCard() + .padding(vertical = 16.dp) + .weight(1f) + ) { + scope.Item { + Title(R.string.home_updated_modules) + Description(it.totalUpdated.toString()) } + } + } - Row( - horizontalArrangement = Arrangement.spacedBy(16.dp), - ) { - Card( - modifier = - Modifier - .padding(vertical = 16.dp) - .weight(1f), - ) { - Relative { - scope.Item { - Title(R.string.home_enabled_modules) - Description(it.totalEnabled.toString()) - } - } - } + Column( + modifier = Modifier + .outlinedCard() + .padding(vertical = 16.dp) + .fillMaxWidth() + ) { + scope.Item { + Title(R.string.home_storage_usage) - Card( - modifier = - Modifier - .padding(vertical = 16.dp) - .weight(1f), + Description { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), ) { - Relative { - scope.Item { - Title(R.string.home_disabled_modules) - Description(it.totalDisabled.toString()) - } - } + Text( + text = it.totalModulesUsageBytes.toFormattedFileSize(), + ) + + LinearProgressIndicator( + progress = { + it.totalStorageUsage + }, + modifier = + Modifier + .height(10.dp) + .weight(1f), + drawStopIndicator = {}, + ) + + Text( + text = it.totalDeviceStorageBytes.toFormattedFileSize(), + ) } } } } - Card( - modifier = - Modifier - .padding(vertical = 16.dp) - .fillMaxWidth(), - onClick = { - browser.openUri("https://github.com/sponsors/MMRLApp") - }, + Row( + horizontalArrangement = Arrangement.spacedBy(16.dp), ) { - Relative { + Column( + modifier = Modifier + .outlinedCard() + .padding(vertical = 16.dp) + .weight(1f) + ) { + scope.Item { + Title(R.string.home_enabled_modules) + Description(it.totalEnabled.toString()) + } + } + + Column( + modifier = Modifier + .outlinedCard() + .padding(vertical = 16.dp) + .weight(1f) + ) { scope.Item { - Title( - id = R.string.home_support_title, - styleTransform = { - val newStyle = it.copy(color = Color.Unspecified) - it.merge(newStyle) - }, - ) - - Description( - id = R.string.home_support_content, - styleTransform = { - val newStyle = it.copy(color = Color.Unspecified) - it.merge(newStyle) - }, - ) + Title(R.string.home_disabled_modules) + Description(it.totalDisabled.toString()) } } } } + } + + Column( + modifier = Modifier + .outlinedCard() + .fillMaxWidth() + .clickable { + browser.openUri("https://github.com/sponsors/MMRLApp") + } + .padding(vertical = 16.dp) + ) { + scope.Item { + Title( + id = R.string.home_support_title, + styleTransform = { + val newStyle = it.copy(color = Color.Unspecified) + it.merge(newStyle) + }, + ) - val paddingValues = LocalMainScreenInnerPaddings.current - Spacer(modifier = Modifier.height(paddingValues.calculateBottomPadding())) + Description( + id = R.string.home_support_content, + styleTransform = { + val newStyle = it.copy(color = Color.Unspecified) + it.merge(newStyle) + }, + ) + } } } } } +} @Composable -private fun TopBar( +private fun TopBar0( onRebootClick: () -> Unit = {}, onInfoClick: () -> Unit = {}, onHeartClick: () -> Unit = {}, - scrollBehavior: TopAppBarScrollBehavior, + scrollBehavior: ToolbarScrollBehavior, ) { - val width = currentScreenWidth() - - BlurToolbar( + Toolbar( scrollBehavior = scrollBehavior, - fade = true, - fadeDistance = 50f, title = { - if (!width.isSmall) return@BlurToolbar - TopAppBarEventIcon() }, actions = { diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/items/NonRootItem.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/items/NonRootItem.kt index 56c03f1b5..20c6e48a7 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/items/NonRootItem.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/items/NonRootItem.kt @@ -1,5 +1,6 @@ package com.dergoogler.mmrl.ui.screens.home.items +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -8,6 +9,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CornerSize import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -16,27 +18,36 @@ import androidx.compose.material3.Text 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.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.dergoogler.mmrl.R -import com.dergoogler.mmrl.ext.takeTrue -import com.dergoogler.mmrl.ui.component.card.Card +import com.dergoogler.mmrl.ui.providable.LocalUserPreferences +import dev.mmrlx.compose.ui.scaffold.PositionLayout +import dev.mmrlx.compose.ui.theme.MMRLXTheme + @Composable -internal fun NonRootItem(developerMode: Boolean = false) = - Card( - color = MaterialTheme.colorScheme.secondaryContainer, +internal fun NonRootItem() { + val userPreferences = LocalUserPreferences.current + val shape = MMRLXTheme.shapes.extraLarge + + PositionLayout( + modifier = Modifier + .clip(shape) + .background(MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.5f)) ) { - developerMode.takeTrue { + if (userPreferences.developerMode) { Surface( shape = RoundedCornerShape( - topEnd = 20.dp, - bottomStart = 15.dp, - bottomEnd = 0.dp, + topStart = CornerSize(0.dp), + topEnd = shape.topEnd, + bottomStart = CornerSize(15.dp), + bottomEnd = CornerSize(0.dp), ), - color = MaterialTheme.colorScheme.primary, + color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.45f), modifier = Modifier .absolute(Alignment.TopEnd), @@ -44,7 +55,7 @@ internal fun NonRootItem(developerMode: Boolean = false) = Text( text = "USER!DEV", style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onPrimary, + color = MaterialTheme.colorScheme.onSecondaryContainer, modifier = Modifier .padding(horizontal = 16.dp, vertical = 8.dp), @@ -53,14 +64,16 @@ internal fun NonRootItem(developerMode: Boolean = false) = } Row( - modifier = Modifier.relative().padding(20.dp), + modifier = Modifier + .relative() + .padding(20.dp), verticalAlignment = Alignment.CenterVertically, ) { Icon( modifier = Modifier.size(45.dp), painter = painterResource(id = R.drawable.info_circle_filled), contentDescription = null, - tint = MaterialTheme.colorScheme.primary, + tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.5f), ) Spacer(modifier = Modifier.width(16.dp)) @@ -82,3 +95,4 @@ internal fun NonRootItem(developerMode: Boolean = false) = } } } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/items/RootItem.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/items/RootItem.kt index 15d9516f2..2700a6d96 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/items/RootItem.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/items/RootItem.kt @@ -1,5 +1,6 @@ package com.dergoogler.mmrl.ui.screens.home.items +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -8,6 +9,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CornerSize import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -16,42 +18,43 @@ import androidx.compose.material3.Text 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.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.dergoogler.mmrl.R -import com.dergoogler.mmrl.ext.nullable -import com.dergoogler.mmrl.ext.takeTrue import com.dergoogler.mmrl.model.local.FeaturedManager import com.dergoogler.mmrl.model.local.FeaturedManager.Companion.name -import com.dergoogler.mmrl.platform.PlatformManager.isAlive -import com.dergoogler.mmrl.platform.PlatformManager.platform -import com.dergoogler.mmrl.ui.component.LabelItem -import com.dergoogler.mmrl.ui.component.card.Card -import com.dergoogler.mmrl.ui.component.text.TextRow +import com.dergoogler.mmrl.platform.PlatformManager import com.dergoogler.mmrl.ui.providable.LocalUserPreferences -import com.dergoogler.mmrl.ui.remember.isLkmMode import com.dergoogler.mmrl.ui.remember.versionCode +import dev.mmrlx.compose.ui.scaffold.PositionLayout +import dev.mmrlx.compose.ui.theme.MMRLXTheme @Composable -internal fun RootItem(developerMode: Boolean = false) { +internal fun RootItem() { val userPreferences = LocalUserPreferences.current + val shape = MMRLXTheme.shapes.extraLarge + val isAlive = PlatformManager.isAlive val manager = FeaturedManager.managers.find { userPreferences.workingMode == it.workingMode } - Card( - color = MaterialTheme.colorScheme.secondaryContainer, + PositionLayout( + modifier = Modifier + .clip(shape) + .background(MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.5f)) ) { - developerMode.takeTrue { + if (userPreferences.developerMode) { Surface( shape = RoundedCornerShape( - topEnd = 20.dp, - bottomStart = 15.dp, - bottomEnd = 0.dp, + topStart = CornerSize(0.dp), + topEnd = shape.topEnd, + bottomStart = CornerSize(15.dp), + bottomEnd = CornerSize(0.dp), ), - color = MaterialTheme.colorScheme.primary, + color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.45f), modifier = Modifier .absolute(Alignment.TopEnd), @@ -59,7 +62,7 @@ internal fun RootItem(developerMode: Boolean = false) { Text( text = "USER!DEV", style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onPrimary, + color = MaterialTheme.colorScheme.onSecondaryContainer, modifier = Modifier .padding(horizontal = 16.dp, vertical = 8.dp), @@ -89,45 +92,33 @@ internal fun RootItem(developerMode: Boolean = false) { modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(4.dp), ) { - TextRow( - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, - leadingContent = - (developerMode && platform.isKernelSuOrNext) nullable { - LabelItem( - text = - when (isLkmMode) { - null -> "LTS" - true -> "LKM" - else -> "GKI" - }, + Text( + text = + if (isAlive) { + stringResource( + id = R.string.settings_root_access, + stringResource(id = R.string.settings_root_granted), + ) + } else { + stringResource( + id = R.string.settings_root_access, + stringResource(id = R.string.settings_root_none), ) }, - ) { - Text( - text = - if (isAlive) { - stringResource( - id = R.string.settings_root_access, - stringResource(id = R.string.settings_root_granted), - ) - } else { - stringResource( - id = R.string.settings_root_access, - stringResource(id = R.string.settings_root_none), - ) - }, - style = MaterialTheme.typography.titleMedium, - color = MaterialTheme.colorScheme.onSecondaryContainer, - ) - } + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSecondaryContainer, + ) Text( text = if (isAlive) { stringResource( id = R.string.settings_root_provider, - manager.name, + if (manager?.name == null) { + "???" + } else { + manager.name + }, ) } else { stringResource( diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/main/MainScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/main/MainScreen.kt index 631fa3220..1c6c73a6e 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/main/MainScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/main/MainScreen.kt @@ -7,14 +7,7 @@ import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.imePadding -import androidx.compose.material3.Badge -import androidx.compose.material3.BadgedBox -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect @@ -22,15 +15,9 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavBackStackEntry import com.dergoogler.mmrl.ext.none -import com.dergoogler.mmrl.ui.component.scaffold.ResponsiveScaffold -import com.dergoogler.mmrl.ui.component.toolbar.BlurBottomToolbar -import com.dergoogler.mmrl.ui.navigation.MainDestination -import com.dergoogler.mmrl.ui.navigation.isAccessible import com.dergoogler.mmrl.ui.providable.LocalBulkInstall import com.dergoogler.mmrl.ui.providable.LocalDestinationsNavigator import com.dergoogler.mmrl.ui.providable.LocalHazeState @@ -46,9 +33,9 @@ import com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationSty import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.generated.NavGraphs -import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.rememberHazeState +import dev.mmrlx.compose.ui.scaffold.Scaffold @Destination @Composable @@ -146,17 +133,11 @@ fun MainScreen() { return@CompositionLocalProvider }*/ - ResponsiveScaffold( - bottomBar = { - BottomNav(updates) - }, - railBar = { -// RailNav(updates) - }, + Scaffold( contentWindowInsets = WindowInsets.none, - ) { paddingValues -> + ) { CompositionLocalProvider( - LocalMainScreenInnerPaddings provides paddingValues, + LocalMainScreenInnerPaddings provides contentPadding, ) { CurrentNavHost( modifier = Modifier.hazeSource(hazeState), @@ -183,137 +164,3 @@ private fun CurrentNavHost(modifier: Modifier = Modifier) { ) } -@Composable -private fun BottomNav(updates: Int) { - val prefs = LocalUserPreferences.current - val navigator = LocalDestinationsNavigator.current - val navController = LocalNavController.current - - BlurBottomToolbar( - modifier = - Modifier - .imePadding(), - ) { - for (screen in MainDestination.entries) { - if (!screen.isAccessible) continue - - val isSelected by navController.isRouteOnBackStackAsState(screen.direction) - - NavigationBarItem( - icon = { - BaseNavIcon(screen, isSelected, updates) - }, - label = { - Text( - text = stringResource(id = screen.label), - style = MaterialTheme.typography.labelLarge, - ) - }, - alwaysShowLabel = !prefs.hideBottomBarLabels, - selected = isSelected, - onClick = { - if (isSelected) { - navigator.popBackStack(screen.direction, false) - } - navigator.navigate(screen.direction) { - popUpTo(NavGraphs.root) { - saveState = true - } - launchSingleTop = true - restoreState = true - } - }, - ) - } - } -} -/* -@Composable -private fun RailNav(updates: Int) { - val prefs = LocalUserPreferences.current - val navigator = LocalDestinationsNavigator.current - val navController = LocalNavController.current - - NavigationRail( - header = { - TopAppBarEventIcon() - }, - ) { - MainDestination.entries.forEach { screen -> - val isSelected by navController.isRouteOnBackStackAsState(screen.direction) - - if (!screen.isAccessible) return@forEach - - NavigationRailItem( - icon = { - BaseNavIcon(screen, isSelected, updates) - }, - label = { - Text( - text = stringResource(id = screen.label), - style = MaterialTheme.typography.labelLarge, - ) - }, - alwaysShowLabel = !prefs.hideBottomBarLabels, - selected = isSelected, - onClick = { - if (isSelected) { - navigator.popBackStack(screen.direction, false) - } - navigator.navigate(screen.direction) { - popUpTo(NavGraphs.root) { - saveState = true - } - launchSingleTop = true - restoreState = true - } - }, - ) - } - } -}*/ - -@Composable -private fun BaseNavIcon( - screen: MainDestination, - selected: Boolean, - updates: Int, -) { - if (screen == MainDestination.Modules && updates > 0) { - BadgedBox( - badge = { - Badge { - Text(text = updates.toString()) - } - }, - ) { - Icon( - painter = - painterResource( - id = - if (selected) { - screen.iconFilled - } else { - screen.icon - }, - ), - contentDescription = null, - ) - } - - return - } - - Icon( - painter = - painterResource( - id = - if (selected) { - screen.iconFilled - } else { - screen.icon - }, - ), - contentDescription = null, - ) -} diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleDetails/DescriptionScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleDetails/DescriptionScreen.kt new file mode 100644 index 000000000..737af2879 --- /dev/null +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleDetails/DescriptionScreen.kt @@ -0,0 +1,177 @@ +package com.dergoogler.mmrl.ui.screens.moduleDetails + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import com.dergoogler.mmrl.BuildConfig +import com.dergoogler.mmrl.R +import com.dergoogler.mmrl.ext.none +import com.dergoogler.mmrl.model.WebColors +import com.dergoogler.mmrl.ui.component.BottomBar +import com.dergoogler.mmrl.ui.providable.LocalDestinationsNavigator +import com.dergoogler.mmrl.ui.providable.LocalUserPreferences +import com.dergoogler.mmrl.utils.MimeUtil +import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.annotation.RootGraph +import com.ramcosta.composedestinations.navigation.DestinationsNavigator +import dev.mmrlx.compose.ui.ext.with +import dev.mmrlx.compose.ui.icon.Icon +import dev.mmrlx.compose.ui.icon.IconButton +import dev.mmrlx.compose.ui.scaffold.Scaffold +import dev.mmrlx.compose.ui.theme.MMRLXTheme +import dev.mmrlx.compose.ui.toolbar.Toolbar +import dev.mmrlx.compose.ui.toolbar.ToolbarDefaults +import dev.mmrlx.compose.ui.toolbar.ToolbarScrollBehavior +import dev.mmrlx.compose.ui.toolbar.ToolbarTitle +import dev.mmrlx.compose.webui.WebUIView +import dev.mmrlx.compose.webui.insets +import dev.mmrlx.compose.webui.rememberWebUIState +import dev.mmrlx.webui.RouteRegistry +import dev.mmrlx.webui.RouteScope +import dev.mmrlx.webui.WebResourceResponse +import dev.mmrlx.webui.WebUIOptions +import java.net.HttpURLConnection +import java.net.URL + +@Composable +@Destination +fun DescriptionScreen(readmeUrl: String) { + val navigator = LocalDestinationsNavigator.current + val scrollBehavior = ToolbarDefaults.pinnedScrollBehavior() + val userPrefs = LocalUserPreferences.current + + val colors = MMRLXTheme.colors + val scheme = MaterialTheme.colorScheme.copy( + background = colors.background, + onBackground = colors.foreground, + surface = colors.code, + surfaceTint = colors.muted + ) + + val wstate = + rememberWebUIState( + "https://desc.mmrl.dev", + WebUIOptions("internal/assets/markdown.html") + ) { + it + .settings { + debug = BuildConfig.DEBUG + forceKillProcess = false + useConsoleInterceptor = false + darkMode = userPrefs.isDarkMode() + } + .client { + // not related to client + it.webview.setBackgroundColor(scheme.background.toArgb()) + } + .chromeClient { } + .registerRoutes { + val webColors = WebColors(scheme) + + internalRoute("readme.md") { + val connection = URL(readmeUrl).openConnection() as HttpURLConnection + connection.connect() + htmlResponse(connection.getInputStream()) + } + + internalRoute("insets.css") { + insets.css.asStyleResponse() + } + + internalRoute("colors.css") { + webColors.allCssColors.asStyleResponse() + } + + internalRoute("assets/*asset") { + val filePath = + request.params["asset"] ?: return@internalRoute notFoundResponse + + try { + val inputStream = kontext.assets.open(filePath) + val mimeType = MimeUtil.getMimeFromFileName(filePath) + response(mimeType = mimeType, data = inputStream) + } catch (e: Exception) { + console.error("Failed to open $filePath from the assets: $e") + notFoundResponse + } + } + } + } + + DisposableEffect(Unit) { + onDispose { + wstate.destroy() + } + } + + // We have to override otherwise the WebUI will close the entire app + BackHandler { + if (wstate.canGoBack()) { + wstate.goBack() + return@BackHandler + } + + navigator.popBackStack() + } + + Scaffold( + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + toolbar = { + TopBar0( + scrollBehavior = scrollBehavior, + navigator = navigator, + ) + }, + bottomBar = { + BottomBar() + }, + contentWindowInsets = WindowInsets.none, + ) { + wstate.insets( + PaddingValues( + top = contentPadding.calculateTopPadding(), + bottom = contentPadding.calculateBottomPadding(), + ) + ) + + WebUIView( + modifier = Modifier + .with(this@Scaffold) { + it.scaffoldHazeSource() + } + .fillMaxSize(), + state = wstate + ) + } +} + +@Composable +private fun TopBar0( + navigator: DestinationsNavigator, + scrollBehavior: ToolbarScrollBehavior, +) = Toolbar( + navigationIcon = { + IconButton(onClick = { navigator.popBackStack() }) { + Icon( + painter = painterResource(id = R.drawable.arrow_left), + contentDescription = null, + ) + } + }, + title = { ToolbarTitle(title = stringResource(id = R.string.view_module_about_this_module)) }, + scrollBehavior = scrollBehavior, +) + +private fun RouteRegistry.internalRoute( + name: String, + content: RouteScope.() -> WebResourceResponse, +) = route("internal/$name", content) \ No newline at end of file diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/NewViewScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/NewViewScreen.kt index 592b389c2..1995c48fb 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/NewViewScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/NewViewScreen.kt @@ -15,7 +15,6 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue @@ -73,6 +72,7 @@ import com.dergoogler.mmrl.viewmodel.ModuleViewModel import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import dev.chrisbanes.haze.hazeSource +import dev.mmrlx.compose.ui.toolbar.ToolbarDefaults import timber.log.Timber internal val listItemContentPaddingValues = PaddingValues(vertical = 16.dp, horizontal = 16.dp) @@ -181,7 +181,7 @@ fun NewViewScreen( ) } - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() + val scrollBehavior = ToolbarDefaults.pinnedScrollBehavior() val isBlacklisted by module.isBlacklisted diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/ViewDescriptionScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/ViewDescriptionScreen.kt index 1f58c4d60..a7fa6a9f5 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/ViewDescriptionScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/ViewDescriptionScreen.kt @@ -10,8 +10,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.Modifier @@ -35,6 +33,8 @@ import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.navigation.DestinationsNavigator import dev.chrisbanes.haze.hazeSource +import dev.mmrlx.compose.ui.toolbar.ToolbarDefaults +import dev.mmrlx.compose.ui.toolbar.ToolbarScrollBehavior import dev.mmrlx.compose.webui.WebUIView import dev.mmrlx.compose.webui.insets import dev.mmrlx.compose.webui.rememberWebUIState @@ -51,7 +51,7 @@ import java.net.URL fun ViewDescriptionScreen(readmeUrl: String) = LocalScreenProvider { val navigator = LocalDestinationsNavigator.current - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() + val scrollBehavior = ToolbarDefaults.pinnedScrollBehavior() val userPrefs = LocalUserPreferences.current val scheme = MaterialTheme.colorScheme @@ -155,7 +155,7 @@ fun ViewDescriptionScreen(readmeUrl: String) = @Composable private fun TopBar( navigator: DestinationsNavigator, - scrollBehavior: TopAppBarScrollBehavior, + scrollBehavior: ToolbarScrollBehavior, ) = BlurToolbar( navigationIcon = { IconButton(onClick = { navigator.popBackStack() }) { diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/AboutModule.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/AboutModule.kt index 49aa14686..3c593a474 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/AboutModule.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/AboutModule.kt @@ -29,7 +29,7 @@ import com.dergoogler.mmrl.ui.providable.LocalDestinationsNavigator import com.dergoogler.mmrl.ui.providable.LocalOnlineModule import com.dergoogler.mmrl.ui.providable.LocalRepo import com.dergoogler.mmrl.ui.screens.moduleView.listItemContentPaddingValues -import com.ramcosta.composedestinations.generated.destinations.ViewDescriptionScreenDestination +import com.ramcosta.composedestinations.generated.destinations.DescriptionScreenDestination @Composable internal fun AboutModule() { @@ -45,7 +45,7 @@ internal fun AboutModule() { if (!module.readme.isNullOrBlank()) { ButtonItem( onClick = { - navigator.navigate(ViewDescriptionScreenDestination(module.readme)) + navigator.navigate(DescriptionScreenDestination(module.readme)) }, ) { Icon( diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/modules/ModulesScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/modules/ModulesScreen.kt index c7a0fd250..991446019 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/modules/ModulesScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/modules/ModulesScreen.kt @@ -15,8 +15,6 @@ import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults.Indicator import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState @@ -56,6 +54,8 @@ import com.dergoogler.mmrl.ui.providable.LocalUserPreferences import com.dergoogler.mmrl.viewmodel.ModulesViewModel import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph +import dev.mmrlx.compose.ui.toolbar.ToolbarDefaults +import dev.mmrlx.compose.ui.toolbar.ToolbarScrollBehavior @Destination @Composable @@ -69,7 +69,7 @@ fun ModulesScreen(viewModel: ModulesViewModel = hiltViewModel()) = val state by viewModel.screenState.collectAsStateWithLifecycle() val isLoading by viewModel.isLoading.collectAsStateWithLifecycle() - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() + val scrollBehavior = ToolbarDefaults.pinnedScrollBehavior() val pullToRefreshState = rememberPullToRefreshState() @@ -178,7 +178,7 @@ private fun TopBar( onOpenSearch: () -> Unit, onCloseSearch: () -> Unit, setMenu: (ModulesMenu) -> Unit, - scrollBehavior: TopAppBarScrollBehavior, + scrollBehavior: ToolbarScrollBehavior, ) { val width = currentScreenWidth() diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repositories/RepositoriesScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repositories/RepositoriesScreen.kt index 1e78e2dd0..2dc769b2b 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repositories/RepositoriesScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repositories/RepositoriesScreen.kt @@ -18,8 +18,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.material3.TextButton -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults.Indicator import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState @@ -52,6 +50,7 @@ import com.dergoogler.mmrl.model.local.BulkModule import com.dergoogler.mmrl.ui.activity.terminal.install.InstallActivity import com.dergoogler.mmrl.ui.animate.slideInTopToBottom import com.dergoogler.mmrl.ui.animate.slideOutBottomToTop +import com.dergoogler.mmrl.ui.component.BottomBar import com.dergoogler.mmrl.ui.component.Loading import com.dergoogler.mmrl.ui.component.LocalScreenProvider import com.dergoogler.mmrl.ui.component.PageIndicator @@ -68,6 +67,8 @@ import com.dergoogler.mmrl.viewmodel.RepositoriesViewModel import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.generated.destinations.SearchScreenDestination +import dev.mmrlx.compose.ui.toolbar.ToolbarDefaults +import dev.mmrlx.compose.ui.toolbar.ToolbarScrollBehavior import timber.log.Timber import kotlin.reflect.KFunction1 @@ -78,7 +79,7 @@ fun RepositoriesScreen() = val viewModel = hiltViewModel() val userPrefs = LocalUserPreferences.current - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() + val scrollBehavior = ToolbarDefaults.pinnedScrollBehavior() val list by viewModel.repos.collectAsStateWithLifecycle() val bulkInstallViewModel = LocalBulkInstall.current val bulkModules by bulkInstallViewModel.bulkModules.collectAsStateWithLifecycle() @@ -164,6 +165,9 @@ fun RepositoriesScreen() = scrollBehavior = scrollBehavior, ) }, + bottomBar = { + BottomBar() + }, contentWindowInsets = WindowInsets.none, floatingActionButton = { if (viewModel.platform.isValid) { @@ -305,7 +309,7 @@ private fun AddDialog( private fun TopBar( onAdd: () -> Unit, setMenu: KFunction1, - scrollBehavior: TopAppBarScrollBehavior, + scrollBehavior: ToolbarScrollBehavior, ) { val width = currentScreenWidth() val navigator = LocalDestinationsNavigator.current diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryScreen.kt index 86be545ce..f05eb90a6 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryScreen.kt @@ -19,8 +19,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect @@ -58,6 +56,7 @@ import com.dergoogler.mmrl.ext.none import com.dergoogler.mmrl.ext.nullply import com.dergoogler.mmrl.ext.onClick import com.dergoogler.mmrl.ext.stripLinks +import com.dergoogler.mmrl.ui.component.BottomBar import com.dergoogler.mmrl.ui.component.Cover import com.dergoogler.mmrl.ui.component.LabelItem import com.dergoogler.mmrl.ui.component.LabelItemDefaults @@ -74,6 +73,8 @@ import com.dergoogler.mmrl.viewmodel.RepositoryViewModel import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import dev.dergoogler.mmrl.compat.core.LocalUriHandler +import dev.mmrlx.compose.ui.toolbar.ToolbarDefaults +import dev.mmrlx.compose.ui.toolbar.ToolbarScrollBehavior @Destination @Composable @@ -92,7 +93,7 @@ fun RepositoryScreen( val closeSearch = viewModel::closeSearch val search = viewModel::search val isSearch by viewModel::isSearch - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() + val scrollBehavior = ToolbarDefaults.pinnedScrollBehavior() var coverHeight by remember { mutableIntStateOf(0) } var cardHeight by remember { mutableIntStateOf(0) } @@ -116,6 +117,9 @@ fun RepositoryScreen( setMenu = viewModel::setRepositoryMenu, ) }, + bottomBar = { + BottomBar() + }, contentWindowInsets = WindowInsets.none, ) { innerPadding -> if (modules.isEmpty()) { @@ -273,7 +277,7 @@ private fun TopBar0( onQueryChange: (String) -> Unit, onOpenSearch: () -> Unit, onCloseSearch: () -> Unit, - scrollBehavior: TopAppBarScrollBehavior, + scrollBehavior: ToolbarScrollBehavior, setMenu: (RepositoryMenu) -> Unit, ) { val state = rememberBlurToolbarState(scrollBehavior, fade = true) diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/logviewer/LogScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/logviewer/LogScreen.kt index 8e202aed4..9c0b235f8 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/logviewer/LogScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/logviewer/LogScreen.kt @@ -27,8 +27,6 @@ import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect @@ -69,6 +67,8 @@ import com.dergoogler.mmrl.utils.log.Logcat import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import dev.chrisbanes.haze.hazeSource +import dev.mmrlx.compose.ui.toolbar.ToolbarDefaults +import dev.mmrlx.compose.ui.toolbar.ToolbarScrollBehavior import kotlinx.coroutines.launch private val priorities = listOf("VERBOSE", "DEBUG", "INFO", "WARN", "ERROR") @@ -116,7 +116,7 @@ object LogColors { fun LogScreen() = LocalScreenProvider { val context = LocalContext.current - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() + val scrollBehavior = ToolbarDefaults.pinnedScrollBehavior() val state = rememberLazyListState() var priority by remember { mutableStateOf("DEBUG") } @@ -205,7 +205,7 @@ fun LogScreen() = private fun TopBar( priority: String, onPriority: (String) -> Unit, - scrollBehavior: TopAppBarScrollBehavior, + scrollBehavior: ToolbarScrollBehavior, ) = BlurNavigateUpToolbar( title = stringResource(id = R.string.settings_log_viewer), navController = LocalNavController.current, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3593d02c5..d6483bf6e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -43,7 +43,7 @@ squareRetrofit = "2.11.0" squareOkhttp = "4.12.0" squareMoshi = "1.15.1" runtimeAndroid = "1.12.1" -mmrlx = "1.0.229" +mmrlx = "1.0.230" workRuntimeKtx = "2.10.0" lifecycleProcess = "2.8.7" apacheCommons = "1.27.1" From f7b6ec4039c081f0a09d960edae70333ef2634bc Mon Sep 17 00:00:00 2001 From: Der_Googler <54764558+DerGoogler@users.noreply.github.com> Date: Sun, 18 Oct 2026 19:47:47 +0200 Subject: [PATCH 19/24] Migrate settings UI to mmrlx list and scaffold Move settings screens from the in-app list item DSL to the dev.mmrlx.compose.ui list, scaffold and toolbar components, and bump mmrlx to 1.0.231. - Rewrite SettingsScaffold on top of the mmrlx Scaffold. It now takes isMainPage and canScroll instead of allowNavigateBack, modifier and absolute. - Add a local NavigateUpToolbar. - Replace TextEditDialogItem, ButtonItem and Labels with InputDialogItem, RawItem and Supporting. - Rename RadioDialogItem option data to RadioDialogOption. - Move the home items package to home.components. - Remove the blacklist screen, its items and the blacklist alert. - Remove the unused RootScreen and the commented-out drawer code in MainScreen, and drop the @Destination annotation from MainScreen. - Drop BottomBar from DescriptionScreen. --- .../mmrl/model/local/FeaturedManager.kt | 4 +- .../dergoogler/mmrl/ui/component/BottomBar.kt | 11 +- .../dergoogler/mmrl/ui/component/Scaffold.kt | 96 +++--- .../mmrl/ui/screens/home/HomeScreen.kt | 6 +- .../home/{items => components}/NonRootItem.kt | 2 +- .../RebootBottomSheet.kt | 2 +- .../home/{items => components}/RootItem.kt | 2 +- .../mmrl/ui/screens/main/MainScreen.kt | 74 ----- .../mmrl/ui/screens/main/RootScreen.kt | 68 ----- .../moduleDetails/DescriptionScreen.kt | 4 - .../ui/screens/moduleView/NewViewScreen.kt | 4 + .../ui/screens/moduleView/sections/Alerts.kt | 24 -- .../mmrl/ui/screens/modules/ModulesScreen.kt | 4 + .../ui/screens/settings/SettingsScreen.kt | 283 +++++++++--------- .../settings/appearance/AppearanceScreen.kt | 18 +- .../appearance/items/DownloadPathItem.kt | 18 +- .../appearance/screens/AppThemeScreen.kt | 2 - .../settings/blacklist/BlacklistScreen.kt | 54 ---- .../settings/blacklist/items/ModuleItem.kt | 121 -------- .../settings/changelogs/ChangelogScreen.kt | 18 +- .../changelogs/items/ChangelogItem.kt | 21 +- .../settings/developer/DeveloperScreen.kt | 14 +- .../screens/settings/modules/ModulesScreen.kt | 23 +- .../ui/screens/settings/other/OtherScreen.kt | 18 +- .../settings/security/SecurityScreen.kt | 10 +- .../settings/terminal/TerminalScreen.kt | 8 +- .../screens/settings/updates/UpdatesScreen.kt | 66 ++-- .../compose/ui/toolbar/NavigateUpToolbar.kt | 129 ++++++++ gradle/libs.versions.toml | 2 +- 29 files changed, 439 insertions(+), 667 deletions(-) rename app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/{items => components}/NonRootItem.kt (98%) rename app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/{items => components}/RebootBottomSheet.kt (98%) rename app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/{items => components}/RootItem.kt (99%) delete mode 100644 app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/main/RootScreen.kt delete mode 100644 app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/blacklist/BlacklistScreen.kt delete mode 100644 app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/blacklist/items/ModuleItem.kt create mode 100644 app/src/main/kotlin/dev/mmrlx/compose/ui/toolbar/NavigateUpToolbar.kt diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/model/local/FeaturedManager.kt b/app/src/main/kotlin/com/dergoogler/mmrl/model/local/FeaturedManager.kt index 9f915262a..db4a3bc9c 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/model/local/FeaturedManager.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/model/local/FeaturedManager.kt @@ -5,7 +5,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import com.dergoogler.mmrl.R import com.dergoogler.mmrl.datastore.model.WorkingMode -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.RadioDialogItem +import dev.mmrlx.compose.ui.list.component.RadioDialogOption data class FeaturedManager( private val nameAs: Any, @@ -15,7 +15,7 @@ data class FeaturedManager( ) { @Composable fun toRadioDialogItem() = - RadioDialogItem( + RadioDialogOption( title = name, value = workingMode, ) diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/BottomBar.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/BottomBar.kt index 9cf34f3ad..e7f784a2b 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/BottomBar.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/BottomBar.kt @@ -42,12 +42,13 @@ fun BottomBar(updates: Int = 0) { BaseNavIcon(screen, isSelected, updates) }, label = { - Text( - text = stringResource(id = screen.label), - style = MMRLXTheme.typography.labelLarge, - ) + if (!prefs.hideBottomBarLabels) { + Text( + text = stringResource(id = screen.label), + style = MMRLXTheme.typography.labelLarge, + ) + } }, - alwaysShowLabel = !prefs.hideBottomBarLabels, selected = isSelected, onClick = { if (isSelected) { diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/Scaffold.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/Scaffold.kt index effb1ecbb..78b8f0884 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/Scaffold.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/Scaffold.kt @@ -1,116 +1,96 @@ package com.dergoogler.mmrl.ui.component import androidx.annotation.StringRes -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.stringResource import com.dergoogler.mmrl.ext.none -import com.dergoogler.mmrl.ext.systemBarsPaddingEnd -import com.dergoogler.mmrl.ui.component.listItem.dsl.List -import com.dergoogler.mmrl.ui.component.listItem.dsl.ListScope -import com.dergoogler.mmrl.ui.component.scaffold.ResponsiveScaffold -import com.dergoogler.mmrl.ui.component.toolbar.BlurNavigateUpToolbar -import com.dergoogler.mmrl.ui.component.toolbar.BlurToolbar -import com.dergoogler.mmrl.ui.component.toolbar.ToolbarTitle -import com.dergoogler.mmrl.ui.providable.LocalHazeState -import com.dergoogler.mmrl.ui.providable.LocalMainScreenInnerPaddings import com.dergoogler.mmrl.ui.providable.LocalNavController -import dev.chrisbanes.haze.hazeSource +import dev.mmrlx.compose.ui.list.List +import dev.mmrlx.compose.ui.list.ListScope +import dev.mmrlx.compose.ui.scaffold.Scaffold +import dev.mmrlx.compose.ui.toolbar.NavigateUpToolbar +import dev.mmrlx.compose.ui.toolbar.Toolbar import dev.mmrlx.compose.ui.toolbar.ToolbarDefaults +import dev.mmrlx.compose.ui.toolbar.ToolbarTitle @Composable fun SettingsScaffold( - modifier: ScaffoldModifier = ScaffoldDefaults.settingsScaffoldScrollModifier, @StringRes title: Int, - allowNavigateBack: Boolean = true, + isMainPage: Boolean = false, + canScroll: Boolean = true, actions: @Composable (RowScope.() -> Unit) = {}, floatingActionButton: @Composable () -> Unit = {}, - absolute: @Composable (BoxScope.() -> Unit) = {}, relative: @Composable (ListScope.() -> Unit), ) = SettingsScaffold( title = stringResource(id = title), - modifier = modifier, actions = actions, - allowNavigateBack = allowNavigateBack, + isMainPage = isMainPage, + canScroll = canScroll, floatingActionButton = floatingActionButton, - absolute = absolute, relative = relative, ) @Composable fun SettingsScaffold( - modifier: ScaffoldModifier = ScaffoldDefaults.settingsScaffoldScrollModifier, title: String, - allowNavigateBack: Boolean = true, + isMainPage: Boolean = false, + canScroll: Boolean = true, actions: @Composable (RowScope.() -> Unit) = {}, floatingActionButton: @Composable () -> Unit = {}, - absolute: @Composable (BoxScope.() -> Unit) = {}, relative: @Composable (ListScope.() -> Unit), -) = LocalScreenProvider { +) { val navController = LocalNavController.current - val scrollBehavior = ToolbarDefaults.pinnedScrollBehavior() + val scrollBehavior = ToolbarDefaults.fadePinnedScrollBehavior() + + val scrollModifier = if (canScroll) { + Modifier.verticalScroll(rememberScrollState()) + } else Modifier - ResponsiveScaffold( + Scaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), - topBar = { - if (allowNavigateBack) { - BlurNavigateUpToolbar( + toolbar = { + if (!isMainPage) { + NavigateUpToolbar( title = title, navController = navController, actions = actions, - fade = true, - fadeDistance = 50f, scrollBehavior = scrollBehavior, ) } else { - BlurToolbar( + Toolbar( title = { ToolbarTitle(title = title) }, - fade = true, - fadeDistance = 50f, actions = actions, scrollBehavior = scrollBehavior, ) } }, + bottomBar = { + if (isMainPage) { + BottomBar() + } + }, floatingActionButton = floatingActionButton, contentWindowInsets = WindowInsets.none, - ) { innerPadding -> - Box( - modifier = - Modifier - .hazeSource(state = LocalHazeState.current) - .then(modifier.box), - ) { - List( - modifier = - modifier.column - .systemBarsPaddingEnd() - .padding(top = innerPadding.calculateTopPadding()), - content = { - relative() - - val paddingValues = LocalMainScreenInnerPaddings.current - Spacer(modifier = Modifier.height(paddingValues.calculateBottomPadding())) - }, - ) - - absolute() - } + ) { + List( + modifier = Modifier + .then(scrollModifier) + .scaffoldHazeSource() + .scaffoldPadding(), + content = { + relative() + }, + ) } } diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/HomeScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/HomeScreen.kt index 7408f2401..9b98cb3f8 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/HomeScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/HomeScreen.kt @@ -57,9 +57,9 @@ import com.dergoogler.mmrl.ui.remember.rememberLocalAnalytics import com.dergoogler.mmrl.ui.remember.seLinuxContext import com.dergoogler.mmrl.ui.remember.superUserCount import com.dergoogler.mmrl.ui.remember.versionName -import com.dergoogler.mmrl.ui.screens.home.items.NonRootItem -import com.dergoogler.mmrl.ui.screens.home.items.RebootBottomSheet -import com.dergoogler.mmrl.ui.screens.home.items.RootItem +import com.dergoogler.mmrl.ui.screens.home.components.NonRootItem +import com.dergoogler.mmrl.ui.screens.home.components.RebootBottomSheet +import com.dergoogler.mmrl.ui.screens.home.components.RootItem import com.dergoogler.mmrl.ui.screens.settings.changelogs.items.ChangelogBottomSheet import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/items/NonRootItem.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/components/NonRootItem.kt similarity index 98% rename from app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/items/NonRootItem.kt rename to app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/components/NonRootItem.kt index 20c6e48a7..7e9f17e94 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/items/NonRootItem.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/components/NonRootItem.kt @@ -1,4 +1,4 @@ -package com.dergoogler.mmrl.ui.screens.home.items +package com.dergoogler.mmrl.ui.screens.home.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/items/RebootBottomSheet.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/components/RebootBottomSheet.kt similarity index 98% rename from app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/items/RebootBottomSheet.kt rename to app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/components/RebootBottomSheet.kt index 47318ff42..c56e288a6 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/items/RebootBottomSheet.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/components/RebootBottomSheet.kt @@ -1,4 +1,4 @@ -package com.dergoogler.mmrl.ui.screens.home.items +package com.dergoogler.mmrl.ui.screens.home.components import android.content.Context import android.os.Build diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/items/RootItem.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/components/RootItem.kt similarity index 99% rename from app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/items/RootItem.kt rename to app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/components/RootItem.kt index 2700a6d96..d444da02e 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/items/RootItem.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/components/RootItem.kt @@ -1,4 +1,4 @@ -package com.dergoogler.mmrl.ui.screens.home.items +package com.dergoogler.mmrl.ui.screens.home.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/main/MainScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/main/MainScreen.kt index 1c6c73a6e..bc28015d4 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/main/MainScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/main/MainScreen.kt @@ -37,7 +37,6 @@ import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.rememberHazeState import dev.mmrlx.compose.ui.scaffold.Scaffold -@Destination @Composable fun MainScreen() { // val width = currentScreenWidth() @@ -60,79 +59,6 @@ fun MainScreen() { LocalSnackbarHost provides snackbarHostState, LocalBulkInstall provides bulkInstallViewModel, ) { - /*if (width.isLarge) { - Scaffold( - contentWindowInsets = WindowInsets.none, - ) { paddingValues -> - val navController = LocalNavController.current - - PermanentNavigationDrawer( - drawerContent = { - PermanentDrawerSheet( - modifier = - Modifier - .width(240.dp), - ) { - TopAppBar( - title = { - TopAppBarEventIcon() - }, - ) - - LazyColumn( - contentPadding = PaddingValues(8.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), - ) { - items( - items = MainDestination.entries, - key = { it.name }, - ) { screen -> - val isSelected by navController.isRouteOnBackStackAsState(screen.direction) - - if (!screen.isAccessible) return@items - - NavigationDrawerItem( - icon = { - BaseNavIcon(screen, isSelected, updates) - }, - label = { - Text( - text = stringResource(id = screen.label), - style = MaterialTheme.typography.labelLarge, - ) - }, - selected = isSelected, - onClick = { - if (isSelected) { - navigator.popBackStack(screen.direction, false) - } - navigator.navigate(screen.direction) { - popUpTo(NavGraphs.root) { - saveState = true - } - launchSingleTop = true - restoreState = true - } - }, - ) - } - } - } - }, - ) { - CompositionLocalProvider( - LocalMainScreenInnerPaddings provides paddingValues, - ) { - CurrentNavHost( - Modifier.padding(paddingValues), - ) - } - } - } - - return@CompositionLocalProvider - }*/ - Scaffold( contentWindowInsets = WindowInsets.none, ) { diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/main/RootScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/main/RootScreen.kt deleted file mode 100644 index f8b1303fe..000000000 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/main/RootScreen.kt +++ /dev/null @@ -1,68 +0,0 @@ -package com.dergoogler.mmrl.ui.screens.main - -import androidx.compose.animation.AnimatedContentTransitionScope -import androidx.compose.animation.EnterTransition -import androidx.compose.animation.ExitTransition -import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.navigation.NavBackStackEntry -import com.dergoogler.mmrl.ext.none -import com.dergoogler.mmrl.ui.component.scaffold.Scaffold -import com.dergoogler.mmrl.ui.providable.LocalBulkInstall -import com.dergoogler.mmrl.ui.providable.LocalNavController -import com.dergoogler.mmrl.ui.providable.LocalSnackbarHost -import com.dergoogler.mmrl.ui.providable.LocalUserPreferences -import com.dergoogler.mmrl.utils.initPlatform -import com.dergoogler.mmrl.viewmodel.BulkInstallViewModel -import com.ramcosta.composedestinations.DestinationsNavHost -import com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationStyle -import com.ramcosta.composedestinations.generated.NavGraphs - -@Composable -fun RootScreen() { - val navController = LocalNavController.current - val snackbarHostState = remember { SnackbarHostState() } - val bulkInstallViewModel: BulkInstallViewModel = hiltViewModel() - val context = LocalContext.current - val userPreferences = LocalUserPreferences.current - - LaunchedEffect(Unit) { - initPlatform(context, userPreferences.workingMode.toPlatform()) - } - - Scaffold( - snackbarHost = { SnackbarHost(snackbarHostState) }, - contentWindowInsets = WindowInsets.none, - ) { paddingValues -> - CompositionLocalProvider( - LocalSnackbarHost provides snackbarHostState, - LocalBulkInstall provides bulkInstallViewModel, - ) { - DestinationsNavHost( - modifier = Modifier.padding(bottom = paddingValues.calculateBottomPadding()), - navGraph = NavGraphs.root, - navController = navController, - defaultTransitions = - object : NavHostAnimatedDestinationStyle() { - override val enterTransition: AnimatedContentTransitionScope.() -> EnterTransition - get() = { fadeIn(animationSpec = tween(340)) } - override val exitTransition: AnimatedContentTransitionScope.() -> ExitTransition - get() = { fadeOut(animationSpec = tween(340)) } - }, - ) - } - } -} diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleDetails/DescriptionScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleDetails/DescriptionScreen.kt index 737af2879..c2961ff64 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleDetails/DescriptionScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleDetails/DescriptionScreen.kt @@ -16,7 +16,6 @@ import com.dergoogler.mmrl.BuildConfig import com.dergoogler.mmrl.R import com.dergoogler.mmrl.ext.none import com.dergoogler.mmrl.model.WebColors -import com.dergoogler.mmrl.ui.component.BottomBar import com.dergoogler.mmrl.ui.providable.LocalDestinationsNavigator import com.dergoogler.mmrl.ui.providable.LocalUserPreferences import com.dergoogler.mmrl.utils.MimeUtil @@ -131,9 +130,6 @@ fun DescriptionScreen(readmeUrl: String) { navigator = navigator, ) }, - bottomBar = { - BottomBar() - }, contentWindowInsets = WindowInsets.none, ) { wstate.insets( diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/NewViewScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/NewViewScreen.kt index 1995c48fb..ef88b9c08 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/NewViewScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/NewViewScreen.kt @@ -42,6 +42,7 @@ import com.dergoogler.mmrl.model.online.OnlineModule import com.dergoogler.mmrl.model.online.VersionItem import com.dergoogler.mmrl.model.online.isBlacklisted import com.dergoogler.mmrl.ui.activity.terminal.install.InstallActivity +import com.dergoogler.mmrl.ui.component.BottomBar import com.dergoogler.mmrl.ui.component.Cover import com.dergoogler.mmrl.ui.component.LocalScreenProvider import com.dergoogler.mmrl.ui.component.scaffold.Scaffold @@ -221,6 +222,9 @@ fun NewViewScreen( topBar = { Toolbar() }, + bottomBar = { + BottomBar() + }, contentWindowInsets = WindowInsets.none, ) { innerPadding -> this@Scaffold.ResponsiveContent { diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Alerts.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Alerts.kt index d726e38ee..892c889d9 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Alerts.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Alerts.kt @@ -5,20 +5,15 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.dergoogler.mmrl.R -import com.dergoogler.mmrl.model.online.hasBlacklist import com.dergoogler.mmrl.model.online.hasValidMessage import com.dergoogler.mmrl.ui.component.Alert import com.dergoogler.mmrl.ui.providable.LocalOnlineModule import com.dergoogler.mmrl.ui.screens.moduleView.providable.LocalModuleViewModel -import com.dergoogler.mmrl.ui.screens.settings.blacklist.items.BlacklistBottomSheet private val alertPadding = PaddingValues(horizontal = 16.dp) @@ -32,25 +27,6 @@ internal fun Alerts() { module.manager(viewModel.platform) } - module.hasBlacklist { - var open by remember { mutableStateOf(false) } - if (open) { - BlacklistBottomSheet( - module = it, - onClose = { open = false }, - ) - } - - Alert( - icon = R.drawable.alert_circle_filled, - title = stringResource(R.string.blacklisted), - backgroundColor = MaterialTheme.colorScheme.errorContainer, - textColor = MaterialTheme.colorScheme.onErrorContainer, - message = stringResource(R.string.blacklisted_desc), - outsideContentPadding = alertPadding, - ) - } - manager.isNotSupportedRootVersion(viewModel.versionCode) { min -> if (min == -1) { Alert( diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/modules/ModulesScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/modules/ModulesScreen.kt index 991446019..1d0abfe6b 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/modules/ModulesScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/modules/ModulesScreen.kt @@ -43,6 +43,7 @@ import com.dergoogler.mmrl.ext.systemBarsPaddingEnd import com.dergoogler.mmrl.model.local.LocalModule import com.dergoogler.mmrl.model.online.VersionItem import com.dergoogler.mmrl.ui.activity.terminal.install.InstallActivity +import com.dergoogler.mmrl.ui.component.BottomBar import com.dergoogler.mmrl.ui.component.Loading import com.dergoogler.mmrl.ui.component.LocalScreenProvider import com.dergoogler.mmrl.ui.component.PageIndicator @@ -109,6 +110,9 @@ fun ModulesScreen(viewModel: ModulesViewModel = hiltViewModel()) = scrollBehavior = scrollBehavior, ) }, + bottomBar = { + BottomBar() + }, floatingActionButton = { AnimatedVisibility( visible = showFab, diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/SettingsScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/SettingsScreen.kt index dcdf30634..7cbe483c5 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/SettingsScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/SettingsScreen.kt @@ -18,14 +18,6 @@ import com.dergoogler.mmrl.model.local.FeaturedManager.Companion.name import com.dergoogler.mmrl.ui.component.SettingsScaffold import com.dergoogler.mmrl.ui.component.dialog.ConfirmData import com.dergoogler.mmrl.ui.component.dialog.rememberConfirm -import com.dergoogler.mmrl.ui.component.listItem.dsl.List -import com.dergoogler.mmrl.ui.component.listItem.dsl.ListItemSlot -import com.dergoogler.mmrl.ui.component.listItem.dsl.ListScope -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.ButtonItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.RadioDialogItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Description -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Icon -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Title import com.dergoogler.mmrl.ui.providable.LocalDestinationsNavigator import com.dergoogler.mmrl.ui.providable.LocalSettings import com.dergoogler.mmrl.ui.providable.LocalUserPreferences @@ -33,7 +25,6 @@ import com.jakewharton.processphoenix.ProcessPhoenix import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.generated.destinations.AppearanceScreenDestination -import com.ramcosta.composedestinations.generated.destinations.BlacklistScreenDestination import com.ramcosta.composedestinations.generated.destinations.ChangelogScreenDestination import com.ramcosta.composedestinations.generated.destinations.DeveloperScreenDestination import com.ramcosta.composedestinations.generated.destinations.LogScreenDestination @@ -43,6 +34,13 @@ import com.ramcosta.composedestinations.generated.destinations.SecurityScreenDes import com.ramcosta.composedestinations.generated.destinations.TerminalScreenDestination import com.ramcosta.composedestinations.generated.destinations.UpdatesScreenDestination import com.ramcosta.composedestinations.spec.DirectionDestinationSpec +import dev.mmrlx.compose.ui.list.ListItemSlot +import dev.mmrlx.compose.ui.list.ListScope +import dev.mmrlx.compose.ui.list.component.ButtonItem +import dev.mmrlx.compose.ui.list.component.RadioDialogItem +import dev.mmrlx.compose.ui.list.component.item.Description +import dev.mmrlx.compose.ui.list.component.item.Icon +import dev.mmrlx.compose.ui.list.component.item.Title @Destination @Composable @@ -54,151 +52,142 @@ fun SettingsScreen() { val confirm = rememberConfirm(context) SettingsScaffold( - allowNavigateBack = false, + isMainPage = true, title = R.string.page_settings, ) { - List { - context.isPackageInstalled(userPreferences.webuixPackageName).takeFalse { - ButtonItem( - onClick = { - browser.openUri( - if (BuildConfig.IS_GOOGLE_PLAY_BUILD) { - "https://play.google.com/store/apps/details?id=com.dergoogler.mmrl.wx" - } else { - "https://github.com/MMRLApp/WebUI-X-Portable" - }, - ) - }, - ) { - Icon( - painter = painterResource(R.drawable.sandbox), - ) - Title(R.string.settings_try_wxp) - Icon( - slot = ListItemSlot.End, - size = 12.dp, - painter = painterResource(R.drawable.external_link), + context.isPackageInstalled(userPreferences.webuixPackageName).takeFalse { + ButtonItem( + onClick = { + browser.openUri( + if (BuildConfig.IS_GOOGLE_PLAY_BUILD) { + "https://play.google.com/store/apps/details?id=com.dergoogler.mmrl.wx" + } else { + "https://github.com/MMRLApp/WebUI-X-Portable" + }, ) - } + }, + ) { + Icon( + painter = painterResource(R.drawable.sandbox), + ) + Title(R.string.settings_try_wxp) + Icon( + painter = painterResource(R.drawable.external_link), + slot = ListItemSlot.End, + size = 12.dp, + ) } + } - NavButton( - route = AppearanceScreenDestination, - icon = R.drawable.color_swatch, - title = R.string.settings_appearance, - desc = R.string.settings_appearance_desc, - ) - - NavButton( - route = SecurityScreenDestination, - icon = R.drawable.shield, - title = R.string.settings_security, - desc = R.string.settings_security_desc, - ) - - NavButton( - route = UpdatesScreenDestination, - icon = R.drawable.refresh, - title = R.string.settings_updates, - desc = R.string.settings_updates_desc, - ) - - NavButton( - route = ModulesScreen2Destination, - icon = R.drawable.stack_middle, - title = R.string.settings_modules, - desc = R.string.settings_modules_desc, - ) - - NavButton( - route = TerminalScreenDestination, - icon = R.drawable.terminal_2_outlined, - title = R.string.settings_terminal, - desc = R.string.settings_terminal_desc, - ) - - NavButton( - route = OtherScreenDestination, - icon = R.drawable.tool, - title = R.string.settings_other, - desc = R.string.settings_other_desc, - ) - - LinkButton( - uri = Const.RESOURCES_URL, - icon = R.drawable.file_3d, - title = R.string.settings_resources, - desc = R.string.settings_resources_desc, - ) - - val manager = - FeaturedManager.managers.find { userPreferences.workingMode == it.workingMode } - - manager.nullable { mng -> - RadioDialogItem( - selection = mng.workingMode, - options = FeaturedManager.managers.map { it.toRadioDialogItem() }, - onConfirm = { - confirm( - ConfirmData( - title = context.getString(R.string.change_platform), - description = context.getString(R.string.working_mode_change_dialog_desc), - closeText = context.getString(R.string.keep), - onClose = {}, - confirmText = context.getString(R.string.apply), - onConfirm = { - viewModel.setWorkingMode(it.value) - ProcessPhoenix.triggerRebirth(context) - }, - ), - ) - }, - ) { - Icon(painter = painterResource(mng.icon)) - Title(R.string.platform) - Description(mng.name) - } + NavButton( + route = AppearanceScreenDestination, + icon = R.drawable.color_swatch, + title = R.string.settings_appearance, + desc = R.string.settings_appearance_desc, + ) + + NavButton( + route = SecurityScreenDestination, + icon = R.drawable.shield, + title = R.string.settings_security, + desc = R.string.settings_security_desc, + ) + + NavButton( + route = UpdatesScreenDestination, + icon = R.drawable.refresh, + title = R.string.settings_updates, + desc = R.string.settings_updates_desc, + ) + + NavButton( + route = ModulesScreen2Destination, + icon = R.drawable.stack_middle, + title = R.string.settings_modules, + desc = R.string.settings_modules_desc, + ) + + NavButton( + route = TerminalScreenDestination, + icon = R.drawable.terminal_2_outlined, + title = R.string.settings_terminal, + desc = R.string.settings_terminal_desc, + ) + + NavButton( + route = OtherScreenDestination, + icon = R.drawable.tool, + title = R.string.settings_other, + desc = R.string.settings_other_desc, + ) + + LinkButton( + uri = Const.RESOURCES_URL, + icon = R.drawable.file_3d, + title = R.string.settings_resources, + desc = R.string.settings_resources_desc, + ) + + val manager = + FeaturedManager.managers.find { userPreferences.workingMode == it.workingMode } + + manager.nullable { mng -> + RadioDialogItem( + selection = mng.workingMode, + options = FeaturedManager.managers.map { it.toRadioDialogItem() }, + onConfirm = { + confirm( + ConfirmData( + title = context.getString(R.string.change_platform), + description = context.getString(R.string.working_mode_change_dialog_desc), + closeText = context.getString(R.string.keep), + onClose = {}, + confirmText = context.getString(R.string.apply), + onConfirm = { + viewModel.setWorkingMode(it.value) + ProcessPhoenix.triggerRebirth(context) + }, + ), + ) + }, + ) { + Icon(painter = painterResource(mng.icon)) + Title(R.string.platform) + Description(mng.name) } - - NavButton( - route = ChangelogScreenDestination, - icon = R.drawable.files, - title = R.string.settings_changelog, - desc = R.string.settings_changelo_desc, - ) - - NavButton( - route = BlacklistScreenDestination, - icon = R.drawable.file_shredder, - title = R.string.settings_blacklist, - desc = R.string.settings_blacklist_desc, - ) - - NavButton( - route = LogScreenDestination, - icon = R.drawable.logs, - title = R.string.settings_log_viewer, - ) - - NavButton( - route = DeveloperScreenDestination, - icon = R.drawable.bug, - title = R.string.settings_developer, - desc = R.string.settings_developer_desc, - ) - - LinkButton( - uri = Const.PRIVACY_POLICY_URL, - icon = R.drawable.spy, - title = R.string.settings_privacy_policy, - ) - - LinkButton( - uri = Const.TERMS_OF_SERVICE_URL, - icon = R.drawable.files, - title = R.string.settings_terms_of_service, - ) } + + NavButton( + route = ChangelogScreenDestination, + icon = R.drawable.files, + title = R.string.settings_changelog, + desc = R.string.settings_changelo_desc, + ) + + NavButton( + route = LogScreenDestination, + icon = R.drawable.logs, + title = R.string.settings_log_viewer, + ) + + NavButton( + route = DeveloperScreenDestination, + icon = R.drawable.bug, + title = R.string.settings_developer, + desc = R.string.settings_developer_desc, + ) + + LinkButton( + uri = Const.PRIVACY_POLICY_URL, + icon = R.drawable.spy, + title = R.string.settings_privacy_policy, + ) + + LinkButton( + uri = Const.TERMS_OF_SERVICE_URL, + icon = R.drawable.files, + title = R.string.settings_terms_of_service, + ) } } diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/appearance/AppearanceScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/appearance/AppearanceScreen.kt index 43a79ec0e..095706acb 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/appearance/AppearanceScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/appearance/AppearanceScreen.kt @@ -7,12 +7,6 @@ import com.dergoogler.mmrl.R import com.dergoogler.mmrl.ext.toFormattedDateSafely import com.dergoogler.mmrl.ui.component.LabelItem import com.dergoogler.mmrl.ui.component.SettingsScaffold -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.SwitchItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.TextEditDialogItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Description -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.DialogDescription -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Labels -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Title import com.dergoogler.mmrl.ui.providable.LocalSettings import com.dergoogler.mmrl.ui.providable.LocalUserPreferences import com.dergoogler.mmrl.ui.screens.settings.NavButton @@ -20,6 +14,12 @@ import com.dergoogler.mmrl.utils.BlurUtil import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.generated.destinations.AppThemeScreenDestination +import dev.mmrlx.compose.ui.list.component.InputDialogItem +import dev.mmrlx.compose.ui.list.component.SwitchItem +import dev.mmrlx.compose.ui.list.component.item.Description +import dev.mmrlx.compose.ui.list.component.item.DialogDescription +import dev.mmrlx.compose.ui.list.component.item.Supporting +import dev.mmrlx.compose.ui.list.component.item.Title @Destination @Composable @@ -36,10 +36,10 @@ fun AppearanceScreen() { route = AppThemeScreenDestination, ) - TextEditDialogItem( + InputDialogItem( value = userPreferences.datePattern, onConfirm = { - viewModel.setDatePattern(it) + viewModel.setDatePattern(it.value) }, ) { Title(R.string.settings_date_pattern) @@ -70,7 +70,7 @@ fun AppearanceScreen() { Description(R.string.settings_enable_blur_desc) if (!isBlurSupported) { - Labels { + Supporting { LabelItem(text = stringResource(R.string.view_module_unsupported)) } } diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/appearance/items/DownloadPathItem.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/appearance/items/DownloadPathItem.kt index 4ad5501b1..5ebca7e3e 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/appearance/items/DownloadPathItem.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/appearance/items/DownloadPathItem.kt @@ -1,5 +1,6 @@ package com.dergoogler.mmrl.ui.screens.settings.appearance.items +import androidx.compose.foundation.clickable import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField @@ -10,15 +11,16 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.dergoogler.mmrl.R import com.dergoogler.mmrl.app.Const import com.dergoogler.mmrl.ui.component.dialog.TextFieldDialog -import com.dergoogler.mmrl.ui.component.listItem.dsl.ListScope -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.ButtonItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Description -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Title +import dev.mmrlx.compose.ui.list.ListScope +import dev.mmrlx.compose.ui.list.component.RawItem +import dev.mmrlx.compose.ui.list.component.item.Description +import dev.mmrlx.compose.ui.list.component.item.Title import java.io.File @Composable @@ -35,8 +37,12 @@ fun ListScope.DownloadPathItem( ) } - ButtonItem( - onClick = { edit = true }, + RawItem( + modifier = Modifier + .clickable { + edit = true + } + .contentPadding(), ) { Title(R.string.settings_download_path) Description(File(downloadPath).absolutePath) diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/appearance/screens/AppThemeScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/appearance/screens/AppThemeScreen.kt index ea72678ae..b54fb6fbf 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/appearance/screens/AppThemeScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/appearance/screens/AppThemeScreen.kt @@ -7,7 +7,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import com.dergoogler.mmrl.R -import com.dergoogler.mmrl.ui.component.ScaffoldDefaults import com.dergoogler.mmrl.ui.component.SettingsScaffold import com.dergoogler.mmrl.ui.providable.LocalSettings import com.dergoogler.mmrl.ui.providable.LocalUserPreferences @@ -25,7 +24,6 @@ fun AppThemeScreen() { val viewModel = LocalSettings.current SettingsScaffold( - modifier = ScaffoldDefaults.settingsScaffoldScrollModifier, title = R.string.settings_app_theme, ) { Column( diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/blacklist/BlacklistScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/blacklist/BlacklistScreen.kt deleted file mode 100644 index 54ab8c796..000000000 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/blacklist/BlacklistScreen.kt +++ /dev/null @@ -1,54 +0,0 @@ -package com.dergoogler.mmrl.ui.screens.settings.blacklist - -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import com.dergoogler.mmrl.R -import com.dergoogler.mmrl.ui.component.PageIndicator -import com.dergoogler.mmrl.ui.component.ScaffoldDefaults -import com.dergoogler.mmrl.ui.component.SettingsScaffold -import com.dergoogler.mmrl.ui.providable.LocalHazeState -import com.dergoogler.mmrl.ui.providable.LocalMainScreenInnerPaddings -import com.dergoogler.mmrl.ui.providable.LocalSettings -import com.dergoogler.mmrl.ui.screens.settings.blacklist.items.ModuleItem -import com.ramcosta.composedestinations.annotation.Destination -import com.ramcosta.composedestinations.annotation.RootGraph -import dev.chrisbanes.haze.hazeSource - -@Destination -@Composable -fun BlacklistScreen() { - val viewModel = LocalSettings.current - val blacklist = viewModel.allBlacklistEntriesAsFlow - - SettingsScaffold( - modifier = ScaffoldDefaults.settingsScaffoldModifier, - title = R.string.settings_blacklist, - ) { - if (blacklist.isEmpty()) { - PageIndicator( - icon = R.drawable.cloud, - text = R.string.search_empty, - ) - } else { - LazyColumn( - modifier = Modifier.hazeSource(LocalHazeState.current), - ) { - items( - items = blacklist, - key = { it.id }, - ) { module -> - ModuleItem(module = module) - } - - item { - val paddingValues = LocalMainScreenInnerPaddings.current - Spacer(modifier = Modifier.height(paddingValues.calculateBottomPadding())) - } - } - } - } -} diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/blacklist/items/ModuleItem.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/blacklist/items/ModuleItem.kt deleted file mode 100644 index 619e20d06..000000000 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/blacklist/items/ModuleItem.kt +++ /dev/null @@ -1,121 +0,0 @@ -package com.dergoogler.mmrl.ui.screens.settings.blacklist.items - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Button -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalUriHandler -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import com.dergoogler.mmrl.R -import com.dergoogler.mmrl.model.online.Blacklist -import com.dergoogler.mmrl.ui.component.BottomSheet -import com.dergoogler.mmrl.ui.component.MarkdownText -import com.dergoogler.mmrl.ui.component.NavigationBarsSpacer -import com.dergoogler.mmrl.ui.component.listItem.dsl.ListScope -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.ButtonItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Description -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Title - -@Composable -fun ListScope.ModuleItem(module: Blacklist) { - var open by remember { mutableStateOf(false) } - if (open) { - BlacklistBottomSheet( - module = module, - onClose = { open = false }, - ) - } - - ButtonItem( - onClick = { open = true }, - ) { - Title(module.id) - Description(module.source) - } -} - -@Composable -fun BlacklistBottomSheet( - module: Blacklist, - onClose: () -> Unit, -) = BottomSheet(onDismissRequest = onClose) { - val browser = LocalUriHandler.current - - Column( - modifier = Modifier.padding(bottom = 18.dp), - ) { - Column( - modifier = - Modifier - .fillMaxWidth() - .padding(16.dp), - ) { - Text( - style = MaterialTheme.typography.titleLarge, - text = module.id, - ) - - Spacer(modifier = Modifier.height(16.dp)) - - module.notes?.let { - if (it.isNotEmpty()) { - Surface( - color = MaterialTheme.colorScheme.surface, - tonalElevation = 1.dp, - shape = RoundedCornerShape(20.dp), - ) { - Column( - modifier = - Modifier - .fillMaxWidth() - .padding(16.dp), - ) { - Text( - text = stringResource(R.string.additional_notes), - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Bold, - modifier = Modifier.padding(bottom = 4.dp), - ) - MarkdownText( - text = it, - style = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.outline), - ) - } - } - - Spacer(modifier = Modifier.height(4.dp)) - } - } - } - - Spacer(modifier = Modifier.height(10.dp)) - - Button( - modifier = - Modifier - .padding(horizontal = 16.dp) - .fillMaxWidth(), - onClick = { - browser.openUri(module.source) - }, - ) { - Text(stringResource(R.string.open_source)) - } - - NavigationBarsSpacer() - } -} diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/changelogs/ChangelogScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/changelogs/ChangelogScreen.kt index adf82b5ec..d6689bf88 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/changelogs/ChangelogScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/changelogs/ChangelogScreen.kt @@ -3,8 +3,6 @@ package com.dergoogler.mmrl.ui.screens.settings.changelogs import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable @@ -13,21 +11,16 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier import com.dergoogler.mmrl.R import com.dergoogler.mmrl.model.online.Changelog import com.dergoogler.mmrl.network.runRequest import com.dergoogler.mmrl.stub.IMMRLApiManager import com.dergoogler.mmrl.ui.component.Loading -import com.dergoogler.mmrl.ui.component.ScaffoldDefaults import com.dergoogler.mmrl.ui.component.SettingsScaffold -import com.dergoogler.mmrl.ui.providable.LocalHazeState -import com.dergoogler.mmrl.ui.providable.LocalMainScreenInnerPaddings import com.dergoogler.mmrl.ui.providable.LocalUserPreferences import com.dergoogler.mmrl.ui.screens.settings.changelogs.items.ChangelogItem import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph -import dev.chrisbanes.haze.hazeSource import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import timber.log.Timber @@ -52,7 +45,7 @@ fun ChangelogScreen() { } SettingsScaffold( - modifier = ScaffoldDefaults.settingsScaffoldModifier, + canScroll = false, title = R.string.settings_changelog, ) { AnimatedVisibility( @@ -69,19 +62,12 @@ fun ChangelogScreen() { exit = fadeOut(), ) { changelog?.let { bl -> - LazyColumn( - modifier = Modifier.hazeSource(LocalHazeState.current), - ) { + LazyColumn { items(items = bl, key = { it.versionCode }) { entry -> if (!userPreferences.checkAppUpdatesPreReleases && entry.preRelease) return@items ChangelogItem(changelog = entry) } - - item { - val paddingValues = LocalMainScreenInnerPaddings.current - Spacer(modifier = Modifier.height(paddingValues.calculateBottomPadding())) - } } } } diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/changelogs/items/ChangelogItem.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/changelogs/items/ChangelogItem.kt index f3f30dd3c..ab89bca1b 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/changelogs/items/ChangelogItem.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/changelogs/items/ChangelogItem.kt @@ -1,5 +1,6 @@ package com.dergoogler.mmrl.ui.screens.settings.changelogs.items +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -28,11 +29,11 @@ import com.dergoogler.mmrl.model.online.Changelog import com.dergoogler.mmrl.ui.component.BottomSheet import com.dergoogler.mmrl.ui.component.LabelItem import com.dergoogler.mmrl.ui.component.MarkdownText -import com.dergoogler.mmrl.ui.component.listItem.dsl.ListScope -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.ButtonItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Description -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Labels -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Title +import dev.mmrlx.compose.ui.list.ListScope +import dev.mmrlx.compose.ui.list.component.RawItem +import dev.mmrlx.compose.ui.list.component.item.Description +import dev.mmrlx.compose.ui.list.component.item.Supporting +import dev.mmrlx.compose.ui.list.component.item.Title @Composable fun ListScope.ChangelogItem(changelog: Changelog) { @@ -41,13 +42,17 @@ fun ListScope.ChangelogItem(changelog: Changelog) { ChangelogBottomSheet(changelog = changelog, onClose = { open = false }) } - ButtonItem( - onClick = { open = true }, + RawItem( + modifier = Modifier + .clickable { + open = true + } + .contentPadding(), ) { Title(changelog.versionName) Description(changelog.versionCode.toString()) changelog.preRelease.takeTrue { - Labels { + Supporting { LabelItem( text = stringResource( diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/developer/DeveloperScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/developer/DeveloperScreen.kt index feb65143b..b367c4e4e 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/developer/DeveloperScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/developer/DeveloperScreen.kt @@ -5,13 +5,13 @@ import androidx.compose.ui.res.stringResource import com.dergoogler.mmrl.BuildConfig import com.dergoogler.mmrl.R import com.dergoogler.mmrl.ui.component.SettingsScaffold -import com.dergoogler.mmrl.ui.component.listItem.dsl.ListItemScope -import com.dergoogler.mmrl.ui.component.listItem.dsl.ListScope -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.Item -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.Section -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.SwitchItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Description -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Title +import dev.mmrlx.compose.ui.list.ListItemScope +import dev.mmrlx.compose.ui.list.ListScope +import dev.mmrlx.compose.ui.list.component.Item +import dev.mmrlx.compose.ui.list.component.Section +import dev.mmrlx.compose.ui.list.component.SwitchItem +import dev.mmrlx.compose.ui.list.component.item.Description +import dev.mmrlx.compose.ui.list.component.item.Title import com.dergoogler.mmrl.ui.providable.LocalSettings import com.dergoogler.mmrl.ui.providable.LocalUserPreferences import com.ramcosta.composedestinations.annotation.Destination diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/modules/ModulesScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/modules/ModulesScreen.kt index 6ca26792c..ba1b18748 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/modules/ModulesScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/modules/ModulesScreen.kt @@ -7,16 +7,17 @@ import com.dergoogler.mmrl.datastore.model.WebUIEngine import com.dergoogler.mmrl.ui.component.APatchLabel import com.dergoogler.mmrl.ui.component.KernelSuLabel import com.dergoogler.mmrl.ui.component.SettingsScaffold -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.RadioDialogItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.Section -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.SwitchItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Description -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Labels -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Title import com.dergoogler.mmrl.ui.providable.LocalSettings import com.dergoogler.mmrl.ui.providable.LocalUserPreferences import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph +import dev.mmrlx.compose.ui.list.component.RadioDialogItem +import dev.mmrlx.compose.ui.list.component.RadioDialogOption +import dev.mmrlx.compose.ui.list.component.Section +import dev.mmrlx.compose.ui.list.component.SwitchItem +import dev.mmrlx.compose.ui.list.component.item.Description +import dev.mmrlx.compose.ui.list.component.item.Supporting +import dev.mmrlx.compose.ui.list.component.item.Title @Destination @Composable @@ -37,7 +38,7 @@ fun ModulesScreen2() { ) { Title(R.string.settings_shell_module_state_change) Description(R.string.settings_shell_module_state_change_desc) - Labels { + Supporting { KernelSuLabel() APatchLabel() } @@ -50,7 +51,7 @@ fun ModulesScreen2() { ) { Title(R.string.settings_use_generic_action) Description(R.string.settings_use_generic_action_desc) - Labels { + Supporting { KernelSuLabel() APatchLabel() } @@ -66,15 +67,15 @@ fun ModulesScreen2() { selection = userPreferences.webuiEngine, options = listOf( - RadioDialogItem( + RadioDialogOption( value = WebUIEngine.WX, title = stringResource(R.string.settings_webui_engine_wx), ), - RadioDialogItem( + RadioDialogOption( value = WebUIEngine.KSU, title = stringResource(R.string.settings_webui_engine_ksu), ), - RadioDialogItem( + RadioDialogOption( value = WebUIEngine.PREFER_MODULE, title = stringResource(R.string.settings_webui_engine_prefer_module), ), diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/other/OtherScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/other/OtherScreen.kt index 12077de1f..65d488c83 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/other/OtherScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/other/OtherScreen.kt @@ -6,16 +6,16 @@ import androidx.compose.ui.platform.LocalContext import com.dergoogler.mmrl.R import com.dergoogler.mmrl.service.ProviderService import com.dergoogler.mmrl.ui.component.SettingsScaffold -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.SwitchItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.TextEditDialogItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Description -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Title +import dev.mmrlx.compose.ui.list.component.SwitchItem +import dev.mmrlx.compose.ui.list.component.item.Description +import dev.mmrlx.compose.ui.list.component.item.Title import com.dergoogler.mmrl.ui.providable.LocalSettings import com.dergoogler.mmrl.ui.providable.LocalSnackbarHost import com.dergoogler.mmrl.ui.providable.LocalUserPreferences import com.dergoogler.mmrl.ui.screens.settings.appearance.items.DownloadPathItem import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph +import dev.mmrlx.compose.ui.list.component.InputDialogItem import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -67,12 +67,14 @@ fun OtherScreen() { Description(R.string.settings_provider_service_desc) } - TextEditDialogItem( + InputDialogItem( value = userPreferences.webuixPackageName, - onConfirm = viewModel::setWebuixPackageName, + onConfirm = { + viewModel.setWebuixPackageName(it.value) + }, ) { - Title(context.getString(R.string.settings_set_spoofed_wxp)) - Description(context.getString(R.string.settings_set_spoofed_wxp_desc)) + Title(R.string.settings_set_spoofed_wxp) + Description(R.string.settings_set_spoofed_wxp_desc) } } } diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/security/SecurityScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/security/SecurityScreen.kt index 94073545f..a238e1ed7 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/security/SecurityScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/security/SecurityScreen.kt @@ -11,14 +11,14 @@ import com.dergoogler.mmrl.R import com.dergoogler.mmrl.platform.ksu.KsuNative import com.dergoogler.mmrl.ui.component.LabelItem import com.dergoogler.mmrl.ui.component.SettingsScaffold -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.SwitchItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Description -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Labels -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Title import com.dergoogler.mmrl.ui.providable.LocalSettings import com.dergoogler.mmrl.ui.providable.LocalUserPreferences import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph +import dev.mmrlx.compose.ui.list.component.SwitchItem +import dev.mmrlx.compose.ui.list.component.item.Description +import dev.mmrlx.compose.ui.list.component.item.Supporting +import dev.mmrlx.compose.ui.list.component.item.Title @Destination @Composable @@ -83,7 +83,7 @@ fun SecurityScreen() { Description(R.string.settings_disable_su_desc) if (!isSuDisableSupported) { - Labels { + Supporting { LabelItem(text = stringResource(R.string.view_module_unsupported)) } } diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/terminal/TerminalScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/terminal/TerminalScreen.kt index e3ad72348..9c982efc8 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/terminal/TerminalScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/terminal/TerminalScreen.kt @@ -5,10 +5,10 @@ import androidx.compose.ui.res.stringResource import com.dergoogler.mmrl.R import com.dergoogler.mmrl.datastore.model.WorkingMode.Companion.isRoot import com.dergoogler.mmrl.ui.component.SettingsScaffold -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.Section -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.SwitchItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Description -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Title +import dev.mmrlx.compose.ui.list.component.Section +import dev.mmrlx.compose.ui.list.component.SwitchItem +import dev.mmrlx.compose.ui.list.component.item.Description +import dev.mmrlx.compose.ui.list.component.item.Title import com.dergoogler.mmrl.ui.providable.LocalSettings import com.dergoogler.mmrl.ui.providable.LocalUserPreferences import com.ramcosta.composedestinations.annotation.Destination diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/updates/UpdatesScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/updates/UpdatesScreen.kt index d061b2831..21257d6e4 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/updates/UpdatesScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/settings/updates/UpdatesScreen.kt @@ -3,8 +3,10 @@ package com.dergoogler.mmrl.ui.screens.settings.updates import android.content.Intent import android.provider.Settings import android.widget.Toast +import androidx.compose.foundation.clickable import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource @@ -15,21 +17,25 @@ import com.dergoogler.mmrl.service.RepositoryService import com.dergoogler.mmrl.ui.component.SettingsScaffold import com.dergoogler.mmrl.ui.component.listItem.dsl.component.ButtonItem import com.dergoogler.mmrl.ui.component.listItem.dsl.component.RadioDialogItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.Section -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.SwitchItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Description -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Title +import dev.mmrlx.compose.ui.list.component.RadioDialogItem +import dev.mmrlx.compose.ui.list.component.Section +import dev.mmrlx.compose.ui.list.component.SwitchItem +import dev.mmrlx.compose.ui.list.component.item.Description +import dev.mmrlx.compose.ui.list.component.item.Title import com.dergoogler.mmrl.ui.providable.LocalSettings import com.dergoogler.mmrl.ui.providable.LocalSnackbarHost import com.dergoogler.mmrl.ui.providable.LocalUserPreferences import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph +import dev.mmrlx.compose.ui.list.component.RadioDialogOption +import dev.mmrlx.compose.ui.list.component.RawItem import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlin.time.Duration.Companion.milliseconds @Composable -fun radioDialogItem(interval: Long): RadioDialogItem = - RadioDialogItem( +fun radioDialogItem(interval: Long): RadioDialogOption = + RadioDialogOption( title = pluralStringResource(id = R.plurals.hours, count = interval.toInt(), interval), value = interval, ) @@ -63,24 +69,26 @@ fun UpdatesScreen() { title = R.string.settings_updates, ) { Section { - ButtonItem( - onClick = { - val intent = - Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply { - putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName) - } + RawItem( + modifier = Modifier + .clickable { + val intent = + Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply { + putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName) + } - if (intent.resolveActivity(context.packageManager) != null) { - context.startActivity(intent, null) - } else { - Toast - .makeText( - context, - "Cannot open notification settings", - Toast.LENGTH_SHORT, - ).show() + if (intent.resolveActivity(context.packageManager) != null) { + context.startActivity(intent, null) + } else { + Toast + .makeText( + context, + "Cannot open notification settings", + Toast.LENGTH_SHORT, + ).show() + } } - }, + .contentPadding(), ) { Title(R.string.settings_open_notification_settings) } @@ -123,7 +131,7 @@ fun UpdatesScreen() { } else { RepositoryService.stop(context) while (RepositoryService.isActive) { - delay(100) + delay(100.milliseconds) } viewModel.setRepositoryServiceEnabled(false) snackbarHost.showSnackbar(context.getString(R.string.repository_service_stopped)) @@ -144,8 +152,10 @@ fun UpdatesScreen() { ) { Title(R.string.settings_repo_update_interval) Description( - R.string.settings_repo_update_interval_desc, - userPreferences.autoUpdateReposInterval, + stringResource( + R.string.settings_repo_update_interval_desc, + userPreferences.autoUpdateReposInterval + ), ) } } @@ -190,8 +200,10 @@ fun UpdatesScreen() { ) { Title(R.string.settings_check_modules_update_interval) Description( - R.string.settings_check_modules_update_interval_desc, - userPreferences.checkModuleUpdatesInterval, + stringResource( + R.string.settings_check_modules_update_interval_desc, + userPreferences.checkModuleUpdatesInterval + ), ) } } diff --git a/app/src/main/kotlin/dev/mmrlx/compose/ui/toolbar/NavigateUpToolbar.kt b/app/src/main/kotlin/dev/mmrlx/compose/ui/toolbar/NavigateUpToolbar.kt new file mode 100644 index 000000000..3675c15aa --- /dev/null +++ b/app/src/main/kotlin/dev/mmrlx/compose/ui/toolbar/NavigateUpToolbar.kt @@ -0,0 +1,129 @@ +package dev.mmrlx.compose.ui.toolbar + +import android.content.Context +import androidx.activity.ComponentActivity +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.navigation.NavController +import com.ramcosta.composedestinations.navigation.DestinationsNavigator +import dev.mmrlx.compose.ui.icon.Icon +import dev.mmrlx.compose.ui.icon.IconButton +import dev.mmrlx.ui.R + +@Composable +fun NavigateUpToolbar( + title: String, + navController: NavController, + modifier: Modifier = Modifier, + subtitle: String? = null, + enable: Boolean = true, + actions: @Composable RowScope.() -> Unit = {}, + windowInsets: WindowInsets = ToolbarDefaults.windowInsets, + scrollBehavior: ToolbarScrollBehavior? = null, +) = NavigateUpToolbar( + modifier = modifier, + title = title, + subtitle = subtitle, + onBack = { navController.popBackStack() }, + actions = actions, + windowInsets = windowInsets, + scrollBehavior = scrollBehavior, + enable = enable, +) + +@Composable +fun NavigateUpToolbar( + title: String, + navigator: DestinationsNavigator, + modifier: Modifier = Modifier, + subtitle: String? = null, + enable: Boolean = true, + actions: @Composable RowScope.() -> Unit = {}, + windowInsets: WindowInsets = ToolbarDefaults.windowInsets, + scrollBehavior: ToolbarScrollBehavior? = null, +) = NavigateUpToolbar( + modifier = modifier, + title = title, + subtitle = subtitle, + onBack = { navigator.popBackStack() }, + actions = actions, + windowInsets = windowInsets, + scrollBehavior = scrollBehavior, + enable = enable, +) + +@Composable +fun NavigateUpToolbar( + title: String, + modifier: Modifier = Modifier, + context: Context = LocalContext.current, + subtitle: String? = null, + enable: Boolean = true, + actions: @Composable RowScope.() -> Unit = {}, + windowInsets: WindowInsets = ToolbarDefaults.windowInsets, + scrollBehavior: ToolbarScrollBehavior? = null, +) = NavigateUpToolbar( + modifier = modifier, + title = title, + subtitle = subtitle, + onBack = { (context as ComponentActivity).finish() }, + actions = actions, + windowInsets = windowInsets, + scrollBehavior = scrollBehavior, + enable = enable, +) + +@Composable +fun NavigateUpToolbar( + title: String, + onBack: () -> Unit, + modifier: Modifier = Modifier, + subtitle: String? = null, + enable: Boolean = true, + actions: @Composable RowScope.() -> Unit = {}, + windowInsets: WindowInsets = ToolbarDefaults.windowInsets, + scrollBehavior: ToolbarScrollBehavior? = null, +) = NavigateUpToolbar( + modifier = modifier, + title = { + ToolbarTitle(title = title, subtitle = subtitle) + }, + onBack = onBack, + actions = actions, + windowInsets = windowInsets, + scrollBehavior = scrollBehavior, + enable = enable, +) + +@Composable +fun NavigateUpToolbar( + title: @Composable () -> Unit, + onBack: () -> Unit, + modifier: Modifier = Modifier, + enable: Boolean = true, + actions: @Composable RowScope.() -> Unit = {}, + windowInsets: WindowInsets = ToolbarDefaults.windowInsets, + scrollBehavior: ToolbarScrollBehavior? = null, +) = Toolbar( + title = title, + modifier = modifier, + navigationIcon = { + if (enable) { + IconButton( + onClick = { if (enable) onBack() }, + ) { + Icon( + painter = painterResource(id = R.drawable.arrow_left), + contentDescription = null, + ) + } + } + }, + actions = actions, + windowInsets = windowInsets, + scrollBehavior = scrollBehavior, +) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d6483bf6e..0b03d2734 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -43,7 +43,7 @@ squareRetrofit = "2.11.0" squareOkhttp = "4.12.0" squareMoshi = "1.15.1" runtimeAndroid = "1.12.1" -mmrlx = "1.0.230" +mmrlx = "1.0.231" workRuntimeKtx = "2.10.0" lifecycleProcess = "2.8.7" apacheCommons = "1.27.1" From a8146f93a0175ec683f33b11602da79a3ce93326 Mon Sep 17 00:00:00 2001 From: Der_Googler <54764558+DerGoogler@users.noreply.github.com> Date: Sun, 18 Oct 2026 19:53:31 +0200 Subject: [PATCH 20/24] add warning for dev builds --- .../dergoogler/mmrl/ui/screens/home/HomeScreen.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/HomeScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/HomeScreen.kt index 9b98cb3f8..406074681 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/HomeScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/HomeScreen.kt @@ -33,6 +33,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp import com.dergoogler.mmrl.R import com.dergoogler.mmrl.datastore.model.WorkingMode.Companion.isNonRoot @@ -67,6 +68,7 @@ import com.ramcosta.composedestinations.generated.destinations.AboutScreenDestin import com.ramcosta.composedestinations.generated.destinations.ThankYouScreenDestination import dev.mmrlx.compose.layout.outlinedCard import dev.mmrlx.compose.ui.LinearProgressIndicator +import dev.mmrlx.compose.ui.LocalTextStyle import dev.mmrlx.compose.ui.Text import dev.mmrlx.compose.ui.icon.Icon import dev.mmrlx.compose.ui.icon.IconButton @@ -212,6 +214,19 @@ fun HomeScreen() { .fillMaxWidth() ) { val uname = Os.uname() + + scope.Item { + Title { + Text( + "DEV BUILD! EXPECT ERRORS AND BROKEN UI/UX", + style = LocalTextStyle.current.copy( + fontFamily = FontFamily.Monospace, + color = Color.Red + ) + ) + } + } + scope.Item { Icon(painter = painterResource(R.drawable.cookie_man)) Title(R.string.kernel) From 43b6575dd98c356a8d5693b84f152a3072a8017f Mon Sep 17 00:00:00 2001 From: Der_Googler <54764558+DerGoogler@users.noreply.github.com> Date: Sun, 25 Oct 2026 11:19:00 +0100 Subject: [PATCH 21/24] . --- .../ui/component/toolbar/BlurSearchToolbar.kt | 27 +- .../mmrl/ui/screens/home/HomeScreen.kt | 2 +- .../mmrl/ui/screens/modules/ModulesList.kt | 89 ++--- .../mmrl/ui/screens/modules/ModulesScreen.kt | 155 ++++----- .../screens/repositories/RepositoriesList.kt | 70 ++-- .../screens/repositories/RepositoriesMenu.kt | 6 +- .../repositories/RepositoriesScreen.kt | 257 +++++++------- .../mmrl/ui/screens/repository/ModulesList.kt | 100 ++---- .../ui/screens/repository/RepositoryMenu.kt | 6 +- .../ui/screens/repository/RepositoryScreen.kt | 325 ++++++++---------- .../dergoogler/mmrl/ext/PaddingValuesExt.kt | 3 +- 11 files changed, 444 insertions(+), 596 deletions(-) diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/toolbar/BlurSearchToolbar.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/toolbar/BlurSearchToolbar.kt index 9606dece8..8c8db2382 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/toolbar/BlurSearchToolbar.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/toolbar/BlurSearchToolbar.kt @@ -5,13 +5,8 @@ import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextFieldDefaults -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember @@ -26,10 +21,14 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import com.dergoogler.mmrl.ui.R +import dev.mmrlx.compose.ui.Text +import dev.mmrlx.compose.ui.icon.Icon +import dev.mmrlx.compose.ui.icon.IconButton +import dev.mmrlx.compose.ui.theme.MMRLXTheme +import dev.mmrlx.compose.ui.toolbar.Toolbar import dev.mmrlx.compose.ui.toolbar.ToolbarDefaults import dev.mmrlx.compose.ui.toolbar.ToolbarScrollBehavior -@OptIn(ExperimentalMaterial3Api::class) @Composable fun BlurSearchToolbar( modifier: Modifier = Modifier, @@ -43,23 +42,11 @@ fun BlurSearchToolbar( actions: @Composable RowScope.() -> Unit = {}, windowInsets: WindowInsets = ToolbarDefaults.windowInsets, scrollBehavior: ToolbarScrollBehavior? = null, - fadeBackgroundIfNoBlur: Boolean = false, - fadeDistance: Float = 200f, - fade: Boolean = false, - state: BlurToolbarState = rememberBlurToolbarState( - scrollBehavior = scrollBehavior, - fade = fade, - fadeDistance = fadeDistance, - ), -) = BlurToolbar( - state = state, +) = Toolbar( modifier = modifier, actions = actions, windowInsets = windowInsets, scrollBehavior = scrollBehavior, - fade = fade, - fadeBackgroundIfNoBlur = fadeBackgroundIfNoBlur, - fadeDistance = fadeDistance, navigationIcon = if (onClose != null && isSearch) { { @@ -117,7 +104,7 @@ fun BlurSearchToolbar( Text(text = stringResource(id = R.string.search_placeholder)) }, singleLine = true, - textStyle = MaterialTheme.typography.bodyLarge, + textStyle = MMRLXTheme.typography.bodyLarge, ) } } else { diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/HomeScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/HomeScreen.kt index 406074681..88a5a882e 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/HomeScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/home/HomeScreen.kt @@ -214,7 +214,7 @@ fun HomeScreen() { .fillMaxWidth() ) { val uname = Os.uname() - + scope.Item { Title { Text( diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/modules/ModulesList.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/modules/ModulesList.kt index b7d998cae..cfea5905e 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/modules/ModulesList.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/modules/ModulesList.kt @@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn @@ -22,7 +21,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -40,18 +38,14 @@ import com.dergoogler.mmrl.model.online.VersionItem import com.dergoogler.mmrl.platform.content.LocalModule.Companion.hasAction import com.dergoogler.mmrl.ui.activity.terminal.action.ActionActivity import com.dergoogler.mmrl.ui.component.VersionItemBottomSheet -import com.dergoogler.mmrl.ui.component.scaffold.ScaffoldScope -import com.dergoogler.mmrl.ui.component.scrollbar.VerticalFastScrollbar -import com.dergoogler.mmrl.ui.providable.LocalHazeState -import com.dergoogler.mmrl.ui.providable.LocalMainScreenInnerPaddings import com.dergoogler.mmrl.ui.providable.LocalModule import com.dergoogler.mmrl.ui.providable.LocalUserPreferences import com.dergoogler.mmrl.viewmodel.ModulesViewModel -import dev.chrisbanes.haze.hazeSource +import dev.mmrlx.compose.ui.ext.with +import dev.mmrlx.compose.ui.scaffold.ScaffoldScope @Composable fun ScaffoldScope.ModulesList( - innerPadding: PaddingValues, list: List, state: LazyListState, onDownload: (LocalModule, VersionItem, Boolean) -> Unit, @@ -60,53 +54,40 @@ fun ScaffoldScope.ModulesList( ) = Box( modifier = Modifier.fillMaxSize(), ) { - val paddingValues = LocalMainScreenInnerPaddings.current val layoutDirection = LocalLayoutDirection.current - this@ModulesList.ResponsiveContent { - LazyColumn( - state = state, - modifier = - Modifier - .fillMaxSize() - .hazeSource(state = LocalHazeState.current), - contentPadding = - PaddingValues( - top = innerPadding.calculateTopPadding() + 16.dp, - start = innerPadding.calculateStartPadding(layoutDirection) + 16.dp, - bottom = paddingValues.calculateBottomPadding() + 16.dp, - end = 16.dp, - ), - verticalArrangement = Arrangement.spacedBy(16.dp), - ) { - items( - items = list, - key = { it.id.id }, - contentType = { "module_item" }, - ) { module -> - CompositionLocalProvider( - LocalModule provides module, - ) { - ModuleItem( - viewModel = viewModel, - onDownload = onDownload, - isProviderAlive = isProviderAlive, - ) - } - } - } - } - - VerticalFastScrollbar( + LazyColumn( state = state, modifier = Modifier - .align(Alignment.CenterEnd) - .padding( - top = innerPadding.calculateTopPadding(), - bottom = paddingValues.calculateBottomPadding(), - ), - ) + .with(this@ModulesList) { + it.scaffoldHazeSource() + }, + contentPadding = + PaddingValues( + top = this@ModulesList.scaffoldTopPadding + 16.dp, + start = this@ModulesList.contentPadding.calculateStartPadding(layoutDirection) + 16.dp, + bottom = this@ModulesList.contentPadding.calculateBottomPadding() + 16.dp, + end = 16.dp, + ), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + items( + items = list, + key = { it.id.id }, + contentType = { "module_item" }, + ) { module -> + CompositionLocalProvider( + LocalModule provides module, + ) { + ModuleItem( + viewModel = viewModel, + onDownload = onDownload, + isProviderAlive = isProviderAlive, + ) + } + } + } } @Composable @@ -230,10 +211,10 @@ private fun ModuleItem( isProviderAlive, ) { isProviderAlive && - ( - !(viewModel.moduleCompatibility.canRestoreModules && userPreferences.useShellForModuleStateChange) || - module.state != State.REMOVE - ) + ( + !(viewModel.moduleCompatibility.canRestoreModules && userPreferences.useShellForModuleStateChange) || + module.state != State.REMOVE + ) } RemoveOrRestore( diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/modules/ModulesScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/modules/ModulesScreen.kt index 1d0abfe6b..b3c9c66b7 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/modules/ModulesScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/modules/ModulesScreen.kt @@ -9,11 +9,10 @@ import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.PressInteraction +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.padding import androidx.compose.material3.FloatingActionButton -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults.Indicator @@ -36,7 +35,6 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.dergoogler.mmrl.R import com.dergoogler.mmrl.datastore.model.ModulesMenu -import com.dergoogler.mmrl.ext.currentScreenWidth import com.dergoogler.mmrl.ext.isScrollingUp import com.dergoogler.mmrl.ext.none import com.dergoogler.mmrl.ext.systemBarsPaddingEnd @@ -45,93 +43,98 @@ import com.dergoogler.mmrl.model.online.VersionItem import com.dergoogler.mmrl.ui.activity.terminal.install.InstallActivity import com.dergoogler.mmrl.ui.component.BottomBar import com.dergoogler.mmrl.ui.component.Loading -import com.dergoogler.mmrl.ui.component.LocalScreenProvider import com.dergoogler.mmrl.ui.component.PageIndicator import com.dergoogler.mmrl.ui.component.TopAppBarEventIcon -import com.dergoogler.mmrl.ui.component.scaffold.Scaffold import com.dergoogler.mmrl.ui.component.toolbar.BlurSearchToolbar import com.dergoogler.mmrl.ui.providable.LocalMainScreenInnerPaddings import com.dergoogler.mmrl.ui.providable.LocalUserPreferences import com.dergoogler.mmrl.viewmodel.ModulesViewModel import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph +import dev.mmrlx.compose.ui.icon.Icon +import dev.mmrlx.compose.ui.icon.IconButton +import dev.mmrlx.compose.ui.scaffold.Scaffold import dev.mmrlx.compose.ui.toolbar.ToolbarDefaults import dev.mmrlx.compose.ui.toolbar.ToolbarScrollBehavior @Destination @Composable -fun ModulesScreen(viewModel: ModulesViewModel = hiltViewModel()) = - LocalScreenProvider { - val userPrefs = LocalUserPreferences.current - val context = LocalContext.current +fun ModulesScreen(viewModel: ModulesViewModel = hiltViewModel()) { + val userPrefs = LocalUserPreferences.current + val context = LocalContext.current - val list by viewModel.local.collectAsStateWithLifecycle() - val query by viewModel.query.collectAsStateWithLifecycle() - val state by viewModel.screenState.collectAsStateWithLifecycle() - val isLoading by viewModel.isLoading.collectAsStateWithLifecycle() + val list by viewModel.local.collectAsStateWithLifecycle() + val query by viewModel.query.collectAsStateWithLifecycle() + val state by viewModel.screenState.collectAsStateWithLifecycle() + val isLoading by viewModel.isLoading.collectAsStateWithLifecycle() - val scrollBehavior = ToolbarDefaults.pinnedScrollBehavior() + val scrollBehavior = ToolbarDefaults.fadePinnedScrollBehavior() - val pullToRefreshState = rememberPullToRefreshState() + val pullToRefreshState = rememberPullToRefreshState() - val isScrollingUp by viewModel.listState.isScrollingUp() - val showFab by remember { - derivedStateOf { - isScrollingUp && !viewModel.isSearch && viewModel.isProviderAlive - } + val isScrollingUp by viewModel.listState.isScrollingUp() + val showFab by remember { + derivedStateOf { + isScrollingUp && !viewModel.isSearch && viewModel.isProviderAlive } + } - val download: (LocalModule, VersionItem, Boolean) -> Unit = { module, item, install -> - viewModel.downloader(context, module, item) { - if (install) { - InstallActivity.start( - context = context, - uri = it.toUri(), - ) - } + val download: (LocalModule, VersionItem, Boolean) -> Unit = { module, item, install -> + viewModel.downloader(context, module, item) { + if (install) { + InstallActivity.start( + context = context, + uri = it.toUri(), + ) } } + } - BackHandler( - enabled = viewModel.isSearch, - onBack = viewModel::closeSearch, - ) + BackHandler( + enabled = viewModel.isSearch, + onBack = viewModel::closeSearch, + ) - Scaffold( - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), - topBar = { - TopBar( - isSearch = viewModel.isSearch, - query = query, - onQueryChange = viewModel::search, - onOpenSearch = viewModel::openSearch, - onCloseSearch = viewModel::closeSearch, - setMenu = viewModel::setModulesMenu, - scrollBehavior = scrollBehavior, - ) - }, - bottomBar = { - BottomBar() - }, - floatingActionButton = { - AnimatedVisibility( - visible = showFab, - enter = - scaleIn( - animationSpec = tween(100), - initialScale = 0.8f, - ), - exit = - scaleOut( - animationSpec = tween(100), - targetScale = 0.8f, - ), - ) { - FloatingButton() - } - }, - contentWindowInsets = WindowInsets.none, - ) { innerPadding -> + Scaffold( + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + toolbar = { + TopBar0( + isSearch = viewModel.isSearch, + query = query, + onQueryChange = viewModel::search, + onOpenSearch = viewModel::openSearch, + onCloseSearch = viewModel::closeSearch, + setMenu = viewModel::setModulesMenu, + scrollBehavior = scrollBehavior, + ) + }, + bottomBar = { + BottomBar() + }, + floatingActionButton = { + AnimatedVisibility( + visible = showFab, + enter = + scaleIn( + animationSpec = tween(100), + initialScale = 0.8f, + ), + exit = + scaleOut( + animationSpec = tween(100), + targetScale = 0.8f, + ), + ) { + FloatingButton() + } + }, + contentWindowInsets = WindowInsets.none, + ) { + Box( + modifier = + Modifier + .scaffoldHazeSource() + ) { if (isLoading) { Loading() } @@ -150,20 +153,13 @@ fun ModulesScreen(viewModel: ModulesViewModel = hiltViewModel()) = indicator = { Indicator( modifier = - Modifier.align(Alignment.TopCenter).let { - if (!userPrefs.enableBlur) { - it.padding(top = innerPadding.calculateTopPadding()) - } else { - it - } - }, + Modifier.align(Alignment.TopCenter), isRefreshing = state.isRefreshing, state = pullToRefreshState, ) }, ) { this@Scaffold.ModulesList( - innerPadding = innerPadding, list = list, state = viewModel.listState, viewModel = viewModel, @@ -173,9 +169,10 @@ fun ModulesScreen(viewModel: ModulesViewModel = hiltViewModel()) = } } } +} @Composable -private fun TopBar( +private fun TopBar0( isSearch: Boolean, query: String, onQueryChange: (String) -> Unit, @@ -184,8 +181,6 @@ private fun TopBar( setMenu: (ModulesMenu) -> Unit, scrollBehavior: ToolbarScrollBehavior, ) { - val width = currentScreenWidth() - var currentQuery by remember { mutableStateOf(query) } DisposableEffect(isSearch) { onDispose { currentQuery = "" } @@ -203,8 +198,6 @@ private fun TopBar( currentQuery = "" }, title = { - if (!width.isSmall) return@BlurSearchToolbar - TopAppBarEventIcon() }, actions = { @@ -224,8 +217,6 @@ private fun TopBar( ) }, scrollBehavior = scrollBehavior, - fadeDistance = 50f, - fade = true, ) } diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repositories/RepositoriesList.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repositories/RepositoriesList.kt index 3418716e4..2b2aa5ec8 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repositories/RepositoriesList.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repositories/RepositoriesList.kt @@ -2,11 +2,7 @@ package com.dergoogler.mmrl.ui.screens.repositories import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.requiredHeightIn import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState @@ -23,18 +19,15 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.dergoogler.mmrl.R +import com.dergoogler.mmrl.ext.plus import com.dergoogler.mmrl.model.state.RepoState -import com.dergoogler.mmrl.ui.component.scaffold.ScaffoldScope import com.dergoogler.mmrl.ui.providable.LocalDestinationsNavigator -import com.dergoogler.mmrl.ui.providable.LocalHazeState -import com.dergoogler.mmrl.ui.providable.LocalMainScreenInnerPaddings import com.dergoogler.mmrl.ui.screens.repositories.items.ExploreReposCard import com.ramcosta.composedestinations.generated.destinations.RepositoryScreenDestination -import dev.chrisbanes.haze.hazeSource +import dev.mmrlx.compose.ui.scaffold.ScaffoldScope @Composable fun ScaffoldScope.RepositoriesList( @@ -42,51 +35,32 @@ fun ScaffoldScope.RepositoriesList( state: LazyListState, delete: (RepoState) -> Unit, getUpdate: (RepoState, (Throwable) -> Unit) -> Unit, - innerPadding: PaddingValues, ) = Box( modifier = Modifier.fillMaxSize(), ) { - val paddingValues = LocalMainScreenInnerPaddings.current - val layoutDirection = LocalLayoutDirection.current val navigator = LocalDestinationsNavigator.current - this@RepositoriesList.ResponsiveContent { - LazyColumn( - state = state, - modifier = - Modifier - .fillMaxSize() - .hazeSource(state = LocalHazeState.current), - contentPadding = - PaddingValues( - top = innerPadding.calculateTopPadding() + 16.dp, - start = innerPadding.calculateStartPadding(layoutDirection) + 16.dp, - bottom = 16.dp, - end = 16.dp, - ), - verticalArrangement = Arrangement.spacedBy(10.dp), - ) { - item { - ExploreReposCard() - } - - items( - items = list, - key = { it.url }, - ) { repo -> - RepositoryItem( - repo = repo, - onClick = { - navigator.navigate(RepositoryScreenDestination(repo.toRepo())) - }, - onUpdate = getUpdate, - onDelete = delete, - ) - } + LazyColumn( + state = state, + contentPadding = this@RepositoriesList.contentPadding + 16.dp, + verticalArrangement = Arrangement.spacedBy(10.dp), + ) { + item { + ExploreReposCard() + } - item { - Spacer(modifier = Modifier.height(paddingValues.calculateBottomPadding())) - } + items( + items = list, + key = { it.url }, + ) { repo -> + RepositoryItem( + repo = repo, + onClick = { + navigator.navigate(RepositoryScreenDestination(repo.toRepo())) + }, + onUpdate = getUpdate, + onDelete = delete, + ) } } } diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repositories/RepositoriesMenu.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repositories/RepositoriesMenu.kt index 4b736cd89..b26767330 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repositories/RepositoriesMenu.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repositories/RepositoriesMenu.kt @@ -6,10 +6,7 @@ import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -29,6 +26,9 @@ import com.dergoogler.mmrl.ui.component.Segment import com.dergoogler.mmrl.ui.component.SegmentedButtons import com.dergoogler.mmrl.ui.component.SegmentedButtonsDefaults import com.dergoogler.mmrl.ui.providable.LocalUserPreferences +import dev.mmrlx.compose.ui.Text +import dev.mmrlx.compose.ui.icon.Icon +import dev.mmrlx.compose.ui.icon.IconButton @Composable fun RepositoriesMenu(setMenu: (RepositoriesMenu) -> Unit) { diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repositories/RepositoriesScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repositories/RepositoriesScreen.kt index 2dc769b2b..b7416dbad 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repositories/RepositoriesScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repositories/RepositoriesScreen.kt @@ -4,6 +4,7 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.tween import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -11,12 +12,9 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.FloatingActionButton -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults.Indicator @@ -41,7 +39,6 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.dergoogler.mmrl.R import com.dergoogler.mmrl.datastore.model.RepositoriesMenu -import com.dergoogler.mmrl.ext.currentScreenWidth import com.dergoogler.mmrl.ext.isScrollingUp import com.dergoogler.mmrl.ext.none import com.dergoogler.mmrl.ext.rememberSaveableLazyListState @@ -52,12 +49,9 @@ import com.dergoogler.mmrl.ui.animate.slideInTopToBottom import com.dergoogler.mmrl.ui.animate.slideOutBottomToTop import com.dergoogler.mmrl.ui.component.BottomBar import com.dergoogler.mmrl.ui.component.Loading -import com.dergoogler.mmrl.ui.component.LocalScreenProvider import com.dergoogler.mmrl.ui.component.PageIndicator import com.dergoogler.mmrl.ui.component.TopAppBarEventIcon import com.dergoogler.mmrl.ui.component.dialog.TextFieldDialog -import com.dergoogler.mmrl.ui.component.scaffold.Scaffold -import com.dergoogler.mmrl.ui.component.toolbar.BlurToolbar import com.dergoogler.mmrl.ui.providable.LocalBulkInstall import com.dergoogler.mmrl.ui.providable.LocalDestinationsNavigator import com.dergoogler.mmrl.ui.providable.LocalMainScreenInnerPaddings @@ -67,6 +61,11 @@ import com.dergoogler.mmrl.viewmodel.RepositoriesViewModel import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.generated.destinations.SearchScreenDestination +import dev.mmrlx.compose.ui.Text +import dev.mmrlx.compose.ui.icon.Icon +import dev.mmrlx.compose.ui.icon.IconButton +import dev.mmrlx.compose.ui.scaffold.Scaffold +import dev.mmrlx.compose.ui.toolbar.Toolbar import dev.mmrlx.compose.ui.toolbar.ToolbarDefaults import dev.mmrlx.compose.ui.toolbar.ToolbarScrollBehavior import timber.log.Timber @@ -74,126 +73,129 @@ import kotlin.reflect.KFunction1 @Destination @Composable -fun RepositoriesScreen() = - LocalScreenProvider { - val viewModel = hiltViewModel() +fun RepositoriesScreen() { + val viewModel = hiltViewModel() - val userPrefs = LocalUserPreferences.current - val scrollBehavior = ToolbarDefaults.pinnedScrollBehavior() - val list by viewModel.repos.collectAsStateWithLifecycle() - val bulkInstallViewModel = LocalBulkInstall.current - val bulkModules by bulkInstallViewModel.bulkModules.collectAsStateWithLifecycle() + val userPrefs = LocalUserPreferences.current + val scrollBehavior = ToolbarDefaults.fadePinnedScrollBehavior() + val list by viewModel.repos.collectAsStateWithLifecycle() + val bulkInstallViewModel = LocalBulkInstall.current + val bulkModules by bulkInstallViewModel.bulkModules.collectAsStateWithLifecycle() - val state by viewModel.screenState.collectAsStateWithLifecycle() - val progress by viewModel.progress.collectAsStateWithLifecycle() + val state by viewModel.screenState.collectAsStateWithLifecycle() + val progress by viewModel.progress.collectAsStateWithLifecycle() - val pullToRefreshState = rememberPullToRefreshState() + val pullToRefreshState = rememberPullToRefreshState() - // Use rememberSaveable to persist scroll position across navigation - val listState = rememberSaveableLazyListState(key = "repositories_list") + // Use rememberSaveable to persist scroll position across navigation + val listState = rememberSaveableLazyListState(key = "repositories_list") - val showFab by listState.isScrollingUp() + val showFab by listState.isScrollingUp() - var repoUrl by remember { mutableStateOf("") } - var message: String by remember { mutableStateOf("") } + var repoUrl by remember { mutableStateOf("") } + var message: String by remember { mutableStateOf("") } - var failure by remember { mutableStateOf(false) } - if (failure) { - FailureDialog( - name = repoUrl, - message = message, - onClose = { - failure = false - message = "" - }, - ) - } - - var add by remember { mutableStateOf(false) } - if (add) { - AddDialog( - onClose = { add = false }, - onAdd = { - repoUrl = it - viewModel.insert(it) { e -> - failure = true - message = e.stackTraceToString() - } - }, - ) - } - - val context = LocalContext.current - var bulkInstallBottomSheet by remember { mutableStateOf(false) } + var failure by remember { mutableStateOf(false) } + if (failure) { + FailureDialog( + name = repoUrl, + message = message, + onClose = { + failure = false + message = "" + }, + ) + } - val bulkDownload: (List, Boolean) -> Unit = { item, install -> - bulkInstallViewModel.downloadMultiple( - items = item, - onAllSuccess = { - bulkInstallViewModel.clearBulkModules() - bulkInstallBottomSheet = false - if (install) { - InstallActivity.start( - context = context, - uri = it, - ) - } - }, - onFailure = { - Timber.e(it) - }, - ) - } + var add by remember { mutableStateOf(false) } + if (add) { + AddDialog( + onClose = { add = false }, + onAdd = { + repoUrl = it + viewModel.insert(it) { e -> + failure = true + message = e.stackTraceToString() + } + }, + ) + } - if (bulkInstallBottomSheet) { - BulkBottomSheet( - onClose = { - bulkInstallBottomSheet = false - }, - modules = bulkModules, - onDownload = bulkDownload, - bulkInstallViewModel = bulkInstallViewModel, - ) - } + val context = LocalContext.current + var bulkInstallBottomSheet by remember { mutableStateOf(false) } - Scaffold( - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), - topBar = { - TopBar( - setMenu = viewModel::setRepositoriesMenu, - onAdd = { add = true }, - scrollBehavior = scrollBehavior, - ) + val bulkDownload: (List, Boolean) -> Unit = { item, install -> + bulkInstallViewModel.downloadMultiple( + items = item, + onAllSuccess = { + bulkInstallViewModel.clearBulkModules() + bulkInstallBottomSheet = false + if (install) { + InstallActivity.start( + context = context, + uri = it, + ) + } }, - bottomBar = { - BottomBar() + onFailure = { + Timber.e(it) }, - contentWindowInsets = WindowInsets.none, - floatingActionButton = { - if (viewModel.platform.isValid) { - AnimatedVisibility( - visible = showFab, - enter = - scaleIn( - animationSpec = tween(100), - initialScale = 0.8f, - ), - exit = - scaleOut( - animationSpec = tween(100), - targetScale = 0.8f, - ), - ) { - FloatingButton( - onClick = { - bulkInstallBottomSheet = true - }, - ) - } - } + ) + } + + if (bulkInstallBottomSheet) { + BulkBottomSheet( + onClose = { + bulkInstallBottomSheet = false }, - ) { innerPadding -> + modules = bulkModules, + onDownload = bulkDownload, + bulkInstallViewModel = bulkInstallViewModel, + ) + } + Scaffold( + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + toolbar = { + TopBar0( + setMenu = viewModel::setRepositoriesMenu, + onAdd = { add = true }, + scrollBehavior = scrollBehavior, + ) + }, + bottomBar = { + BottomBar() + }, + contentWindowInsets = WindowInsets.none, + floatingActionButton = { + if (viewModel.platform.isValid) { + AnimatedVisibility( + visible = showFab, + enter = + scaleIn( + animationSpec = tween(100), + initialScale = 0.8f, + ), + exit = + scaleOut( + animationSpec = tween(100), + targetScale = 0.8f, + ), + ) { + FloatingButton( + onClick = { + bulkInstallBottomSheet = true + }, + ) + } + } + }, + ) { + Box( + modifier = + Modifier + .scaffoldHazeSource() + ) { if (viewModel.isLoading) { Loading() } @@ -211,21 +213,13 @@ fun RepositoriesScreen() = onRefresh = viewModel::getRepoAll, indicator = { Indicator( - modifier = - Modifier.align(Alignment.TopCenter).let { - if (!userPrefs.enableBlur) { - it.padding(top = innerPadding.calculateTopPadding()) - } else { - it - } - }, + modifier = Modifier.align(Alignment.TopCenter), isRefreshing = state.isRefreshing, state = pullToRefreshState, ) }, ) { this@Scaffold.RepositoriesList( - innerPadding = innerPadding, list = list, state = listState, delete = viewModel::delete, @@ -239,19 +233,13 @@ fun RepositoriesScreen() = exit = slideOutBottomToTop(), ) { LinearProgressIndicator( - modifier = - Modifier.fillMaxWidth().let { - if (!userPrefs.enableBlur) { - it.padding(top = innerPadding.calculateTopPadding()) - } else { - it - } - }, + modifier = Modifier.fillMaxWidth(), strokeCap = StrokeCap.Round, ) } } } +} @Composable private fun AddDialog( @@ -306,23 +294,18 @@ private fun AddDialog( } @Composable -private fun TopBar( +private fun TopBar0( onAdd: () -> Unit, setMenu: KFunction1, scrollBehavior: ToolbarScrollBehavior, ) { - val width = currentScreenWidth() val navigator = LocalDestinationsNavigator.current - BlurToolbar( + Toolbar( scrollBehavior = scrollBehavior, title = { - if (!width.isSmall) return@BlurToolbar - TopAppBarEventIcon() }, - fadeDistance = 50f, - fade = true, actions = { IconButton( onClick = { diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/ModulesList.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/ModulesList.kt index bfbae90f2..4849f60ac 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/ModulesList.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/ModulesList.kt @@ -4,10 +4,7 @@ import androidx.compose.animation.animateContentSize import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope @@ -15,27 +12,21 @@ import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.dp import com.dergoogler.mmrl.database.entity.Repo import com.dergoogler.mmrl.datastore.model.RepoListMode import com.dergoogler.mmrl.model.online.OnlineModule import com.dergoogler.mmrl.model.state.OnlineState -import com.dergoogler.mmrl.ui.component.scaffold.ScaffoldScope import com.dergoogler.mmrl.ui.providable.LocalDestinationsNavigator -import com.dergoogler.mmrl.ui.providable.LocalHazeState -import com.dergoogler.mmrl.ui.providable.LocalMainScreenInnerPaddings import com.dergoogler.mmrl.ui.providable.LocalOnlineModule import com.dergoogler.mmrl.ui.providable.LocalOnlineModuleState import com.dergoogler.mmrl.ui.providable.LocalUserPreferences import com.ramcosta.composedestinations.generated.destinations.NewViewScreenDestination -import dev.chrisbanes.haze.hazeSource +import dev.mmrlx.compose.ui.scaffold.ScaffoldScope @Composable fun ScaffoldScope.ModulesList( - innerPadding: PaddingValues, repo: Repo, list: List>, state: LazyListState, @@ -45,69 +36,50 @@ fun ScaffoldScope.ModulesList( val userPreferences = LocalUserPreferences.current val menu = userPreferences.repositoryMenu - val paddingValues = LocalMainScreenInnerPaddings.current - val layoutDirection = LocalLayoutDirection.current - - val pad = remember(menu) { if (menu.repoListMode == RepoListMode.Compact) 0.dp else 16.dp } - Box( modifier = Modifier.fillMaxSize(), ) { - this@ModulesList.ResponsiveContent { - LazyColumn( - state = state, - modifier = Modifier - .fillMaxSize() - .animateContentSize() - .hazeSource(LocalHazeState.current), - contentPadding = - PaddingValues( - // top = innerPadding.calculateTopPadding(), - bottom = pad, - ), - verticalArrangement = Arrangement.spacedBy(16.dp), - ) { - beforeContent() + LazyColumn( + state = state, + modifier = Modifier + .animateContentSize(), + contentPadding = + PaddingValues( + bottom = this@ModulesList.scaffoldBottomPadding + 16.dp, + ), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + beforeContent() - items( - items = list, - key = { it.second.id }, - ) { (moduleState, module) -> - CompositionLocalProvider( - LocalOnlineModuleState provides moduleState, - LocalOnlineModule provides module, - ) { - val click = fun() { - navigator.navigate( - NewViewScreenDestination(repo, module), - ) - } + items( + items = list, + key = { it.second.id }, + ) { (moduleState, module) -> + CompositionLocalProvider( + LocalOnlineModuleState provides moduleState, + LocalOnlineModule provides module, + ) { + val click = fun() { + navigator.navigate( + NewViewScreenDestination(repo, module), + ) + } - Box( - modifier = Modifier.padding( - start = innerPadding.calculateStartPadding(layoutDirection) + pad, - end = pad, - ) - ) { - when (menu.repoListMode) { - RepoListMode.Compact -> - ModuleItemCompact( - onClick = click, - ) + Box(Modifier.padding(horizontal = 16.dp)) { + when (menu.repoListMode) { + RepoListMode.Compact -> + ModuleItemCompact( + onClick = click, + ) - RepoListMode.Detailed -> - ModuleItemDetailed( - onClick = click, - ) - } + RepoListMode.Detailed -> + ModuleItemDetailed( + onClick = click, + ) } } } - - item { - Spacer(modifier = Modifier.height(paddingValues.calculateBottomPadding())) - } } } } -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryMenu.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryMenu.kt index 1c706f7b8..b6376bc9a 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryMenu.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryMenu.kt @@ -6,10 +6,7 @@ import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -30,6 +27,9 @@ import com.dergoogler.mmrl.ui.component.Segment import com.dergoogler.mmrl.ui.component.SegmentedButtons import com.dergoogler.mmrl.ui.component.SegmentedButtonsDefaults import com.dergoogler.mmrl.ui.providable.LocalUserPreferences +import dev.mmrlx.compose.ui.Text +import dev.mmrlx.compose.ui.icon.Icon +import dev.mmrlx.compose.ui.icon.IconButton @Composable fun RepositoryMenu(setMenu: (RepositoryMenu) -> Unit) { diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryScreen.kt index f05eb90a6..cb41360b1 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryScreen.kt @@ -2,24 +2,10 @@ package com.dergoogler.mmrl.ui.screens.repository import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.layout.Arrangement +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.FlowRow -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue @@ -27,23 +13,14 @@ import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.core.net.toUri import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.dergoogler.mmrl.R @@ -51,28 +28,19 @@ import com.dergoogler.mmrl.database.entity.Repo import com.dergoogler.mmrl.datastore.model.RepoListMode import com.dergoogler.mmrl.datastore.model.RepositoryMenu import com.dergoogler.mmrl.ext.fadingEdge -import com.dergoogler.mmrl.ext.isNotNullOrBlank import com.dergoogler.mmrl.ext.none -import com.dergoogler.mmrl.ext.nullply -import com.dergoogler.mmrl.ext.onClick -import com.dergoogler.mmrl.ext.stripLinks import com.dergoogler.mmrl.ui.component.BottomBar import com.dergoogler.mmrl.ui.component.Cover -import com.dergoogler.mmrl.ui.component.LabelItem -import com.dergoogler.mmrl.ui.component.LabelItemDefaults -import com.dergoogler.mmrl.ui.component.LocalScreenProvider import com.dergoogler.mmrl.ui.component.PageIndicator -import com.dergoogler.mmrl.ui.component.card.Card -import com.dergoogler.mmrl.ui.component.scaffold.Scaffold -import com.dergoogler.mmrl.ui.component.text.BBCodeText import com.dergoogler.mmrl.ui.component.toolbar.BlurSearchToolbar -import com.dergoogler.mmrl.ui.component.toolbar.ToolbarTitle -import com.dergoogler.mmrl.ui.component.toolbar.rememberBlurToolbarState import com.dergoogler.mmrl.ui.providable.LocalUserPreferences import com.dergoogler.mmrl.viewmodel.RepositoryViewModel import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import dev.dergoogler.mmrl.compat.core.LocalUriHandler +import dev.mmrlx.compose.ui.icon.Icon +import dev.mmrlx.compose.ui.icon.IconButton +import dev.mmrlx.compose.ui.scaffold.Scaffold import dev.mmrlx.compose.ui.toolbar.ToolbarDefaults import dev.mmrlx.compose.ui.toolbar.ToolbarScrollBehavior @@ -80,7 +48,7 @@ import dev.mmrlx.compose.ui.toolbar.ToolbarScrollBehavior @Composable fun RepositoryScreen( repo: Repo, -) = LocalScreenProvider { +) { val userPreferences = LocalUserPreferences.current val browser = LocalUriHandler.current val density = LocalDensity.current @@ -93,7 +61,7 @@ fun RepositoryScreen( val closeSearch = viewModel::closeSearch val search = viewModel::search val isSearch by viewModel::isSearch - val scrollBehavior = ToolbarDefaults.pinnedScrollBehavior() + val scrollBehavior = ToolbarDefaults.fadePinnedScrollBehavior() var coverHeight by remember { mutableIntStateOf(0) } var cardHeight by remember { mutableIntStateOf(0) } @@ -101,11 +69,11 @@ fun RepositoryScreen( val menu = userPreferences.repositoryMenu val pad = remember(menu) { if (menu.repoListMode == RepoListMode.Compact) 0.dp else 16.dp } - BackHandler(enabled = viewModel.isSearch, onBack = viewModel::closeSearch) + BackHandler(enabled = viewModel.isSearch, onBack = closeSearch) Scaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), - topBar = { + toolbar = { TopBar0( repo = repo, isSearch = isSearch, @@ -121,146 +89,143 @@ fun RepositoryScreen( BottomBar() }, contentWindowInsets = WindowInsets.none, - ) { innerPadding -> - if (modules.isEmpty()) { - PageIndicator( - icon = R.drawable.cloud, - text = if (isSearch) R.string.search_empty else R.string.repository_empty, - ) - - return@Scaffold - } - - this@Scaffold.ModulesList( - innerPadding = innerPadding, - repo = repo, - list = modules, - state = viewModel.listState, + ) { + Box( + modifier = + Modifier + .scaffoldHazeSource() ) { - item { - AnimatedVisibility(isSearch) { - Spacer(Modifier.height(innerPadding.calculateTopPadding() + pad)) - } + if (modules.isEmpty()) { + PageIndicator( + icon = R.drawable.cloud, + text = if (isSearch) R.string.search_empty else R.string.repository_empty, + ) - AnimatedVisibility(!isSearch) { - Box(Modifier.animateEnterExit()) { - Cover( - modifier = - Modifier - .fadingEdge( - brush = - Brush.verticalGradient( - colors = listOf( - Color.Transparent, - Color.Black - ), - startY = Float.POSITIVE_INFINITY, - endY = 0f, - ), - ) - .onGloballyPositioned { coordinates -> - coverHeight = coordinates.size.height - }, - url = repo.cover ?: "ERROR ME", - errorIcon = R.drawable.box, - ) + return@Scaffold + } - Box( - modifier = - Modifier - .offset(y = 164.4.dp) - .align(Alignment.TopCenter) - .fillMaxWidth(0.85f) - .onGloballyPositioned { coordinates -> - cardHeight = coordinates.size.height - }, - ) { - Card( + this@Scaffold.ModulesList( + repo = repo, + list = modules, + state = viewModel.listState, + ) { + item { + AnimatedVisibility(!isSearch, enter = scaleIn(), exit = scaleOut()) { + Box(Modifier.animateEnterExit(scaleIn(), scaleOut())) { + Cover( modifier = Modifier - .align(Alignment.BottomCenter) - .background( + .fadingEdge( brush = - Brush.linearGradient( - colors = - listOf( - MaterialTheme.colorScheme.surfaceColorAtElevation( - 1.dp, - ), - MaterialTheme.colorScheme.background, - ), - start = Offset(0f, 0f), - end = - Offset( - Float.POSITIVE_INFINITY, - Float.POSITIVE_INFINITY, - ), + Brush.verticalGradient( + colors = listOf( + Color.Transparent, + Color.Black + ), + startY = Float.POSITIVE_INFINITY, + endY = 0f, ), - RoundedCornerShape(20.dp), ) - .border( - Dp.Hairline, - MaterialTheme.colorScheme.outlineVariant, - RoundedCornerShape(20.dp), - ), - ) { - Column( - modifier = - Modifier - .relative() - .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(16.dp), - ) { - Text( - text = repo.name, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - textAlign = TextAlign.Center, - style = MaterialTheme.typography.headlineMedium, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.onBackground, - ) - - BBCodeText( - text = - ( - repo.description - ?: stringResource(R.string.view_module_no_description) - ).stripLinks( - "", - ), - style = MaterialTheme.typography.bodyMedium, - textAlign = TextAlign.Center, - color = MaterialTheme.colorScheme.outlineVariant, - lineHeight = 20.sp, - ) - - FlowRow { - repo.nullply { - if (submission.isNotNullOrBlank()) { - LabelItem( - style = - LabelItemDefaults.style.copy( - containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, - contentColor = MaterialTheme.colorScheme.onSurfaceVariant, - ), - modifier = - Modifier.onClick { - browser.openUri(submission) - }, - icon = getDomainIcon(submission), - text = stringResource(R.string.repo_options_submission), - ) - } - } - } - } - } - } - - with(density) { - Spacer(modifier = Modifier.height((((coverHeight / 1.05f) + cardHeight)).toDp())) + .onGloballyPositioned { coordinates -> + coverHeight = coordinates.size.height + }, + url = repo.cover ?: "ERROR ME", + errorIcon = R.drawable.box, + ) +// +// Box( +// modifier = +// Modifier +// .offset(y = 164.4.dp) +// .align(Alignment.TopCenter) +// .fillMaxWidth(0.85f) +// .onGloballyPositioned { coordinates -> +// cardHeight = coordinates.size.height +// }, +// ) { +// Card( +// modifier = +// Modifier +// .align(Alignment.BottomCenter) +// .background( +// brush = +// Brush.linearGradient( +// colors = +// listOf( +// MaterialTheme.colorScheme.surfaceColorAtElevation( +// 1.dp, +// ), +// MaterialTheme.colorScheme.background, +// ), +// start = Offset(0f, 0f), +// end = +// Offset( +// Float.POSITIVE_INFINITY, +// Float.POSITIVE_INFINITY, +// ), +// ), +// RoundedCornerShape(20.dp), +// ) +// .border( +// Dp.Hairline, +// MaterialTheme.colorScheme.outlineVariant, +// RoundedCornerShape(20.dp), +// ), +// ) { +// Column( +// modifier = +// Modifier +// .relative() +// .padding(16.dp), +// horizontalAlignment = Alignment.CenterHorizontally, +// verticalArrangement = Arrangement.spacedBy(16.dp), +// ) { +// Text( +// text = repo.name, +// maxLines = 2, +// overflow = TextOverflow.Ellipsis, +// textAlign = TextAlign.Center, +// style = MaterialTheme.typography.headlineMedium, +// fontWeight = FontWeight.Bold, +// color = MaterialTheme.colorScheme.onBackground, +// ) +// +// BBCodeText( +// text = +// ( +// repo.description +// ?: stringResource(R.string.view_module_no_description) +// ).stripLinks( +// "", +// ), +// style = MaterialTheme.typography.bodyMedium, +// textAlign = TextAlign.Center, +// color = MaterialTheme.colorScheme.outlineVariant, +// lineHeight = 20.sp, +// ) +// +// FlowRow { +// repo.nullply { +// if (submission.isNotNullOrBlank()) { +// LabelItem( +// style = +// LabelItemDefaults.style.copy( +// containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, +// contentColor = MaterialTheme.colorScheme.onSurfaceVariant, +// ), +// modifier = +// Modifier.onClick { +// browser.openUri(submission) +// }, +// icon = getDomainIcon(submission), +// text = stringResource(R.string.repo_options_submission), +// ) +// } +// } +// } +// } +// } +// } } } } @@ -280,21 +245,15 @@ private fun TopBar0( scrollBehavior: ToolbarScrollBehavior, setMenu: (RepositoryMenu) -> Unit, ) { - val state = rememberBlurToolbarState(scrollBehavior, fade = true) - var currentQuery by remember { mutableStateOf(query) } DisposableEffect(isSearch) { onDispose { currentQuery = "" } } BlurSearchToolbar( - state = state, - fadeBackgroundIfNoBlur = true, isSearch = isSearch, query = currentQuery, scrollBehavior = scrollBehavior, - fade = true, - fadeDistance = 200f, onQueryChange = { onQueryChange(it) currentQuery = it @@ -304,10 +263,10 @@ private fun TopBar0( currentQuery = "" }, title = { - ToolbarTitle( - title = repo.name, - modifier = Modifier.graphicsLayer { alpha = state.alpha }, - ) +// ToolbarTitle( +// title = repo.name, +// modifier = Modifier.graphicsLayer { alpha = scrollBehavior.fadeDistance }, +// ) }, actions = { if (!isSearch) { diff --git a/ext/src/main/kotlin/com/dergoogler/mmrl/ext/PaddingValuesExt.kt b/ext/src/main/kotlin/com/dergoogler/mmrl/ext/PaddingValuesExt.kt index 06e3121f8..add7da461 100644 --- a/ext/src/main/kotlin/com/dergoogler/mmrl/ext/PaddingValuesExt.kt +++ b/ext/src/main/kotlin/com/dergoogler/mmrl/ext/PaddingValuesExt.kt @@ -11,7 +11,8 @@ import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.LayoutDirection -inline operator fun PaddingValues.plus(other: PaddingValues): PaddingValues = OperatorPaddingValues(this, other, Dp::plus) +inline operator fun PaddingValues.plus(other: Dp): PaddingValues = OperatorPaddingValues(this, + PaddingValues(other), Dp::plus) inline operator fun PaddingValues.minus(other: PaddingValues): PaddingValues = OperatorPaddingValues(this, other, Dp::minus) From 612f6234c2126f9c5e402eb2aa682fa50ff417d4 Mon Sep 17 00:00:00 2001 From: Der_Googler <54764558+DerGoogler@users.noreply.github.com> Date: Sun, 25 Oct 2026 11:28:20 +0100 Subject: [PATCH 22/24] . --- .../DescriptionScreen.kt | 2 +- .../ui/screens/moduleView/NewViewScreen.kt | 6 +- .../moduleView/ViewDescriptionScreen.kt | 175 ------------------ .../ui/screens/moduleView/sections/Toolbar.kt | 34 +--- .../mmrl/ui/screens/modules/ModulesMenu.kt | 6 +- .../ui/screens/repository/RepositoryScreen.kt | 6 - 6 files changed, 9 insertions(+), 220 deletions(-) rename app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/{moduleDetails => moduleView}/DescriptionScreen.kt (99%) delete mode 100644 app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/ViewDescriptionScreen.kt diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleDetails/DescriptionScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/DescriptionScreen.kt similarity index 99% rename from app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleDetails/DescriptionScreen.kt rename to app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/DescriptionScreen.kt index c2961ff64..fd97cbaa2 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleDetails/DescriptionScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/DescriptionScreen.kt @@ -1,4 +1,4 @@ -package com.dergoogler.mmrl.ui.screens.moduleDetails +package com.dergoogler.mmrl.ui.screens.moduleView import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.PaddingValues diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/NewViewScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/NewViewScreen.kt index ef88b9c08..0b2680d2f 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/NewViewScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/NewViewScreen.kt @@ -42,7 +42,6 @@ import com.dergoogler.mmrl.model.online.OnlineModule import com.dergoogler.mmrl.model.online.VersionItem import com.dergoogler.mmrl.model.online.isBlacklisted import com.dergoogler.mmrl.ui.activity.terminal.install.InstallActivity -import com.dergoogler.mmrl.ui.component.BottomBar import com.dergoogler.mmrl.ui.component.Cover import com.dergoogler.mmrl.ui.component.LocalScreenProvider import com.dergoogler.mmrl.ui.component.scaffold.Scaffold @@ -182,7 +181,7 @@ fun NewViewScreen( ) } - val scrollBehavior = ToolbarDefaults.pinnedScrollBehavior() + val scrollBehavior = ToolbarDefaults.fadePinnedScrollBehavior() val isBlacklisted by module.isBlacklisted @@ -222,9 +221,6 @@ fun NewViewScreen( topBar = { Toolbar() }, - bottomBar = { - BottomBar() - }, contentWindowInsets = WindowInsets.none, ) { innerPadding -> this@Scaffold.ResponsiveContent { diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/ViewDescriptionScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/ViewDescriptionScreen.kt deleted file mode 100644 index a7fa6a9f5..000000000 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/ViewDescriptionScreen.kt +++ /dev/null @@ -1,175 +0,0 @@ -package com.dergoogler.mmrl.ui.screens.moduleView - -import android.annotation.SuppressLint -import androidx.activity.compose.BackHandler -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.toArgb -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import com.dergoogler.mmrl.BuildConfig -import com.dergoogler.mmrl.R -import com.dergoogler.mmrl.ext.none -import com.dergoogler.mmrl.model.WebColors -import com.dergoogler.mmrl.ui.component.LocalScreenProvider -import com.dergoogler.mmrl.ui.component.scaffold.Scaffold -import com.dergoogler.mmrl.ui.component.toolbar.BlurToolbar -import com.dergoogler.mmrl.ui.providable.LocalDestinationsNavigator -import com.dergoogler.mmrl.ui.providable.LocalHazeState -import com.dergoogler.mmrl.ui.providable.LocalMainScreenInnerPaddings -import com.dergoogler.mmrl.ui.providable.LocalUserPreferences -import com.dergoogler.mmrl.utils.MimeUtil -import com.ramcosta.composedestinations.annotation.Destination -import com.ramcosta.composedestinations.annotation.RootGraph -import com.ramcosta.composedestinations.navigation.DestinationsNavigator -import dev.chrisbanes.haze.hazeSource -import dev.mmrlx.compose.ui.toolbar.ToolbarDefaults -import dev.mmrlx.compose.ui.toolbar.ToolbarScrollBehavior -import dev.mmrlx.compose.webui.WebUIView -import dev.mmrlx.compose.webui.insets -import dev.mmrlx.compose.webui.rememberWebUIState -import dev.mmrlx.webui.RouteRegistry -import dev.mmrlx.webui.RouteScope -import dev.mmrlx.webui.WebResourceResponse -import dev.mmrlx.webui.WebUIOptions -import java.net.HttpURLConnection -import java.net.URL - -@SuppressLint("SetJavaScriptEnabled", "JavascriptInterface") -@Composable -@Destination -fun ViewDescriptionScreen(readmeUrl: String) = - LocalScreenProvider { - val navigator = LocalDestinationsNavigator.current - val scrollBehavior = ToolbarDefaults.pinnedScrollBehavior() - val userPrefs = LocalUserPreferences.current - val scheme = MaterialTheme.colorScheme - - val wstate = - rememberWebUIState( - "https://desc.mmrl.dev", - WebUIOptions("internal/assets/markdown.html") - ) { - it - .settings { - debug = BuildConfig.DEBUG - forceKillProcess = false - useConsoleInterceptor = false - darkMode = userPrefs.isDarkMode() - } - .client { - // not related to client - it.webview.setBackgroundColor(scheme.background.toArgb()) - } - .chromeClient { } - .registerRoutes { - val webColors = WebColors(scheme) - - internalRoute("readme.md") { - val connection = URL(readmeUrl).openConnection() as HttpURLConnection - connection.connect() - htmlResponse(connection.getInputStream()) - } - - internalRoute("insets.css") { - insets.css.asStyleResponse() - } - - internalRoute("colors.css") { - webColors.allCssColors.asStyleResponse() - } - - internalRoute("assets/*asset") { - val filePath = - request.params["asset"] ?: return@internalRoute notFoundResponse - - try { - val inputStream = kontext.assets.open(filePath) - val mimeType = MimeUtil.getMimeFromFileName(filePath) - response(mimeType = mimeType, data = inputStream) - } catch (e: Exception) { - console.error("Failed to open $filePath from the assets: $e") - notFoundResponse - } - } - } - } - - DisposableEffect(Unit) { - onDispose { - wstate.destroy() - } - } - - // We have to override otherwise the WebUI will close the entire app - BackHandler { - if (wstate.canGoBack()) { - wstate.goBack() - return@BackHandler - } - - navigator.popBackStack() - } - - Scaffold( - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), - topBar = { - TopBar( - scrollBehavior = scrollBehavior, - navigator = navigator, - ) - }, - contentWindowInsets = WindowInsets.none, - ) { innerPadding -> - val bottomBarPaddingValues = LocalMainScreenInnerPaddings.current - wstate.insets( - PaddingValues( - top = innerPadding.calculateTopPadding(), - bottom = bottomBarPaddingValues.calculateBottomPadding(), - ) - ) - - Column( - modifier = - Modifier - .fillMaxSize() - .hazeSource(LocalHazeState.current), - ) { - this@Scaffold.ResponsiveContent { - WebUIView(wstate) - } - } - } - } - -@Composable -private fun TopBar( - navigator: DestinationsNavigator, - scrollBehavior: ToolbarScrollBehavior, -) = BlurToolbar( - navigationIcon = { - IconButton(onClick = { navigator.popBackStack() }) { - Icon( - painter = painterResource(id = R.drawable.arrow_left), - contentDescription = null, - ) - } - }, - title = { Text(text = stringResource(id = R.string.view_module_about_this_module)) }, - scrollBehavior = scrollBehavior, -) - -private fun RouteRegistry.internalRoute( - name: String, - content: RouteScope.() -> WebResourceResponse, -) = route("internal/$name", content) \ No newline at end of file diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Toolbar.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Toolbar.kt index 92dbf8484..4dd84f826 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Toolbar.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Toolbar.kt @@ -4,18 +4,9 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.width import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.SnackbarDuration -import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue -import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler @@ -28,7 +19,6 @@ import com.dergoogler.mmrl.ext.shareText import com.dergoogler.mmrl.model.local.BulkModule import com.dergoogler.mmrl.model.online.isValid import com.dergoogler.mmrl.platform.content.isValid -import com.dergoogler.mmrl.ui.component.toolbar.BlurToolbar import com.dergoogler.mmrl.ui.providable.LocalBulkInstall import com.dergoogler.mmrl.ui.providable.LocalDestinationsNavigator import com.dergoogler.mmrl.ui.providable.LocalModule @@ -41,6 +31,9 @@ import com.dergoogler.mmrl.ui.providable.LocalVersionItem import com.dergoogler.mmrl.ui.screens.moduleView.items.VersionsItem import com.dergoogler.mmrl.ui.screens.moduleView.providable.LocalModuleViewModel import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi +import dev.mmrlx.compose.ui.Text +import dev.mmrlx.compose.ui.icon.Icon +import dev.mmrlx.compose.ui.icon.IconButton import kotlinx.coroutines.launch @OptIn(ExperimentalHazeMaterialsApi::class) @@ -61,24 +54,7 @@ internal fun Toolbar() { val scope = rememberCoroutineScope() - val state = scrollBehavior.state - - var alpha by remember { mutableFloatStateOf(0f) } - val fadeDistance = 200f - - LaunchedEffect(state) { - snapshotFlow { state.contentOffset } - .collect { offset -> - val newAlpha = ((-offset) / fadeDistance).coerceIn(0f, 1f) - - // Only update if it actually changes meaningfully - if ((newAlpha < 1f && newAlpha > 0f) || newAlpha != alpha) { - alpha = newAlpha - } - } - } - - BlurToolbar( + dev.mmrlx.compose.ui.toolbar.Toolbar( title = {}, navigationIcon = { IconButton(onClick = { navigator.popBackStack() }) { @@ -88,8 +64,6 @@ internal fun Toolbar() { ) } }, - fade = true, - fadeBackgroundIfNoBlur = true, scrollBehavior = scrollBehavior, actions = { VersionsItem( diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/modules/ModulesMenu.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/modules/ModulesMenu.kt index a2e8c6fd7..78eeae984 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/modules/ModulesMenu.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/modules/ModulesMenu.kt @@ -8,11 +8,8 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -38,6 +35,9 @@ import com.dergoogler.mmrl.ui.component.Segment import com.dergoogler.mmrl.ui.component.SegmentedButtons import com.dergoogler.mmrl.ui.component.SegmentedButtonsDefaults import com.dergoogler.mmrl.ui.providable.LocalUserPreferences +import dev.mmrlx.compose.ui.Text +import dev.mmrlx.compose.ui.icon.Icon +import dev.mmrlx.compose.ui.icon.IconButton @Composable fun ModulesMenu(setMenu: (ModulesMenu) -> Unit) { diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryScreen.kt index cb41360b1..e66ca0773 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/repository/RepositoryScreen.kt @@ -20,12 +20,10 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource -import androidx.compose.ui.unit.dp import androidx.core.net.toUri import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.dergoogler.mmrl.R import com.dergoogler.mmrl.database.entity.Repo -import com.dergoogler.mmrl.datastore.model.RepoListMode import com.dergoogler.mmrl.datastore.model.RepositoryMenu import com.dergoogler.mmrl.ext.fadingEdge import com.dergoogler.mmrl.ext.none @@ -64,10 +62,6 @@ fun RepositoryScreen( val scrollBehavior = ToolbarDefaults.fadePinnedScrollBehavior() var coverHeight by remember { mutableIntStateOf(0) } - var cardHeight by remember { mutableIntStateOf(0) } - - val menu = userPreferences.repositoryMenu - val pad = remember(menu) { if (menu.repoListMode == RepoListMode.Compact) 0.dp else 16.dp } BackHandler(enabled = viewModel.isSearch, onBack = closeSearch) From f563294931a8c0ac4b1b4083f861c224a1c7da2f Mon Sep 17 00:00:00 2001 From: Der_Googler <54764558+DerGoogler@users.noreply.github.com> Date: Sun, 25 Oct 2026 14:01:22 +0100 Subject: [PATCH 23/24] . --- .../mmrl/ui/component/AntiFeaturesItem.kt | 8 +- .../mmrl/ui/component/PermissionItem.kt | 12 +- .../screens/moduleView/DescriptionScreen.kt | 2 +- .../ui/screens/moduleView/NewViewScreen.kt | 163 +++++++++--------- .../moduleView/sections/AboutModule.kt | 14 +- .../ui/screens/moduleView/sections/Header.kt | 31 ++-- .../moduleView/sections/Information.kt | 14 +- .../moduleView/sections/Information0.kt | 139 ++++++++------- .../sections/{Toolbar.kt => Toolbar0.kt} | 2 +- .../mmrl/viewmodel/RepositoryViewModel.kt | 3 +- 10 files changed, 200 insertions(+), 188 deletions(-) rename app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/{Toolbar.kt => Toolbar0.kt} (99%) diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/AntiFeaturesItem.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/AntiFeaturesItem.kt index 476e6c6c6..db46d0ce7 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/AntiFeaturesItem.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/AntiFeaturesItem.kt @@ -3,10 +3,10 @@ package com.dergoogler.mmrl.ui.component import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.Composable import com.dergoogler.mmrl.R -import com.dergoogler.mmrl.ui.component.listItem.dsl.ListScope -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.Item -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Description -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Title +import dev.mmrlx.compose.ui.list.ListScope +import dev.mmrlx.compose.ui.list.component.Item +import dev.mmrlx.compose.ui.list.component.item.Description +import dev.mmrlx.compose.ui.list.component.item.Title @Composable fun ListScope.AntiFeaturesItem( diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/PermissionItem.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/PermissionItem.kt index 785cb31cd..c65d61aef 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/PermissionItem.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/component/PermissionItem.kt @@ -6,11 +6,11 @@ import androidx.compose.foundation.layout.RowScope import androidx.compose.runtime.Composable import com.dergoogler.mmrl.R import com.dergoogler.mmrl.ext.nullable -import com.dergoogler.mmrl.ui.component.listItem.dsl.ListScope -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.Item -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Description -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Labels -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Title +import dev.mmrlx.compose.ui.list.ListScope +import dev.mmrlx.compose.ui.list.component.Item +import dev.mmrlx.compose.ui.list.component.item.Description +import dev.mmrlx.compose.ui.list.component.item.Supporting +import dev.mmrlx.compose.ui.list.component.item.Title @Composable fun ListScope.PermissionItem( @@ -25,7 +25,7 @@ fun ListScope.PermissionItem( Title(p.title) Description(p.desc) p.rootSolutions.nullable { - Labels { + Supporting { it() } } diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/DescriptionScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/DescriptionScreen.kt index fd97cbaa2..5a407934e 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/DescriptionScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/DescriptionScreen.kt @@ -45,7 +45,7 @@ import java.net.URL @Destination fun DescriptionScreen(readmeUrl: String) { val navigator = LocalDestinationsNavigator.current - val scrollBehavior = ToolbarDefaults.pinnedScrollBehavior() + val scrollBehavior = ToolbarDefaults.fadePinnedScrollBehavior() val userPrefs = LocalUserPreferences.current val colors = MMRLXTheme.colors diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/NewViewScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/NewViewScreen.kt index 0b2680d2f..4f7749f20 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/NewViewScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/NewViewScreen.kt @@ -6,13 +6,10 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable @@ -36,7 +33,6 @@ import com.dergoogler.mmrl.ext.fadingEdge import com.dergoogler.mmrl.ext.ifNotEmpty import com.dergoogler.mmrl.ext.none import com.dergoogler.mmrl.ext.nullable -import com.dergoogler.mmrl.ext.systemBarsPaddingEnd import com.dergoogler.mmrl.model.local.BulkModule import com.dergoogler.mmrl.model.online.OnlineModule import com.dergoogler.mmrl.model.online.VersionItem @@ -44,11 +40,9 @@ import com.dergoogler.mmrl.model.online.isBlacklisted import com.dergoogler.mmrl.ui.activity.terminal.install.InstallActivity import com.dergoogler.mmrl.ui.component.Cover import com.dergoogler.mmrl.ui.component.LocalScreenProvider -import com.dergoogler.mmrl.ui.component.scaffold.Scaffold import com.dergoogler.mmrl.ui.providable.LocalBulkInstall import com.dergoogler.mmrl.ui.providable.LocalDestinationsNavigator import com.dergoogler.mmrl.ui.providable.LocalHazeState -import com.dergoogler.mmrl.ui.providable.LocalMainScreenInnerPaddings import com.dergoogler.mmrl.ui.providable.LocalModule import com.dergoogler.mmrl.ui.providable.LocalOnlineModule import com.dergoogler.mmrl.ui.providable.LocalRepo @@ -67,11 +61,14 @@ import com.dergoogler.mmrl.ui.screens.moduleView.sections.Header import com.dergoogler.mmrl.ui.screens.moduleView.sections.Information import com.dergoogler.mmrl.ui.screens.moduleView.sections.Information0 import com.dergoogler.mmrl.ui.screens.moduleView.sections.Screenshots -import com.dergoogler.mmrl.ui.screens.moduleView.sections.Toolbar +import com.dergoogler.mmrl.ui.screens.moduleView.sections.Toolbar0 import com.dergoogler.mmrl.viewmodel.ModuleViewModel import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph -import dev.chrisbanes.haze.hazeSource +import dev.mmrlx.compose.ui.HorizontalDivider +import dev.mmrlx.compose.ui.LinearProgressIndicator +import dev.mmrlx.compose.ui.ext.with +import dev.mmrlx.compose.ui.scaffold.Scaffold import dev.mmrlx.compose.ui.toolbar.ToolbarDefaults import timber.log.Timber @@ -218,98 +215,94 @@ fun NewViewScreen( Scaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), snackbarHost = { SnackbarHost(snackbarHostState) }, - topBar = { - Toolbar() + toolbar = { + Toolbar0() }, contentWindowInsets = WindowInsets.none, - ) { innerPadding -> - this@Scaffold.ResponsiveContent { - Column( - modifier = - Modifier - .let { - if (repositoryMenu.showCover && module.hasCover) { - Modifier - } else { - it.padding(innerPadding) - } - }.verticalScroll(rememberScrollState()) - .hazeSource(state = hazeState), - ) { - module.cover.nullable(repositoryMenu.showCover) { - if (it.isNotEmpty()) { - Cover( - modifier = - Modifier.fadingEdge( - Brush.verticalGradient( - colors = - listOf( - Color.Transparent, - Color.Black, - ), - startY = Float.POSITIVE_INFINITY, - endY = 0f, - ), - ), - url = it, - ) - } - } - - Column( - modifier = - Modifier - .systemBarsPaddingEnd(), - ) { - Spacer(modifier = Modifier.height(16.dp)) + ) { + val scope = this - Header() - - val progress = - lastVersionItem?.let { - viewModel.getProgress(it) - } ?: 0f - - if (progress != 0f) { - LinearProgressIndicator( - progress = { progress }, - strokeCap = StrokeCap.Round, - modifier = - Modifier - .padding(vertical = 16.dp) - .height(0.9.dp) - .fillMaxWidth(), - ) - } else { - HorizontalDivider( - modifier = Modifier.padding(vertical = 16.dp), - thickness = 0.9.dp, - ) + Column( + modifier = + Modifier + .verticalScroll(rememberScrollState()) + .scaffoldHazeSource() + .let { + if (repositoryMenu.showCover && module.hasCover) { + it + } else { + it.padding(top = scaffoldTopPadding) + } } + ) { + module.cover.nullable(repositoryMenu.showCover) { + if (it.isNotEmpty()) { + Cover( + modifier = + Modifier.fadingEdge( + Brush.verticalGradient( + colors = + listOf( + Color.Transparent, + Color.Black, + ), + startY = Float.POSITIVE_INFINITY, + endY = 0f, + ), + ), + url = it, + ) + } + } - Alerts() - - AboutModule() - - Screenshots() + Column( + modifier = Modifier.with(scope) { + it.scaffoldBottomPadding() + } + ) { + Spacer(modifier = Modifier.height(16.dp)) - Spacer(modifier = Modifier.height(16.dp)) + Header() - Information0() + val progress = + lastVersionItem?.let { + viewModel.getProgress(it) + } ?: 0f - // Information section + if (progress != 0f) { + LinearProgressIndicator( + progress = { progress }, + strokeCap = StrokeCap.Round, + modifier = + Modifier + .padding(vertical = 16.dp) + .height(0.9.dp) + .fillMaxWidth(), + ) + } else { HorizontalDivider( modifier = Modifier.padding(vertical = 16.dp), thickness = 0.9.dp, ) + } - Information() + Alerts() - Spacer(modifier = Modifier.navigationBarsPadding()) + AboutModule() - val paddingValues = LocalMainScreenInnerPaddings.current - Spacer(modifier = Modifier.height(paddingValues.calculateBottomPadding())) - } + Screenshots() + + Spacer(modifier = Modifier.height(16.dp)) + + Information0() + + // Information section + HorizontalDivider( + modifier = Modifier.padding(vertical = 16.dp), + thickness = 0.9.dp, + ) + + Information() } } } diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/AboutModule.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/AboutModule.kt index 3c593a474..fcb4b7986 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/AboutModule.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/AboutModule.kt @@ -10,7 +10,6 @@ import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.AssistChip 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.painterResource @@ -19,17 +18,18 @@ import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.unit.dp import com.dergoogler.mmrl.R import com.dergoogler.mmrl.model.online.hasCategories -import com.dergoogler.mmrl.ui.component.listItem.dsl.List -import com.dergoogler.mmrl.ui.component.listItem.dsl.ListItemSlot -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.ButtonItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.Item -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Icon -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Title import com.dergoogler.mmrl.ui.providable.LocalDestinationsNavigator import com.dergoogler.mmrl.ui.providable.LocalOnlineModule import com.dergoogler.mmrl.ui.providable.LocalRepo import com.dergoogler.mmrl.ui.screens.moduleView.listItemContentPaddingValues import com.ramcosta.composedestinations.generated.destinations.DescriptionScreenDestination +import dev.mmrlx.compose.ui.Text +import dev.mmrlx.compose.ui.list.List +import dev.mmrlx.compose.ui.list.ListItemSlot +import dev.mmrlx.compose.ui.list.component.ButtonItem +import dev.mmrlx.compose.ui.list.component.Item +import dev.mmrlx.compose.ui.list.component.item.Icon +import dev.mmrlx.compose.ui.list.component.item.Title @Composable internal fun AboutModule() { diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Header.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Header.kt index dea019632..453b6bdb3 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Header.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Header.kt @@ -15,7 +15,6 @@ import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -23,19 +22,18 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import coil.compose.AsyncImage import com.dergoogler.mmrl.R import com.dergoogler.mmrl.ext.isNotNullOrBlank -import com.dergoogler.mmrl.ext.nullable import com.dergoogler.mmrl.model.local.State import com.dergoogler.mmrl.model.online.isBlacklisted import com.dergoogler.mmrl.platform.content.LocalModule.Companion.isEmpty import com.dergoogler.mmrl.platform.content.isValid import com.dergoogler.mmrl.ui.component.Logo -import com.dergoogler.mmrl.ui.component.text.IconText import com.dergoogler.mmrl.ui.providable.LocalDestinationsNavigator import com.dergoogler.mmrl.ui.providable.LocalModule import com.dergoogler.mmrl.ui.providable.LocalOnlineModule @@ -43,6 +41,10 @@ import com.dergoogler.mmrl.ui.providable.LocalRepo import com.dergoogler.mmrl.ui.providable.LocalUserPreferences import com.dergoogler.mmrl.ui.providable.LocalVersionItem import com.dergoogler.mmrl.ui.screens.moduleView.providable.LocalModuleViewModel +import dev.mmrlx.compose.ui.Text +import dev.mmrlx.compose.ui.icon.Icon +import dev.mmrlx.compose.ui.text.FormatText +import dev.mmrlx.compose.ui.theme.MMRLXTheme @Composable internal fun Header() { @@ -88,15 +90,22 @@ internal fun Header() { Column( modifier = Modifier.weight(1f), ) { - IconText( - style = MaterialTheme.typography.titleLarge, - tint = MaterialTheme.colorScheme.surfaceTint, - alignment = Alignment.End, + FormatText( + style = MMRLXTheme.typography.titleMedium, + text = module.name + if (module.isVerified) " %y" else "", maxLines = 2, overflow = TextOverflow.Ellipsis, - text = module.name, - resId = module.isVerified nullable R.drawable.rosette_discount_check, - ) + ) { + if (module.isVerified) { + composable { + Icon( + tint = MaterialTheme.colorScheme.surfaceTint, + modifier = Modifier.size(fontSize.dp), + painter = painterResource(id = R.drawable.rosette_discount_check), + ) + } + } + } Spacer(modifier = Modifier.height(4.dp)) @@ -144,7 +153,7 @@ internal fun Header() { OutlinedButton( enabled = viewModel.isProviderAlive && - (!userPreferences.useShellForModuleStateChange || it.state != com.dergoogler.mmrl.model.local.State.REMOVE), + (!userPreferences.useShellForModuleStateChange || it.state != com.dergoogler.mmrl.model.local.State.REMOVE), modifier = Modifier .fillMaxWidth() diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Information.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Information.kt index 94ef5eda7..2c8fa0fb1 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Information.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Information.kt @@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth 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.graphics.graphicsLayer @@ -22,11 +21,6 @@ import com.dergoogler.mmrl.ext.takeTrue import com.dergoogler.mmrl.model.online.isValid import com.dergoogler.mmrl.platform.content.isValid import com.dergoogler.mmrl.platform.file.SuFile.Companion.toFormattedFileSize -import com.dergoogler.mmrl.ui.component.listItem.dsl.List -import com.dergoogler.mmrl.ui.component.listItem.dsl.ListItemSlot -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.CollapseItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Icon -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Title import com.dergoogler.mmrl.ui.providable.LocalModule import com.dergoogler.mmrl.ui.providable.LocalOnlineModule import com.dergoogler.mmrl.ui.providable.LocalUserPreferences @@ -34,6 +28,12 @@ import com.dergoogler.mmrl.ui.providable.LocalVersionItem import com.dergoogler.mmrl.ui.screens.moduleView.items.LicenseItem import com.dergoogler.mmrl.ui.screens.moduleView.providable.LocalModuleViewModel import com.dergoogler.mmrl.utils.toFormattedDateSafely +import dev.mmrlx.compose.ui.Text +import dev.mmrlx.compose.ui.list.List +import dev.mmrlx.compose.ui.list.ListItemSlot +import dev.mmrlx.compose.ui.list.component.CollapseItem +import dev.mmrlx.compose.ui.list.component.item.Icon +import dev.mmrlx.compose.ui.list.component.item.Title @Composable internal fun Information() { @@ -162,7 +162,7 @@ internal fun Information() { ), ) { CollapseItem( - meta = { icon, rotation -> + first = { icon, rotation -> Title( id = R.string.module_installed, styleTransform = { diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Information0.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Information0.kt index 27b61f48e..54d7a3bff 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Information0.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Information0.kt @@ -12,21 +12,21 @@ import com.dergoogler.mmrl.ext.ifNotNullOrBlank import com.dergoogler.mmrl.ui.component.AntiFeaturesItem import com.dergoogler.mmrl.ui.component.LabelItem import com.dergoogler.mmrl.ui.component.PermissionItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.List -import com.dergoogler.mmrl.ui.component.listItem.dsl.ListItemSlot -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.ButtonItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.CollapseItem -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.Item -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Description -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Icon -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Labels -import com.dergoogler.mmrl.ui.component.listItem.dsl.component.item.Title import com.dergoogler.mmrl.ui.providable.LocalOnlineModule import com.dergoogler.mmrl.ui.screens.moduleView.items.OtherSourcesItem import com.dergoogler.mmrl.ui.screens.moduleView.listItemContentPaddingValues import com.dergoogler.mmrl.ui.screens.moduleView.providable.LocalModuleViewModel import com.dergoogler.mmrl.ui.screens.moduleView.providable.LocalRequireModules import com.dergoogler.mmrl.ui.screens.moduleView.subListItemContentPaddingValues +import dev.mmrlx.compose.ui.list.List +import dev.mmrlx.compose.ui.list.ListItemSlot +import dev.mmrlx.compose.ui.list.component.ButtonItem +import dev.mmrlx.compose.ui.list.component.CollapseItem +import dev.mmrlx.compose.ui.list.component.Item +import dev.mmrlx.compose.ui.list.component.item.Description +import dev.mmrlx.compose.ui.list.component.item.Icon +import dev.mmrlx.compose.ui.list.component.item.Supporting +import dev.mmrlx.compose.ui.list.component.item.Title @Composable internal fun Information0() { @@ -39,7 +39,7 @@ internal fun Information0() { contentPadding = listItemContentPaddingValues, ) { CollapseItem( - meta = { icon, rotation -> + first = { icon, rotation -> Title(R.string.view_module_module_support) Icon( slot = ListItemSlot.End, @@ -50,57 +50,57 @@ internal fun Information0() { ) }, ) { - module.donate.ifNotNullOrBlank { - ButtonItem( - contentPadding = subListItemContentPaddingValues, - onClick = { - browser.openUri(it) - }, - ) { - Icon(painter = painterResource(id = R.drawable.currency_dollar)) - Title(R.string.view_module_donate) - Description(R.string.view_module_donate_desc) - } - } - - ButtonItem( + List( contentPadding = subListItemContentPaddingValues, - onClick = { - browser.openUri(module.track.source) - }, ) { - Icon(painter = painterResource(id = R.drawable.brand_git)) - Title(R.string.view_module_source) - } + module.donate.ifNotNullOrBlank { + ButtonItem( + onClick = { + browser.openUri(it) + }, + ) { + Icon(painter = painterResource(id = R.drawable.currency_dollar)) + Title(R.string.view_module_donate) + Description(R.string.view_module_donate_desc) + } + } - module.homepage.ifNotNullOrBlank { ButtonItem( - contentPadding = subListItemContentPaddingValues, onClick = { - browser.openUri(it) + browser.openUri(module.track.source) }, ) { - Icon(painter = painterResource(id = R.drawable.world_www)) - Title(R.string.view_module_homepage) + Icon(painter = painterResource(id = R.drawable.brand_git)) + Title(R.string.view_module_source) } - } - module.support?.ifNotNullOrBlank { - ButtonItem( - contentPadding = subListItemContentPaddingValues, - onClick = { - browser.openUri(it) - }, - ) { - Icon(painter = painterResource(id = R.drawable.heart_handshake)) - Title(R.string.view_module_support) + module.homepage.ifNotNullOrBlank { + ButtonItem( + onClick = { + browser.openUri(it) + }, + ) { + Icon(painter = painterResource(id = R.drawable.world_www)) + Title(R.string.view_module_homepage) + } + } + + module.support?.ifNotNullOrBlank { + ButtonItem( + onClick = { + browser.openUri(it) + }, + ) { + Icon(painter = painterResource(id = R.drawable.heart_handshake)) + Title(R.string.view_module_support) + } } } } module.permissions.ifNotEmpty { CollapseItem( - meta = { icon, rotation -> + first = { icon, rotation -> Title(R.string.view_module_permissions) Icon( slot = ListItemSlot.End, @@ -109,7 +109,7 @@ internal fun Information0() { .graphicsLayer(rotationZ = rotation), painter = painterResource(id = icon), ) - Labels { + Supporting { LabelItem( text = stringResource( @@ -120,16 +120,20 @@ internal fun Information0() { } }, ) { - PermissionItem( + List( contentPadding = subListItemContentPaddingValues, - permissions = it, - ) + ) { + PermissionItem( + contentPadding = subListItemContentPaddingValues, + permissions = it, + ) + } } } module.track.antifeatures.ifNotEmpty { CollapseItem( - meta = { icon, rotation -> + first = { icon, rotation -> Title(R.string.view_module_antifeatures) Icon( slot = ListItemSlot.End, @@ -138,7 +142,7 @@ internal fun Information0() { .graphicsLayer(rotationZ = rotation), painter = painterResource(id = icon), ) - Labels { + Supporting { LabelItem( text = stringResource( @@ -149,16 +153,20 @@ internal fun Information0() { } }, ) { - AntiFeaturesItem( + List( contentPadding = subListItemContentPaddingValues, - antifeatures = it, - ) + ) { + AntiFeaturesItem( + contentPadding = subListItemContentPaddingValues, + antifeatures = it, + ) + } } } requires.ifNotEmpty { requiredIds -> CollapseItem( - meta = { icon, rotation -> + first = { icon, rotation -> Title(R.string.view_module_dependencies) Icon( slot = ListItemSlot.End, @@ -167,7 +175,7 @@ internal fun Information0() { .graphicsLayer(rotationZ = rotation), painter = painterResource(id = icon), ) - Labels { + Supporting { LabelItem( text = stringResource( @@ -178,13 +186,16 @@ internal fun Information0() { } }, ) { - requiredIds.forEach { onlineModule -> - ButtonItem( - contentPadding = subListItemContentPaddingValues, - onClick = {}, - ) { - Title(onlineModule.name) - Description(onlineModule.versionCode.toString()) + List( + contentPadding = subListItemContentPaddingValues, + ) { + requiredIds.forEach { onlineModule -> + ButtonItem( + onClick = {}, + ) { + Title(onlineModule.name) + Description(onlineModule.versionCode.toString()) + } } } } diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Toolbar.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Toolbar0.kt similarity index 99% rename from app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Toolbar.kt rename to app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Toolbar0.kt index 4dd84f826..17aa0ee6e 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Toolbar.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/sections/Toolbar0.kt @@ -38,7 +38,7 @@ import kotlinx.coroutines.launch @OptIn(ExperimentalHazeMaterialsApi::class) @Composable -internal fun Toolbar() { +internal fun Toolbar0() { val context = LocalContext.current val viewModel = LocalModuleViewModel.current val repo = LocalRepo.current diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/viewmodel/RepositoryViewModel.kt b/app/src/main/kotlin/com/dergoogler/mmrl/viewmodel/RepositoryViewModel.kt index 371cb1c53..094be327b 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/viewmodel/RepositoryViewModel.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/viewmodel/RepositoryViewModel.kt @@ -6,7 +6,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue -import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.viewModelScope import com.dergoogler.mmrl.database.entity.Repo import com.dergoogler.mmrl.datastore.UserPreferencesRepository @@ -205,7 +204,7 @@ constructor( companion object { @Composable fun build(repo: Repo): RepositoryViewModel = - hiltViewModel { factory -> + androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel { factory -> factory.create(repo) } } From 4c5838e3bd601701f6077c6eacc16bf89cd7ec39 Mon Sep 17 00:00:00 2001 From: Der_Googler <54764558+DerGoogler@users.noreply.github.com> Date: Sun, 25 Oct 2026 16:19:15 +0100 Subject: [PATCH 24/24] . --- .../mmrl/ui/screens/moduleView/DescriptionScreen.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/DescriptionScreen.kt b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/DescriptionScreen.kt index 5a407934e..8da2090a8 100644 --- a/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/DescriptionScreen.kt +++ b/app/src/main/kotlin/com/dergoogler/mmrl/ui/screens/moduleView/DescriptionScreen.kt @@ -4,14 +4,17 @@ import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.navigationBars import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import com.dergoogler.mmrl.BuildConfig import com.dergoogler.mmrl.R import com.dergoogler.mmrl.ext.none @@ -45,7 +48,7 @@ import java.net.URL @Destination fun DescriptionScreen(readmeUrl: String) { val navigator = LocalDestinationsNavigator.current - val scrollBehavior = ToolbarDefaults.fadePinnedScrollBehavior() + val scrollBehavior = ToolbarDefaults.pinnedScrollBehavior() val userPrefs = LocalUserPreferences.current val colors = MMRLXTheme.colors @@ -135,7 +138,7 @@ fun DescriptionScreen(readmeUrl: String) { wstate.insets( PaddingValues( top = contentPadding.calculateTopPadding(), - bottom = contentPadding.calculateBottomPadding(), + bottom = with(LocalDensity.current) { WindowInsets.navigationBars.getBottom(this).dp }, ) )