From 5d4474ead65649377929f8fc37a50521afb30767 Mon Sep 17 00:00:00 2001 From: Akshay Komar <146421342+Akshaykomar890@users.noreply.github.com> Date: Thu, 30 Jan 2025 13:11:27 +0530 Subject: [PATCH] Migrated AboutActivity from Java to Kotlin (#6158) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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() 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(...)) 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 --- .../fr/free/nrw/commons/AboutActivity.java | 187 ---------------- .../java/fr/free/nrw/commons/AboutActivity.kt | 209 ++++++++++++++++++ 2 files changed, 209 insertions(+), 187 deletions(-) delete mode 100644 app/src/main/java/fr/free/nrw/commons/AboutActivity.java create mode 100644 app/src/main/java/fr/free/nrw/commons/AboutActivity.kt diff --git a/app/src/main/java/fr/free/nrw/commons/AboutActivity.java b/app/src/main/java/fr/free/nrw/commons/AboutActivity.java deleted file mode 100644 index dcc9bfd43..000000000 --- a/app/src/main/java/fr/free/nrw/commons/AboutActivity.java +++ /dev/null @@ -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 sortedLocalizedNamesRef = CommonsApplication.getInstance().getLanguageLookUpTable().getCanonicalNames(); - Collections.sort(sortedLocalizedNamesRef); - final ArrayAdapter 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 - ); - } - -} diff --git a/app/src/main/java/fr/free/nrw/commons/AboutActivity.kt b/app/src/main/java/fr/free/nrw/commons/AboutActivity.kt new file mode 100644 index 000000000..143f5e569 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/AboutActivity.kt @@ -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 + ) + } +}