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.activity.ktx)
implementation(libs.androidx.appcompat) implementation(libs.androidx.appcompat)
implementation(platform(libs.androidx.compose.bom)) 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.constraintlayout)
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.gridlayout) implementation(libs.androidx.gridlayout)
// implementation(libs.androidx.lifecycle.runtime.ktx)
// implementation(libs.androidx.material3)
implementation(libs.androidx.palette.ktx) implementation(libs.androidx.palette.ktx)
implementation(libs.androidx.preference.ktx) implementation(libs.androidx.preference.ktx)
implementation(libs.androidx.recyclerview) 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.google.material)
implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.serialization.json)
implementation(libs.jonahbauer.android.preference.annotations) implementation(libs.jonahbauer.android.preference.annotations)
implementation(libs.androidx.ui.tooling.preview.android)
kapt(libs.jonahbauer.android.preference.annotations) kapt(libs.jonahbauer.android.preference.annotations)
testImplementation(libs.junit) testImplementation(libs.junit)
// androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(libs.androidx.espresso.core)
// androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.test.ext.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 package de.jrpie.android.launcher.ui.settings.meta
import android.app.AlertDialog import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Button import androidx.compose.foundation.clickable
import android.widget.TextView 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 androidx.fragment.app.Fragment
import de.jrpie.android.launcher.BuildConfig import de.jrpie.android.launcher.BuildConfig
import de.jrpie.android.launcher.R import de.jrpie.android.launcher.R
import de.jrpie.android.launcher.copyToClipboard import de.jrpie.android.launcher.copyToClipboard
import de.jrpie.android.launcher.databinding.SettingsMetaBinding
import de.jrpie.android.launcher.getDeviceInfo import de.jrpie.android.launcher.getDeviceInfo
import de.jrpie.android.launcher.openInBrowser import de.jrpie.android.launcher.openInBrowser
import de.jrpie.android.launcher.openTutorial import de.jrpie.android.launcher.openTutorial
@ -30,13 +47,21 @@ import de.jrpie.android.launcher.ui.UIObject
*/ */
class SettingsFragmentMeta : Fragment(), UIObject { class SettingsFragmentMeta : Fragment(), UIObject {
private lateinit var binding: SettingsMetaBinding
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = SettingsMetaBinding.inflate(inflater, container, false) return ComposeView(requireContext()).apply {
return binding.root setContent {
MaterialTheme {
SettingsMetaScreen(
context = requireContext(),
onResetConfirmed = { requireActivity().finish() }
)
}
}
}
} }
override fun onStart() { override fun onStart() {
@ -45,99 +70,204 @@ class SettingsFragmentMeta : Fragment(), UIObject {
} }
override fun setOnClicks() { override fun setOnClicks() {
// No longer needed as click handlers are defined in Compose
}
}
fun bindURL(view: View, urlRes: Int) { // Here we define what the settings meta screen looks like
view.setOnClickListener { @Composable
openInBrowser( fun SettingsMetaScreen(
getString(urlRes), context: Context,
requireContext() 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) }
) )
}
}
binding.settingsMetaButtonViewTutorial.setOnClickListener { // Reset Settings
openTutorial(requireContext()) 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)
} }
)
// prompting for settings-reset confirmation SettingsButtonSpacer()
binding.settingsMetaButtonResetSettings.setOnClickListener {
AlertDialog.Builder(this.requireContext(), R.style.AlertDialogCustom) // Join Chat
.setTitle(getString(R.string.settings_meta_reset)) SettingsButton(
.setMessage(getString(R.string.settings_meta_reset_confirm)) text = stringResource(R.string.settings_meta_join_chat),
.setPositiveButton( onClick = { openInBrowser(context.getString(R.string.settings_meta_chat_url), context) }
android.R.string.ok )
) { _, _ ->
resetPreferences(this.requireContext()) // Contact Fork Developer
requireActivity().finish() SettingsButton(
} text = stringResource(R.string.settings_meta_fork_contact),
.setNegativeButton(android.R.string.cancel, null) onClick = { openInBrowser(context.getString(R.string.settings_meta_fork_contact_url), context) }
.setIcon(android.R.drawable.ic_dialog_alert) )
.show()
// 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
// view code Text(
bindURL(binding.settingsMetaButtonViewCode, R.string.settings_meta_link_github) text = BuildConfig.VERSION_NAME,
// TODO: Implement matching font to Launcher Appearance font
// report a bug textAlign = TextAlign.Right,
binding.settingsMetaButtonReportBug.setOnClickListener { 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() val deviceInfo = getDeviceInfo()
AlertDialog.Builder(context, R.style.AlertDialogCustom).apply { copyToClipboard(context, deviceInfo)
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) { _, _ -> } // Reset Settings Dialog
}.create().also { it.show() }.apply { if (openAlertDialog.value) {
val info = findViewById<TextView>(R.id.dialog_report_bug_device_info) AlertDialogResetSettings(
val buttonClipboard = findViewById<Button>(R.id.dialog_report_bug_button_clipboard) onDismissRequest = { openAlertDialog.value = false },
val buttonSecurity = findViewById<Button>(R.id.dialog_report_bug_button_security) onConfirmation = {
info.text = deviceInfo openAlertDialog.value = false
buttonClipboard.setOnClickListener { resetPreferences(context)
copyToClipboard(requireContext(), deviceInfo) onResetConfirmed()
} },
info.setOnClickListener { dialogTitle = stringResource(R.string.settings_meta_reset),
copyToClipboard(requireContext(), deviceInfo) dialogText = stringResource(R.string.settings_meta_reset_confirm),
} icon = Icons.Default.Warning
buttonSecurity.setOnClickListener {
openInBrowser(
getString(R.string.settings_meta_report_vulnerability_link),
requireContext()
) )
} }
} }
} }
// join chat // Here we define what a settings button looks like
bindURL(binding.settingsMetaButtonJoinChat, R.string.settings_meta_chat_url) @Composable
fun SettingsButton(
// contact developer text: String,
// bindURL(binding.settingsMetaButtonContact, R.string.settings_meta_contact_url) onClick: () -> Unit
) {
// contact fork developer Button(
bindURL(binding.settingsMetaButtonForkContact, R.string.settings_meta_fork_contact_url) onClick = onClick,
modifier = Modifier.fillMaxWidth(),
// donate contentPadding = PaddingValues(8.dp),
bindURL(binding.settingsMetaButtonDonate, R.string.settings_meta_donate_url) shape = RoundedCornerShape(4.dp),
// TODO: colors can be changed to match the dynamic system theme
// privacy policy // https://developer.android.com/codelabs/jetpack-compose-theming#0
bindURL(binding.settingsMetaButtonPrivacy, R.string.settings_meta_privacy_url) colors = ButtonDefaults.buttonColors(
containerColor = colorResource(R.color.finnmglasTheme_accent_color),
// legal info contentColor = colorResource(R.color.finnmglasTheme_text_color)
binding.settingsMetaButtonLicenses.setOnClickListener { )
startActivity(Intent(this.context, LegalInfoActivity::class.java)) ) {
} Text(
text = text,
// version style = MaterialTheme.typography.bodyLarge,
binding.settingsMetaTextVersion.text = BuildConfig.VERSION_NAME modifier = Modifier.align(Alignment.CenterVertically),
binding.settingsMetaTextVersion.setOnClickListener { color = colorResource(R.color.finnmglasTheme_text_color)
val deviceInfo = getDeviceInfo() )
copyToClipboard(requireContext(), deviceInfo)
}
} }
} }
// 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] [versions]
activity = "1.10.1" activity = "1.10.1"
activity-ktx = "1.8.0" activity-ktx = "1.10.1"
activityCompose = "1.10.1" activityCompose = "1.10.1"
agp = "8.10.0" agp = "8.10.0"
android-preference-annotations = "1.1.2" android-preference-annotations = "1.1.2"
appcompat = "1.7.0" appcompat = "1.7.0"
composeBom = "2025.05.00" composeBom = "2025.05.00"
constraintlayout = "2.2.0" compose-latest = "1.8.1"
core-ktx = "1.15.0" constraintlayout = "2.2.1"
core-ktx = "1.16.0"
espresso-core = "3.6.1" espresso-core = "3.6.1"
gridlayout = "1.0.0" gridlayout = "1.1.0"
junit = "4.13.2" junit = "4.13.2"
junitVersion = "1.2.1" junitVersion = "1.2.1"
kapt = "1.8.10" kapt = "1.8.10"
kotlin = "2.1.0" kotlin = "2.1.0"
kotlinx-serialization-json = "1.7.3" kotlinx-serialization-json = "1.7.3"
# lifecycleRuntimeKtx = "2.9.0"
material = "1.12.0" material = "1.12.0"
palette-ktx = "1.0.0" palette-ktx = "1.0.0"
preference-ktx = "1.2.1" preference-ktx = "1.2.1"
recyclerview = "1.4.0" recyclerview = "1.4.0"
serialization = "1.8.10" serialization = "1.8.10"
uiToolingPreviewAndroid = "1.8.1"
[libraries] [libraries]
# AndroidX dependencies # 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-activity-ktx = { group = "androidx.activity", name = "activity-ktx", version.ref = "activity-ktx" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } 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-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" }
androidx-gridlayout = { group = "androidx.gridlayout", name = "gridlayout", version.ref = "gridlayout" } androidx-gridlayout = { group = "androidx.gridlayout", name = "gridlayout", version.ref = "gridlayout" }
androidx-palette-ktx = { group = "androidx.palette", name = "palette-ktx", version.ref = "palette-ktx" } 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-preference-ktx = { group = "androidx.preference", name = "preference-ktx", version.ref = "preference-ktx" }
androidx-recyclerview = { group = "androidx.recyclerview", name = "recyclerview", version.ref = "recyclerview" } 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
google-material = { group = "com.google.android.material", name = "material", version.ref = "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 # Testing dependencies
junit = { group = "junit", name = "junit", version.ref = "junit" } 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-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-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] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }