Migrated AboutActivity from Java to Kotlin (#6158)

* Rename Constants to Follow Kotlin Naming Conventions

>This PR refactors constant names in the project to adhere to Kotlin's UPPERCASE_SNAKE_CASE naming convention, improving code readability and maintaining consistency across the codebase.

>Renamed the following constants in LoginActivity:
>saveProgressDialog → SAVE_PROGRESS_DIALOG
>saveErrorMessage → SAVE_ERROR_MESSAGE
>saveUsername → SAVE_USERNAME
>savePassword → SAVE_PASSWORD

>Updated all references to these constants throughout the project.

* Update Project_Default.xml

* Refactor variable names to adhere to naming conventions

Renamed variables to use camel case:
-UPLOAD_COUNT_THRESHOLD → uploadCountThreshold
-REVERT_PERCENTAGE_FOR_MESSAGE → revertPercentageForMessage
-REVERT_SHARED_PREFERENCE → revertSharedPreference
-UPLOAD_SHARED_PREFERENCE → uploadSharedPreference

Renamed variables with uppercase initials to lowercase for alignment with Kotlin conventions:
-Latitude → latitude
-Longitude → longitude
-Accuracy → accuracy

Refactored the following variable names:
-NUMBER_OF_QUESTIONS → numberOfQuestions
-MULTIPLIER_TO_GET_PERCENTAGE → multiplierToGetPercentage

* Refactor Dialog View Initialization with Null-Safe Calls

This PR refactors the dialog setup code in CustomSelectorActivity to improve safety and readability by replacing explicit casts with null-safe generic calls for findViewById.

>Replaced explicit casting (as Button and as TextView) with the generic findViewById<T>() method for improved type safety.
>Added null-safety (?.) to avoid potential crashes if a view is not found in the dialog layout.

why changed:-
>Prevents runtime crashes caused by NullPointerException when a view is missing in the layout.

* Refactor Unit Test: Replace Unsafe Casting with Type-Safe Mocking for findViewById

>PR refactors the unit test for NearbyParentFragment by replacing unsafe casting in the findViewById mocking statements with type-safe

>Ensured all findViewById mocks now use a consistent, type-safe format (findViewById<View>(...)) to reduce verbosity and potential casting errors.

>Verified the functionality of prepareViewsForSheetPosition remains unchanged, ensuring no regression in test behavior.

* Update NearbyParentFragmentUnitTest.kt

* Refactor: Rename Constants to Follow CamelCase Naming Convention

>Updated all constant variable names to follow the camelCase naming convention, removing underscores in the middle or end.

>Ensured variable names remain descriptive and align with code readability best practices.

* Replace private val with const val for URL constants in QuizController

* Renaming the constant to use UPPER_SNAKE_CASE

* Renaming the constant to use UPPER_SNAKE_CASE

* Update Done

* **Refactor: Convert `minimumThresholdForSwipe` to a compile-time constant**

* Convert AboutActivity from Java to Kotlin

This PR converts the AboutActivity class from Java to Kotlin

>Testing:
>Verified all functionalities of the AboutActivity, including toolbar setup, intent launches, and dialog interactions, to ensure behavior remains consistent post-conversion.
>Successfully ran unit tests for AboutActivity to confirm the correctness of methods and logic.

* Thank you for the suggestion! Since these methods all take a single View parameter, replacing them with method references is a great way to simplify the code and improve readability. I'll updated the code accordingly. Added a TODO in the code as a reminder to refactor this in the future.

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
This commit is contained in:
Akshay Komar 2025-01-30 13:11:27 +05:30 committed by GitHub
parent 36f844a709
commit 5d4474ead6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 209 additions and 187 deletions

View file

@ -1,187 +0,0 @@
package fr.free.nrw.commons;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.Spinner;
import androidx.annotation.NonNull;
import fr.free.nrw.commons.databinding.ActivityAboutBinding;
import fr.free.nrw.commons.theme.BaseActivity;
import fr.free.nrw.commons.utils.ConfigUtils;
import fr.free.nrw.commons.utils.DialogUtil;
import java.util.Collections;
import java.util.List;
/**
* Represents about screen of this app
*/
public class AboutActivity extends BaseActivity {
/*
This View Binding class is auto-generated for each xml file. The format is usually the name
of the file with PascalCasing (The underscore characters will be ignored).
More information is available at https://developer.android.com/topic/libraries/view-binding
*/
private ActivityAboutBinding binding;
/**
* This method helps in the creation About screen
*
* @param savedInstanceState Data bundle
*/
@Override
@SuppressLint("StringFormatInvalid")
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/*
Instead of just setting the view with the xml file. We need to use View Binding class.
*/
binding = ActivityAboutBinding.inflate(getLayoutInflater());
final View view = binding.getRoot();
setContentView(view);
setSupportActionBar(binding.toolbarBinding.toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
final String aboutText = getString(R.string.about_license);
/*
We can then access all the views by just using the id names like this.
camelCasing is used with underscore characters being ignored.
*/
binding.aboutLicense.setHtmlText(aboutText);
@SuppressLint("StringFormatMatches")
String improveText = String.format(getString(R.string.about_improve), Urls.NEW_ISSUE_URL);
binding.aboutImprove.setHtmlText(improveText);
binding.aboutVersion.setText(ConfigUtils.getVersionNameWithSha(getApplicationContext()));
Utils.setUnderlinedText(binding.aboutFaq, R.string.about_faq, getApplicationContext());
Utils.setUnderlinedText(binding.aboutRateUs, R.string.about_rate_us, getApplicationContext());
Utils.setUnderlinedText(binding.aboutUserGuide, R.string.user_guide, getApplicationContext());
Utils.setUnderlinedText(binding.aboutPrivacyPolicy, R.string.about_privacy_policy, getApplicationContext());
Utils.setUnderlinedText(binding.aboutTranslate, R.string.about_translate, getApplicationContext());
Utils.setUnderlinedText(binding.aboutCredits, R.string.about_credits, getApplicationContext());
/*
To set listeners, we can create a separate method and use lambda syntax.
*/
binding.facebookLaunchIcon.setOnClickListener(this::launchFacebook);
binding.githubLaunchIcon.setOnClickListener(this::launchGithub);
binding.websiteLaunchIcon.setOnClickListener(this::launchWebsite);
binding.aboutRateUs.setOnClickListener(this::launchRatings);
binding.aboutCredits.setOnClickListener(this::launchCredits);
binding.aboutPrivacyPolicy.setOnClickListener(this::launchPrivacyPolicy);
binding.aboutUserGuide.setOnClickListener(this::launchUserGuide);
binding.aboutFaq.setOnClickListener(this::launchFrequentlyAskedQuesions);
binding.aboutTranslate.setOnClickListener(this::launchTranslate);
}
@Override
public boolean onSupportNavigateUp() {
onBackPressed();
return true;
}
public void launchFacebook(View view) {
Intent intent;
try {
intent = new Intent(Intent.ACTION_VIEW, Uri.parse(Urls.FACEBOOK_APP_URL));
intent.setPackage(Urls.FACEBOOK_PACKAGE_NAME);
startActivity(intent);
} catch (Exception e) {
Utils.handleWebUrl(this, Uri.parse(Urls.FACEBOOK_WEB_URL));
}
}
public void launchGithub(View view) {
Intent intent;
try {
intent = new Intent(Intent.ACTION_VIEW, Uri.parse(Urls.GITHUB_REPO_URL));
intent.setPackage(Urls.GITHUB_PACKAGE_NAME);
startActivity(intent);
} catch (Exception e) {
Utils.handleWebUrl(this, Uri.parse(Urls.GITHUB_REPO_URL));
}
}
public void launchWebsite(View view) {
Utils.handleWebUrl(this, Uri.parse(Urls.WEBSITE_URL));
}
public void launchRatings(View view){
Utils.rateApp(this);
}
public void launchCredits(View view) {
Utils.handleWebUrl(this, Uri.parse(Urls.CREDITS_URL));
}
public void launchUserGuide(View view) {
Utils.handleWebUrl(this, Uri.parse(Urls.USER_GUIDE_URL));
}
public void launchPrivacyPolicy(View view) {
Utils.handleWebUrl(this, Uri.parse(BuildConfig.PRIVACY_POLICY_URL));
}
public void launchFrequentlyAskedQuesions(View view) {
Utils.handleWebUrl(this, Uri.parse(Urls.FAQ_URL));
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_about, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.share_app_icon:
String shareText = String.format(getString(R.string.share_text), Urls.PLAY_STORE_URL_PREFIX + this.getPackageName());
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, shareText);
sendIntent.setType("text/plain");
startActivity(Intent.createChooser(sendIntent, getString(R.string.share_via)));
return true;
default:
return super.onOptionsItemSelected(item);
}
}
public void launchTranslate(View view) {
@NonNull List<String> sortedLocalizedNamesRef = CommonsApplication.getInstance().getLanguageLookUpTable().getCanonicalNames();
Collections.sort(sortedLocalizedNamesRef);
final ArrayAdapter<String> languageAdapter = new ArrayAdapter<>(AboutActivity.this,
android.R.layout.simple_spinner_dropdown_item, sortedLocalizedNamesRef);
final Spinner spinner = new Spinner(AboutActivity.this);
spinner.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
spinner.setAdapter(languageAdapter);
spinner.setGravity(17);
spinner.setPadding(50,0,0,0);
Runnable positiveButtonRunnable = () -> {
String langCode = CommonsApplication.getInstance().getLanguageLookUpTable().getCodes().get(spinner.getSelectedItemPosition());
Utils.handleWebUrl(AboutActivity.this, Uri.parse(Urls.TRANSLATE_WIKI_URL + langCode));
};
DialogUtil.showAlertDialog(this,
getString(R.string.about_translate_title),
getString(R.string.about_translate_message),
getString(R.string.about_translate_proceed),
getString(R.string.about_translate_cancel),
positiveButtonRunnable,
() -> {},
spinner
);
}
}

