mirror of
https://github.com/CatimaLoyalty/Android.git
synced 2026-04-08 09:27:57 -04:00
Kotlin conversion of AboutActivity with tests also in Kotlin (#2360)
This commit is contained in:
@@ -1,146 +0,0 @@
|
||||
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.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import protect.card_locker.databinding.AboutActivityBinding;
|
||||
|
||||
public class AboutActivity extends CatimaAppCompatActivity {
|
||||
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
private AboutActivityBinding binding;
|
||||
private AboutContent content;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
binding = AboutActivityBinding.inflate(getLayoutInflater());
|
||||
content = new AboutContent(this);
|
||||
setTitle(content.getPageTitle());
|
||||
setContentView(binding.getRoot());
|
||||
setSupportActionBar(binding.toolbar);
|
||||
enableToolbarBackButton();
|
||||
|
||||
TextView copyright = binding.creditsSub;
|
||||
copyright.setText(content.getCopyrightShort());
|
||||
TextView versionHistory = binding.versionHistorySub;
|
||||
versionHistory.setText(content.getVersionHistory());
|
||||
|
||||
binding.versionHistory.setTag("https://catima.app/changelog/");
|
||||
binding.translate.setTag("https://hosted.weblate.org/engage/catima/");
|
||||
binding.license.setTag("https://github.com/CatimaLoyalty/Android/blob/main/LICENSE");
|
||||
binding.repo.setTag("https://github.com/CatimaLoyalty/Android/");
|
||||
binding.privacy.setTag("https://catima.app/privacy-policy/");
|
||||
binding.reportError.setTag("https://github.com/CatimaLoyalty/Android/issues");
|
||||
binding.rate.setTag("https://play.google.com/store/apps/details?id=me.hackerchick.catima");
|
||||
binding.donate.setTag("https://catima.app/donate");
|
||||
|
||||
// Hide Google Play rate button if not on Google Play
|
||||
binding.rate.setVisibility(BuildConfig.showRateOnGooglePlay ? View.VISIBLE : View.GONE);
|
||||
// Hide donate button on Google Play (Google Play doesn't allow donation links)
|
||||
binding.donate.setVisibility(BuildConfig.showDonate ? View.VISIBLE : View.GONE);
|
||||
|
||||
bindClickListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
if (id == android.R.id.home) {
|
||||
finish();
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
content.destroy();
|
||||
clearClickListeners();
|
||||
binding = null;
|
||||
}
|
||||
|
||||
private void bindClickListeners() {
|
||||
binding.versionHistory.setOnClickListener(this::showHistory);
|
||||
binding.translate.setOnClickListener(this::openExternalBrowser);
|
||||
binding.license.setOnClickListener(this::showLicense);
|
||||
binding.repo.setOnClickListener(this::openExternalBrowser);
|
||||
binding.privacy.setOnClickListener(this::showPrivacy);
|
||||
binding.reportError.setOnClickListener(this::openExternalBrowser);
|
||||
binding.rate.setOnClickListener(this::openExternalBrowser);
|
||||
binding.donate.setOnClickListener(this::openExternalBrowser);
|
||||
|
||||
binding.credits.setOnClickListener(view -> showCredits());
|
||||
}
|
||||
|
||||
private void clearClickListeners() {
|
||||
binding.versionHistory.setOnClickListener(null);
|
||||
binding.translate.setOnClickListener(null);
|
||||
binding.license.setOnClickListener(null);
|
||||
binding.repo.setOnClickListener(null);
|
||||
binding.privacy.setOnClickListener(null);
|
||||
binding.reportError.setOnClickListener(null);
|
||||
binding.rate.setOnClickListener(null);
|
||||
binding.donate.setOnClickListener(null);
|
||||
|
||||
binding.credits.setOnClickListener(null);
|
||||
}
|
||||
|
||||
private void showCredits() {
|
||||
showHTML(R.string.credits, content.getContributorInfo(), null);
|
||||
}
|
||||
|
||||
private void showHistory(View view) {
|
||||
showHTML(R.string.version_history, content.getHistoryInfo(), view);
|
||||
}
|
||||
|
||||
private void showLicense(View view) {
|
||||
showHTML(R.string.license, content.getLicenseInfo(), view);
|
||||
}
|
||||
|
||||
private void showPrivacy(View view) {
|
||||
showHTML(R.string.privacy_policy, content.getPrivacyInfo(), view);
|
||||
}
|
||||
|
||||
private void showHTML(@StringRes int title, final Spanned text, @Nullable View view) {
|
||||
int dialogContentPadding = getResources().getDimensionPixelSize(R.dimen.alert_dialog_content_padding);
|
||||
TextView textView = new TextView(this);
|
||||
textView.setText(text);
|
||||
Utils.makeTextViewLinksClickable(textView, text);
|
||||
ScrollView scrollView = new ScrollView(this);
|
||||
scrollView.addView(textView);
|
||||
scrollView.setPadding(dialogContentPadding, dialogContentPadding / 2, dialogContentPadding, 0);
|
||||
|
||||
// Create dialog
|
||||
MaterialAlertDialogBuilder materialAlertDialogBuilder = new MaterialAlertDialogBuilder(this);
|
||||
materialAlertDialogBuilder
|
||||
.setTitle(title)
|
||||
.setView(scrollView)
|
||||
.setPositiveButton(R.string.ok, null);
|
||||
|
||||
// Add View online button if an URL is linked to this view
|
||||
if (view != null && view.getTag() != null) {
|
||||
materialAlertDialogBuilder.setNeutralButton(R.string.view_online, (dialog, which) -> openExternalBrowser(view));
|
||||
}
|
||||
|
||||
// Show dialog
|
||||
materialAlertDialogBuilder.show();
|
||||
}
|
||||
|
||||
private void openExternalBrowser(View view) {
|
||||
Object tag = view.getTag();
|
||||
if (tag instanceof String && ((String) tag).startsWith("https://")) {
|
||||
(new OpenWebLinkHandler()).openBrowser(this, (String) tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
149
app/src/main/java/protect/card_locker/AboutActivity.kt
Normal file
149
app/src/main/java/protect/card_locker/AboutActivity.kt
Normal file
@@ -0,0 +1,149 @@
|
||||
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.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
|
||||
private lateinit var content: AboutContent
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
171
app/src/test/java/protect/card_locker/AboutActivityTest.kt
Normal file
171
app/src/test/java/protect/card_locker/AboutActivityTest.kt
Normal file
@@ -0,0 +1,171 @@
|
||||
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())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user