Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ plugins {
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.ksp)
alias(libs.plugins.jetbrains.kotlin.serialization)
alias(libs.plugins.stability.analyzer)
id("kotlin-parcelize")
}

Expand All @@ -17,8 +18,8 @@ android {
applicationId = AppConfig.appId
minSdk = AppConfig.minSdkVersion
targetSdk = AppConfig.targetSdkVersion
versionCode = 22
versionName = "3.0.3"
versionCode = 23
versionName = "3.1.0"
vectorDrawables.useSupportLibrary = true
androidResources {
localeFilters += listOf("en", "de", "pt-rBR", "tr")
Expand Down Expand Up @@ -129,4 +130,4 @@ dependencies {
implementation(libs.koin)
implementation(libs.koin.compose)
implementation(libs.bundles.libsu)
}
}
3 changes: 3 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
<activity
android:name=".ui.start.StartErrorActivity"
android:theme="@style/AppTheme" />
<activity
android:name=".ui.start.ConsentActivity"
android:theme="@style/AppTheme" />
<activity
android:name=".ui.tasker.TaskerPluginActivity"
android:exported="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import androidx.compose.runtime.Immutable
* route information to be associated with the top-level destination.
* @property name The human-readable name of the top-level destination, used for labels.
* @property route The actual [UiRoute] object that defines the navigation destination.
* @property selectedIconRes The icon to display when this top-level route is currently selected.
* @property unselectedIconRes The icon to display when this top-level route is not selected.
* @property selectedAnimatedIconRes The icon to display when this top-level route is currently selected.
* @property unselectedAnimatedIconRes The icon to display when this top-level route is not selected.
*/
@Immutable
data class TopLevelRoute<T : UiRoute>(
val name: String,
val route: T,
@param:DrawableRes val selectedIconRes: Int,
@param:DrawableRes val unselectedIconRes: Int
@param:DrawableRes val selectedAnimatedIconRes: Int,
@param:DrawableRes val unselectedAnimatedIconRes: Int
)
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.androidvip.sysctlgui.models

import androidx.compose.runtime.Immutable

/**
* Represents a search hint displayed to the user.
*
Expand All @@ -9,6 +11,7 @@ package com.androidvip.sysctlgui.models
* @property hint The text of the search hint.
* @property isFromHistory A boolean flag indicating whether the hint is from the user's search history
*/
@Immutable
data class SearchHint(
val hint: String,
val isFromHistory: Boolean = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.androidvip.sysctlgui.models

import android.os.Build
import android.os.Parcelable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.Immutable
import com.androidvip.sysctlgui.domain.models.KernelParam
import com.androidvip.sysctlgui.utils.Consts
import kotlinx.parcelize.IgnoredOnParcel
Expand All @@ -14,7 +14,7 @@ import kotlin.io.path.isDirectory
/**
* Represents a kernel parameter with additional UI-specific properties.
*/
@Stable
@Immutable
@Parcelize
data class UiKernelParam(
override val name: String = "",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.androidvip.sysctlgui.models

import androidx.compose.runtime.Immutable
import com.androidvip.sysctlgui.domain.models.ParamDocumentation

@Immutable
data class UiParamDocumentation(
override val title: String = "",
override val documentationText: String = "",
override val documentationHtml: String? = null,
override val url: String? = null
) : ParamDocumentation

fun ParamDocumentation.toUiParamDocumentation() = UiParamDocumentation(
title = this.title,
documentationText = this.documentationText,
documentationHtml = this.documentationHtml,
url = this.url
)
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
Expand Down Expand Up @@ -53,6 +54,7 @@ internal fun ErrorContainer(message: String, onAnimationEnd: () -> Unit) {

Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(24.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.errorContainer,
contentColor = MaterialTheme.colorScheme.onErrorContainer
Expand All @@ -75,8 +77,8 @@ internal fun ErrorContainer(message: String, onAnimationEnd: () -> Unit) {
) {
Icon(
modifier = Modifier.size(40.dp),
painter = painterResource(R.drawable.ic_close),
contentDescription = null,
painter = painterResource(R.drawable.ic_warning),
contentDescription = stringResource(R.string.error),
tint = MaterialTheme.colorScheme.onErrorContainer
)
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ class MainActivity : ComponentActivity() {
registerForActivityResult(ActivityResultContracts.RequestPermission()) { _ -> }

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
updateEdgeToEdgeConfiguration(prefs.forceDark)
super.onCreate(savedInstanceState)

setContent {
val themeState by mainViewModel.themeState.collectAsStateWithLifecycle()
Expand Down
26 changes: 11 additions & 15 deletions app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainNavBar.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.androidvip.sysctlgui.ui.main

import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.graphics.res.animatedVectorResource
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
import androidx.compose.animation.graphics.vector.AnimatedImageVector
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
Expand All @@ -9,7 +11,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewDynamicColors
import androidx.compose.ui.tooling.preview.PreviewLightDark
Expand All @@ -35,20 +36,15 @@ internal fun MainNavBar(navController: NavHostController = rememberNavController
?.hierarchy
?.any { it.hasRoute(route.route::class) } == true

val iconRes = if (selected) route.selectedAnimatedIconRes else route.unselectedAnimatedIconRes
val imageVector = AnimatedImageVector.animatedVectorResource(id = iconRes)
val animatedPainter = rememberAnimatedVectorPainter(
animatedImageVector = imageVector,
atEnd = selected
)

NavigationBarItem(
icon = {
AnimatedContent(targetState = selected) { selectedState ->
val iconRes = if (selectedState) {
route.selectedIconRes
} else {
route.unselectedIconRes
}
Icon(
painter = painterResource(iconRes),
contentDescription = route.name,
)
}
},
icon = { Icon(painter = animatedPainter, contentDescription = route.name) },
label = {
Text(
text = route.name,
Expand Down
27 changes: 12 additions & 15 deletions app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainNavRail.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.androidvip.sysctlgui.ui.main

import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.graphics.res.animatedVectorResource
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
import androidx.compose.animation.graphics.vector.AnimatedImageVector
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationRail
import androidx.compose.material3.NavigationRailItem
Expand All @@ -9,7 +11,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.navigation.NavDestination.Companion.hasRoute
Expand All @@ -34,20 +35,16 @@ internal fun MainNavRail(navController: NavHostController = rememberNavControlle
?.hierarchy
?.any { it.hasRoute(route.route::class) } == true

val iconRes = if (selected) route.selectedAnimatedIconRes else route.unselectedAnimatedIconRes
val imageVector = AnimatedImageVector.animatedVectorResource(id = iconRes)
val animatedPainter = rememberAnimatedVectorPainter(
animatedImageVector = imageVector,
atEnd = selected
)

NavigationRailItem(
icon = {
AnimatedContent(targetState = selected) { selectedState ->
val iconRes = if (selectedState) {
route.selectedIconRes
} else {
route.unselectedIconRes
}
Icon(
painterResource(iconRes),
contentDescription = route.name,
)
}
},
icon = { Icon(painter = animatedPainter, contentDescription = route.name) },

label = {
Text(
text = route.name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
Expand Down Expand Up @@ -60,6 +62,7 @@ fun MainScreen(
MainScreenContent(state, navController, snackbarHostState, startDestination)
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun MainScreenContent(
state: MainViewState,
Expand All @@ -71,6 +74,7 @@ private fun MainScreenContent(
val isLandscape = isLandscape()

Scaffold(
contentWindowInsets = WindowInsets(0, 0, 0, 0),
topBar = {
AnimatedVisibility(
visible = state.showTopBar,
Expand Down Expand Up @@ -116,7 +120,9 @@ private fun MainScreenContent(
}

AppNavHost(
modifier = Modifier.weight(1f),
modifier = Modifier
.weight(1f)
.then(if (isLandscape) Modifier.navigationBarsPadding() else Modifier),
innerPadding = innerPadding,
navController = navController,
startDestination = startDestination
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class MainViewModel(
) : BaseViewModel<MainViewEvent, MainViewState, MainViewEffect>() {
private val themeSettingsFlow: Flow<ThemeSettings> = combine(
appPrefs.observeKey(Prefs.ForceDarkTheme.key, false),
appPrefs.observeKey(Prefs.DynamicColors.key, false),
appPrefs.observeKey(Prefs.DynamicColors.key, true),
appPrefs.observeKey(Prefs.ContrastLevel.key, CONTRAST_LEVEL_NORMAL)
) { forceDark, dynamicColors, contrastLevel ->
ThemeSettings(forceDark, dynamicColors, contrastLevel)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.androidvip.sysctlgui.ui.main

import androidx.compose.material3.SnackbarResult
import androidx.compose.runtime.Immutable
import com.androidvip.sysctlgui.data.repository.CONTRAST_LEVEL_NORMAL

@Immutable
data class MainViewState(
val topBarTitle: String = "Sysctl GUI",
val showTopBar: Boolean = true,
Expand All @@ -13,7 +15,7 @@ data class MainViewState(

data class ThemeSettings(
val forceDark: Boolean = false,
val dynamicColors: Boolean = false,
val dynamicColors: Boolean = true,
val contrastLevel: Int = CONTRAST_LEVEL_NORMAL
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,26 @@ object TopLevelRouteProvider {
TopLevelRoute(
name = context.getString(R.string.browse),
route = UiRoute.BrowseParams,
selectedIconRes = R.drawable.ic_home_filled,
unselectedIconRes = R.drawable.ic_home
selectedAnimatedIconRes = R.drawable.avd_home_off,
unselectedAnimatedIconRes = R.drawable.avd_home_on
),
TopLevelRoute(
name = context.getString(R.string.presets),
route = UiRoute.Presets,
selectedIconRes = R.drawable.ic_build_filled,
unselectedIconRes = R.drawable.ic_build
selectedAnimatedIconRes = R.drawable.avd_build_off,
unselectedAnimatedIconRes = R.drawable.avd_build_on
),
TopLevelRoute(
name = context.getString(R.string.favorites),
route = UiRoute.Favorites,
selectedIconRes = R.drawable.ic_favorite,
unselectedIconRes = R.drawable.ic_favorite_outlined
selectedAnimatedIconRes = R.drawable.avd_favorite_off,
unselectedAnimatedIconRes = R.drawable.avd_favorite_on
),
TopLevelRoute(
name = context.getString(R.string.settings),
route = UiRoute.Settings,
selectedIconRes = R.drawable.ic_settings_filled,
unselectedIconRes = R.drawable.ic_settings
selectedAnimatedIconRes = R.drawable.avd_settings_off,
unselectedAnimatedIconRes = R.drawable.avd_settings_on
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,18 @@ import androidx.compose.ui.text.fromHtml
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp
import androidx.core.text.HtmlCompat
import com.androidvip.sysctlgui.R
import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme
import com.androidvip.sysctlgui.domain.models.ParamDocumentation
import com.androidvip.sysctlgui.models.UiParamDocumentation
import com.androidvip.sysctlgui.utils.browse
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.intellij.lang.annotations.Language
import kotlin.text.append

@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun DocumentationBottomSheet(
documentation: ParamDocumentation,
documentation: UiParamDocumentation,
sheetState: SheetState
) {
val coroutineScope = rememberCoroutineScope()
Expand All @@ -57,7 +55,7 @@ internal fun DocumentationBottomSheet(
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun DocumentationBottomSheetContent(
documentation: ParamDocumentation,
documentation: UiParamDocumentation,
sheetState: SheetState,
coroutineScope: CoroutineScope = rememberCoroutineScope(),
) {
Expand Down Expand Up @@ -98,7 +96,7 @@ private fun DocumentationBottomSheetContent(
if (documentation.url != null) {
TextButton(
onClick = {
context.browse(documentation.url.orEmpty())
context.browse(documentation.url)
coroutineScope.launch { sheetState.hide() }
},
modifier = Modifier
Expand Down Expand Up @@ -137,7 +135,7 @@ private fun DocumentationBottomSheetPreview() {
</ul>
""".trimIndent()

val documentation = ParamDocumentation(
val documentation = UiParamDocumentation(
title = "/proc/sys/fs",
url = "https://docs.kernel.org/admin-guide/sysctl/fs.html",
documentationText = """
Expand Down
Loading
Loading