From 303def13444a95e808d53b3de1f99c10fb43984a Mon Sep 17 00:00:00 2001 From: LoveSy Date: Wed, 26 Jan 2022 11:50:47 +0800 Subject: [PATCH] Proper support RTL (#1562) --- app/build.gradle.kts | 2 +- app/src/main/assets/webview/template.html | 2 +- .../SubtitleCollapsingToolbarLayout.java | 17 +++++++++++ .../SubtitleCollapsingTextHelper.java | 24 ++++++++++++++-- .../main/java/org/lsposed/manager/App.java | 2 +- .../manager/adapters/ScopeAdapter.java | 4 +-- .../ui/activity/CrashReportActivity.java | 4 ++- .../manager/ui/fragment/AppListFragment.java | 6 ++-- .../manager/ui/fragment/HomeFragment.java | 15 +++++----- .../manager/ui/fragment/LogsFragment.java | 7 +++-- .../manager/ui/fragment/ModulesFragment.java | 4 ++- .../manager/ui/fragment/RepoFragment.java | 1 + .../manager/ui/fragment/RepoItemFragment.java | 18 ++++++++---- .../manager/ui/fragment/SettingsFragment.java | 6 ++-- .../org/lsposed/manager/util/UpdateUtil.java | 4 +-- .../main/res/layout-sw600dp/activity_main.xml | 6 ++-- app/src/main/res/layout/activity_main.xml | 8 +++--- app/src/main/res/layout/fragment_settings.xml | 1 + app/src/main/res/layout/item_repo_release.xml | 28 ++++++++++--------- app/src/main/res/values/styles.xml | 4 +++ app/src/main/res/values/themes.xml | 2 ++ app/src/main/res/xml/prefs.xml | 1 + .../lsposed/lspd/service/ServiceManager.java | 2 +- 23 files changed, 114 insertions(+), 54 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index fe022628..c352758d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -174,7 +174,7 @@ afterEvaluate { doLast { val langList = File(projectDir, "src/main/res").listFiles { dir -> dir.name.startsWith("values-") && File(dir, "strings.xml").exists() - }.orEmpty().map { + }.orEmpty().sorted().map { it.name.substring(7).split("-", limit = 2) }.map { if (it.size == 1) Locale(it[0]) diff --git a/app/src/main/assets/webview/template.html b/app/src/main/assets/webview/template.html index ea35654c..c6c9e00f 100644 --- a/app/src/main/assets/webview/template.html +++ b/app/src/main/assets/webview/template.html @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/java/com/google/android/material/appbar/SubtitleCollapsingToolbarLayout.java b/app/src/main/java/com/google/android/material/appbar/SubtitleCollapsingToolbarLayout.java index d26f4e7b..f2207f1a 100644 --- a/app/src/main/java/com/google/android/material/appbar/SubtitleCollapsingToolbarLayout.java +++ b/app/src/main/java/com/google/android/material/appbar/SubtitleCollapsingToolbarLayout.java @@ -95,6 +95,7 @@ public class SubtitleCollapsingToolbarLayout extends FrameLayout { collapsingTextHelper = new SubtitleCollapsingTextHelper(this); collapsingTextHelper.setTextSizeInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR); + collapsingTextHelper.setRtlTextDirectionHeuristicsEnabled(false); TypedArray a = ThemeEnforcement.obtainStyledAttributes( context, @@ -1073,6 +1074,22 @@ public class SubtitleCollapsingToolbarLayout extends FrameLayout { requestLayout(); } + /** + * Sets whether {@code TextDirectionHeuristics} should be used to determine whether the title text + * is RTL. Experimental Feature. + */ + public void setRtlTextDirectionHeuristicsEnabled(boolean rtlTextDirectionHeuristicsEnabled) { + collapsingTextHelper.setRtlTextDirectionHeuristicsEnabled(rtlTextDirectionHeuristicsEnabled); + } + + /** + * Gets whether {@code TextDirectionHeuristics} should be used to determine whether the title text + * is RTL. Experimental Feature. + */ + public boolean isRtlTextDirectionHeuristicsEnabled() { + return collapsingTextHelper.isRtlTextDirectionHeuristicsEnabled(); + } + /** * Set the amount of visible height in pixels used to define when to trigger a scrim visibility * change. diff --git a/app/src/main/java/com/google/android/material/internal/SubtitleCollapsingTextHelper.java b/app/src/main/java/com/google/android/material/internal/SubtitleCollapsingTextHelper.java index d9a156e7..95848058 100644 --- a/app/src/main/java/com/google/android/material/internal/SubtitleCollapsingTextHelper.java +++ b/app/src/main/java/com/google/android/material/internal/SubtitleCollapsingTextHelper.java @@ -85,6 +85,7 @@ public final class SubtitleCollapsingTextHelper { @Nullable private CharSequence titleToDraw, subtitleToDraw; private boolean isRtl; + private boolean isRtlTextDirectionHeuristicsEnabled = true; private boolean useTexture; @Nullable @@ -615,6 +616,14 @@ public final class SubtitleCollapsingTextHelper { return expandedSubtitleTextSize; } + public void setRtlTextDirectionHeuristicsEnabled(boolean rtlTextDirectionHeuristicsEnabled) { + isRtlTextDirectionHeuristicsEnabled = rtlTextDirectionHeuristicsEnabled; + } + + public boolean isRtlTextDirectionHeuristicsEnabled() { + return isRtlTextDirectionHeuristicsEnabled; + } + private void calculateCurrentOffsets() { calculateOffsets(expandedFraction); } @@ -889,10 +898,21 @@ public final class SubtitleCollapsingTextHelper { } private boolean calculateIsRtl(@NonNull CharSequence text) { - final boolean defaultIsRtl = ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_RTL; + final boolean defaultIsRtl = isDefaultIsRtl(); + return isRtlTextDirectionHeuristicsEnabled + ? isTextDirectionHeuristicsIsRtl(text, defaultIsRtl) + : defaultIsRtl; + } + + private boolean isDefaultIsRtl() { + return ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_RTL; + } + + private boolean isTextDirectionHeuristicsIsRtl(@NonNull CharSequence text, boolean defaultIsRtl) { return (defaultIsRtl ? TextDirectionHeuristicsCompat.FIRSTSTRONG_RTL - : TextDirectionHeuristicsCompat.FIRSTSTRONG_LTR).isRtl(text, 0, text.length()); + : TextDirectionHeuristicsCompat.FIRSTSTRONG_LTR) + .isRtl(text, 0, text.length()); } private void setInterpolatedTitleTextSize(float textSize) { diff --git a/app/src/main/java/org/lsposed/manager/App.java b/app/src/main/java/org/lsposed/manager/App.java index 85f5cccc..1a41b376 100644 --- a/app/src/main/java/org/lsposed/manager/App.java +++ b/app/src/main/java/org/lsposed/manager/App.java @@ -78,7 +78,7 @@ public class App extends Application { return result.toString(StandardCharsets.UTF_8.name()); } catch (IOException e) { Log.e(App.TAG, "read webview HTML", e); - return "@body@"; + return "@body@"; } } diff --git a/app/src/main/java/org/lsposed/manager/adapters/ScopeAdapter.java b/app/src/main/java/org/lsposed/manager/adapters/ScopeAdapter.java index c0239b90..a515dcf9 100644 --- a/app/src/main/java/org/lsposed/manager/adapters/ScopeAdapter.java +++ b/app/src/main/java/org/lsposed/manager/adapters/ScopeAdapter.java @@ -78,12 +78,12 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.HashSet; import java.util.List; -import java.util.Locale; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import rikka.core.util.ResourceUtils; +import rikka.material.app.LocaleDelegate; import rikka.widget.switchbar.SwitchBar; @SuppressLint("NotifyDataSetChanged") @@ -253,7 +253,7 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapterGitHub", "Telegram"), HtmlCompat.FROM_HTML_MODE_LEGACY)); - binding.designAboutVersion.setText(String.format(Locale.ROOT, "%s (%s)", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)); + binding.designAboutVersion.setText(String.format(LocaleDelegate.getDefaultLocale(), "%s (%d)", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)); return new BlurBehindDialogBuilder(requireContext()) .setView(binding.getRoot()).create(); } diff --git a/app/src/main/java/org/lsposed/manager/ui/fragment/LogsFragment.java b/app/src/main/java/org/lsposed/manager/ui/fragment/LogsFragment.java index 7d98b3a6..fbbbb611 100644 --- a/app/src/main/java/org/lsposed/manager/ui/fragment/LogsFragment.java +++ b/app/src/main/java/org/lsposed/manager/ui/fragment/LogsFragment.java @@ -60,7 +60,6 @@ import java.time.LocalDateTime; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Locale; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.zip.Deflater; @@ -68,6 +67,7 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import rikka.core.os.FileUtils; +import rikka.material.app.LocaleDelegate; import rikka.recyclerview.RecyclerViewKt; public class LogsFragment extends BaseFragment { @@ -166,7 +166,7 @@ public class LogsFragment extends BaseFragment { private void save() { LocalDateTime now = LocalDateTime.now(); - String filename = String.format(Locale.ROOT, "LSPosed_%s.zip", now.toString()); + String filename = String.format(LocaleDelegate.getDefaultLocale(), "LSPosed_%s.zip", now.toString()); saveLogsLauncher.launch(filename); } @@ -275,6 +275,8 @@ public class LogsFragment extends BaseFragment { binding.recyclerView.setAdapter(adaptor); layoutManager = new LinearLayoutManager(requireActivity()); binding.recyclerView.setLayoutManager(layoutManager); + // ltr even for rtl languages because of log format + binding.recyclerView.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); binding.swipeRefreshLayout.setProgressViewEndTarget(true, binding.swipeRefreshLayout.getProgressViewEndOffset()); RecyclerViewKt.fixEdgeEffect(binding.recyclerView, false, true); binding.swipeRefreshLayout.setOnRefreshListener(adaptor::fullRefresh); @@ -378,6 +380,7 @@ public class LogsFragment extends BaseFragment { HorizontalScrollView horizontalScrollView = new HorizontalScrollView(getContext()); horizontalScrollView.setFillViewport(true); horizontalScrollView.setHorizontalScrollBarEnabled(false); + horizontalScrollView.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); binding.swipeRefreshLayout.addView(horizontalScrollView); horizontalScrollView.addView(binding.recyclerView); binding.recyclerView.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT; diff --git a/app/src/main/java/org/lsposed/manager/ui/fragment/ModulesFragment.java b/app/src/main/java/org/lsposed/manager/ui/fragment/ModulesFragment.java index 0d0c03d5..e9a13048 100644 --- a/app/src/main/java/org/lsposed/manager/ui/fragment/ModulesFragment.java +++ b/app/src/main/java/org/lsposed/manager/ui/fragment/ModulesFragment.java @@ -90,6 +90,7 @@ import java.util.function.Consumer; import java.util.stream.IntStream; import rikka.core.util.ResourceUtils; +import rikka.material.app.LocaleDelegate; import rikka.recyclerview.RecyclerViewKt; public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleListener, RepoLoader.RepoListener { @@ -203,6 +204,7 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi public void onViewDetachedFromWindow(View v) { } }); + searchView.findViewById(androidx.appcompat.R.id.search_edit_frame).setLayoutDirection(View.LAYOUT_DIRECTION_INHERIT); } @Override @@ -515,7 +517,7 @@ public class ModulesFragment extends BaseFragment implements ModuleUtil.ModuleLi ModuleUtil.InstalledModule item = showList.get(position); String appName; if (item.userId != 0) { - appName = String.format("%s (%s)", item.getAppName(), item.userId); + appName = String.format(LocaleDelegate.getDefaultLocale(), "%s (%d)", item.getAppName(), item.userId); } else { appName = item.getAppName(); } diff --git a/app/src/main/java/org/lsposed/manager/ui/fragment/RepoFragment.java b/app/src/main/java/org/lsposed/manager/ui/fragment/RepoFragment.java index bef09ff3..8f71cb74 100644 --- a/app/src/main/java/org/lsposed/manager/ui/fragment/RepoFragment.java +++ b/app/src/main/java/org/lsposed/manager/ui/fragment/RepoFragment.java @@ -183,6 +183,7 @@ public class RepoFragment extends BaseFragment implements RepoLoader.RepoListene binding.recyclerView.setNestedScrollingEnabled(true); } }); + searchView.findViewById(androidx.appcompat.R.id.search_edit_frame).setLayoutDirection(View.LAYOUT_DIRECTION_INHERIT); int sort = App.getPreferences().getInt("repo_sort", 0); if (sort == 0) { menu.findItem(R.id.item_sort_by_name).setChecked(true); diff --git a/app/src/main/java/org/lsposed/manager/ui/fragment/RepoItemFragment.java b/app/src/main/java/org/lsposed/manager/ui/fragment/RepoItemFragment.java index 5137d399..0d9e9abc 100644 --- a/app/src/main/java/org/lsposed/manager/ui/fragment/RepoItemFragment.java +++ b/app/src/main/java/org/lsposed/manager/ui/fragment/RepoItemFragment.java @@ -79,7 +79,6 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.ListIterator; -import java.util.Locale; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -87,6 +86,7 @@ import okhttp3.Headers; import okhttp3.Request; import okhttp3.Response; import rikka.core.util.ResourceUtils; +import rikka.material.app.LocaleDelegate; import rikka.recyclerview.RecyclerViewKt; import rikka.widget.borderview.BorderView; @@ -152,10 +152,16 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.RepoLis setting.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); setting.setTextZoom(80); String body; - if (ResourceUtils.isNightMode(getResources().getConfiguration())) { - body = App.HTML_TEMPLATE_DARK.get().replace("@body@", text); + String direction; + if (getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { + direction = "rtl"; } else { - body = App.HTML_TEMPLATE.get().replace("@body@", text); + direction = "ltr"; + } + if (ResourceUtils.isNightMode(getResources().getConfiguration())) { + body = App.HTML_TEMPLATE_DARK.get().replace("@dir@", direction).replace("@body@", text); + } else { + body = App.HTML_TEMPLATE.get().replace("@dir@", direction).replace("@body@", text); } view.setWebViewClient(new WebViewClient() { @Override @@ -353,12 +359,12 @@ public class RepoItemFragment extends BaseFragment implements RepoLoader.RepoLis if (channel.equals(channels[0])) { this.items = releases.parallelStream().filter(t -> { if (t.getIsPrerelease()) return false; - var name = t.getName().toLowerCase(Locale.ROOT); + var name = t.getName().toLowerCase(LocaleDelegate.getDefaultLocale()); return !name.startsWith("snapshot") && !name.startsWith("nightly"); }).collect(Collectors.toList()); } else if (channel.equals(channels[1])) { this.items = releases.parallelStream().filter(t -> { - var name = t.getName().toLowerCase(Locale.ROOT); + var name = t.getName().toLowerCase(LocaleDelegate.getDefaultLocale()); return !name.startsWith("snapshot") && !name.startsWith("nightly"); }).collect(Collectors.toList()); } else this.items = releases; diff --git a/app/src/main/java/org/lsposed/manager/ui/fragment/SettingsFragment.java b/app/src/main/java/org/lsposed/manager/ui/fragment/SettingsFragment.java index e4ca7705..6461bf39 100644 --- a/app/src/main/java/org/lsposed/manager/ui/fragment/SettingsFragment.java +++ b/app/src/main/java/org/lsposed/manager/ui/fragment/SettingsFragment.java @@ -77,10 +77,10 @@ public class SettingsFragment extends BaseFragment { .add(R.id.setting_container, new PreferenceFragment()).commitNow(); } if (ConfigManager.isBinderAlive()) { - binding.toolbar.setSubtitle(String.format(Locale.ROOT, "%s (%d) - %s", + binding.toolbar.setSubtitle(String.format(LocaleDelegate.getDefaultLocale(), "%s (%d) - %s", ConfigManager.getXposedVersionName(), ConfigManager.getXposedVersionCode(), ConfigManager.getApi())); } else { - binding.toolbar.setSubtitle(String.format(Locale.ROOT, "%s (%d) - %s", + binding.toolbar.setSubtitle(String.format(LocaleDelegate.getDefaultLocale(), "%s (%d) - %s", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE, getString(R.string.not_installed))); } return binding.getRoot(); @@ -163,7 +163,7 @@ public class SettingsFragment extends BaseFragment { backup.setEnabled(installed); backup.setOnPreferenceClickListener(preference -> { LocalDateTime now = LocalDateTime.now(); - backupLauncher.launch(String.format(Locale.ROOT, + backupLauncher.launch(String.format(LocaleDelegate.getDefaultLocale(), "LSPosed_%s.lsp", now.toString())); return true; }); diff --git a/app/src/main/java/org/lsposed/manager/util/UpdateUtil.java b/app/src/main/java/org/lsposed/manager/util/UpdateUtil.java index 1240039e..15f15e57 100644 --- a/app/src/main/java/org/lsposed/manager/util/UpdateUtil.java +++ b/app/src/main/java/org/lsposed/manager/util/UpdateUtil.java @@ -16,13 +16,13 @@ import java.io.File; import java.io.IOException; import java.time.Instant; import java.time.ZoneOffset; -import java.util.Locale; import okhttp3.Call; import okhttp3.Callback; import okhttp3.Request; import okhttp3.Response; import okio.Okio; +import rikka.material.app.LocaleDelegate; public class UpdateUtil { public static void loadRemoteVersion() { @@ -42,7 +42,7 @@ public class UpdateUtil { var notes = info.get("body").getAsString(); var assetsArray = info.getAsJsonArray("assets"); for (var assets : assetsArray) { - checkAssets(assets.getAsJsonObject(), notes, api.toLowerCase(Locale.ROOT)); + checkAssets(assets.getAsJsonObject(), notes, api.toLowerCase(LocaleDelegate.getDefaultLocale())); } } catch (Throwable t) { Log.e(App.TAG, t.getMessage(), t); diff --git a/app/src/main/res/layout-sw600dp/activity_main.xml b/app/src/main/res/layout-sw600dp/activity_main.xml index ca466e8b..4a622f17 100644 --- a/app/src/main/res/layout-sw600dp/activity_main.xml +++ b/app/src/main/res/layout-sw600dp/activity_main.xml @@ -35,8 +35,8 @@ android:background="?android:colorBackground" app:defaultNavHost="true" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintLeft_toRightOf="@id/nav" - app:layout_constraintRight_toRightOf="parent" + app:layout_constraintStart_toEndOf="@id/nav" + app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" app:navGraph="@navigation/main_nav" /> @@ -45,7 +45,7 @@ android:layout_width="wrap_content" android:layout_height="match_parent" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:menu="@menu/navigation_menu" app:menuGravity="center" /> diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 4c3942ed..7e956da9 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -36,8 +36,8 @@ android:background="?android:colorBackground" app:defaultNavHost="true" app:layout_constraintBottom_toTopOf="@id/nav" - app:layout_constraintLeft_toLeftOf="parent" - app:layout_constraintRight_toRightOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" app:navGraph="@navigation/main_nav" /> @@ -47,8 +47,8 @@ android:layout_height="wrap_content" app:fitsSystemWindowsInsets="bottom" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintLeft_toLeftOf="parent" - app:layout_constraintRight_toRightOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" app:menu="@menu/navigation_menu" /> diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index 8afd2f40..cab64c86 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -52,6 +52,7 @@ android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:elevation="0dp" + android:theme="@style/ThemeOverlay.MaterialComponents.ActionBar" app:layout_collapseMode="pin" /> diff --git a/app/src/main/res/layout/item_repo_release.xml b/app/src/main/res/layout/item_repo_release.xml index ecf41923..b1cff881 100644 --- a/app/src/main/res/layout/item_repo_release.xml +++ b/app/src/main/res/layout/item_repo_release.xml @@ -25,19 +25,19 @@ android:layout_height="wrap_content" android:background="?selectableItemBackground" android:clickable="true" + android:clipChildren="false" + android:clipToPadding="false" android:focusable="true" android:minHeight="?attr/listPreferredItemHeight" android:paddingVertical="16dp" android:paddingStart="16dp" - android:paddingEnd="24dp" - android:clipToPadding="false" - android:clipChildren="false"> + android:paddingEnd="24dp"> + app:layout_constraintTop_toBottomOf="@id/description" /> + app:layout_constraintTop_toBottomOf="@id/description" /> diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 541743ad..b4a15b8f 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -31,4 +31,8 @@ ?android:attr/textColorSecondary + + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 5e972267..ab02f42b 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -32,6 +32,7 @@ ?colorPrimary ?colorPrimary + viewStart