mirror of
https://github.com/CatimaLoyalty/Android.git
synced 2025-12-24 15:47:53 -05:00
Compare commits
35 Commits
v2.40.0
...
feature/ab
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47bdd5209d | ||
|
|
1f6db32155 | ||
|
|
705b80a32c | ||
|
|
91bb808d19 | ||
|
|
497669b3f8 | ||
|
|
3fb77bd607 | ||
|
|
cc2fe5b52b | ||
|
|
abb01eed66 | ||
|
|
a1469aa550 | ||
|
|
f8deeee32c | ||
|
|
021fd13ea6 | ||
|
|
3f753e8f51 | ||
|
|
2c1210e8cd | ||
|
|
10193ffd85 | ||
|
|
17d3e9b3d0 | ||
|
|
de47b9e774 | ||
|
|
acfa8d9fe2 | ||
|
|
930246e6c5 | ||
|
|
c6b8272448 | ||
|
|
24b832a217 | ||
|
|
3acf002f95 | ||
|
|
16f9b3f6b1 | ||
|
|
3c38c7cc25 | ||
|
|
efbc930125 | ||
|
|
90b326e6b9 | ||
|
|
d3e7fe212d | ||
|
|
803d83f8e1 | ||
|
|
59b060fbc0 | ||
|
|
6d64bd4cdf | ||
|
|
ada4850f65 | ||
|
|
2c0b49d7f8 | ||
|
|
e534eebc4d | ||
|
|
db16676cc4 | ||
|
|
0f1e5b858b | ||
|
|
a39d2e46e1 |
5
.github/dependabot.yml
vendored
5
.github/dependabot.yml
vendored
@@ -9,10 +9,15 @@ updates:
|
||||
- mavenCentral
|
||||
schedule:
|
||||
interval: "daily"
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
# Workaround for https://github.com/dependabot/dependabot-core/issues/6888
|
||||
registries:
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased - 157
|
||||
|
||||
- Add duplicate option to main screen and reorder options slightly
|
||||
|
||||
## v2.40.0 - 156 (2025-12-08)
|
||||
|
||||
- Copy card ID to clipboard from view dialog or long press
|
||||
|
||||
@@ -3,6 +3,7 @@ import com.android.build.gradle.internal.tasks.factory.dependsOn
|
||||
plugins {
|
||||
alias(libs.plugins.com.android.application)
|
||||
alias(libs.plugins.org.jetbrains.kotlin.android)
|
||||
alias(libs.plugins.org.jetbrains.kotlin.plugin.compose)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
@@ -47,6 +48,7 @@ android {
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
compose = true
|
||||
viewBinding = true
|
||||
}
|
||||
|
||||
@@ -103,11 +105,7 @@ android {
|
||||
lintConfig = file("lint.xml")
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "21"
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
jvmTarget = "17"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,6 +122,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)
|
||||
@@ -143,6 +154,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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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 = "<br/>" + Utils.readTextFile(context, R.raw.contributors);
|
||||
@@ -65,7 +62,7 @@ public class AboutContent {
|
||||
return contributors.replace("\n", "<br />");
|
||||
}
|
||||
|
||||
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", "<br />");
|
||||
}
|
||||
|
||||
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", "<br />");
|
||||
}
|
||||
|
||||
public String getThirdPartyLibraries() {
|
||||
public String getThirdPartyLibrariesHtml() {
|
||||
final List<ThirdPartyInfo> 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<ThirdPartyInfo> 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("<br/><br/>");
|
||||
contributorInfo.append(context.getString(R.string.app_copyright_old));
|
||||
contributorInfo.append("<br/><br/>");
|
||||
contributorInfo.append(String.format(context.getString(R.string.app_contributors), getContributors()));
|
||||
contributorInfo.append(String.format(context.getString(R.string.app_contributors), getContributorsHtml()));
|
||||
contributorInfo.append("<br/><br/>");
|
||||
contributorInfo.append(String.format(context.getString(R.string.app_libraries), getThirdPartyLibraries()));
|
||||
contributorInfo.append(String.format(context.getString(R.string.app_libraries), getThirdPartyLibrariesHtml()));
|
||||
contributorInfo.append("<br/><br/>");
|
||||
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() {
|
||||
|
||||
@@ -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<Intent?>
|
||||
private lateinit var mSettingsLauncher: ActivityResultLauncher<Intent?>
|
||||
private lateinit var mBarcodeScannerLauncher: ActivityResultLauncher<Intent>
|
||||
private lateinit var mSettingsLauncher: ActivityResultLauncher<Intent>
|
||||
|
||||
private val mCurrentActionModeCallback: ActionMode.Callback = object : ActionMode.Callback {
|
||||
override fun onCreateActionMode(inputMode: ActionMode, inputMenu: Menu?): Boolean {
|
||||
@@ -105,6 +105,24 @@ class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
|
||||
inputMode.finish()
|
||||
return true
|
||||
}
|
||||
R.id.action_duplicate -> {
|
||||
require(mAdapter.selectedItemCount == 1) { "Cannot duplicate more than 1 card at a time" }
|
||||
|
||||
startActivity(
|
||||
Intent(applicationContext, LoyaltyCardEditActivity::class.java).apply {
|
||||
putExtras(Bundle().apply {
|
||||
putInt(
|
||||
LoyaltyCardEditActivity.BUNDLE_ID,
|
||||
mAdapter.getSelectedItems()[0].id
|
||||
)
|
||||
putBoolean(LoyaltyCardEditActivity.BUNDLE_DUPLICATE_ID, true)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
inputMode.finish()
|
||||
return true
|
||||
}
|
||||
R.id.action_delete -> {
|
||||
MaterialAlertDialogBuilder(this@MainActivity).apply {
|
||||
// The following may seem weird, but it is necessary to give translators enough flexibility.
|
||||
@@ -824,6 +842,7 @@ class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
|
||||
)
|
||||
|
||||
val editItem = mCurrentActionMode!!.menu.findItem(R.id.action_edit)
|
||||
val duplicateItem = mCurrentActionMode!!.menu.findItem(R.id.action_duplicate)
|
||||
val archiveItem = mCurrentActionMode!!.menu.findItem(R.id.action_archive)
|
||||
val unarchiveItem = mCurrentActionMode!!.menu.findItem(R.id.action_unarchive)
|
||||
val starItem = mCurrentActionMode!!.menu.findItem(R.id.action_star)
|
||||
@@ -861,12 +880,16 @@ class MainActivity : CatimaAppCompatActivity(), CardAdapterListener {
|
||||
unstarItem.isVisible = !hasUnstarred
|
||||
editItem.isVisible = true
|
||||
editItem.isEnabled = true
|
||||
duplicateItem.isVisible = true
|
||||
duplicateItem.isEnabled = true
|
||||
} else {
|
||||
starItem.isVisible = hasUnstarred
|
||||
unstarItem.isVisible = hasStarred
|
||||
|
||||
editItem.isVisible = false
|
||||
editItem.isEnabled = false
|
||||
duplicateItem.isVisible = false
|
||||
duplicateItem.isEnabled = false
|
||||
}
|
||||
|
||||
mCurrentActionMode!!.invalidate()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -538,7 +538,7 @@ class ScanActivity : CatimaAppCompatActivity() {
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<String?>,
|
||||
permissions: Array<String>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
@@ -548,7 +548,7 @@ class ScanActivity : CatimaAppCompatActivity() {
|
||||
|
||||
override fun onMockedRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<String?>,
|
||||
permissions: Array<String>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
val granted =
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
34
app/src/main/java/protect/card_locker/compose/Catima.kt
Normal file
34
app/src/main/java/protect/card_locker/compose/Catima.kt
Normal file
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
46
app/src/main/java/protect/card_locker/compose/theme/Theme.kt
Normal file
46
app/src/main/java/protect/card_locker/compose/theme/Theme.kt
Normal file
@@ -0,0 +1,46 @@
|
||||
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.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))
|
||||
}
|
||||
|
||||
val darkTheme = if (isDynamicColorSupported) {
|
||||
dynamicDarkColorScheme(context)
|
||||
} else {
|
||||
darkColorScheme(primary = colorResource(id = R.color.md_theme_dark_primary))
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
@@ -1,421 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context="protect.card_locker.MainActivity">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
style="?attr/toolbarStyle" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="?attr/actionBarSize"
|
||||
android:paddingVertical="8dp"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/version_history"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="8dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:background="?android:selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/version_history_main"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingEnd="30dp"
|
||||
android:text="@string/version_history"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/version_history_sub"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingEnd="30dp"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/version_history_main" />
|
||||
|
||||
<TextView
|
||||
android:importantForAccessibility="no"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:text="@string/arrow"
|
||||
android:textSize="20sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/credits"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="8dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:background="?android:selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/credits_main"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingEnd="30dp"
|
||||
android:text="@string/credits"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/credits_sub"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingEnd="30dp"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/credits_main" />
|
||||
|
||||
<TextView
|
||||
android:importantForAccessibility="no"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:text="@string/arrow"
|
||||
android:textSize="20sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/translate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="8dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:background="?android:selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/translate_main"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingEnd="30dp"
|
||||
android:text="@string/help_translate_this_app"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/translate_sub"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingEnd="30dp"
|
||||
android:text="@string/translate_platform"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/translate_main"/>
|
||||
|
||||
<TextView
|
||||
android:importantForAccessibility="no"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:text="@string/arrow"
|
||||
android:textSize="20sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/license"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="8dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:background="?android:selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/license_main"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingEnd="30dp"
|
||||
android:text="@string/license"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/license_sub"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingEnd="30dp"
|
||||
android:text="@string/app_license"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/license_main"/>
|
||||
|
||||
<TextView
|
||||
android:importantForAccessibility="no"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:text="@string/arrow"
|
||||
android:textSize="20sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/repo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="8dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:background="?android:selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/repo_main"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingEnd="30dp"
|
||||
android:text="@string/source_repository"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/repo_sub"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingEnd="30dp"
|
||||
android:text="@string/on_github"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/repo_main" />
|
||||
|
||||
<TextView
|
||||
android:importantForAccessibility="no"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:text="@string/arrow"
|
||||
android:textSize="20sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/privacy"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="8dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:background="?android:selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/privacy_main"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingEnd="30dp"
|
||||
android:text="@string/privacy_policy"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/privacy_sub"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingEnd="30dp"
|
||||
android:text="@string/and_data_usage"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/privacy_main" />
|
||||
|
||||
<TextView
|
||||
android:importantForAccessibility="no"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:text="@string/arrow"
|
||||
android:textSize="20sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/donate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="8dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:background="?android:selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/donate_main"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingEnd="30dp"
|
||||
android:text="@string/donate"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:importantForAccessibility="no"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:text="@string/arrow"
|
||||
android:textSize="20sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/rate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="8dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:background="?android:selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/rate_main"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingEnd="30dp"
|
||||
android:text="@string/rate_this_app"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/rate_sub"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingEnd="30dp"
|
||||
android:text="@string/on_google_play"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/rate_main" />
|
||||
|
||||
<TextView
|
||||
android:importantForAccessibility="no"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:text="@string/arrow"
|
||||
android:textSize="20sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/report_error"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="8dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:background="?android:selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/report_error_main"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingEnd="30dp"
|
||||
android:text="@string/report_error"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/report_error_sub"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/report_error_main"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingEnd="30dp"
|
||||
android:textSize="16sp"
|
||||
android:text="@string/on_github" />
|
||||
|
||||
<TextView
|
||||
android:importantForAccessibility="no"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:text="@string/arrow"
|
||||
android:textSize="20sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -40,10 +40,16 @@
|
||||
android:titleCondensed="@string/unarchive"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_duplicate"
|
||||
android:title="@string/duplicateCard"
|
||||
android:titleCondensed="@string/duplicateCard"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_delete"
|
||||
android:icon="@drawable/ic_delete_white_24dp"
|
||||
android:title="@string/delete"
|
||||
android:titleCondensed="@string/delete"
|
||||
app:showAsAction="never"/>
|
||||
</menu>
|
||||
</menu>
|
||||
|
||||
@@ -21,25 +21,25 @@
|
||||
app:showAsAction="always">
|
||||
|
||||
<menu>
|
||||
<item
|
||||
android:id="@+id/action_archive"
|
||||
android:title="@string/archive"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_unarchive"
|
||||
android:title="@string/unarchive"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_duplicate"
|
||||
android:title="@string/duplicateCard"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_archive"
|
||||
android:title="@string/archive"
|
||||
app:showAsAction="never"/>
|
||||
<item
|
||||
android:id="@+id/action_unarchive"
|
||||
android:title="@string/unarchive"
|
||||
app:showAsAction="never"/>
|
||||
<item
|
||||
android:id="@+id/action_delete"
|
||||
android:title="@string/delete"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
</menu>
|
||||
|
||||
</item>
|
||||
|
||||
@@ -48,13 +48,13 @@ Balázs Meskó
|
||||
Cliff Heraldo
|
||||
Sergio Paredes
|
||||
Ankit Tiwari
|
||||
Arno-github
|
||||
Feike Donia
|
||||
109247019824
|
||||
Feike Donia
|
||||
Arno-github
|
||||
Jose Delvani
|
||||
mdvhimself
|
||||
Milan Šalka
|
||||
Robin
|
||||
mdvhimself
|
||||
தமிழ்நேரம்
|
||||
damjang
|
||||
Govindgopalyadav
|
||||
@@ -72,11 +72,13 @@ JungHee Lee
|
||||
hajertabbane
|
||||
inavleb
|
||||
Ziad OUALHADJ
|
||||
Robin Liu
|
||||
Aliaksandr Trush
|
||||
Denis Shilin
|
||||
Traductor
|
||||
Gideon
|
||||
Renko
|
||||
Ricky Tigg
|
||||
Robin Liu
|
||||
しいたけ
|
||||
Alexander Ivanov
|
||||
Miha Frangež
|
||||
@@ -85,15 +87,13 @@ mrestivill
|
||||
ehrt74
|
||||
Virginie
|
||||
Tim Trek
|
||||
Peter Dave Hello
|
||||
MisterCosta96
|
||||
arshbeerSingh
|
||||
Augustin LAVILLE
|
||||
Traductor
|
||||
Freddo espresso
|
||||
Gideon
|
||||
vasudev-cell
|
||||
Kim Seohyun
|
||||
rudy3
|
||||
Michael Gangolf
|
||||
PRATHAMESH BHAGAT
|
||||
Peter Dave Hello
|
||||
|
||||
@@ -311,4 +311,7 @@
|
||||
<string name="acra_crash_email_subject">Reporte del fallo <xliff:g id="app_name">%s</xliff:g></string>
|
||||
<string name="pref_enable_acra">Solicitar envío de reportes de fallos</string>
|
||||
<string name="pref_enable_acra_summary">Cuando está activado, se le pedirá que informe sobre un fallo cuando ocurra. Los informes de fallo nunca se envían automáticamente.</string>
|
||||
<string name="copy_value">Copia valor</string>
|
||||
<string name="copied_to_clipboard">Copiado al portapapeles</string>
|
||||
<string name="nothing_to_copy">Ningún valor encontrado</string>
|
||||
</resources>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<string name="scanCardBarcode">اسکن بارکد</string>
|
||||
<string name="cardShortcut">میانبر کارت</string>
|
||||
<string name="noCardsMessage">ابتدا یک کارت بیافزایید</string>
|
||||
<string name="noCardExistsError">کارت پیدا نشد</string>
|
||||
<string name="noCardExistsError">آن کارت پیدا نشد</string>
|
||||
<string name="importFailedTitle">ایمپورت ناموفق بود</string>
|
||||
<string name="importFailed">نمیتوان ایمپورت کرد</string>
|
||||
<string name="exportSuccessfulTitle">خروجی گرفته شده</string>
|
||||
@@ -68,7 +68,7 @@
|
||||
<string name="permissionReadCardsDescription">کارت های کاتیما و تمام جزئیاتشان از جمله یادداشتها و عکسها را بخوانید</string>
|
||||
<string name="cameraPermissionDeniedTitle">نمیتوان به دوربین دسترسی پیدا کرد</string>
|
||||
<string name="noCameraPermissionDirectToSystemSetting">برای اسکن بارکد ها، کاتیما نیاز دارد به دوربین شما دسترسی داشته باشد. اینجا بزنید تا تنظیمات دسترسی خود را تغییر دهید.</string>
|
||||
<string name="importExport">ایمپورت/خروجی گرفتن</string>
|
||||
<string name="importExport">واردات/صادرات</string>
|
||||
<string name="settings_category_title_privacy">حریم شخصی</string>
|
||||
<string name="settings_category_title_general">عمومی</string>
|
||||
<string name="settings_category_title_cards">نمایش کارت</string>
|
||||
@@ -117,8 +117,8 @@
|
||||
<string name="importCatimaMessage">فایل <i>catima.zip</i> خروجی خود را از Catima برای وارد کردن انتخاب کنید.\nآن را از منوی وارد/صادر کردن در یک اپلیکیشن دیگر Catima با فشردن دکمه صادرکردن ابتدا ایجاد کنید.</string>
|
||||
<string name="unsupportedBarcodeType">این نوع بارکد هنوز نمیتواند نمایش داده شود. ممکن است در نسخه آینده برنامه پشتیبانی شود.</string>
|
||||
<plurals name="balancePoints">
|
||||
<item quantity="one"><xliff:g>%s</xliff:g> امتیاز</item>
|
||||
<item quantity="other"><xliff:g>%s</xliff:g> امتیاز</item>
|
||||
<item quantity="one"><xliff:g>%s</xliff:g> نقطه</item>
|
||||
<item quantity="other"><xliff:g>%s</xliff:g> نقطه</item>
|
||||
</plurals>
|
||||
<string name="importFidmeMessage">فایل خروجی <i>fidme-export-request-xxxxxx.zip</i> خود را از FidMe برای وارد کردن انتخاب کنید، و سپس نوع بارکدها را به صورت دستی مشخص کنید.\nآن را از پروفایل FidMe خود با انتخاب گزینه حفاظت از داده و سپس فشار دادن گزینه استخراج داده من ابتدا ایجاد کنید.</string>
|
||||
<string name="leaveWithoutSaveTitle">خروج</string>
|
||||
@@ -294,4 +294,5 @@
|
||||
<string name="spend">خرج کردن</string>
|
||||
<string name="addFromPkpass">یک فایل دفترچه حساب (.pkpass) انتخاب کنید</string>
|
||||
<string name="noCameraFoundGuideText">به نظر نمیرسد دستگاه شما دوربین داشته باشد. اگر دارد، دستگاه را مجدداً راهاندازی کنید. در غیر این صورت، از دکمه گزینههای بیشتر در زیر برای افزودن بارکد به روش دیگری استفاده کنید.</string>
|
||||
<string name="card_list_widget_empty">بعد از اینکه چند کارت وفاداری در کاتیما اضافه کردید، آنها اینجا ظاهر میشوند. اگر کارت دارید، مطمئن شوید که همه آنها بایگانی نشدهاند.</string>
|
||||
</resources>
|
||||
|
||||
@@ -305,4 +305,7 @@
|
||||
<string name="acra_crash_email_subject"><xliff:g id="app_name">%s</xliff:g> foutrapport</string>
|
||||
<string name="pref_enable_acra">Vraag om foutrapporten te versturen</string>
|
||||
<string name="pref_enable_acra_summary">Als dit aanstaat, zal je gevraagd worden om foutrapporten te sturen als de app crasht. Dit zal nooit automatisch gebeuren.</string>
|
||||
<string name="nothing_to_copy">Geen waarde gevonden</string>
|
||||
<string name="copied_to_clipboard">Gekopieerd naar klembord</string>
|
||||
<string name="copy_value">Kopieer waarde</string>
|
||||
</resources>
|
||||
|
||||
@@ -305,4 +305,7 @@
|
||||
<string name="acra_crash_email_subject"><xliff:g id="app_name">%s</xliff:g> çökme raporu</string>
|
||||
<string name="pref_enable_acra_summary">Etkinleştirildiğinde, bir çökmeyi şikayet etmeniz istenecektir. Çökme raporları hiç bir zaman otomatik olarak gönderilmez.</string>
|
||||
<string name="pref_enable_acra">Çökme bildirimlerini göndermeyi iste</string>
|
||||
<string name="copy_value">Değeri kopyala</string>
|
||||
<string name="copied_to_clipboard">Panoya kopyalandı</string>
|
||||
<string name="nothing_to_copy">Değer bulunamadı</string>
|
||||
</resources>
|
||||
|
||||
@@ -361,4 +361,5 @@
|
||||
<string name="copy_value">Copy value</string>
|
||||
<string name="copied_to_clipboard">Copied to clipboard</string>
|
||||
<string name="nothing_to_copy">No value found</string>
|
||||
<string name="back">Back</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<resources>
|
||||
|
||||
<style name="AppTheme" parent="Theme.Material3.Light.NoActionBar">
|
||||
<item name="colorPrimary">@color/md_theme_light_primary</item>
|
||||
<item name="colorOnPrimary">@color/md_theme_light_onPrimary</item>
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
package protect.card_locker
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.isVisible
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Assert.fail
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.Robolectric
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.Shadows.shadowOf
|
||||
import org.robolectric.shadows.ShadowActivity
|
||||
import org.robolectric.shadows.ShadowLog
|
||||
import java.lang.reflect.Method
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class AboutActivityTest {
|
||||
private lateinit var activityController: org.robolectric.android.controller.ActivityController<AboutActivity>
|
||||
private lateinit var activity: AboutActivity
|
||||
private lateinit var shadowActivity: ShadowActivity
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
ShadowLog.stream = System.out
|
||||
activityController = Robolectric.buildActivity(AboutActivity::class.java)
|
||||
activity = activityController.get()
|
||||
shadowActivity = shadowOf(activity)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testActivityCreation() {
|
||||
activityController.create().start().resume()
|
||||
|
||||
// Verify activity title is set correctly
|
||||
assertEquals(activity.title.toString(),
|
||||
activity.getString(R.string.about_title_fmt, activity.getString(R.string.app_name)))
|
||||
|
||||
// Check key elements are initialized
|
||||
assertNotNull(activity.findViewById(R.id.toolbar))
|
||||
assertNotNull(activity.findViewById(R.id.credits_sub))
|
||||
assertNotNull(activity.findViewById(R.id.version_history_sub))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDisplayOptionsBasedOnConfig() {
|
||||
activityController.create().start().resume()
|
||||
|
||||
// Test Google Play rate button visibility based on BuildConfig
|
||||
val rateButton = activity.findViewById<View>(R.id.rate)
|
||||
assertEquals(BuildConfig.showRateOnGooglePlay, rateButton.isVisible)
|
||||
|
||||
// Test donate button visibility based on BuildConfig
|
||||
val donateButton = activity.findViewById<View>(R.id.donate)
|
||||
assertEquals(BuildConfig.showDonate, donateButton.isVisible)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testClickListeners() {
|
||||
activityController.create().start().resume()
|
||||
|
||||
// Test clicking on a link that opens external browser
|
||||
val repoButton = activity.findViewById<View>(R.id.repo)
|
||||
repoButton.performClick()
|
||||
|
||||
val startedIntent = shadowActivity.nextStartedActivity
|
||||
assertEquals(Intent.ACTION_VIEW, startedIntent.action)
|
||||
assertEquals(Uri.parse("https://github.com/CatimaLoyalty/Android/"),
|
||||
startedIntent.data)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testActivityDestruction() {
|
||||
activityController.create().start().resume()
|
||||
|
||||
// Verify a view exists before destruction
|
||||
assertNotNull(activity.findViewById(R.id.credits_sub))
|
||||
|
||||
activityController.pause().stop().destroy()
|
||||
|
||||
// Verify activity was destroyed
|
||||
assertTrue(activity.isDestroyed)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDialogContentMethods() {
|
||||
activityController.create().start().resume()
|
||||
|
||||
// Use reflection to test private methods
|
||||
try {
|
||||
val showCreditsMethod: Method = AboutActivity::class.java.getDeclaredMethod("showCredits")
|
||||
showCreditsMethod.isAccessible = true
|
||||
showCreditsMethod.invoke(activity) // Should not throw exception
|
||||
|
||||
val showHistoryMethod: Method = AboutActivity::class.java.getDeclaredMethod("showHistory", View::class.java)
|
||||
showHistoryMethod.isAccessible = true
|
||||
showHistoryMethod.invoke(activity, activity.findViewById(R.id.version_history)) // Should not throw exception
|
||||
} catch (e: Exception) {
|
||||
fail("Exception when calling dialog methods: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testExternalBrowserWithDifferentURLs() {
|
||||
activityController.create().start().resume()
|
||||
|
||||
try {
|
||||
// Get access to the private method
|
||||
val openExternalBrowserMethod: Method = AboutActivity::class.java.getDeclaredMethod("openExternalBrowser", View::class.java)
|
||||
openExternalBrowserMethod.isAccessible = true
|
||||
|
||||
// Create test URLs
|
||||
val testUrls = arrayOf(
|
||||
"https://hosted.weblate.org/engage/catima/",
|
||||
"https://github.com/CatimaLoyalty/Android/blob/main/LICENSE",
|
||||
"https://catima.app/privacy-policy/",
|
||||
"https://github.com/CatimaLoyalty/Android/issues"
|
||||
)
|
||||
|
||||
for (url in testUrls) {
|
||||
// Create a View with the URL as tag
|
||||
val testView = View(activity)
|
||||
testView.tag = url
|
||||
|
||||
// Call the method directly
|
||||
openExternalBrowserMethod.invoke(activity, testView)
|
||||
|
||||
// Verify the intent
|
||||
val intent = shadowActivity.nextStartedActivity
|
||||
assertNotNull("No intent launched for URL: $url", intent)
|
||||
assertEquals(Intent.ACTION_VIEW, intent.action)
|
||||
assertEquals(Uri.parse(url), intent.data)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
fail("Exception during reflection: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testButtonVisibilityBasedOnBuildConfig() {
|
||||
activityController.create().start().resume()
|
||||
|
||||
// Get the current values from BuildConfig
|
||||
val showRateOnGooglePlay = BuildConfig.showRateOnGooglePlay
|
||||
val showDonate = BuildConfig.showDonate
|
||||
|
||||
// Test that the visibility matches the BuildConfig values
|
||||
assertEquals(showRateOnGooglePlay, activity.findViewById<View>(R.id.rate).isVisible)
|
||||
assertEquals(showDonate, activity.findViewById<View>(R.id.donate).isVisible)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAboutScreenTextContent() {
|
||||
activityController.create().start().resume()
|
||||
|
||||
// Verify that text fields contain the expected content
|
||||
val creditsSub = activity.findViewById<TextView>(R.id.credits_sub)
|
||||
assertNotNull(creditsSub.text)
|
||||
assertFalse(creditsSub.text.toString().isEmpty())
|
||||
|
||||
val versionHistorySub = activity.findViewById<TextView>(R.id.version_history_sub)
|
||||
assertNotNull(versionHistorySub.text)
|
||||
assertFalse(versionHistorySub.text.toString().isEmpty())
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,12 @@
|
||||
# When releasing, do the following:
|
||||
1. Press "Commit" and "Push" on Weblate to ensure all translations are up to date
|
||||
2. Merge Weblate pull request
|
||||
3. Update `CHANGELOG.md` with the new version name and the release date
|
||||
4. Update `app/build.gradle.kts` with the new `versionCode` and `versionName`
|
||||
5. Create a commit for the new release: `git add CHANGELOG.md app/build.gradle.kts && git commit -m "Release Catima <VERSION>"`
|
||||
6. Build the new .apks: `KEYSTORE=/path/to/keystore KEYSTORE_ALIAS=catima ./build.sh`
|
||||
7. Upload `app/build/outputs/apk/gplay/release/app-gplay-release.apk` to Google Play Open Testing
|
||||
8. Push the version update commit: `git push`
|
||||
9. Create a new release on GitHub and attach the `app/build/outputs/apk/foss/release/app-foss-release.apk` and `SHA256SUMS` files
|
||||
10. When pushing the release to Google Play Production, update the metadata there: `bundle exec fastlane supply --version_code <VERSION_CODE>`
|
||||
3. Make sure to pull the `main` branch locally
|
||||
4. Update `CHANGELOG.md` with the new version name and the release date
|
||||
5. Update `app/build.gradle.kts` with the new `versionCode` and `versionName`
|
||||
6. Create a commit for the new release: `git add CHANGELOG.md app/build.gradle.kts && git commit -m "Release Catima <VERSION>"`
|
||||
7. Build the new .apks: `KEYSTORE=/path/to/keystore KEYSTORE_ALIAS=catima ./build.sh`
|
||||
8. Upload `app/build/outputs/apk/gplay/release/app-gplay-release.apk` to Google Play Open Testing
|
||||
9. Push the version update commit: `git push`
|
||||
10. Create a new release on GitHub and attach the `app/build/outputs/apk/foss/release/app-foss-release.apk` and `SHA256SUMS` files
|
||||
11. When pushing the release to Google Play Production, update the metadata there: `bundle exec fastlane supply --version_code <VERSION_CODE>`
|
||||
|
||||
1
fastlane/metadata/android/en-US/changelogs/157.txt
Normal file
1
fastlane/metadata/android/en-US/changelogs/157.txt
Normal file
@@ -0,0 +1 @@
|
||||
- Add duplicate option to main screen and reorder options slightly
|
||||
1
fastlane/metadata/android/fa-IR/changelogs/134.txt
Normal file
1
fastlane/metadata/android/fa-IR/changelogs/134.txt
Normal file
@@ -0,0 +1 @@
|
||||
- پشتیبانی از اسکن فایلهای PDF برای بارکد - پشتیبانی از فایلهای تصویری با چندین بارکد - اصلاحات جزئی رابط کاربری
|
||||
1
fastlane/metadata/android/fa-IR/changelogs/135.txt
Normal file
1
fastlane/metadata/android/fa-IR/changelogs/135.txt
Normal file
@@ -0,0 +1 @@
|
||||
- رفع اشکالات و بهبودهای مختلف برای حفظ تعادل در هندلینگ
|
||||
1
fastlane/metadata/android/fa-IR/changelogs/138.txt
Normal file
1
fastlane/metadata/android/fa-IR/changelogs/138.txt
Normal file
@@ -0,0 +1 @@
|
||||
- رفع مشکل ژست بازگشت در صفحه اصلی که کیبورد و جستجو را در اندروید ۱۳+ غیرفعال میکرد
|
||||
3
fastlane/metadata/android/fa-IR/changelogs/139.txt
Normal file
3
fastlane/metadata/android/fa-IR/changelogs/139.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
- گزینهای برای پیمایش کارتها با استفاده از دکمههای صدا
|
||||
- رفع مشکل وارد کردن کارتهای Stocard
|
||||
- رفع مشکل نمایش پیام "وارد کردن لغو شد" پس از وارد کردن موفقیتآمیز
|
||||
1
fastlane/metadata/android/fa-IR/changelogs/140.txt
Normal file
1
fastlane/metadata/android/fa-IR/changelogs/140.txt
Normal file
@@ -0,0 +1 @@
|
||||
- رفع مشکل بستهبندی متن در پنجرهی افزودن
|
||||
4
fastlane/metadata/android/fa-IR/changelogs/141.txt
Normal file
4
fastlane/metadata/android/fa-IR/changelogs/141.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
- ستون پیشفرض را در صفحههای عریض به ۴ تغییر دهید
|
||||
- در تنظیمات، تعداد ستونهای اضافی را برای حالت عمودی و افقی مجاز کنید
|
||||
- هنگام چرخاندن صفحه یا باز کردن کارت، فیلتر جستجوی صفحه اصلی را حفظ کنید
|
||||
- حداکثر طول نمایش یادداشت را در صفحه اصلی محدود کنید
|
||||
3
fastlane/metadata/android/fa-IR/changelogs/142.txt
Normal file
3
fastlane/metadata/android/fa-IR/changelogs/142.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
- اضافه شدن پشتیبانی از Passbook (.pkpass)
|
||||
- رفع مشکل وارد کردن فایلهای PDF شفاف
|
||||
- بهبود نمایش تصاویر بندانگشتی شفاف
|
||||
1
fastlane/metadata/android/fa-IR/changelogs/143.txt
Normal file
1
fastlane/metadata/android/fa-IR/changelogs/143.txt
Normal file
@@ -0,0 +1 @@
|
||||
- رفع مشکل کرش هنگام باز کردن فایلهای pkpass نامعتبر
|
||||
1
fastlane/metadata/android/fa-IR/changelogs/144.txt
Normal file
1
fastlane/metadata/android/fa-IR/changelogs/144.txt
Normal file
@@ -0,0 +1 @@
|
||||
- بهبود نمایش آیکونهای بایگانی/ستارهدار
|
||||
3
fastlane/metadata/android/fa-IR/changelogs/145.txt
Normal file
3
fastlane/metadata/android/fa-IR/changelogs/145.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
- هدف اندروید ۱۵
|
||||
- رفع مشکل پوشش دکمه ذخیره توسط کیبورد در صفحه ویرایش
|
||||
- رفع مشکل عدم شناسایی برخی از فایلهای pkpass به عنوان pkpass (پشتیبانی از نوع mime application/vnd-com.apple.pkpass)
|
||||
2
fastlane/metadata/android/fa-IR/changelogs/146.txt
Normal file
2
fastlane/metadata/android/fa-IR/changelogs/146.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
- امکان مرتبسازی کارتها بر اساس شروع اعتبار
|
||||
- برای رفع برخی مشکلات رابط کاربری، موقتاً به اندروید ۱۴ برگردید
|
||||
2
fastlane/metadata/android/tr-TR/changelogs/156.txt
Normal file
2
fastlane/metadata/android/tr-TR/changelogs/156.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
- Kart kimliğini iletişim penceresinden veya uzun basarak panoya kopyalayın
|
||||
- İstenmeyen yuvarlama işlemlerini azaltmak için bakiye ve para birimi alanlarını değiştirin
|
||||
5
fastlane/metadata/android/tr-TR/changelogs/16.txt
Normal file
5
fastlane/metadata/android/tr-TR/changelogs/16.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
- Kart eklerken veya değiştirirken ana ekranda kısayolların desteklenmesi eklendi
|
||||
- Kısayolların yerine geçen ve yetersiz olan widget kaldırıldı
|
||||
- Android 7+'da yedeklemelerin dışa aktarılması düzeltildi
|
||||
- Yedekleme verilerini dışa aktarırken daha doğru mime türü raporu
|
||||
- "Bir harita değiştirilemedi" hatası düzeltildi
|
||||
2
fastlane/metadata/android/tr-TR/changelogs/17.txt
Normal file
2
fastlane/metadata/android/tr-TR/changelogs/17.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
- Başlatıcı/ana ekrandan sadakat kartı kısayollarının eklenmesi desteği eklendi. (pull #161 (https://github.com/brarcher/loyalty-card-locker/pull/161))
|
||||
- Uygulamanın içinden sadakat kartı kısayollarının eklenmesi desteği kaldırıldı. Bu, kısayol izni gerekliliğini ortadan kaldırır. (pull #163 (https://github.com/brarcher/loyalty-card-locker/pull/163))
|
||||
2
fastlane/metadata/android/tr-TR/changelogs/18.txt
Normal file
2
fastlane/metadata/android/tr-TR/changelogs/18.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
- Android SDK+24'te dosya seçicinin "içe aktar" seçeneğinin kullanılmasıyla uygulamanın çökmesine neden olan sorun düzeltildi (pull #170 (https://github.com/brarcher/loyalty-card-locker/pull/170))
|
||||
- Yeni simge ve grafik şablonu (pull #171 (https://github.com/brarcher/loyalty-card-locker/pull/171))
|
||||
@@ -1,3 +1,3 @@
|
||||
- İtalyanca çeviri
|
||||
- Tüm 1B barkod türleri için destek. (Başlangıçta yalnızca ürün 1B barkodları destekleniyordu)
|
||||
- Başlangıçta eksik olan gerekli kamera izni eklendi.
|
||||
- Tüm 1D barkod türlerinin desteği eklendi. (Şimdiye kadar sadece 1D ürün barkodları destekleniyordu)
|
||||
- Kamera kullanım izni eklendi.
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
Mağazada veya internette ödeme sırasında plastik ödül kartlarını aramaya son verin.
|
||||
<b>Kamerasını kullanarak barkodları aygıtınıza tarayın, kartları unutun.</b>
|
||||
Mağaza veya çevrimiçi mağazalarda ödeme yaparken plastik sadakat kartlarını aramayı bırakın.
|
||||
<b>Cihazınızın kamerasını kullanarak barkodları tarayın, kartları unutun</b>.
|
||||
|
||||
Cüzdanınızı unutun veya değerli eşyalarınız için aşırı hafif tutun.
|
||||
Cüzdanınızı unutun veya değerli eşyalarınız için ultra hafif tutun.
|
||||
|
||||
Bu temel günlük taşıma aracıyla işe yaramaz plastiği nakitle değiştirebilirsiniz.
|
||||
Günlük hayatta yanınızda taşımanız gereken bu temel araçla, gereksiz plastik kartları nakit parayla değiştirebilirsiniz.
|
||||
|
||||
- Çok az izinle casusluktan kaçının. İnternet erişimi ve reklam yok.
|
||||
- Adları ve özelleştirilebilir renkleri olan kartlar veya kodlar ekleyin.
|
||||
- Kaydedilecek barkod yoksa veya kullanılamıyorsa elle kod girişi.
|
||||
- Dosyalardan, Catima, FidMe, Loyalty Card Keychain, Stocard ve Voucher Vault'tan kartları ve kodları içe aktarın.
|
||||
- Tüm kartlarınızın yedeğini alın ve isterseniz yeni bir aygıta aktarın.
|
||||
- Herhangi bir uygulamayı kullanarak kuponları, özel teklifleri, tanıtım kodlarını veya kartları ve kodları paylaşın.
|
||||
- Çok az izinle casusluğu önleyin. İnternet erişimi ve reklam yok.
|
||||
- Özelleştirilebilir isimler ve renklerle kartlar veya kodlar ekleyin.
|
||||
- Saklanacak barkod yoksa veya kullanılamıyorsa kodu manuel olarak girin.
|
||||
- Kartları ve kodları dosyalardan, Catima, FidMe, Loyalty Card Keychain ve Voucher Vault'tan içe aktarın.
|
||||
- Tüm kartlarınızı yedekleyin ve isterseniz yeni bir cihaza aktarın.
|
||||
- Herhangi bir uygulamayı kullanarak kuponları, özel teklifleri, promosyon kodlarını veya kartları ve kodları paylaşın.
|
||||
- Görme engelli kullanıcılar için koyu tema ve erişilebilirlik seçenekleri.
|
||||
- Özgür yazılım topluluğu tarafından herkes için yapıldı.
|
||||
- 40'den fazla dil için yerelleştirilen el yapımı çeviriler.
|
||||
- Topluluk katkılarıyla desteklenir ve ücretsizdir.
|
||||
- Dilediğiniz gibi kullanın, inceleyin, değiştirin ve <i>herkesle</i> paylaşın.
|
||||
- Yalnızca Özgür Yazılım / Açık Kaynaklı değil. <i>Copyleft lisanslı</i> özgür (GPLv3+) kart yönetimi yazılımı.
|
||||
- Özgür yazılım topluluğu tarafından herkes için yapılmıştır.
|
||||
- 40'tan fazla dil için yerelleştirilmiş çeviriler.
|
||||
- Ücretsiz, topluluğun katkılarıyla desteklenmektedir.
|
||||
- İstediğiniz gibi kullanın, inceleyin, değiştirin ve paylaşın; <i>herkesle</i>.
|
||||
- Sadece özgür ve açık kaynaklı yazılım değil, kart yönetimi için <i>copyleft özgür yazılım</i> (GPLv3+).
|
||||
|
||||
Hayatınızı ve alışverişinizi basitleştirin ve bir daha asla kağıt fatura, mağaza içi ödemeli hediye kartı veya uçak bileti kaybetmeyin.
|
||||
Tüm ödüllerinizi yanınıza alın ve kaydedin.
|
||||
Hayatınızı ve alışverişlerinizi kolaylaştırın, bir daha asla kağıt fiş, mağaza ödeme kartı veya uçak bileti kaybetmeyin.
|
||||
Tüm ödüllerinizi ve primlerinizi yanınızda taşıyın ve her seferinde tasarruf edin.
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
[versions]
|
||||
# AndroidX
|
||||
compose = "2025.11.01"
|
||||
|
||||
# Third-party
|
||||
acra = "5.13.1"
|
||||
|
||||
@@ -18,6 +21,16 @@ androidx-preference-preference = { group = "androidx.preference", name = "prefer
|
||||
com-google-android-material-material = { group = "com.google.android.material", name = "material", version = "1.13.0" }
|
||||
com-android-tools-desugar_jdk_libs = { group = "com.android.tools", name = "desugar_jdk_libs", version = "2.1.5" }
|
||||
|
||||
# Compose
|
||||
androidx-activity-activity-compose = { group = "androidx.activity", name = "activity-compose", version = "1.10.1" }
|
||||
androidx-compose-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose" }
|
||||
androidx-compose-foundation-foundation = { group = "androidx.compose.foundation", name = "foundation" }
|
||||
androidx-compose-material3-material3 = { group = "androidx.compose.material3", name = "material3"}
|
||||
androidx-compose-material-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended", version = "1.7.8" }
|
||||
androidx-compose-ui-ui-tooling-preview-android = { group = "androidx.compose.ui", name = "ui-tooling-preview-android" }
|
||||
androidx-compose-ui-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
|
||||
androidx-compose-ui-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
||||
|
||||
# Third-party
|
||||
com-journeyapps-zxing_android_embedded = { group = "com.journeyapps", name = "zxing-android-embedded", version = "4.3.0" }
|
||||
com-github-yalantis-ucrop = { group = "com.github.yalantis", name = "ucrop", version = "2.2.11" }
|
||||
@@ -30,6 +43,7 @@ ch-acra-acra-dialog = { group = "ch.acra", name = "acra-dialog", version.ref = "
|
||||
|
||||
# Testing
|
||||
androidx-test-core = { group = "androidx.test", name = "core", version.ref = "androidXTest" }
|
||||
androidx-test-rules = { group = "androidx.test", name = "rules", version.ref = "androidXTest" }
|
||||
androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "androidXTest" }
|
||||
androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version = "1.3.0" }
|
||||
androidx-test-uiautomator-uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version = "2.3.0" }
|
||||
@@ -38,8 +52,9 @@ junit-junit = { group = "junit", name = "junit", version = "4.13.2" }
|
||||
org-robolectric-robolectric = { group = "org.robolectric", name = "robolectric", version = "4.16" }
|
||||
|
||||
[plugins]
|
||||
com-android-application = { id = "com.android.application", version = "8.13.1" }
|
||||
com-android-application = { id = "com.android.application", version = "8.13.2" }
|
||||
org-jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version = "2.2.21" }
|
||||
org-jetbrains-kotlin-plugin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version = "2.1.21" }
|
||||
|
||||
[bundles]
|
||||
acra = ["ch-acra-acra-mail", "ch-acra-acra-dialog"]
|
||||
|
||||
Reference in New Issue
Block a user