new meta, holy butts we've got ourselves some Jetpack Compose

This commit is contained in:
Luke Wass 2025-05-12 22:22:13 -05:00
parent 656c7cdbea
commit 8040d22fca
3 changed files with 248 additions and 124 deletions

View file

@ -88,27 +88,22 @@ dependencies {
implementation(libs.androidx.activity.ktx)
implementation(libs.androidx.appcompat)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.material)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.runtime)
implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.gridlayout)
// implementation(libs.androidx.lifecycle.runtime.ktx)
// implementation(libs.androidx.material3)
implementation(libs.androidx.palette.ktx)
implementation(libs.androidx.preference.ktx)
implementation(libs.androidx.recyclerview)
// implementation(libs.androidx.ui)
// implementation(libs.androidx.ui.graphics)
// implementation(libs.androidx.ui.tooling.preview)
implementation(libs.google.material)
implementation(libs.kotlinx.serialization.json)
implementation(libs.jonahbauer.android.preference.annotations)
implementation(libs.androidx.ui.tooling.preview.android)
kapt(libs.jonahbauer.android.preference.annotations)
testImplementation(libs.junit)
// androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.espresso.core)
// androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.test.ext.junit)
// androidTestImplementation(libs.androidx.ui.test.junit4)
// debugImplementation(libs.androidx.ui.test.manifest)
// debugImplementation(libs.androidx.ui.tooling)
}

View file

