From 1488f13e95e8caf7fa97a7f7622f168dc4628098 Mon Sep 17 00:00:00 2001 From: Fox2Code Date: Sat, 16 Jul 2022 21:29:25 +0200 Subject: [PATCH] Introduce new `modules.json` format extensions. --- DEVELOPERS.md | 27 +++++ app/build.gradle | 8 +- .../mmm/androidacy/AndroidacyActivity.java | 37 +++++- .../mmm/androidacy/AndroidacyRepoData.java | 10 ++ .../mmm/androidacy/AndroidacyWebAPI.java | 9 +- .../fox2code/mmm/module/ActionButtonType.java | 21 ++-- .../fox2code/mmm/repo/LimitedRepoData.java | 21 ++++ .../java/com/fox2code/mmm/repo/RepoData.java | 77 +++++++++++-- .../com/fox2code/mmm/repo/RepoManager.java | 15 ++- .../mmm/settings/SettingsActivity.java | 108 ++++++++++-------- app/src/main/res/layout/webview.xml | 12 ++ app/src/main/res/xml/repo_preferences.xml | 10 ++ 12 files changed, 282 insertions(+), 73 deletions(-) create mode 100644 app/src/main/java/com/fox2code/mmm/repo/LimitedRepoData.java diff --git a/DEVELOPERS.md b/DEVELOPERS.md index acb92b7..477dca9 100644 --- a/DEVELOPERS.md +++ b/DEVELOPERS.md @@ -27,6 +27,33 @@ it is a check that verify that the module is declaring the minimum required to allow the app to show your module to the user without hurting his experience. Filling all basic Magisk properties is often enough to not get filtered out by it. +## Custom Repo format + +Note: This feature is for `0.6.0` version that is not released yet. + +`last_update` fields uses unix millis. + +Json format is +```json +{ + "name": "Repo name", + "website": "repo website", + "support": "optional support url", + "donate": "optional support url", + "submitModule": "optional submit module URL", + "last_update": 0, + "modules": [ + { + "id": "module id", + "last_update": 0, + "notes_url": "notes url", + "prop_url": "module.prop url", + "zip_url": "module.zip url" + } + ] +} +``` + ## Properties In addition to the following required magisk properties diff --git a/app/build.gradle b/app/build.gradle index 2453325..e8de5b9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -36,8 +36,8 @@ android { buildConfigField "boolean", "ENABLE_AUTO_UPDATER", "true" buildConfigField( "java.util.List", - "DISABLED_REPOS", - "java.util.Arrays.asList()", + "ENABLED_REPOS", + "java.util.Arrays.asList(\"magisk_alt_repo\", \"androidacy_repo\")", ) } @@ -54,8 +54,8 @@ android { // F-Droid flavor. buildConfigField( "java.util.List", - "DISABLED_REPOS", - "java.util.Arrays.asList(\"androidacy_repo\")", + "ENABLED_REPOS", + "java.util.Arrays.asList(\"magisk_alt_repo\")", ) } } diff --git a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java index fe178bd..c38598d 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java @@ -8,11 +8,14 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.util.Log; +import android.view.View; +import android.webkit.ConsoleMessage; import android.webkit.ValueCallback; import android.webkit.WebChromeClient; import android.webkit.WebResourceRequest; import android.webkit.WebSettings; import android.webkit.WebView; +import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; @@ -46,6 +49,7 @@ public class AndroidacyActivity extends FoxActivity { } WebView webView; + TextView webViewNote; AndroidacyWebAPI androidacyWebAPI; boolean backOnResume; @@ -108,6 +112,7 @@ public class AndroidacyActivity extends FoxActivity { } } this.webView = this.findViewById(R.id.webView); + this.webViewNote = this.findViewById(R.id.webViewNote); WebSettings webSettings = this.webView.getSettings(); webSettings.setUserAgentString(Http.getAndroidacyUA()); webSettings.setDomStorageEnabled(true); @@ -140,12 +145,20 @@ public class AndroidacyActivity extends FoxActivity { this.pageUrl = url; } - private void onReceivedError(String url,int errorCode) { + @Override + public void onPageFinished(WebView view, String url) { + webViewNote.setVisibility(View.GONE); + } + + private void onReceivedError(String url, int errorCode) { if ((url.startsWith("https://api.androidacy.com/magisk/") || url.equals(pageUrl)) && (errorCode == 419 || errorCode == 429 || errorCode == 503)) { Toast.makeText(AndroidacyActivity.this, "Too many requests!", Toast.LENGTH_LONG).show(); AndroidacyActivity.this.runOnUiThread(AndroidacyActivity.this::onBackPressed); + } else if (url.equals(this.pageUrl)) { + postOnUiThread(() -> + webViewNote.setVisibility(View.VISIBLE)); } } @@ -172,6 +185,28 @@ public class AndroidacyActivity extends FoxActivity { FileChooserParams.parseResult(code, data))); return true; } + + @Override + public boolean onConsoleMessage(ConsoleMessage consoleMessage) { + switch (consoleMessage.messageLevel()) { + case TIP: + Log.v(TAG, consoleMessage.message()); + break; + case LOG: + Log.i(TAG, consoleMessage.message()); + break; + case WARNING: + Log.w(TAG, consoleMessage.message()); + break; + case ERROR: + Log.e(TAG, consoleMessage.message()); + break; + case DEBUG: + Log.d(TAG, consoleMessage.message()); + break; + } + return super.onConsoleMessage(consoleMessage); + } }); this.webView.setDownloadListener(( downloadUrl, userAgent, contentDisposition, mimetype, contentLength) -> { diff --git a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java index c8a8490..736edef 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java @@ -7,6 +7,7 @@ import android.webkit.CookieManager; import com.fox2code.mmm.R; import com.fox2code.mmm.manager.ModuleInfo; import com.fox2code.mmm.repo.RepoData; +import com.fox2code.mmm.repo.RepoManager; import com.fox2code.mmm.repo.RepoModule; import com.fox2code.mmm.utils.Http; import com.fox2code.mmm.utils.PropUtils; @@ -49,6 +50,11 @@ public class AndroidacyRepoData extends RepoData { this.androidacyBlockade = 0; // Don't allow time travel. Well why not??? } } + this.defaultName = "Androidacy Modules Repo"; + this.defaultWebsite = RepoManager.ANDROIDACY_MAGISK_REPO_HOMEPAGE; + this.defaultSupport = "https://t.me/androidacy_discussions"; + this.defaultDonate = "https://patreon.com/androidacy"; + this.defaultSubmitModule = "https://www.androidacy.com/module-repository-applications/"; } private static String getCookies() { @@ -231,6 +237,10 @@ public class AndroidacyRepoData extends RepoData { } this.lastUpdate = lastLastUpdate; this.name = name; + this.website = jsonObject.optString("website"); + this.support = jsonObject.optString("support"); + this.donate = jsonObject.optString("donate"); + this.submitModule = jsonObject.optString("submitModule"); return newModules; } diff --git a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.java b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.java index a8a6a77..25c4d25 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.java +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.java @@ -6,15 +6,14 @@ import android.net.Uri; import android.os.Build; import android.util.Log; import android.util.TypedValue; +import android.view.View; import android.webkit.JavascriptInterface; import android.widget.Button; import android.widget.Toast; import androidx.annotation.Keep; -import androidx.annotation.RequiresApi; import androidx.appcompat.app.AlertDialog; import androidx.core.content.ContextCompat; -import androidx.core.graphics.ColorUtils; import com.fox2code.foxcompat.FoxDisplay; import com.fox2code.mmm.BuildConfig; @@ -42,6 +41,7 @@ public class AndroidacyWebAPI { private static final int MAX_COMPAT_MODE = 1; private final AndroidacyActivity activity; private final boolean allowInstall; + private boolean allowHideNote = true; boolean consumedAction; boolean downloadMode; int effectiveCompatMode; @@ -322,6 +322,11 @@ public class AndroidacyWebAPI { this.activity.runOnUiThread(() -> { this.activity.hideActionBar(); this.consumedAction = false; + if (this.allowHideNote) { + this.allowHideNote = false; + this.activity.webViewNote + .setVisibility(View.GONE); + } }); } diff --git a/app/src/main/java/com/fox2code/mmm/module/ActionButtonType.java b/app/src/main/java/com/fox2code/mmm/module/ActionButtonType.java index 00b4cf5..0157144 100644 --- a/app/src/main/java/com/fox2code/mmm/module/ActionButtonType.java +++ b/app/src/main/java/com/fox2code/mmm/module/ActionButtonType.java @@ -218,13 +218,8 @@ public enum ActionButtonType { @Override public void update(Chip button, ModuleHolder moduleHolder) { ModuleInfo moduleInfo = moduleHolder.getMainModuleInfo(); - int icon = R.drawable.ic_baseline_monetization_on_24; - if (moduleInfo.donate.startsWith("https://www.paypal.me/")) { - icon = R.drawable.ic_baseline_paypal_24; - } else if (moduleInfo.donate.startsWith("https://www.patreon.com/")) { - icon = R.drawable.ic_patreon; - } - button.setChipIcon(button.getContext().getDrawable(icon)); + button.setChipIcon(button.getContext().getDrawable( + donateIconForUrl(moduleInfo.donate))); button.setText(R.string.donate); } @@ -252,6 +247,18 @@ public enum ActionButtonType { return icon; } + @DrawableRes + public static int donateIconForUrl(String url) { + int icon = R.drawable.ic_baseline_monetization_on_24; + 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/")) { + icon = R.drawable.ic_patreon; + } + return icon; + } + @DrawableRes private final int iconId; diff --git a/app/src/main/java/com/fox2code/mmm/repo/LimitedRepoData.java b/app/src/main/java/com/fox2code/mmm/repo/LimitedRepoData.java new file mode 100644 index 0000000..1ca5449 --- /dev/null +++ b/app/src/main/java/com/fox2code/mmm/repo/LimitedRepoData.java @@ -0,0 +1,21 @@ +package com.fox2code.mmm.repo; + +import android.content.SharedPreferences; + +import java.io.File; + +public class LimitedRepoData extends RepoData { + LimitedRepoData(String url, File cacheRoot, SharedPreferences cachedPreferences) { + super(url, cacheRoot, cachedPreferences); + } + + @Override + public final boolean isEnabledByDefault() { + return false; + } + + @Override + public final boolean isLimited() { + return true; + } +} diff --git a/app/src/main/java/com/fox2code/mmm/repo/RepoData.java b/app/src/main/java/com/fox2code/mmm/repo/RepoData.java index f55a472..e40985b 100644 --- a/app/src/main/java/com/fox2code/mmm/repo/RepoData.java +++ b/app/src/main/java/com/fox2code/mmm/repo/RepoData.java @@ -1,6 +1,9 @@ package com.fox2code.mmm.repo; import android.content.SharedPreferences; +import android.net.Uri; + +import androidx.annotation.NonNull; import com.fox2code.mmm.BuildConfig; import com.fox2code.mmm.MainApplication; @@ -32,7 +35,9 @@ public class RepoData extends XRepo { public final File metaDataCache; public final HashMap moduleHashMap; public long lastUpdate; - public String name; + protected String defaultName, defaultWebsite, + defaultSupport, defaultDonate, defaultSubmitModule; + public String name, website, support, donate, submitModule; private boolean enabled; // Cache for speed protected RepoData(String url, File cacheRoot, SharedPreferences cachedPreferences) { @@ -45,6 +50,8 @@ public class RepoData extends XRepo { this.name = this.url; // Set url as default name this.enabled = MainApplication.getSharedPreferences() .getBoolean("pref_" + this.id + "_enabled", this.isEnabledByDefault()); + this.defaultName = url; + this.defaultWebsite = "https://" + Uri.parse(url).getHost() + "/"; if (!this.cacheRoot.isDirectory()) { this.cacheRoot.mkdirs(); } else { @@ -114,7 +121,7 @@ public class RepoData extends XRepo { repoModule.propUrl = modulePropsUrl; repoModule.zipUrl = moduleZipUrl; repoModule.checksum = moduleChecksum; - if (!moduleStars.isEmpty()) { + if (!moduleStars.isEmpty() && !this.isLimited()) { try { repoModule.qualityValue = Integer.parseInt(moduleStars); repoModule.qualityText = R.string.module_stars; @@ -133,13 +140,17 @@ public class RepoData extends XRepo { // Update final metadata this.name = name; this.lastUpdate = lastUpdate; + this.website = jsonObject.optString("website"); + this.support = jsonObject.optString("support"); + this.donate = jsonObject.optString("donate"); + this.submitModule = jsonObject.optString("submitModule"); } return newModules; } @Override public boolean isEnabledByDefault() { - return !BuildConfig.DISABLED_REPOS.contains(this.id); + return BuildConfig.ENABLED_REPOS.contains(this.id); } public void storeMetadata(RepoModule repoModule,byte[] data) throws IOException { @@ -166,12 +177,6 @@ public class RepoData extends XRepo { return false; } - public String getNameOrFallback(String fallback) { - return this.name == null || - this.name.equals(this.url) ? - fallback : this.name; - } - @Override public boolean isEnabled() { return this.enabled; @@ -181,15 +186,65 @@ public class RepoData extends XRepo { public void setEnabled(boolean enabled) { this.enabled = enabled; MainApplication.getSharedPreferences().edit() - .putBoolean("pref_" + this.id + "_enabled", enabled).apply(); + .putBoolean("pref_" + this.getPreferenceId() + "_enabled", enabled).apply(); } public void updateEnabledState() { this.enabled = MainApplication.getSharedPreferences() - .getBoolean("pref_" + this.id + "_enabled", this.isEnabledByDefault()); + .getBoolean("pref_" + this.getPreferenceId() + "_enabled", this.isEnabledByDefault()); } public String getUrl() { return this.url; } + + public boolean isLimited() { + return false; + } + + public String getPreferenceId() { + return this.id; + } + + // Repo data info getters + @NonNull + public String getName() { + if (this.name != null && + !this.name.isEmpty()) + return this.name; + if (this.defaultName != null) + return this.defaultName; + return this.url; + } + + @NonNull + public String getWebsite() { + if (this.website != null && + !this.website.isEmpty()) + return this.website; + if (this.defaultWebsite != null) + return this.defaultWebsite; + return this.url; + } + + public String getSupport() { + if (this.support != null && + !this.support.isEmpty()) + return this.support; + return this.defaultSupport; + } + + public String getDonate() { + if (this.donate != null && + !this.donate.isEmpty()) + return this.donate; + return this.defaultDonate; + } + + public String getSubmitModule() { + if (this.submitModule != null && + !this.submitModule.isEmpty()) + return this.submitModule; + return this.defaultSubmitModule; + } } diff --git a/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java b/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java index 663a88b..03204d1 100644 --- a/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java +++ b/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java @@ -68,7 +68,14 @@ public final class RepoManager { this.repoData = new LinkedHashMap<>(); this.modules = new HashMap<>(); // We do not have repo list config yet. - this.addRepoData(MAGISK_ALT_REPO); + RepoData altRepo = this.addRepoData(MAGISK_ALT_REPO); + altRepo.defaultName = "Magisk Modules Alt Repo"; + altRepo.defaultWebsite = RepoManager.MAGISK_ALT_REPO_HOMEPAGE; + altRepo.defaultSubmitModule = + "https://github.com/Magisk-Modules-Alt-Repo/submission/issues"; + /*RepoData dgRepo = this.addRepoData(DG_MAGISK_REPO); + dgRepo.defaultName = "DerGoogler Magisk Repo"; + dgRepo.defaultWebsite = "https://repo.dergoogler.com/";*/ this.androidacyRepoData = this.addAndroidacyRepoData(); // Populate default cache @@ -258,11 +265,15 @@ public final class RepoManager { } private RepoData addRepoData(String url) { + if (MAGISK_ALT_REPO_JSDELIVR.equals(url)) + url = MAGISK_ALT_REPO; String id = internalIdOfUrl(url); File cacheRoot = new File(this.mainApplication.getCacheDir(), id); SharedPreferences sharedPreferences = this.mainApplication .getSharedPreferences("mmm_" + id, Context.MODE_PRIVATE); - RepoData repoData = new RepoData(url, cacheRoot, sharedPreferences); + RepoData repoData = id.startsWith("repo_") ? + new LimitedRepoData(url, cacheRoot, sharedPreferences) : + new RepoData(url, cacheRoot, sharedPreferences); this.repoData.put(url, repoData); if (this.initialized) { this.populateDefaultCache(repoData); diff --git a/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java b/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java index 859f892..d38bed5 100644 --- a/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java +++ b/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java @@ -8,6 +8,7 @@ import android.content.Intent; import android.content.res.Configuration; import android.os.Build; import android.os.Bundle; +import android.util.Log; import android.widget.Toast; import androidx.annotation.StringRes; @@ -26,6 +27,7 @@ import com.fox2code.mmm.MainActivity; import com.fox2code.mmm.MainApplication; import com.fox2code.mmm.R; import com.fox2code.mmm.installer.InstallerInitializer; +import com.fox2code.mmm.module.ActionButtonType; import com.fox2code.mmm.repo.RepoData; import com.fox2code.mmm.repo.RepoManager; import com.fox2code.mmm.utils.Http; @@ -239,29 +241,17 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { getPreferenceManager().setSharedPreferencesName("mmm"); setPreferencesFromResource(R.xml.repo_preferences, rootKey); - setRepoData(RepoManager.MAGISK_ALT_REPO, - "Magisk Modules Alt Repo", RepoManager.MAGISK_ALT_REPO_HOMEPAGE, - null, null, - "https://github.com/Magisk-Modules-Alt-Repo/submission/issues"); + setRepoData(RepoManager.MAGISK_ALT_REPO); // Androidacy backend not yet implemented! - setRepoData(RepoManager.ANDROIDACY_MAGISK_REPO_ENDPOINT, - "Androidacy Modules Repo", - RepoManager.ANDROIDACY_MAGISK_REPO_HOMEPAGE, - "https://t.me/androidacy_discussions", - "https://patreon.com/androidacy", - "https://www.androidacy.com/module-repository-applications/"); + setRepoData(RepoManager.ANDROIDACY_MAGISK_REPO_ENDPOINT); } - private void setRepoData(String url, - String fallbackTitle, String homepage, - String supportUrl, String donateUrl, - String submissionUrl) { + private void setRepoData(String url) { String preferenceName = "pref_" + RepoManager.internalIdOfUrl(url); Preference preference = findPreference(preferenceName); if (preference == null) return; final RepoData repoData = RepoManager.getINSTANCE().get(url); - preference.setTitle(repoData == null ? fallbackTitle : - repoData.getNameOrFallback(fallbackTitle)); + preference.setTitle(repoData == null ? url : repoData.getName()); preference = findPreference(preferenceName + "_enabled"); if (preference != null) { if (repoData == null) { @@ -279,42 +269,68 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { } } preference = findPreference(preferenceName + "_website"); - if (preference != null && homepage != null) { - preference.setOnPreferenceClickListener(p -> { - if (homepage.startsWith("https://www.androidacy.com/")) { - IntentHelper.openUrlAndroidacy( - getFoxActivity(this), homepage, true); - } else { - IntentHelper.openUrl(getFoxActivity(this), homepage); - } - return true; - }); + String homepage = repoData == null ? null : repoData.getWebsite(); + if (preference != null) { + if (homepage != null && !homepage.isEmpty()) { + preference.setVisible(true); + preference.setOnPreferenceClickListener(p -> { + if (homepage.startsWith("https://www.androidacy.com/")) { + IntentHelper.openUrlAndroidacy( + getFoxActivity(this), homepage, true); + } else { + IntentHelper.openUrl(getFoxActivity(this), homepage); + } + return true; + }); + } else { + preference.setVisible(false); + } } preference = findPreference(preferenceName + "_support"); - if (preference != null && supportUrl != null) { - preference.setOnPreferenceClickListener(p -> { - IntentHelper.openUrl(getFoxActivity(this), supportUrl); - return true; - }); + String supportUrl = repoData == null ? null : repoData.getSupport(); + if (preference != null) { + if (supportUrl != null && !supportUrl.isEmpty()) { + preference.setVisible(true); + preference.setIcon(ActionButtonType.supportIconForUrl(supportUrl)); + preference.setOnPreferenceClickListener(p -> { + IntentHelper.openUrl(getFoxActivity(this), supportUrl); + return true; + }); + } else { + preference.setVisible(false); + } } preference = findPreference(preferenceName + "_donate"); - if (preference != null && donateUrl != null) { - preference.setOnPreferenceClickListener(p -> { - IntentHelper.openUrl(getFoxActivity(this), donateUrl); - return true; - }); + String donateUrl = repoData == null ? null : repoData.getDonate(); + if (preference != null) { + if (donateUrl != null) { + preference.setVisible(true); + preference.setIcon(ActionButtonType.donateIconForUrl(donateUrl)); + preference.setOnPreferenceClickListener(p -> { + IntentHelper.openUrl(getFoxActivity(this), donateUrl); + return true; + }); + } else { + preference.setVisible(false); + } } preference = findPreference(preferenceName + "_submit"); - if (preference != null && submissionUrl != null) { - preference.setOnPreferenceClickListener(p -> { - if (submissionUrl.startsWith("https://www.androidacy.com/")) { - IntentHelper.openUrlAndroidacy( - getFoxActivity(this), submissionUrl, true); - } else { - IntentHelper.openUrl(getFoxActivity(this), submissionUrl); - } - return true; - }); + String submissionUrl = repoData == null ? null : repoData.getSubmitModule(); + if (preference != null) { + if (submissionUrl != null && !submissionUrl.isEmpty()) { + preference.setVisible(true); + preference.setOnPreferenceClickListener(p -> { + if (submissionUrl.startsWith("https://www.androidacy.com/")) { + IntentHelper.openUrlAndroidacy( + getFoxActivity(this), submissionUrl, true); + } else { + IntentHelper.openUrl(getFoxActivity(this), submissionUrl); + } + return true; + }); + } else { + preference.setVisible(false); + } } } } diff --git a/app/src/main/res/layout/webview.xml b/app/src/main/res/layout/webview.xml index a5b88e7..df26e5f 100644 --- a/app/src/main/res/layout/webview.xml +++ b/app/src/main/res/layout/webview.xml @@ -2,6 +2,7 @@ @@ -9,4 +10,15 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/webView" /> + + \ No newline at end of file diff --git a/app/src/main/res/xml/repo_preferences.xml b/app/src/main/res/xml/repo_preferences.xml index 5fb37de..d55e48a 100644 --- a/app/src/main/res/xml/repo_preferences.xml +++ b/app/src/main/res/xml/repo_preferences.xml @@ -14,6 +14,16 @@ app:icon="@drawable/ic_baseline_language_24" app:title="@string/website" app:singleLineTitle="false" /> + +