View file

@ -0,0 +1,209 @@
package fr.free.nrw.commons
import android.annotation.SuppressLint
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.ArrayAdapter
import android.widget.LinearLayout
import android.widget.Spinner
import fr.free.nrw.commons.CommonsApplication.Companion.instance
import fr.free.nrw.commons.databinding.ActivityAboutBinding
import fr.free.nrw.commons.theme.BaseActivity
import fr.free.nrw.commons.utils.ConfigUtils.getVersionNameWithSha
import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog
import java.util.Collections
/**
* Represents about screen of this app
*/
class AboutActivity : BaseActivity() {
/*
This View Binding class is auto-generated for each xml file. The format is usually the name
of the file with PascalCasing (The underscore characters will be ignored).
More information is available at https://developer.android.com/topic/libraries/view-binding
*/
private var binding: ActivityAboutBinding? = null
/**
* This method helps in the creation About screen
*
* @param savedInstanceState Data bundle
*/
@SuppressLint("StringFormatInvalid") //TODO:
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
/*
Instead of just setting the view with the xml file. We need to use View Binding class.
*/
binding = ActivityAboutBinding.inflate(layoutInflater)
val view: View = binding!!.root
setContentView(view)
setSupportActionBar(binding!!.toolbarBinding.toolbar)
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
val aboutText = getString(R.string.about_license)
/*
We can then access all the views by just using the id names like this.
camelCasing is used with underscore characters being ignored.
*/
binding!!.aboutLicense.setHtmlText(aboutText)
@SuppressLint("StringFormatMatches") // TODO:
val improveText =
String.format(getString(R.string.about_improve), Urls.NEW_ISSUE_URL)
binding!!.aboutImprove.setHtmlText(improveText)
binding!!.aboutVersion.text = applicationContext.getVersionNameWithSha()
Utils.setUnderlinedText(
binding!!.aboutFaq, R.string.about_faq,
applicationContext
)
Utils.setUnderlinedText(
binding!!.aboutRateUs, R.string.about_rate_us,
applicationContext
)
Utils.setUnderlinedText(
binding!!.aboutUserGuide, R.string.user_guide,
applicationContext
)
Utils.setUnderlinedText(
binding!!.aboutPrivacyPolicy, R.string.about_privacy_policy,
applicationContext
)
Utils.setUnderlinedText(
binding!!.aboutTranslate, R.string.about_translate,
applicationContext
)
Utils.setUnderlinedText(
binding!!.aboutCredits, R.string.about_credits,
applicationContext
)
/*
To set listeners, we can create a separate method and use lambda syntax.
*/
binding!!.facebookLaunchIcon.setOnClickListener(::launchFacebook)
binding!!.githubLaunchIcon.setOnClickListener(::launchGithub)
binding!!.websiteLaunchIcon.setOnClickListener(::launchWebsite)
binding!!.aboutRateUs.setOnClickListener(::launchRatings)
binding!!.aboutCredits.setOnClickListener(::launchCredits)
binding!!.aboutPrivacyPolicy.setOnClickListener(::launchPrivacyPolicy)
binding!!.aboutUserGuide.setOnClickListener(::launchUserGuide)
binding!!.aboutFaq.setOnClickListener(::launchFrequentlyAskedQuesions)
binding!!.aboutTranslate.setOnClickListener(::launchTranslate)
}
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
fun launchFacebook(view: View?) {
val intent: Intent
try {
intent = Intent(Intent.ACTION_VIEW, Uri.parse(Urls.FACEBOOK_APP_URL))
intent.setPackage(Urls.FACEBOOK_PACKAGE_NAME)
startActivity(intent)
} catch (e: Exception) {
Utils.handleWebUrl(this, Uri.parse(Urls.FACEBOOK_WEB_URL))
}
}
fun launchGithub(view: View?) {
val intent: Intent
try {
intent = Intent(Intent.ACTION_VIEW, Uri.parse(Urls.GITHUB_REPO_URL))
intent.setPackage(Urls.GITHUB_PACKAGE_NAME)
startActivity(intent)
} catch (e: Exception) {
Utils.handleWebUrl(this, Uri.parse(Urls.GITHUB_REPO_URL))
}
}
fun launchWebsite(view: View?) {
Utils.handleWebUrl(this, Uri.parse(Urls.WEBSITE_URL))
}
fun launchRatings(view: View?) {
Utils.rateApp(this)
}
fun launchCredits(view: View?) {
Utils.handleWebUrl(this, Uri.parse(Urls.CREDITS_URL))
}
fun launchUserGuide(view: View?) {
Utils.handleWebUrl(this, Uri.parse(Urls.USER_GUIDE_URL))
}
fun launchPrivacyPolicy(view: View?) {
Utils.handleWebUrl(this, Uri.parse(BuildConfig.PRIVACY_POLICY_URL))
}
fun launchFrequentlyAskedQuesions(view: View?) {
Utils.handleWebUrl(this, Uri.parse(Urls.FAQ_URL))
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
val inflater = menuInflater
inflater.inflate(R.menu.menu_about, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.share_app_icon -> {
val shareText = String.format(
getString(R.string.share_text),
Urls.PLAY_STORE_URL_PREFIX + this.packageName
)
val sendIntent = Intent()
sendIntent.setAction(Intent.ACTION_SEND)
sendIntent.putExtra(Intent.EXTRA_TEXT, shareText)
sendIntent.setType("text/plain")
startActivity(Intent.createChooser(sendIntent, getString(R.string.share_via)))
return true
}
else -> return super.onOptionsItemSelected(item)
}
}
fun launchTranslate(view: View?) {
val sortedLocalizedNamesRef = instance.languageLookUpTable!!.getCanonicalNames()
Collections.sort(sortedLocalizedNamesRef)
val languageAdapter = ArrayAdapter(
this@AboutActivity,
android.R.layout.simple_spinner_dropdown_item, sortedLocalizedNamesRef
)
val spinner = Spinner(this@AboutActivity)
spinner.layoutParams =
LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
spinner.adapter = languageAdapter
spinner.gravity = 17
spinner.setPadding(50, 0, 0, 0)
val positiveButtonRunnable = Runnable {
val langCode = instance.languageLookUpTable!!.getCodes()[spinner.selectedItemPosition]
Utils.handleWebUrl(this@AboutActivity, Uri.parse(Urls.TRANSLATE_WIKI_URL + langCode))
}
showAlertDialog(
this,
getString(R.string.about_translate_title),
getString(R.string.about_translate_message),
getString(R.string.about_translate_proceed),
getString(R.string.about_translate_cancel),
positiveButtonRunnable,
{},
spinner
)
}
}