@ -1,18 +1,35 @@
package de.jrpie.android.launcher.ui.settings.meta
import android.app.AlertDialog
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.TextView
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties
import androidx.fragment.app.Fragment
import de.jrpie.android.launcher.BuildConfig
import de.jrpie.android.launcher.R
import de.jrpie.android.launcher.copyToClipboard
import de.jrpie.android.launcher.databinding.SettingsMetaBinding
import de.jrpie.android.launcher.getDeviceInfo
import de.jrpie.android.launcher.openInBrowser
import de.jrpie.android.launcher.openTutorial
@ -30,13 +47,21 @@ import de.jrpie.android.launcher.ui.UIObject
*/
class SettingsFragmentMeta : Fragment(), UIObject {
private lateinit var binding: SettingsMetaBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = SettingsMetaBinding.inflate(inflater, container, false)
return binding.root
return ComposeView(requireContext()).apply {
setContent {
MaterialTheme {
SettingsMetaScreen(
context = requireContext(),
onResetConfirmed = { requireActivity().finish() }
)
}
}
}
}
override fun onStart() {
@ -45,99 +70,204 @@ class SettingsFragmentMeta : Fragment(), UIObject {
}
override fun setOnClicks() {
fun bindURL(view: View, urlRes: Int) {
view.setOnClickListener {
openInBrowser(
getString(urlRes),
requireContext()
)
}
}
binding.settingsMetaButtonViewTutorial.setOnClickListener {
openTutorial(requireContext())
}
// prompting for settings-reset confirmation
binding.settingsMetaButtonResetSettings.setOnClickListener {
AlertDialog.Builder(this.requireContext(), R.style.AlertDialogCustom)
.setTitle(getString(R.string.settings_meta_reset))
.setMessage(getString(R.string.settings_meta_reset_confirm))
.setPositiveButton(
android.R.string.ok
) { _, _ ->
resetPreferences(this.requireContext())
requireActivity().finish()
}
.setNegativeButton(android.R.string.cancel, null)
.setIcon(android.R.drawable.ic_dialog_alert)
.show()
}
// view code
bindURL(binding.settingsMetaButtonViewCode, R.string.settings_meta_link_github)
// report a bug
binding.settingsMetaButtonReportBug.setOnClickListener {
val deviceInfo = getDeviceInfo()
AlertDialog.Builder(context, R.style.AlertDialogCustom).apply {
setView(R.layout.dialog_report_bug)
setTitle(R.string.dialog_report_bug_title)
setPositiveButton(R.string.dialog_report_bug_create_report) { _, _ ->
openInBrowser(
getString(R.string.settings_meta_report_bug_link),
requireContext()
)
}
setNegativeButton(R.string.dialog_cancel) { _, _ -> }
}.create().also { it.show() }.apply {
val info = findViewById<TextView>(R.id.dialog_report_bug_device_info)
val buttonClipboard = findViewById<Button>(R.id.dialog_report_bug_button_clipboard)
val buttonSecurity = findViewById<Button>(R.id.dialog_report_bug_button_security)
info.text = deviceInfo
buttonClipboard.setOnClickListener {
copyToClipboard(requireContext(), deviceInfo)
}
info.setOnClickListener {
copyToClipboard(requireContext(), deviceInfo)
}
buttonSecurity.setOnClickListener {
openInBrowser(
getString(R.string.settings_meta_report_vulnerability_link),
requireContext()
)
}
}
}
// join chat
bindURL(binding.settingsMetaButtonJoinChat, R.string.settings_meta_chat_url)
// contact developer
// bindURL(binding.settingsMetaButtonContact, R.string.settings_meta_contact_url)
// contact fork developer
bindURL(binding.settingsMetaButtonForkContact, R.string.settings_meta_fork_contact_url)
// donate
bindURL(binding.settingsMetaButtonDonate, R.string.settings_meta_donate_url)
// privacy policy
bindURL(binding.settingsMetaButtonPrivacy, R.string.settings_meta_privacy_url)
// legal info
binding.settingsMetaButtonLicenses.setOnClickListener {
startActivity(Intent(this.context, LegalInfoActivity::class.java))
}
// version
binding.settingsMetaTextVersion.text = BuildConfig.VERSION_NAME
binding.settingsMetaTextVersion.setOnClickListener {
val deviceInfo = getDeviceInfo()
copyToClipboard(requireContext(), deviceInfo)
}
// No longer needed as click handlers are defined in Compose
}
}
// Here we define what the settings meta screen looks like
@Composable
fun SettingsMetaScreen(
context: Context,
onResetConfirmed: () -> Unit
) {
val openAlertDialog = remember { mutableStateOf(false) }
// Here we tell the screen to lay out the settings buttons in a column.
// This could also be a row, but that's hella ugly in this use case.
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
// View Tutorial
SettingsButton(
text = stringResource(R.string.settings_meta_show_tutorial),
onClick = { openTutorial(context) }
)
// Reset Settings
SettingsButton(
text = stringResource(R.string.settings_meta_reset),
onClick = { openAlertDialog.value = true }
)
SettingsButtonSpacer()
// View Code
SettingsButton(
text = stringResource(R.string.settings_meta_view_code),
onClick = { openInBrowser(context.getString(R.string.settings_meta_link_github), context) }
)
// Report a Bug (Placeholder for dialog, simplified for now)
SettingsButton(
text = stringResource(R.string.settings_meta_report_bug),
onClick = {
// Simplified: Directly open bug report link
// TODO: Implement Compose dialog for bug reporting if needed
openInBrowser(context.getString(R.string.settings_meta_report_bug_link), context)
}
)
SettingsButtonSpacer()
// Join Chat
SettingsButton(
text = stringResource(R.string.settings_meta_join_chat),
onClick = { openInBrowser(context.getString(R.string.settings_meta_chat_url), context) }
)
// Contact Fork Developer
SettingsButton(
text = stringResource(R.string.settings_meta_fork_contact),
onClick = { openInBrowser(context.getString(R.string.settings_meta_fork_contact_url), context) }
)
// Donate
SettingsButton(
text = stringResource(R.string.settings_meta_donate),
onClick = { openInBrowser(context.getString(R.string.settings_meta_donate_url), context) }
)
SettingsButtonSpacer()
// Privacy Policy
SettingsButton(
text = stringResource(R.string.settings_meta_privacy),
onClick = { openInBrowser(context.getString(R.string.settings_meta_privacy_url), context) }
)
// Legal Info
SettingsButton(
text = stringResource(R.string.settings_meta_licenses),
onClick = {
context.startActivity(Intent(context, LegalInfoActivity::class.java))
}
)
// Version
Text(
text = BuildConfig.VERSION_NAME,
// TODO: Implement matching font to Launcher Appearance font
textAlign = TextAlign.Right,
color = colorResource(R.color.finnmglasTheme_text_color),
modifier = Modifier
.fillMaxWidth(1f)
.padding(
top = 16.dp,
bottom = 8.dp,
end = 8.dp
)
.clickable {
val deviceInfo = getDeviceInfo()
copyToClipboard(context, deviceInfo)
}
)
// Reset Settings Dialog
if (openAlertDialog.value) {
AlertDialogResetSettings(
onDismissRequest = { openAlertDialog.value = false },
onConfirmation = {
openAlertDialog.value = false
resetPreferences(context)
onResetConfirmed()
},
dialogTitle = stringResource(R.string.settings_meta_reset),
dialogText = stringResource(R.string.settings_meta_reset_confirm),
icon = Icons.Default.Warning
)
}
}
}
// Here we define what a settings button looks like
@Composable
fun SettingsButton(
text: String,
onClick: () -> Unit
) {
Button(
onClick = onClick,
modifier = Modifier.fillMaxWidth(),
contentPadding = PaddingValues(8.dp),
shape = RoundedCornerShape(4.dp),
// TODO: colors can be changed to match the dynamic system theme
// https://developer.android.com/codelabs/jetpack-compose-theming#0
colors = ButtonDefaults.buttonColors(
containerColor = colorResource(R.color.finnmglasTheme_accent_color),
contentColor = colorResource(R.color.finnmglasTheme_text_color)
)
) {
Text(
text = text,
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.align(Alignment.CenterVertically),
color = colorResource(R.color.finnmglasTheme_text_color)
)
}
}
// Here we define some space to put between the buttons
@Composable
fun SettingsButtonSpacer() {
Spacer(modifier = Modifier.height(48.dp))
}
// Here we define what an alert dialog looks like
@Composable
fun AlertDialogResetSettings(
onDismissRequest: () -> Unit,
onConfirmation: () -> Unit,
dialogTitle: String,
dialogText: String,
icon: ImageVector
) {
AlertDialog(
icon = {
Icon(
imageVector = icon,
contentDescription = stringResource(android.R.string.dialog_alert_title),
tint = MaterialTheme.colorScheme.error
)
},
title = {
Text(text = dialogTitle)
},
text = {
Text(text = dialogText)
},
onDismissRequest = onDismissRequest,
confirmButton = {
TextButton(
onClick = onConfirmation
)
{
Text(stringResource(android.R.string.ok))
}
},
dismissButton = {
TextButton(
onClick = onDismissRequest
) {
Text(stringResource(android.R.string.cancel))
}
},
properties = DialogProperties(
dismissOnBackPress = true,
dismissOnClickOutside = true
)
)
}

