From b06f24075561de70fd6f44c07341ce4ddd28bdba Mon Sep 17 00:00:00 2001 From: Sylvia van Os Date: Sat, 27 Dec 2025 13:00:13 +0100 Subject: [PATCH] AboutActivity to Jetpack Compose (#2489) * WIP * test: Add test tags for compose components * test: Add basic test for compose about screen * refactor: Add defaults for `AboutScreenContent` * refactor: Move compose tests to unit tests * refactor: Make `showRateOnGooglePlay` default to `app/build.gradle.kts/defaultConfig` value * refactor: Best practise to make previews private to reduce pollution * refactor: Best practise apply theme as high as possible for most cases * style: Format AboutActivity.kt * test: Add more comprehensive tests for about screen * test: Fix configuration of compose tests * Fix Gradle setup * Fix build issues * Adjust text sizing * Use full black OLED theme in Compose if chosen in settings --------- Co-authored-by: LooKeR --- app/build.gradle.kts | 26 +- .../protect/card_locker/AboutActivityTest.kt | 89 ++++ .../java/protect/card_locker/AboutActivity.kt | 286 ++++++------ .../protect/card_locker/AboutContent.java | 37 +- .../java/protect/card_locker/MainActivity.kt | 4 +- .../card_locker/OpenWebLinkHandler.java | 5 +- .../java/protect/card_locker/ScanActivity.kt | 4 +- .../card_locker/compose/AboutActivity.kt | 97 ++++ .../protect/card_locker/compose/Catima.kt | 34 ++ .../card_locker/compose/theme/Theme.kt | 51 +++ app/src/main/res/layout/about_activity.xml | 421 ------------------ app/src/main/res/values/strings.xml | 1 + app/src/main/res/values/themes.xml | 1 - .../protect/card_locker/AboutActivityTest.kt | 171 ------- gradle/libs.versions.toml | 15 + 15 files changed, 479 insertions(+), 763 deletions(-) create mode 100644 app/src/androidTest/java/protect/card_locker/AboutActivityTest.kt create mode 100644 app/src/main/java/protect/card_locker/compose/AboutActivity.kt create mode 100644 app/src/main/java/protect/card_locker/compose/Catima.kt create mode 100644 app/src/main/java/protect/card_locker/compose/theme/Theme.kt delete mode 100644 app/src/main/res/layout/about_activity.xml delete mode 100644 app/src/test/java/protect/card_locker/AboutActivityTest.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f948787ba..a019ca8be 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -4,6 +4,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.com.android.application) alias(libs.plugins.org.jetbrains.kotlin.android) + alias(libs.plugins.org.jetbrains.kotlin.plugin.compose) } kotlin { @@ -48,6 +49,7 @@ android { buildFeatures { buildConfig = true + compose = true viewBinding = true } @@ -93,9 +95,10 @@ android { lint { lintConfig = file("lint.xml") } + kotlin { compilerOptions { - jvmTarget = JvmTarget.JVM_21 + jvmTarget = JvmTarget.JVM_17 } } compileOptions { @@ -103,8 +106,10 @@ android { // Flag to enable support for the new language APIs isCoreLibraryDesugaringEnabled = true - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 + + + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } } @@ -121,6 +126,19 @@ dependencies { implementation(libs.com.google.android.material.material) coreLibraryDesugaring(libs.com.android.tools.desugar.jdk.libs) + // Compose + implementation(libs.androidx.activity.activity.compose) + val composeBom = platform(libs.androidx.compose.compose.bom) + implementation(composeBom) + implementation(libs.androidx.compose.foundation.foundation) + implementation(libs.androidx.compose.material3.material3) + implementation(libs.androidx.compose.material.material.icons.extended) + implementation(libs.androidx.compose.ui.ui.tooling.preview.android) + debugImplementation(libs.androidx.compose.ui.ui.test.manifest) + + androidTestImplementation(composeBom) + androidTestImplementation(libs.androidx.compose.ui.ui.test.junit4) + // Third-party implementation(libs.com.journeyapps.zxing.android.embedded) implementation(libs.com.github.yalantis.ucrop) @@ -140,6 +158,8 @@ dependencies { androidTestImplementation(libs.bundles.androidx.test) androidTestImplementation(libs.junit.junit) androidTestImplementation(libs.androidx.test.ext.junit) + androidTestImplementation(libs.androidx.test.rules) + androidTestImplementation(libs.androidx.test.runner) androidTestImplementation(libs.androidx.test.uiautomator.uiautomator) androidTestImplementation(libs.androidx.test.espresso.espresso.core) } diff --git a/app/src/androidTest/java/protect/card_locker/AboutActivityTest.kt b/app/src/androidTest/java/protect/card_locker/AboutActivityTest.kt new file mode 100644 index 000000000..7555fa5a9 --- /dev/null +++ b/app/src/androidTest/java/protect/card_locker/AboutActivityTest.kt @@ -0,0 +1,89 @@ +package protect.card_locker + +import android.app.Instrumentation +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsNotDisplayed +import androidx.compose.ui.test.junit4.ComposeContentTestRule +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performScrollTo +import androidx.compose.ui.test.runComposeUiTest +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import protect.card_locker.compose.theme.CatimaTheme + +@OptIn(ExperimentalTestApi::class) +@RunWith(AndroidJUnit4::class) +class AboutActivityTest { + @get:Rule + private val rule: ComposeContentTestRule = createComposeRule() + + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + + private val content: AboutContent = AboutContent(instrumentation.targetContext) + + @Test + fun testInitialState(): Unit = runComposeUiTest { + setContent { + AboutScreenContent(content = content) + } + + onNodeWithTag("topbar_catima").assertIsDisplayed() + + onNodeWithTag("card_version_history").assertIsDisplayed() + onNodeWithText(content.versionHistory).assertIsDisplayed() + + onNodeWithTag("card_credits").assertIsDisplayed() + onNodeWithText(content.copyrightShort).assertIsDisplayed() + + onNodeWithTag("card_translate").assertIsDisplayed() + onNodeWithTag("card_license").assertIsDisplayed() + + // We might be off the screen so start scrolling + onNodeWithTag("card_source_github").performScrollTo().assertIsDisplayed() + onNodeWithTag("card_privacy_policy").performScrollTo().assertIsDisplayed() + onNodeWithTag("card_donate").performScrollTo().assertIsDisplayed() + // Dont scroll to this, since its not displayed + onNodeWithTag("card_rate_google").assertIsNotDisplayed() + onNodeWithTag("card_report_error").performScrollTo().assertIsDisplayed() + } + + @Test + fun testDonateAndGoogleCardVisible(): Unit = runComposeUiTest { + setContent { + CatimaTheme { + AboutScreenContent( + content = content, + showDonate = true, + showRateOnGooglePlay = true, + ) + } + } + + onNodeWithTag("card_donate").performScrollTo().assertIsDisplayed() + onNodeWithTag("card_rate_google").performScrollTo().assertIsDisplayed() + } + + @Test + fun testDonateAndGoogleCardHidden(): Unit = runComposeUiTest { + setContent { + CatimaTheme { + AboutScreenContent( + content = content, + showDonate = false, + showRateOnGooglePlay = false, + ) + } + } + + onNodeWithTag("card_privacy_policy").performScrollTo().assertIsDisplayed() + onNodeWithTag("card_donate").assertIsNotDisplayed() + onNodeWithTag("card_rate_google").assertIsNotDisplayed() + onNodeWithTag("card_report_error").performScrollTo().assertIsDisplayed() + } +} \ No newline at end of file diff --git a/app/src/main/java/protect/card_locker/AboutActivity.kt b/app/src/main/java/protect/card_locker/AboutActivity.kt index ed3af7da7..add6c1c07 100644 --- a/app/src/main/java/protect/card_locker/AboutActivity.kt +++ b/app/src/main/java/protect/card_locker/AboutActivity.kt @@ -1,149 +1,167 @@ package protect.card_locker import android.os.Bundle -import android.text.Spanned -import android.view.MenuItem -import android.view.View -import android.widget.ScrollView -import android.widget.TextView +import androidx.activity.ComponentActivity +import androidx.activity.OnBackPressedDispatcher +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextLinkStyles +import androidx.compose.ui.text.fromHtml +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.tooling.preview.Preview +import protect.card_locker.compose.CatimaAboutSection +import protect.card_locker.compose.CatimaTopAppBar +import protect.card_locker.compose.theme.CatimaTheme -import androidx.annotation.StringRes -import androidx.core.view.isVisible - -import com.google.android.material.dialog.MaterialAlertDialogBuilder - -import protect.card_locker.databinding.AboutActivityBinding - -class AboutActivity : CatimaAppCompatActivity() { - private companion object { - private const val TAG = "Catima" - } - - private lateinit var binding: AboutActivityBinding +class AboutActivity : ComponentActivity() { private lateinit var content: AboutContent + @OptIn(ExperimentalMaterial3Api::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - binding = AboutActivityBinding.inflate(layoutInflater) content = AboutContent(this) title = content.pageTitle - setContentView(binding.root) - setSupportActionBar(binding.toolbar) - enableToolbarBackButton() - binding.apply { - creditsSub.text = content.copyrightShort - versionHistorySub.text = content.versionHistory - - versionHistory.tag = "https://catima.app/changelog/" - translate.tag = "https://hosted.weblate.org/engage/catima/" - license.tag = "https://github.com/CatimaLoyalty/Android/blob/main/LICENSE" - repo.tag = "https://github.com/CatimaLoyalty/Android/" - privacy.tag = "https://catima.app/privacy-policy/" - reportError.tag = "https://github.com/CatimaLoyalty/Android/issues" - rate.tag = "https://play.google.com/store/apps/details?id=me.hackerchick.catima" - donate.tag = "https://catima.app/donate" - - // Hide Google Play rate button if not on Google Play - rate.isVisible = BuildConfig.showRateOnGooglePlay - // Hide donate button on Google Play (Google Play doesn't allow donation links) - donate.isVisible = BuildConfig.showDonate - } - - bindClickListeners() - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { - android.R.id.home -> { - finish() - true + setContent { + CatimaTheme { + AboutScreenContent( + content = content, + showDonate = BuildConfig.showDonate, + showRateOnGooglePlay = BuildConfig.showRateOnGooglePlay, + onBackPressedDispatcher = onBackPressedDispatcher + ) } - - else -> super.onOptionsItemSelected(item) - } - } - - override fun onDestroy() { - super.onDestroy() - content.destroy() - clearClickListeners() - } - - private fun bindClickListeners() { - binding.apply { - versionHistory.setOnClickListener { showHistory(it) } - translate.setOnClickListener { openExternalBrowser(it) } - license.setOnClickListener { showLicense(it) } - repo.setOnClickListener { openExternalBrowser(it) } - privacy.setOnClickListener { showPrivacy(it) } - reportError.setOnClickListener { openExternalBrowser(it) } - rate.setOnClickListener { openExternalBrowser(it) } - donate.setOnClickListener { openExternalBrowser(it) } - credits.setOnClickListener { showCredits() } - } - } - - private fun clearClickListeners() { - binding.apply { - versionHistory.setOnClickListener(null) - translate.setOnClickListener(null) - license.setOnClickListener(null) - repo.setOnClickListener(null) - privacy.setOnClickListener(null) - reportError.setOnClickListener(null) - rate.setOnClickListener(null) - donate.setOnClickListener(null) - credits.setOnClickListener(null) - } - } - - private fun showCredits() { - showHTML(R.string.credits, content.contributorInfo, null) - } - - private fun showHistory(view: View) { - showHTML(R.string.version_history, content.historyInfo, view) - } - - private fun showLicense(view: View) { - showHTML(R.string.license, content.licenseInfo, view) - } - - private fun showPrivacy(view: View) { - showHTML(R.string.privacy_policy, content.privacyInfo, view) - } - - private fun showHTML(@StringRes title: Int, text: Spanned, view: View?) { - val dialogContentPadding = resources.getDimensionPixelSize(R.dimen.alert_dialog_content_padding) - val textView = TextView(this).apply { - setText(text) - Utils.makeTextViewLinksClickable(this, text) - } - - val scrollView = ScrollView(this).apply { - addView(textView) - setPadding(dialogContentPadding, dialogContentPadding / 2, dialogContentPadding, 0) - } - - MaterialAlertDialogBuilder(this).apply { - setTitle(title) - setView(scrollView) - setPositiveButton(R.string.ok, null) - - // Add View online button if an URL is linked to this view - view?.tag?.let { - setNeutralButton(R.string.view_online) { _, _ -> openExternalBrowser(view) } - } - - show() - } - } - - private fun openExternalBrowser(view: View) { - val tag = view.tag - if (tag is String && tag.startsWith("https://")) { - OpenWebLinkHandler().openBrowser(this, tag) } } } + +@Composable +fun AboutScreenContent( + content: AboutContent, + showDonate: Boolean = true, + showRateOnGooglePlay: Boolean = false, + onBackPressedDispatcher: OnBackPressedDispatcher? = null, +) { + Scaffold( + topBar = { CatimaTopAppBar(content.pageTitle.toString(), onBackPressedDispatcher) } + ) { innerPadding -> + Column( + modifier = Modifier + .padding(innerPadding) + .verticalScroll(rememberScrollState()) + ) { + CatimaAboutSection( + stringResource(R.string.version_history), + content.versionHistory, + modifier = Modifier.testTag("card_version_history"), + onClickUrl = "https://catima.app/changelog/", + onClickDialogText = AnnotatedString.fromHtml( + htmlString = content.historyHtml, + linkStyles = TextLinkStyles( + style = SpanStyle( + textDecoration = TextDecoration.Underline, + color = MaterialTheme.colorScheme.primary + ) + ) + ) + ) + CatimaAboutSection( + stringResource(R.string.credits), + content.copyrightShort, + modifier = Modifier.testTag("card_credits"), + onClickDialogText = AnnotatedString.fromHtml( + htmlString = content.contributorInfoHtml, + linkStyles = TextLinkStyles( + style = SpanStyle( + textDecoration = TextDecoration.Underline, + color = MaterialTheme.colorScheme.primary + ) + ) + ) + ) + CatimaAboutSection( + stringResource(R.string.help_translate_this_app), + stringResource(R.string.translate_platform), + modifier = Modifier.testTag("card_translate"), + onClickUrl = "https://hosted.weblate.org/engage/catima/" + ) + CatimaAboutSection( + stringResource(R.string.license), + stringResource(R.string.app_license), + modifier = Modifier.testTag("card_license"), + onClickUrl = "https://github.com/CatimaLoyalty/Android/blob/main/LICENSE", + onClickDialogText = AnnotatedString.fromHtml( + htmlString = content.licenseHtml, + linkStyles = TextLinkStyles( + style = SpanStyle( + textDecoration = TextDecoration.Underline, + color = MaterialTheme.colorScheme.primary + ) + ) + ) + ) + CatimaAboutSection( + stringResource(R.string.source_repository), + stringResource(R.string.on_github), + modifier = Modifier.testTag("card_source_github"), + onClickUrl = "https://github.com/CatimaLoyalty/Android/" + ) + CatimaAboutSection( + stringResource(R.string.privacy_policy), + stringResource(R.string.and_data_usage), + modifier = Modifier.testTag("card_privacy_policy"), + onClickUrl = "https://catima.app/privacy-policy/", + onClickDialogText = AnnotatedString.fromHtml( + htmlString = content.privacyHtml, + linkStyles = TextLinkStyles( + style = SpanStyle( + textDecoration = TextDecoration.Underline, + color = MaterialTheme.colorScheme.primary + ) + ) + ) + ) + if (showDonate) { + CatimaAboutSection( + stringResource(R.string.donate), + "", + modifier = Modifier.testTag("card_donate"), + onClickUrl = "https://catima.app/donate" + ) + } + if (showRateOnGooglePlay) { + CatimaAboutSection( + stringResource(R.string.rate_this_app), + stringResource(R.string.on_google_play), + modifier = Modifier.testTag("card_rate_google"), + onClickUrl = "https://play.google.com/store/apps/details?id=me.hackerchick.catima" + ) + } + CatimaAboutSection( + stringResource(R.string.report_error), + stringResource(R.string.on_github), + modifier = Modifier.testTag("card_report_error"), + onClickUrl = "https://github.com/CatimaLoyalty/Android/issues" + ) + } + } +} + +@Preview +@Composable +private fun AboutActivityPreview() { + AboutScreenContent(AboutContent(LocalContext.current)) +} diff --git a/app/src/main/java/protect/card_locker/AboutContent.java b/app/src/main/java/protect/card_locker/AboutContent.java index aa717ad70..50daf1589 100644 --- a/app/src/main/java/protect/card_locker/AboutContent.java +++ b/app/src/main/java/protect/card_locker/AboutContent.java @@ -3,11 +3,8 @@ package protect.card_locker; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.text.Spanned; import android.util.Log; -import androidx.core.text.HtmlCompat; - import java.io.IOException; import java.util.ArrayList; import java.util.Calendar; @@ -55,7 +52,7 @@ public class AboutContent { return context.getString(R.string.app_copyright_short); } - public String getContributors() { + public String getContributorsHtml() { String contributors; try { contributors = "
" + Utils.readTextFile(context, R.raw.contributors); @@ -65,7 +62,7 @@ public class AboutContent { return contributors.replace("\n", "
"); } - public String getHistory() { + public String getHistoryHtml() { String versionHistory; try { versionHistory = Utils.readTextFile(context, R.raw.changelog) @@ -77,7 +74,7 @@ public class AboutContent { .replace("\n", "
"); } - public String getLicense() { + public String getLicenseHtml() { try { return Utils.readTextFile(context, R.raw.license); } catch (IOException ignored) { @@ -85,7 +82,7 @@ public class AboutContent { } } - public String getPrivacy() { + public String getPrivacyHtml() { String privacyPolicy; try { privacyPolicy = Utils.readTextFile(context, R.raw.privacy) @@ -97,7 +94,7 @@ public class AboutContent { .replace("\n", "
"); } - public String getThirdPartyLibraries() { + public String getThirdPartyLibrariesHtml() { final List usedLibraries = new ArrayList<>(); usedLibraries.add(new ThirdPartyInfo("ACRA", "https://github.com/ACRA/acra", "Apache 2.0")); usedLibraries.add(new ThirdPartyInfo("Color Picker", "https://github.com/jaredrummler/ColorPicker", "Apache 2.0")); @@ -116,7 +113,7 @@ public class AboutContent { return result.toString(); } - public String getUsedThirdPartyAssets() { + public String getUsedThirdPartyAssetsHtml() { final List usedAssets = new ArrayList<>(); usedAssets.add(new ThirdPartyInfo("Android icons", "https://fonts.google.com/icons?selected=Material+Icons", "Apache 2.0")); @@ -129,31 +126,19 @@ public class AboutContent { return result.toString(); } - public Spanned getContributorInfo() { + public String getContributorInfoHtml() { StringBuilder contributorInfo = new StringBuilder(); contributorInfo.append(getCopyright()); contributorInfo.append("

"); contributorInfo.append(context.getString(R.string.app_copyright_old)); contributorInfo.append("

"); - contributorInfo.append(String.format(context.getString(R.string.app_contributors), getContributors())); + contributorInfo.append(String.format(context.getString(R.string.app_contributors), getContributorsHtml())); contributorInfo.append("

"); - contributorInfo.append(String.format(context.getString(R.string.app_libraries), getThirdPartyLibraries())); + contributorInfo.append(String.format(context.getString(R.string.app_libraries), getThirdPartyLibrariesHtml())); contributorInfo.append("

"); - contributorInfo.append(String.format(context.getString(R.string.app_resources), getUsedThirdPartyAssets())); + contributorInfo.append(String.format(context.getString(R.string.app_resources), getUsedThirdPartyAssetsHtml())); - return HtmlCompat.fromHtml(contributorInfo.toString(), HtmlCompat.FROM_HTML_MODE_COMPACT); - } - - public Spanned getHistoryInfo() { - return HtmlCompat.fromHtml(getHistory(), HtmlCompat.FROM_HTML_MODE_COMPACT); - } - - public Spanned getLicenseInfo() { - return HtmlCompat.fromHtml(getLicense(), HtmlCompat.FROM_HTML_MODE_LEGACY); - } - - public Spanned getPrivacyInfo() { - return HtmlCompat.fromHtml(getPrivacy(), HtmlCompat.FROM_HTML_MODE_COMPACT); + return contributorInfo.toString(); } public String getVersionHistory() { diff --git a/app/src/main/java/protect/card_locker/MainActivity.kt b/app/src/main/java/protect/card_locker/MainActivity.kt index 23471b964..e4083ef78 100644 --- a/app/src/main/java/protect/card_locker/MainActivity.kt +++ b/app/src/main/java/protect/card_locker/MainActivity.kt @@ -58,8 +58,8 @@ class MainActivity : CatimaAppCompatActivity(), CardAdapterListener { private var selectedTab: Int = 0 private lateinit var groupsTabLayout: TabLayout private lateinit var mUpdateLoyaltyCardListRunnable: Runnable - private lateinit var mBarcodeScannerLauncher: ActivityResultLauncher - private lateinit var mSettingsLauncher: ActivityResultLauncher + private lateinit var mBarcodeScannerLauncher: ActivityResultLauncher + private lateinit var mSettingsLauncher: ActivityResultLauncher private val mCurrentActionModeCallback: ActionMode.Callback = object : ActionMode.Callback { override fun onCreateActionMode(inputMode: ActionMode, inputMenu: Menu?): Boolean { diff --git a/app/src/main/java/protect/card_locker/OpenWebLinkHandler.java b/app/src/main/java/protect/card_locker/OpenWebLinkHandler.java index 1586ac4f2..8bfcef4f6 100644 --- a/app/src/main/java/protect/card_locker/OpenWebLinkHandler.java +++ b/app/src/main/java/protect/card_locker/OpenWebLinkHandler.java @@ -1,18 +1,17 @@ package protect.card_locker; +import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Intent; import android.net.Uri; import android.util.Log; import android.widget.Toast; -import androidx.appcompat.app.AppCompatActivity; - public class OpenWebLinkHandler { private static final String TAG = "Catima"; - public void openBrowser(AppCompatActivity activity, String url) { + public void openBrowser(Activity activity, String url) { if (url == null) { return; } diff --git a/app/src/main/java/protect/card_locker/ScanActivity.kt b/app/src/main/java/protect/card_locker/ScanActivity.kt index f1dbf6b97..52beb68ea 100644 --- a/app/src/main/java/protect/card_locker/ScanActivity.kt +++ b/app/src/main/java/protect/card_locker/ScanActivity.kt @@ -538,7 +538,7 @@ class ScanActivity : CatimaAppCompatActivity() { override fun onRequestPermissionsResult( requestCode: Int, - permissions: Array, + permissions: Array, grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) @@ -548,7 +548,7 @@ class ScanActivity : CatimaAppCompatActivity() { override fun onMockedRequestPermissionsResult( requestCode: Int, - permissions: Array, + permissions: Array, grantResults: IntArray ) { val granted = diff --git a/app/src/main/java/protect/card_locker/compose/AboutActivity.kt b/app/src/main/java/protect/card_locker/compose/AboutActivity.kt new file mode 100644 index 000000000..5411063cc --- /dev/null +++ b/app/src/main/java/protect/card_locker/compose/AboutActivity.kt @@ -0,0 +1,97 @@ +package protect.card_locker.compose + +import androidx.activity.compose.LocalActivity +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +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.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.unit.dp +import protect.card_locker.OpenWebLinkHandler +import protect.card_locker.R + +@Composable +fun CatimaAboutSection( + title: String, + message: String, + modifier: Modifier = Modifier, + onClickUrl: String? = null, + onClickDialogText: AnnotatedString? = null, +) { + val activity = LocalActivity.current + + val openDialog = remember { mutableStateOf(false) } + + Row( + modifier = modifier + .padding(horizontal = 16.dp, vertical = 8.dp) + .clickable { + if (onClickDialogText != null) { + openDialog.value = true + } else if (onClickUrl != null) { + OpenWebLinkHandler().openBrowser(activity, onClickUrl) + } + } + ) { + Column(modifier = Modifier.weight(1F)) { + Text( + text = title, + style = MaterialTheme.typography.titleMedium + ) + Text(text = message) + } + Text(modifier = Modifier.align(Alignment.CenterVertically), + text = ">", + style = MaterialTheme.typography.bodyMedium + ) + } + if (openDialog.value && onClickDialogText != null) { + AlertDialog( + icon = {}, + title = { + Text(text = title) + }, + text = { + Text( + text = onClickDialogText, + modifier = Modifier.verticalScroll(rememberScrollState()) + ) + }, + onDismissRequest = { + openDialog.value = false + }, + confirmButton = { + TextButton( + onClick = { + openDialog.value = false + } + ) { + Text(stringResource(R.string.ok)) + } + }, + dismissButton = { + if (onClickUrl != null) { + TextButton( + onClick = { + OpenWebLinkHandler().openBrowser(activity, onClickUrl) + } + ) { + Text(stringResource(R.string.view_online)) + } + } + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/protect/card_locker/compose/Catima.kt b/app/src/main/java/protect/card_locker/compose/Catima.kt new file mode 100644 index 000000000..2fd71cc03 --- /dev/null +++ b/app/src/main/java/protect/card_locker/compose/Catima.kt @@ -0,0 +1,34 @@ +package protect.card_locker.compose + +import androidx.activity.OnBackPressedDispatcher +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.stringResource +import protect.card_locker.R + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun CatimaTopAppBar(title: String, onBackPressedDispatcher: OnBackPressedDispatcher?) { + TopAppBar( + modifier = Modifier.testTag("topbar_catima"), + title = { Text(text = title) }, + navigationIcon = { + if (onBackPressedDispatcher != null) { + IconButton(onClick = { onBackPressedDispatcher.onBackPressed() }) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = stringResource(R.string.back) + ) + } + } + } + ) +} \ No newline at end of file diff --git a/app/src/main/java/protect/card_locker/compose/theme/Theme.kt b/app/src/main/java/protect/card_locker/compose/theme/Theme.kt new file mode 100644 index 000000000..c6d2ceb5f --- /dev/null +++ b/app/src/main/java/protect/card_locker/compose/theme/Theme.kt @@ -0,0 +1,51 @@ +package protect.card_locker.compose.theme + +import android.os.Build +import androidx.appcompat.app.AppCompatDelegate +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.colorResource +import protect.card_locker.R +import protect.card_locker.preferences.Settings + +@Composable +fun CatimaTheme(content: @Composable () -> Unit) { + val context = LocalContext.current + val settings = Settings(context) + + val isDynamicColorSupported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S + + val lightTheme = if (isDynamicColorSupported) { + dynamicLightColorScheme(context) + } else { + lightColorScheme(primary = colorResource(id = R.color.md_theme_light_primary)) + } + + var darkTheme = if (isDynamicColorSupported) { + dynamicDarkColorScheme(context) + } else { + darkColorScheme(primary = colorResource(id = R.color.md_theme_dark_primary)) + } + + if (settings.oledDark) { + darkTheme = darkTheme.copy(background = Color.Black) + } + + val colorScheme = when (settings.theme) { + AppCompatDelegate.MODE_NIGHT_NO -> lightTheme + AppCompatDelegate.MODE_NIGHT_YES -> darkTheme + else -> if (isSystemInDarkTheme()) darkTheme else lightTheme + } + + MaterialTheme( + colorScheme = colorScheme, + content = content + ) +} \ No newline at end of file diff --git a/app/src/main/res/layout/about_activity.xml b/app/src/main/res/layout/about_activity.xml deleted file mode 100644 index 989f6d2af..000000000 --- a/app/src/main/res/layout/about_activity.xml +++ /dev/null @@ -1,421 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fc69ae460..6ddbc4109 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -363,4 +363,5 @@ No value found Barcode encoding Automatic + Back diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 94d92ee69..a4de1cbbe 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,5 +1,4 @@ -