From 9e7e4146856d087aebbbd40770f45f6aed98a5ce Mon Sep 17 00:00:00 2001 From: androidacy-user Date: Sat, 6 May 2023 12:00:09 -0400 Subject: [PATCH] more kotlin Signed-off-by: androidacy-user --- .../java/com/fox2code/mmm/MainActivity.java | 180 +------------- .../com/fox2code/mmm/utils/RuntimeUtils.kt | 222 ++++++++++++++++++ 2 files changed, 234 insertions(+), 168 deletions(-) create mode 100644 app/src/main/java/com/fox2code/mmm/utils/RuntimeUtils.kt diff --git a/app/src/main/java/com/fox2code/mmm/MainActivity.java b/app/src/main/java/com/fox2code/mmm/MainActivity.java index 3fee630..c7d44d3 100644 --- a/app/src/main/java/com/fox2code/mmm/MainActivity.java +++ b/app/src/main/java/com/fox2code/mmm/MainActivity.java @@ -3,22 +3,19 @@ package com.fox2code.mmm; import static com.fox2code.mmm.MainApplication.Iof; import static com.fox2code.mmm.manager.ModuleInfo.FLAG_MM_REMOTE_MODULE; -import android.Manifest; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.annotation.SuppressLint; +import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Color; -import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; -import android.provider.Settings; import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; @@ -26,15 +23,11 @@ import android.view.WindowManager; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.view.inputmethod.EditorInfo; -import android.widget.CheckBox; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.widget.SearchView; import androidx.cardview.widget.CardView; -import androidx.core.app.NotificationManagerCompat; -import androidx.core.content.ContextCompat; -import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; @@ -51,19 +44,17 @@ import com.fox2code.mmm.repo.RepoManager; import com.fox2code.mmm.repo.RepoModule; import com.fox2code.mmm.settings.SettingsActivity; import com.fox2code.mmm.utils.ExternalHelper; +import com.fox2code.mmm.utils.RuntimeUtils; import com.fox2code.mmm.utils.io.net.Http; import com.fox2code.mmm.utils.realm.ReposList; import com.google.android.material.bottomnavigation.BottomNavigationView; -import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.progressindicator.LinearProgressIndicator; -import com.google.android.material.snackbar.Snackbar; import org.matomo.sdk.extra.TrackHelper; import java.sql.Timestamp; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import io.realm.Realm; import io.realm.RealmConfiguration; @@ -91,6 +82,7 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe private CardView searchCard; private SearchView searchView; private boolean initMode; + private RuntimeUtils runtimeUtils; public MainActivity() { this.moduleViewListBuilder = new ModuleViewListBuilder(this); @@ -171,10 +163,11 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe this.moduleListOnline.setLayoutManager(new LinearLayoutManager(this)); this.moduleList.setItemViewCacheSize(4); // Default is 2 this.swipeRefreshLayout.setOnRefreshListener(this); + this.runtimeUtils = new RuntimeUtils(); // add background blur if enabled this.updateBlurState(); //hideActionBar(); - checkShowInitialSetup(); + runtimeUtils.checkShowInitialSetup(this, this); this.moduleList.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { @@ -326,7 +319,8 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe }); } updateScreenInsets(); // Fix an edge case - if (waitInitialSetupFinished()) { + Context context = MainActivity.this; + if (runtimeUtils.waitInitialSetupFinished(context, MainActivity.this)) { Timber.d("waiting..."); return; } @@ -432,7 +426,7 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe // if system lang is not in MainApplication.supportedLocales, show a snackbar to ask user to help translate if (!MainApplication.supportedLocales.contains(this.getResources().getConfiguration().getLocales().get(0).getLanguage())) { // call showWeblateSnackbar() with language code and language name - showWeblateSnackbar(this.getResources().getConfiguration().getLocales().get(0).getLanguage(), this.getResources().getConfiguration().getLocales().get(0).getDisplayLanguage()); + runtimeUtils.showWeblateSnackbar(this, this, this.getResources().getConfiguration().getLocales().get(0).getLanguage(), this.getResources().getConfiguration().getLocales().get(0).getDisplayLanguage()); } ExternalHelper.INSTANCE.refreshHelper(this); this.initMode = false; @@ -466,7 +460,7 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe this.searchCard.setAlpha(iconified ? 0.80F : 1F); } - private void updateScreenInsets() { + public void updateScreenInsets() { this.runOnUiThread(() -> this.updateScreenInsets(this.getResources().getConfiguration())); } @@ -517,7 +511,9 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe InstallerInitializer.tryGetMagiskPathAsync(new InstallerInitializer.Callback() { @Override public void onPathReceived(String path) { - checkShowInitialSetup(); + Context context = MainActivity.this; + MainActivity mainActivity = MainActivity.this; + runtimeUtils.checkShowInitialSetup(context, mainActivity); // Wait for doSetupNow to finish while (doSetupNowRunning) { try { @@ -706,156 +702,4 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe super.onWindowFocusChanged(hasFocus); this.updateScreenInsets(); } - - @SuppressLint("RestrictedApi") - private void ensurePermissions() { - if (BuildConfig.DEBUG) Timber.i("Ensure Permissions"); - // First, check if user has said don't ask again by checking if pref_dont_ask_again_notification_permission is true - if (!PreferenceManager.getDefaultSharedPreferences(this).getBoolean("pref_dont_ask_again_notification_permission", false)) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { - if (BuildConfig.DEBUG) Timber.i("Request Notification Permission"); - if (FoxActivity.getFoxActivity(this).shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) { - // Show a dialog explaining why we need this permission, which is to show - // notifications for updates - runOnUiThread(() -> { - if (BuildConfig.DEBUG) Timber.i("Show Notification Permission Dialog"); - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); - builder.setTitle(R.string.permission_notification_title); - builder.setMessage(R.string.permission_notification_message); - // Don't ask again checkbox - View view = getLayoutInflater().inflate(R.layout.dialog_checkbox, null); - CheckBox checkBox = view.findViewById(R.id.checkbox); - checkBox.setText(R.string.dont_ask_again); - checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> PreferenceManager.getDefaultSharedPreferences(this).edit().putBoolean("pref_dont_ask_again_notification_permission", isChecked).apply()); - builder.setView(view); - builder.setPositiveButton(R.string.permission_notification_grant, (dialog, which) -> { - // Request the permission - this.requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 0); - doSetupNowRunning = false; - }); - builder.setNegativeButton(R.string.cancel, (dialog, which) -> { - // Set pref_background_update_check to false and dismiss dialog - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - prefs.edit().putBoolean("pref_background_update_check", false).apply(); - dialog.dismiss(); - doSetupNowRunning = false; - }); - builder.show(); - if (BuildConfig.DEBUG) Timber.i("Show Notification Permission Dialog Done"); - }); - } else { - // Request the permission - if (BuildConfig.DEBUG) Timber.i("Request Notification Permission"); - this.requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 0); - if (BuildConfig.DEBUG) { - // Log if granted via onRequestPermissionsResult - boolean granted = ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED; - Timber.i("Request Notification Permission Done. Result: %s", granted); - } - doSetupNowRunning = false; - } - // Next branch is for < android 13 and user has blocked notifications - } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU && !NotificationManagerCompat.from(this).areNotificationsEnabled()) { - runOnUiThread(() -> { - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); - builder.setTitle(R.string.permission_notification_title); - builder.setMessage(R.string.permission_notification_message); - // Don't ask again checkbox - View view = getLayoutInflater().inflate(R.layout.dialog_checkbox, null); - CheckBox checkBox = view.findViewById(R.id.checkbox); - checkBox.setText(R.string.dont_ask_again); - checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> PreferenceManager.getDefaultSharedPreferences(this).edit().putBoolean("pref_dont_ask_again_notification_permission", isChecked).apply()); - builder.setView(view); - builder.setPositiveButton(R.string.permission_notification_grant, (dialog, which) -> { - // Open notification settings - Intent intent = new Intent(); - intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); - Uri uri = Uri.fromParts("package", getPackageName(), null); - intent.setData(uri); - startActivity(intent); - doSetupNowRunning = false; - }); - builder.setNegativeButton(R.string.cancel, (dialog, which) -> { - // Set pref_background_update_check to false and dismiss dialog - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - prefs.edit().putBoolean("pref_background_update_check", false).apply(); - dialog.dismiss(); - doSetupNowRunning = false; - }); - builder.show(); - }); - } else { - doSetupNowRunning = false; - } - } else { - if (BuildConfig.DEBUG) - Timber.i("Notification Permission Already Granted or Don't Ask Again"); - doSetupNowRunning = false; - } - } - - // Method to show a setup box on first launch - @SuppressLint({"InflateParams", "RestrictedApi", "UnspecifiedImmutableFlag", "ApplySharedPref"}) - private void checkShowInitialSetup() { - if (BuildConfig.DEBUG) Timber.i("Checking if we need to run setup"); - // Check if this is the first launch using prefs and if doSetupRestarting was passed in the intent - SharedPreferences prefs = MainApplication.getSharedPreferences("mmm"); - boolean firstLaunch = !Objects.equals(prefs.getString("last_shown_setup", null), "v2"); - // First launch - // this is intentionally separate from the above if statement, because it needs to be checked even if the first launch check is true due to some weird edge cases - if (getIntent().getBooleanExtra("doSetupRestarting", false)) { - // Restarting setup - firstLaunch = false; - } - if (BuildConfig.DEBUG) { - Timber.i("First launch: %s, pref value: %s", firstLaunch, prefs.getString("last_shown_setup", null)); - } - if (firstLaunch) { - doSetupNowRunning = true; - // Launch setup wizard - if (BuildConfig.DEBUG) Timber.i("Launching setup wizard"); - // Show setup activity - Intent intent = new Intent(this, SetupActivity.class); - finish(); - startActivity(intent); - } else { - ensurePermissions(); - } - } - - /** - * @return true if the load workflow must be stopped. - */ - private boolean waitInitialSetupFinished() { - if (BuildConfig.DEBUG) Timber.i("waitInitialSetupFinished"); - if (doSetupNowRunning) updateScreenInsets(); // Fix an edge case - try { - // Wait for doSetupNow to finish - while (doSetupNowRunning) { - //noinspection BusyWait - Thread.sleep(50); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return true; - } - return doSetupRestarting; - } - - /** - * Shows a snackbar offering to take users to Weblate if their language is not available. - * - * @param language The language code. - * @param languageName The language name. - */ - @SuppressLint("RestrictedApi") - private void showWeblateSnackbar(String language, String languageName) { - Snackbar snackbar = Snackbar.make(findViewById(R.id.root_container), getString(R.string.language_not_available, languageName), Snackbar.LENGTH_LONG); - snackbar.setAction(R.string.ok, v -> { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse("https://translate.nift4.org/engage/foxmmm/?language=" + language)); - startActivity(intent); - }); - snackbar.show(); - } } \ No newline at end of file diff --git a/app/src/main/java/com/fox2code/mmm/utils/RuntimeUtils.kt b/app/src/main/java/com/fox2code/mmm/utils/RuntimeUtils.kt new file mode 100644 index 0000000..7501605 --- /dev/null +++ b/app/src/main/java/com/fox2code/mmm/utils/RuntimeUtils.kt @@ -0,0 +1,222 @@ +package com.fox2code.mmm.utils + +import android.Manifest +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Build +import android.provider.Settings +import android.view.View +import android.widget.CheckBox +import android.widget.CompoundButton +import androidx.core.app.NotificationManagerCompat +import androidx.core.content.ContextCompat +import androidx.preference.PreferenceManager +import com.fox2code.foxcompat.app.FoxActivity +import com.fox2code.mmm.BuildConfig +import com.fox2code.mmm.MainActivity +import com.fox2code.mmm.MainApplication +import com.fox2code.mmm.R +import com.fox2code.mmm.SetupActivity +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.material.snackbar.Snackbar +import timber.log.Timber + +@Suppress("UNUSED_PARAMETER") +class RuntimeUtils { + @SuppressLint("RestrictedApi") + private fun ensurePermissions(context: Context, activity: MainActivity) { + if (BuildConfig.DEBUG) Timber.i("Ensure Permissions") + // First, check if user has said don't ask again by checking if pref_dont_ask_again_notification_permission is true + if (!PreferenceManager.getDefaultSharedPreferences(context) + .getBoolean("pref_dont_ask_again_notification_permission", false) + ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && ContextCompat.checkSelfPermission( + context, Manifest.permission.POST_NOTIFICATIONS + ) != PackageManager.PERMISSION_GRANTED + ) { + if (BuildConfig.DEBUG) Timber.i("Request Notification Permission") + if (FoxActivity.getFoxActivity(context) + .shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS) + ) { + // Show a dialog explaining why we need context permission, which is to show + // notifications for updates + activity.runOnUiThread { + if (BuildConfig.DEBUG) Timber.i("Show Notification Permission Dialog") + val builder = MaterialAlertDialogBuilder(context) + builder.setTitle(R.string.permission_notification_title) + builder.setMessage(R.string.permission_notification_message) + // Don't ask again checkbox + val view: View = + activity.layoutInflater.inflate(R.layout.dialog_checkbox, null) + val checkBox = view.findViewById(R.id.checkbox) + checkBox.setText(R.string.dont_ask_again) + checkBox.setOnCheckedChangeListener { buttonView: CompoundButton?, isChecked: Boolean -> + PreferenceManager.getDefaultSharedPreferences( + context + ).edit().putBoolean( + "pref_dont_ask_again_notification_permission", isChecked + ).apply() + } + builder.setView(view) + builder.setPositiveButton(R.string.permission_notification_grant) { dialog, which -> + // Request the permission + activity.requestPermissions( + arrayOf(Manifest.permission.POST_NOTIFICATIONS), 0 + ) + MainActivity.doSetupNowRunning = false + } + builder.setNegativeButton(R.string.cancel) { dialog, which -> + // Set pref_background_update_check to false and dismiss dialog + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + prefs.edit().putBoolean("pref_background_update_check", false).apply() + dialog.dismiss() + MainActivity.doSetupNowRunning = false + } + builder.show() + if (BuildConfig.DEBUG) Timber.i("Show Notification Permission Dialog Done") + } + } else { + // Request the permission + if (BuildConfig.DEBUG) Timber.i("Request Notification Permission") + activity.requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS), 0) + if (BuildConfig.DEBUG) { + // Log if granted via onRequestPermissionsResult + val granted = ContextCompat.checkSelfPermission( + context, Manifest.permission.POST_NOTIFICATIONS + ) == PackageManager.PERMISSION_GRANTED + Timber.i("Request Notification Permission Done. Result: %s", granted) + } + MainActivity.doSetupNowRunning = false + } + // Next branch is for < android 13 and user has blocked notifications + } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU && !NotificationManagerCompat.from( + context + ).areNotificationsEnabled() + ) { + activity.runOnUiThread { + val builder = MaterialAlertDialogBuilder(context) + builder.setTitle(R.string.permission_notification_title) + builder.setMessage(R.string.permission_notification_message) + // Don't ask again checkbox + val view: View = activity.layoutInflater.inflate(R.layout.dialog_checkbox, null) + val checkBox = view.findViewById(R.id.checkbox) + checkBox.setText(R.string.dont_ask_again) + checkBox.setOnCheckedChangeListener { buttonView: CompoundButton?, isChecked: Boolean -> + PreferenceManager.getDefaultSharedPreferences( + context + ).edit() + .putBoolean("pref_dont_ask_again_notification_permission", isChecked) + .apply() + } + builder.setView(view) + builder.setPositiveButton(R.string.permission_notification_grant) { dialog, which -> + // Open notification settings + val intent = Intent() + intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS + val uri = Uri.fromParts("package", activity.packageName, null) + intent.data = uri + activity.startActivity(intent) + MainActivity.doSetupNowRunning = false + } + builder.setNegativeButton(R.string.cancel) { dialog, which -> + // Set pref_background_update_check to false and dismiss dialog + val prefs = MainApplication.getSharedPreferences("mmm") + prefs.edit().putBoolean("pref_background_update_check", false).apply() + dialog.dismiss() + MainActivity.doSetupNowRunning = false + } + builder.show() + } + } else { + MainActivity.doSetupNowRunning = false + } + } else { + if (BuildConfig.DEBUG) Timber.i("Notification Permission Already Granted or Don't Ask Again") + MainActivity.doSetupNowRunning = false + } + } + + /** + * Shows setup activity if it's the first launch + */ + // Method to show a setup box on first launch + @SuppressLint("InflateParams", "RestrictedApi", "UnspecifiedImmutableFlag", "ApplySharedPref") + fun checkShowInitialSetup(context: Context, activity: MainActivity) { + if (BuildConfig.DEBUG) Timber.i("Checking if we need to run setup") + // Check if context is the first launch using prefs and if doSetupRestarting was passed in the intent + val prefs = MainApplication.getSharedPreferences("mmm") + var firstLaunch = prefs.getString("last_shown_setup", null) != "v2" + // First launch + // context is intentionally separate from the above if statement, because it needs to be checked even if the first launch check is true due to some weird edge cases + if (activity.intent.getBooleanExtra("doSetupRestarting", false)) { + // Restarting setup + firstLaunch = false + } + if (BuildConfig.DEBUG) { + Timber.i( + "First launch: %s, pref value: %s", + firstLaunch, + prefs.getString("last_shown_setup", null) + ) + } + if (firstLaunch) { + MainActivity.doSetupNowRunning = true + // Launch setup wizard + if (BuildConfig.DEBUG) Timber.i("Launching setup wizard") + // Show setup activity + val intent = Intent(context, SetupActivity::class.java) + activity.finish() + activity.startActivity(intent) + } else { + ensurePermissions(context, activity) + } + } + + /** + * @return true if the load workflow must be stopped. + */ + fun waitInitialSetupFinished(context: Context, activity: MainActivity): Boolean { + if (BuildConfig.DEBUG) Timber.i("waitInitialSetupFinished") + if (MainActivity.doSetupNowRunning) activity.updateScreenInsets() // Fix an edge case + try { + // Wait for doSetupNow to finish + while (MainActivity.doSetupNowRunning) { + Thread.sleep(50) + } + } catch (e: InterruptedException) { + Thread.currentThread().interrupt() + return true + } + return MainActivity.doSetupRestarting + } + + /** + * Shows a snackbar offering to take users to Weblate if their language is not available. + * + * @param language The language code. + * @param languageName The language name. + */ + @SuppressLint("RestrictedApi") + fun showWeblateSnackbar( + context: Context, activity: MainActivity, language: String, languageName: String + ) { + // if we haven't shown context snackbar for context version yet + val prefs = MainApplication.getSharedPreferences("mmm") + if (prefs.getInt("weblate_snackbar_shown", 0) == BuildConfig.VERSION_CODE) return + val snackbar: Snackbar = Snackbar.make( + activity.findViewById(R.id.root_container), + activity.getString(R.string.language_not_available, languageName), + Snackbar.LENGTH_LONG + ) + snackbar.setAction(R.string.ok) { v -> + val intent = Intent(Intent.ACTION_VIEW) + intent.data = Uri.parse("https://translate.nift4.org/engage/foxmmm/?language=$language") + activity.startActivity(intent) + } + snackbar.show() + prefs.edit().putInt("weblate_snackbar_shown", BuildConfig.VERSION_CODE).apply() + } +} \ No newline at end of file