View file

@ -1,26 +1,27 @@
[versions]
activity = "1.10.1"
activity-ktx = "1.8.0"
activity-ktx = "1.10.1"
activityCompose = "1.10.1"
agp = "8.10.0"
android-preference-annotations = "1.1.2"
appcompat = "1.7.0"
composeBom = "2025.05.00"
constraintlayout = "2.2.0"
core-ktx = "1.15.0"
compose-latest = "1.8.1"
constraintlayout = "2.2.1"
core-ktx = "1.16.0"
espresso-core = "3.6.1"
gridlayout = "1.0.0"
gridlayout = "1.1.0"
junit = "4.13.2"
junitVersion = "1.2.1"
kapt = "1.8.10"
kotlin = "2.1.0"
kotlinx-serialization-json = "1.7.3"
# lifecycleRuntimeKtx = "2.9.0"
material = "1.12.0"
palette-ktx = "1.0.0"
preference-ktx = "1.2.1"
recyclerview = "1.4.0"
serialization = "1.8.10"
uiToolingPreviewAndroid = "1.8.1"
[libraries]
# AndroidX dependencies
@ -29,18 +30,16 @@ androidx-activity-compose = { group = "androidx.activity", name = "activity-comp
androidx-activity-ktx = { group = "androidx.activity", name = "activity-ktx", version.ref = "activity-ktx" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-compose-material = { module = "androidx.compose.material:material", version.ref = "compose-latest" }
androidx-compose-material3 = { module = "androidx.compose.material3:material3" }
androidx-compose-runtime = { module = "androidx.compose.runtime:runtime" }
androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" }
androidx-gridlayout = { group = "androidx.gridlayout", name = "gridlayout", version.ref = "gridlayout" }
androidx-palette-ktx = { group = "androidx.palette", name = "palette-ktx", version.ref = "palette-ktx" }
androidx-preference-ktx = { group = "androidx.preference", name = "preference-ktx", version.ref = "preference-ktx" }
androidx-recyclerview = { group = "androidx.recyclerview", name = "recyclerview", version.ref = "recyclerview" }
# androidx-ui = { group = "androidx.compose.ui", name = "ui" }
# androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
# androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
# androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
# androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
# androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
# Google Material
google-material = { group = "com.google.android.material", name = "material", version.ref = "material" }
@ -53,9 +52,9 @@ jonahbauer-android-preference-annotations = { group = "eu.jonahbauer", name = "a
# Testing dependencies
junit = { group = "junit", name = "junit", version.ref = "junit" }
# androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" }
androidx-ui-tooling-preview-android = { group = "androidx.compose.ui", name = "ui-tooling-preview-android", version.ref = "uiToolingPreviewAndroid" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }