Implement background module update check & improve translator utils. (New strings)

pull/178/head
Fox2Code 2 years ago
parent 54879213bd
commit 58a29f006d

@ -97,6 +97,7 @@ dependencies {
implementation 'com.github.Fox2Code:FoxCompat:0.0.2'
// Utils
implementation 'androidx.work:work-runtime:2.7.1'
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.3'
implementation 'com.squareup.okhttp3:okhttp-brotli:4.9.3'
implementation 'com.github.topjohnwu.libsu:io:5.0.1'

@ -11,6 +11,8 @@
<uses-permission android:name="android.permission.INTERNET" />
<!-- WebView offline webpage support -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Check if there is modules updates on boot -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- Open config apps for applications -->
<uses-permission-sdk-23 android:name="android.permission.QUERY_ALL_PACKAGES" />
<!-- Supposed to fix bugs with old firmware, only requested on pre Marshmallow -->
@ -31,7 +33,14 @@
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="false"
tools:targetApi="s"
tools:replace="android:supportsRtl">
tools:replace="android:supportsRtl"
tools:ignore="ManifestResource">
<receiver android:name="com.fox2code.mmm.background.BackgroundBootListener"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<activity
android:name=".settings.SettingsActivity"
android:parentActivityName=".MainActivity"

@ -16,6 +16,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.SearchView;
import androidx.cardview.widget.CardView;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.graphics.ColorUtils;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@ -23,6 +24,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.fox2code.foxcompat.FoxActivity;
import com.fox2code.foxcompat.FoxDisplay;
import com.fox2code.mmm.background.BackgroundUpdateChecker;
import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.manager.LocalModuleInfo;
import com.fox2code.mmm.manager.ModuleManager;
@ -64,9 +66,16 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
this.moduleViewListBuilder.addNotification(NotificationType.INSTALL_FROM_STORAGE);
}
@Override
protected void onResume() {
BackgroundUpdateChecker.onMainActivityResume(this);
super.onResume();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
this.initMode = true;
BackgroundUpdateChecker.onMainActivityCreate(this);
super.onCreate(savedInstanceState);
this.setActionBarExtraMenuButton(R.drawable.ic_baseline_settings_24, v -> {
IntentHelper.startActivity(this, SettingsActivity.class);
@ -395,7 +404,8 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
this.searchView.clearFocus();
if (this.initMode) return false;
if (this.moduleViewListBuilder.setQueryChange(query)) {
new Thread(() -> this.moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter), "Query update thread").start();
new Thread(() -> this.moduleViewListBuilder.applyTo(
moduleList, moduleViewAdapter), "Query update thread").start();
}
return true;
}
@ -404,7 +414,8 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
public boolean onQueryTextChange(String query) {
if (this.initMode) return false;
if (this.moduleViewListBuilder.setQueryChange(query)) {
new Thread(() -> this.moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter), "Query update thread").start();
new Thread(() -> this.moduleViewListBuilder.applyTo(
moduleList, moduleViewAdapter), "Query update thread").start();
}
return false;
}
@ -413,7 +424,8 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe
public boolean onClose() {
if (this.initMode) return false;
if (this.moduleViewListBuilder.setQueryChange(null)) {
new Thread(() -> this.moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter), "Query update thread").start();
new Thread(() -> this.moduleViewListBuilder.applyTo(
moduleList, moduleViewAdapter), "Query update thread").start();
}
return false;
}

@ -17,10 +17,16 @@ import androidx.annotation.StyleRes;
import androidx.emoji2.text.DefaultEmojiCompatConfig;
import androidx.emoji2.text.EmojiCompat;
import androidx.emoji2.text.FontRequestEmojiCompatConfig;
import androidx.work.Constraints;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.NetworkType;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
import com.fox2code.foxcompat.FoxActivity;
import com.fox2code.foxcompat.FoxApplication;
import com.fox2code.foxcompat.FoxThemeWrapper;
import com.fox2code.mmm.background.BackgroundUpdateChecker;
import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.utils.GMSProviderInstaller;
import com.fox2code.mmm.utils.Http;
@ -31,6 +37,7 @@ import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import io.noties.markwon.Markwon;
import io.noties.markwon.html.HtmlPlugin;
@ -47,7 +54,8 @@ import io.noties.prism4j.annotations.PrismBundle;
includeAll = true,
grammarLocatorClassName = ".Prism4jGrammarLocator"
)
public class MainApplication extends FoxApplication {
public class MainApplication extends FoxApplication
implements androidx.work.Configuration.Provider {
private static final String timeFormatString = "dd MMM yyyy"; // Example: 13 july 2001
private static Locale timeFormatLocale =
Resources.getSystem().getConfiguration().locale;
@ -146,6 +154,10 @@ public class MainApplication extends FoxApplication {
&& isDeveloper();
}
public static boolean isBackgroundUpdateCheckEnabled() {
return getSharedPreferences().getBoolean("pref_background_update_check", true);
}
public static boolean isFirstBoot() {
return firstBoot;
}
@ -196,6 +208,12 @@ public class MainApplication extends FoxApplication {
return this.markwonThemeContext;
}
@NonNull
@Override
public androidx.work.Configuration getWorkManagerConfiguration() {
return new androidx.work.Configuration.Builder().build();
}
private class Prism4jSwitchTheme implements Prism4jTheme {
private final Prism4jTheme light = new Prism4jThemeDefault(Color.TRANSPARENT);
private final Prism4jTheme dark = new Prism4jThemeDarkula(Color.TRANSPARENT);

@ -0,0 +1,19 @@
package com.fox2code.mmm.background;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import com.fox2code.mmm.MainApplication;
public class BackgroundBootListener extends BroadcastReceiver {
private static final String BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED";
@Override
public void onReceive(Context context, Intent intent) {
if (!BOOT_COMPLETED.equals(intent.getAction())) return;
if (!MainApplication.isBackgroundUpdateCheckEnabled()) return;
BackgroundUpdateChecker.onMainActivityCreate(context);
BackgroundUpdateChecker.doCheck(context);
}
}

@ -0,0 +1,115 @@
package com.fox2code.mmm.background;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationChannelCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.work.Constraints;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.NetworkType;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import com.fox2code.mmm.MainActivity;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.R;
import com.fox2code.mmm.manager.LocalModuleInfo;
import com.fox2code.mmm.manager.ModuleManager;
import com.fox2code.mmm.repo.RepoManager;
import com.fox2code.mmm.repo.RepoModule;
import java.util.Random;
import java.util.concurrent.TimeUnit;
public class BackgroundUpdateChecker extends Worker {
private static boolean easterEggActive = false;
public static final String NOTIFICATION_CHANNEL_ID = "background_update";
public static final int NOTIFICATION_ID = 1;
public BackgroundUpdateChecker(@NonNull Context context,
@NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@NonNull
@Override
public Result doWork() {
if (!NotificationManagerCompat.from(this.getApplicationContext()).areNotificationsEnabled()
|| !MainApplication.isBackgroundUpdateCheckEnabled()) return Result.success();
doCheck(this.getApplicationContext());
return Result.success();
}
static void doCheck(Context context) {
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
RepoManager.getINSTANCE().update(null);
ModuleManager.getINSTANCE().scan();
ModuleManager.getINSTANCE().scan();
int moduleUpdateCount = 0;
for (LocalModuleInfo localModuleInfo :
ModuleManager.getINSTANCE().getModules().values()) {
RepoModule repoModule = RepoManager.getINSTANCE()
.getModules().get(localModuleInfo.id);
localModuleInfo.checkModuleUpdate();
if (localModuleInfo.updateVersionCode > localModuleInfo.versionCode) {
moduleUpdateCount++;
} else if (repoModule != null &&
repoModule.moduleInfo.versionCode > localModuleInfo.versionCode) {
moduleUpdateCount++;
}
}
if (moduleUpdateCount != 0) {
postNotification(context, moduleUpdateCount);
}
}
public static void postNotification(Context context, int updateCount) {
if (!easterEggActive) easterEggActive = new Random().nextInt(100) <= updateCount;
NotificationCompat.Builder builder = new NotificationCompat.Builder(
context, NOTIFICATION_CHANNEL_ID)
.setContentTitle(context.getString(easterEggActive ?
R.string.notification_update_title_easter_egg :
R.string.notification_update_title)
.replace("%i", String.valueOf(updateCount)))
.setContentText(context.getString(R.string.notification_update_subtitle))
.setSmallIcon(R.drawable.ic_baseline_extension_24)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(PendingIntent.getActivity(context, 0,
new Intent(context, MainActivity.class).setFlags(
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK),
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ?
PendingIntent.FLAG_IMMUTABLE : 0)).setAutoCancel(true);
NotificationManagerCompat.from(context).notify(NOTIFICATION_ID, builder.build());
}
public static void onMainActivityCreate(Context context) {
NotificationManagerCompat notificationManagerCompat =
NotificationManagerCompat.from(context);
notificationManagerCompat.createNotificationChannel(
new NotificationChannelCompat.Builder(NOTIFICATION_CHANNEL_ID,
NotificationManagerCompat.IMPORTANCE_HIGH).setShowBadge(true)
.setName(context.getString(R.string.notification_update_pref)).build());
notificationManagerCompat.cancel(BackgroundUpdateChecker.NOTIFICATION_ID);
BackgroundUpdateChecker.easterEggActive = false;
WorkManager.getInstance(context).enqueueUniquePeriodicWork("background_checker",
ExistingPeriodicWorkPolicy.REPLACE, new PeriodicWorkRequest.Builder(
BackgroundUpdateChecker.class, 6, TimeUnit.HOURS)
.setConstraints(new Constraints.Builder().setRequiresBatteryNotLow(true)
.setRequiredNetworkType(NetworkType.UNMETERED).build()).build());
}
public static void onMainActivityResume(Context context) {
NotificationManagerCompat.from(context).cancel(
BackgroundUpdateChecker.NOTIFICATION_ID);
BackgroundUpdateChecker.easterEggActive = false;
}
}

@ -253,7 +253,8 @@ public enum ActionButtonType {
if (url.startsWith("https://www.paypal.me/") ||
url.startsWith("https://www.paypal.com/paypalme/")) {
icon = R.drawable.ic_baseline_paypal_24;
} else if (url.startsWith("https://www.patreon.com/")) {
} else if (url.startsWith("https://patreon.com/") ||
url.startsWith("https://www.patreon.com/")) {
icon = R.drawable.ic_patreon;
}
return icon;

@ -212,11 +212,14 @@ public class RepoData extends XRepo {
return this.id;
}
private static boolean isNonNull(String str) {
return str != null && !str.isEmpty() && !"null".equals(str);
}
// Repo data info getters
@NonNull
public String getName() {
if (this.name != null &&
!this.name.isEmpty())
if (isNonNull(this.name))
return this.name;
if (this.defaultName != null)
return this.defaultName;
@ -225,8 +228,7 @@ public class RepoData extends XRepo {
@NonNull
public String getWebsite() {
if (this.website != null &&
!this.website.isEmpty())
if (isNonNull(this.website))
return this.website;
if (this.defaultWebsite != null)
return this.defaultWebsite;
@ -234,22 +236,19 @@ public class RepoData extends XRepo {
}
public String getSupport() {
if (this.support != null &&
!this.support.isEmpty())
if (isNonNull(this.support))
return this.support;
return this.defaultSupport;
}
public String getDonate() {
if (this.donate != null &&
!this.donate.isEmpty())
if (isNonNull(this.donate))
return this.donate;
return this.defaultDonate;
}
public String getSubmitModule() {
if (this.submitModule != null &&
!this.submitModule.isEmpty())
if (isNonNull(this.submitModule))
return this.submitModule;
return this.defaultSubmitModule;
}

@ -6,7 +6,6 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
@ -33,6 +32,7 @@ import com.fox2code.mmm.Constants;
import com.fox2code.mmm.MainActivity;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.R;
import com.fox2code.mmm.background.BackgroundUpdateChecker;
import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.module.ActionButtonType;
import com.fox2code.mmm.repo.CustomRepoData;
@ -54,8 +54,10 @@ import org.json.JSONException;
import java.io.IOException;
import java.util.HashSet;
import java.util.Objects;
import java.util.Random;
public class SettingsActivity extends FoxActivity implements LanguageActivity {
private static final int LANGUAGE_SUPPORT_LEVEL = 1;
private static final String TAG = "SettingsActivity";
private static int devModeStep = 0;
@ -88,6 +90,12 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
System.exit(0); // Exit app process
}
@Override
protected void onPause() {
BackgroundUpdateChecker.onMainActivityResume(this);
super.onPause();
}
public static class SettingsFragment extends PreferenceFragmentCompat
implements FoxActivity.OnBackPressedCallback {
@Override
@ -145,10 +153,12 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
HashSet<String> supportedLocales = new HashSet<>();
supportedLocales.add("cs");
supportedLocales.add("de");
supportedLocales.add("el");
supportedLocales.add("es-rMX");
supportedLocales.add("et");
supportedLocales.add("fr");
supportedLocales.add("id");
supportedLocales.add("it");
supportedLocales.add("ja");
supportedLocales.add("nb-rNO");
supportedLocales.add("pl");
@ -170,18 +180,20 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
return true;
});
int nightModeFlags =
getContext().getResources().getConfiguration().uiMode &
Configuration.UI_MODE_NIGHT_MASK;
switch (nightModeFlags) {
case Configuration.UI_MODE_NIGHT_YES:
findPreference("pref_force_dark_terminal").setEnabled(false);
break;
case Configuration.UI_MODE_NIGHT_NO:
case Configuration.UI_MODE_NIGHT_UNDEFINED:
findPreference("pref_force_dark_terminal").setEnabled(true);
break;
int level = this.currentLanguageLevel();
if (level != LANGUAGE_SUPPORT_LEVEL) {
Log.e(TAG, "Detected language level " + level +
", latest is " + LANGUAGE_SUPPORT_LEVEL);
languageSelector.setSummary(R.string.language_support_outdated);
} else {
if (!"Translated by Fox2Code".equals( // I don't "translate" english
this.getString(R.string.language_translated_by))) {
languageSelector.setSummary(R.string.language_translated_by);
} else {
languageSelector.setSummary(null);
}
}
if (!MainApplication.isDeveloper()) {
findPreference("pref_disable_low_quality_module_filter").setVisible(false);
}
@ -189,6 +201,23 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
|| !MainApplication.isDeveloper()) {
findPreference("pref_use_magisk_install_command").setVisible(false);
}
Preference debugNotification = findPreference("pref_background_update_check_debug");
debugNotification.setEnabled(MainApplication.isBackgroundUpdateCheckEnabled());
debugNotification.setVisible(MainApplication.isDeveloper());
debugNotification.setOnPreferenceClickListener(preference -> {
BackgroundUpdateChecker.postNotification(
this.requireContext(), new Random().nextInt(4) + 2);
return true;
});
findPreference("pref_background_update_check").setOnPreferenceChangeListener((preference, newValue) -> {
boolean enabled = Boolean.parseBoolean(String.valueOf(newValue));
debugNotification.setEnabled(enabled);
if (!enabled) {
BackgroundUpdateChecker.onMainActivityResume(
this.requireContext());
}
return true;
});
final LibsBuilder libsBuilder = new LibsBuilder().withShowLoadingProgress(false)
.withLicenseShown(true).withAboutMinimalDesign(false);
@ -201,13 +230,30 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
"https://github.com/Fox2Code/FoxMagiskModuleManager/releases");
return true;
});
if (BuildConfig.DEBUG || BuildConfig.ENABLE_AUTO_UPDATER) {
findPreference("pref_report_bug").setOnPreferenceClickListener(p -> {
devModeStep = 0;
IntentHelper.openUrl(p.getContext(),
"https://github.com/Fox2Code/FoxMagiskModuleManager/issues");
return true;
});
} else {
findPreference("pref_report_bug").setVisible(false);
}
findPreference("pref_source_code").setOnPreferenceClickListener(p -> {
if (devModeStep == 2 && (BuildConfig.DEBUG || !MainApplication.isDeveloper())) {
if (devModeStep == 2) {
devModeStep = 0;
MainApplication.getSharedPreferences().edit()
.putBoolean("developer", true).apply();
Toast.makeText(getContext(), // Tell the user something changed
R.string.dev_mode_enabled, Toast.LENGTH_SHORT).show();
if (MainApplication.isDeveloper() && !BuildConfig.DEBUG) {
MainApplication.getSharedPreferences().edit()
.putBoolean("developer", false).apply();
Toast.makeText(getContext(), // Tell the user something changed
R.string.dev_mode_disabled, Toast.LENGTH_SHORT).show();
} else {
MainApplication.getSharedPreferences().edit()
.putBoolean("developer", true).apply();
Toast.makeText(getContext(), // Tell the user something changed
R.string.dev_mode_enabled, Toast.LENGTH_SHORT).show();
}
return true;
}
IntentHelper.openUrl(p.getContext(),
@ -221,6 +267,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
});
findPreference("pref_show_licenses").setOnPreferenceClickListener(p -> {
devModeStep = devModeStep == 1 ? 2 : 0;
BackgroundUpdateChecker.onMainActivityResume(this.requireContext());
openFragment(libsBuilder.supportFragment(), R.string.licenses);
return true;
});
@ -250,6 +297,21 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
.commit();
return true;
}
private int currentLanguageLevel() {
int declaredLanguageLevel =
this.getResources().getInteger(R.integer.language_support_level);
if (declaredLanguageLevel != LANGUAGE_SUPPORT_LEVEL)
return declaredLanguageLevel;
if (!this.getResources().getConfiguration().locale.getLanguage().equals("en") &&
this.getResources().getString(R.string.notification_update_pref)
.equals("Background modules update check") &&
this.getResources().getString(R.string.notification_update_desc)
.equals("May increase battery usage")) {
return 0;
}
return LANGUAGE_SUPPORT_LEVEL;
}
}
public static class RepoFragment extends PreferenceFragmentCompat {

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2z"/>
</vector>

@ -27,6 +27,10 @@
<string name="description">Description</string>
<string name="uninstall">Uninstall</string>
<string name="config">Config</string>
<string name="favourite">Favourite</string>
<string name="report_bugs">Report bugs</string>
<string name="sniffed_modules">Sniffed modules</string>
<string name="sniffing_modules">Sniffing modules</string>
<string name="submit_modules">Submit a module</string>
<string name="require_android_6">Requires Android 6.0+</string>
<string name="require_android_12">Requires Android 12+</string>
@ -96,6 +100,7 @@
so this option behind is hidden behind developer mode.\nTurn this on at your own risk!
</string>
<string name="dev_mode_enabled">Developer mode on</string>
<string name="dev_mode_disabled">Developer mode off</string>
<string name="force_english_pref">English app language</string>
<string name="disable_low_quality_module_filter_pref">Show low-quality modules</string>
<string name="disable_low_quality_module_filter_desc">
@ -128,6 +133,21 @@
<string name="backup_module_list">Backup modules</string>
<string name="restore_module_list">Restore modules</string>
<string name="require_internet">This operation require an internet connection</string>
<!-- Background Notification translation -->
<string name="notification_update_title">Found %i module updates</string>
<string name="notification_update_title_easter_egg">Sniffed %i module updates</string>
<string name="notification_update_subtitle">Click to open the app</string>
<string name="notification_update_pref">Background modules update check</string>
<string name="notification_update_desc">May increase battery usage</string>
<string name="notification_update_debug_pref">Test Notification</string>
<!-- Set to true in translation file if your language is right to left -->
<bool name="lang_support_rtl">false</bool>
<!-- Always copy language_support_level when translating -->
<integer name="language_support_level">1</integer>
<string name="language_support_outdated">Some translations for the current language are
not up-to-date, please consider contributing to the app translations on GitHub</string>
<!-- Replace with your own username when translating -->
<string name="language_translated_by">Translated by Fox2Code</string>
</resources>

@ -31,6 +31,19 @@
app:title="@string/use_magisk_install_command_pref"
app:summary="@string/use_magisk_install_command_desc"
app:singleLineTitle="false" />
<SwitchPreferenceCompat
app:defaultValue="true"
app:key="pref_background_update_check"
app:icon="@drawable/ic_baseline_notifications_24"
app:title="@string/notification_update_pref"
app:summary="@string/notification_update_desc"
app:singleLineTitle="false" />
<Preference
app:key="pref_background_update_check_debug"
app:title="@string/notification_update_debug_pref"
app:singleLineTitle="false" />
</PreferenceCategory>
<PreferenceCategory app:title="@string/pref_category_appearance">
@ -111,6 +124,11 @@
app:icon="@drawable/ic_baseline_system_update_24"
app:title="@string/app_update"
app:singleLineTitle="false" />
<Preference
app:key="pref_report_bug"
app:icon="@drawable/ic_baseline_bug_report_24"
app:title="@string/report_bugs"
app:singleLineTitle="false" />
<Preference
app:key="pref_source_code"
app:icon="@drawable/ic_github"

Loading…
Cancel
Save