diff --git a/app/src/main/java/com/fox2code/mmm/MainActivity.java b/app/src/main/java/com/fox2code/mmm/MainActivity.java index 07b4430..f3cb5eb 100644 --- a/app/src/main/java/com/fox2code/mmm/MainActivity.java +++ b/app/src/main/java/com/fox2code/mmm/MainActivity.java @@ -3,17 +3,24 @@ package com.fox2code.mmm; import androidx.annotation.NonNull; import androidx.appcompat.widget.SearchView; import androidx.cardview.widget.CardView; +import androidx.core.view.WindowInsetsCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; +import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.PixelFormat; +import android.os.Build; import android.os.Bundle; import android.util.Log; import android.util.TypedValue; import android.view.View; +import android.view.ViewGroup; +import android.view.Window; import android.view.WindowManager; import android.view.inputmethod.EditorInfo; +import android.widget.TextView; import com.fox2code.mmm.compat.CompatActivity; import com.fox2code.mmm.installer.InstallerInitializer; @@ -25,6 +32,9 @@ import com.fox2code.mmm.utils.Http; import com.fox2code.mmm.utils.IntentHelper; import com.google.android.material.progressindicator.LinearProgressIndicator; +import eightbitlab.com.blurview.BlurView; +import eightbitlab.com.blurview.RenderScriptBlur; + public class MainActivity extends CompatActivity implements SwipeRefreshLayout.OnRefreshListener, SearchView.OnQueryTextListener, SearchView.OnCloseListener { private static final String TAG = "MainActivity"; @@ -33,7 +43,11 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O public LinearProgressIndicator progressIndicator; private ModuleViewAdapter moduleViewAdapter; private SwipeRefreshLayout swipeRefreshLayout; + private int swipeRefreshLayoutOrigStartOffset; + private int swipeRefreshLayoutOrigEndOffset; private long swipeRefreshBlocker = 0; + private TextView actionBarPadding; + private BlurView actionBarBlur; private RecyclerView moduleList; private CardView searchCard; private SearchView searchView; @@ -55,10 +69,24 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O setContentView(R.layout.activity_main); this.setTitle(R.string.app_name); this.getWindow().setFlags( - WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, - WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); + WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION | + WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, + WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION | + WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + WindowManager.LayoutParams layoutParams = this.getWindow().getAttributes(); + layoutParams.layoutInDisplayCutoutMode = // Support cutout in Android 9 + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + this.getWindow().setAttributes(layoutParams); + } + this.actionBarPadding = findViewById(R.id.action_bar_padding); + this.actionBarBlur = findViewById(R.id.action_bar_blur); this.progressIndicator = findViewById(R.id.progress_bar); this.swipeRefreshLayout = findViewById(R.id.swipe_refresh); + this.swipeRefreshLayoutOrigStartOffset = + this.swipeRefreshLayout.getProgressViewStartOffset(); + this.swipeRefreshLayoutOrigEndOffset = + this.swipeRefreshLayout.getProgressViewEndOffset(); this.swipeRefreshBlocker = Long.MAX_VALUE; this.moduleList = findViewById(R.id.module_list); this.searchCard = findViewById(R.id.search_card); @@ -68,6 +96,11 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O this.moduleList.setLayoutManager(new LinearLayoutManager(this)); this.moduleList.setItemViewCacheSize(4); // Default is 2 this.swipeRefreshLayout.setOnRefreshListener(this); + this.actionBarBlur.setupWith(this.moduleList).setFrameClearDrawable( + this.getWindow().getDecorView().getBackground()) + .setBlurAlgorithm(new RenderScriptBlur(this)) + .setBlurRadius(5F).setBlurAutoUpdate(true) + .setHasFixedTransformationMatrix(true); this.moduleList.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { @@ -158,6 +191,7 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O progressIndicator.setProgressCompat(PRECISION, true); progressIndicator.setVisibility(View.GONE); searchView.setEnabled(true); + setActionBarBackground(null); }); moduleViewListBuilder.appendRemoteModules(); moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter); @@ -175,12 +209,29 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O TypedValue value = new TypedValue(); theme.resolveAttribute(backgroundAttr, value, true); this.searchCard.setCardBackgroundColor(value.data); - this.searchCard.setAlpha(iconified ? 0.70F : 1F); + this.searchCard.setAlpha(iconified ? 0.80F : 1F); } private void updateScreenInsets() { - this.moduleViewListBuilder.setFooterPx( - this.getNavigationBarHeight() + this.searchCard.getHeight()); + this.runOnUiThread(() -> this.updateScreenInsets( + this.getResources().getConfiguration())); + } + + private void updateScreenInsets(Configuration configuration) { + boolean landscape = configuration.orientation == + Configuration.ORIENTATION_LANDSCAPE; + int statusBarHeight = getStatusBarHeight(); + int actionBarHeight = getActionBarHeight(); + int combinedBarsHeight = statusBarHeight + actionBarHeight; + this.actionBarPadding.setMinHeight(combinedBarsHeight); + this.swipeRefreshLayout.setProgressViewOffset(false, + swipeRefreshLayoutOrigStartOffset + combinedBarsHeight, + swipeRefreshLayoutOrigEndOffset + combinedBarsHeight); + this.moduleViewListBuilder.setHeaderPx(actionBarHeight); + this.moduleViewListBuilder.setFooterPx((landscape ? 0 : + this.getNavigationBarHeight()) + this.searchCard.getHeight()); + this.moduleViewListBuilder.updateInsets(); + this.actionBarBlur.invalidate(); } @Override @@ -234,6 +285,12 @@ public class MainActivity extends CompatActivity implements SwipeRefreshLayout.O this.initMode = false; } + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + this.updateScreenInsets(newConfig); + super.onConfigurationChanged(newConfig); + } + @Override public void onRefresh() { if (this.swipeRefreshBlocker > System.currentTimeMillis() || diff --git a/app/src/main/java/com/fox2code/mmm/ModuleHolder.java b/app/src/main/java/com/fox2code/mmm/ModuleHolder.java index 2c81567..73ff0dd 100644 --- a/app/src/main/java/com/fox2code/mmm/ModuleHolder.java +++ b/app/src/main/java/com/fox2code/mmm/ModuleHolder.java @@ -24,7 +24,7 @@ public final class ModuleHolder implements Comparable { public final String moduleId; public final NotificationType notificationType; public final Type separator; - public final int footerPx; + public int footerPx; public View.OnClickListener onClickListener; public LocalModuleInfo moduleInfo; public RepoModule repoModule; @@ -34,32 +34,33 @@ public final class ModuleHolder implements Comparable { this.moduleId = Objects.requireNonNull(moduleId); this.notificationType = null; this.separator = null; - this.footerPx = 0; + this.footerPx = -1; } public ModuleHolder(NotificationType notificationType) { this.moduleId = ""; this.notificationType = Objects.requireNonNull(notificationType); this.separator = null; - this.footerPx = 0; + this.footerPx = -1; } public ModuleHolder(Type separator) { this.moduleId = ""; this.notificationType = null; this.separator = separator; - this.footerPx = 0; + this.footerPx = -1; } - public ModuleHolder(int footerPx) { + public ModuleHolder(int footerPx,boolean header) { this.moduleId = ""; this.notificationType = null; this.separator = null; this.footerPx = footerPx; + this.filterLevel = header ? 1 : 0; } public boolean isModuleHolder() { - return this.notificationType == null && this.separator == null && this.footerPx == 0; + return this.notificationType == null && this.separator == null && this.footerPx == -1; } public ModuleInfo getMainModuleInfo() { @@ -115,7 +116,7 @@ public final class ModuleHolder implements Comparable { } public Type getType() { - if (this.footerPx != 0) { + if (this.footerPx != -1) { return Type.FOOTER; } else if (this.separator != null) { return Type.SEPARATOR; @@ -145,7 +146,7 @@ public final class ModuleHolder implements Comparable { public boolean shouldRemove() { return this.notificationType != null ? this.notificationType.shouldRemove() : - this.footerPx == 0 && this.moduleInfo == null && + this.footerPx == -1 && this.moduleInfo == null && (this.repoModule == null || !this.repoModule.repoData.isEnabled() || (PropUtils.isLowQualityModule(this.repoModule.moduleInfo) && !MainApplication.isDisableLowQualityModuleFilter())); @@ -207,6 +208,7 @@ public final class ModuleHolder implements Comparable { } public enum Type implements Comparator { + HEADER(R.string.loading, false, false), SEPARATOR(R.string.loading, false, false) { @Override @SuppressWarnings("ConstantConditions") diff --git a/app/src/main/java/com/fox2code/mmm/ModuleViewAdapter.java b/app/src/main/java/com/fox2code/mmm/ModuleViewAdapter.java index fdc6d83..0b44297 100644 --- a/app/src/main/java/com/fox2code/mmm/ModuleViewAdapter.java +++ b/app/src/main/java/com/fox2code/mmm/ModuleViewAdapter.java @@ -26,6 +26,7 @@ import com.google.android.material.switchmaterial.SwitchMaterial; import com.topjohnwu.superuser.internal.UiThreadHandler; import java.util.ArrayList; +import java.util.Objects; public final class ModuleViewAdapter extends RecyclerView.Adapter { private static final boolean DEBUG = false; @@ -182,14 +183,16 @@ public final class ModuleViewAdapter extends RecyclerView.Adapter localModuleInfo.updateVersionCode) { this.creditText.setText((localModuleInfo == null || - moduleInfo.version.equals(localModuleInfo.version) ? + Objects.equals(moduleInfo.version, localModuleInfo.version) ? moduleInfo.version : localModuleInfo.version + " (" + this.getString(R.string.module_last_update) + moduleInfo.version + ")") + " " + this.getString(R.string.module_by) + " " + moduleInfo.author); } else { this.creditText.setText(localModuleInfo.version + ( - localModuleInfo.version.equals(localModuleInfo.updateVersion) ? + (localModuleInfo.updateVersion != null && + Objects.equals(localModuleInfo.version, + localModuleInfo.updateVersion)) ? "" : " (" + this.getString(R.string.module_last_update) + localModuleInfo.updateVersion + ")") + " " + this.getString(R.string.module_by) + " " + localModuleInfo.author); diff --git a/app/src/main/java/com/fox2code/mmm/ModuleViewListBuilder.java b/app/src/main/java/com/fox2code/mmm/ModuleViewListBuilder.java index 70069f5..b09af2a 100644 --- a/app/src/main/java/com/fox2code/mmm/ModuleViewListBuilder.java +++ b/app/src/main/java/com/fox2code/mmm/ModuleViewListBuilder.java @@ -23,6 +23,7 @@ import java.util.Locale; public class ModuleViewListBuilder { private static final String TAG = "ModuleViewListBuilder"; + private static final Runnable RUNNABLE = () -> {}; private final EnumSet notifications = EnumSet.noneOf(NotificationType.class); private final HashMap mappedModuleHolders = new HashMap<>(); private final Object updateLock = new Object(); @@ -31,8 +32,10 @@ public class ModuleViewListBuilder { @NonNull private String query = ""; private boolean updating; + private int headerPx; private int footerPx; private ModuleSorter moduleSorter = ModuleSorter.UPDATE; + private Runnable updateInsets = RUNNABLE; public ModuleViewListBuilder(Activity activity) { this.activity = activity; @@ -100,6 +103,7 @@ public class ModuleViewListBuilder { this.updating = true; final ArrayList moduleHolders; final int newNotificationsLen; + final ModuleHolder[] headerFooter = new ModuleHolder[2]; try { synchronized (this.updateLock) { // Build start @@ -150,9 +154,12 @@ public class ModuleViewListBuilder { } } Collections.sort(moduleHolders, this.moduleSorter); - if (this.footerPx != 0) { // Footer is always last - moduleHolders.add(new ModuleHolder(this.footerPx)); - } + // Header is always first + moduleHolders.add(0, headerFooter[0] = + new ModuleHolder(this.headerPx, true)); + // Footer is always last + moduleHolders.add(headerFooter[1] = + new ModuleHolder(this.footerPx, false)); Log.i(TAG, "Got " + moduleHolders.size() + " entries!"); // Build end } @@ -160,6 +167,7 @@ public class ModuleViewListBuilder { this.updating = false; } this.activity.runOnUiThread(() -> { + this.updateInsets = RUNNABLE; final EnumSet oldNotifications = EnumSet.noneOf(NotificationType.class); boolean isTop = !moduleList.canScrollVertically(-1); @@ -172,7 +180,8 @@ public class ModuleViewListBuilder { oldNotifications.add(notificationType); if (!notificationType.special) oldNotificationsLen++; - } + } else if (moduleHolder.footerPx != -1) + oldNotificationsLen++; // Fix header if (moduleHolder.separator == ModuleHolder.Type.INSTALLABLE) break; oldOfflineModulesLen++; @@ -208,6 +217,13 @@ public class ModuleViewListBuilder { } if (isTop) moduleList.scrollToPosition(0); if (isBottom) moduleList.scrollToPosition(newLen); + this.updateInsets = () -> { + headerFooter[0].footerPx = this.headerPx; + headerFooter[1].footerPx = this.footerPx; + notifySizeChanged(moduleViewAdapter, 0, 1, 1); + notifySizeChanged(moduleViewAdapter, + moduleHolders.size(), 1, 1); + }; }); } @@ -281,6 +297,14 @@ public class ModuleViewListBuilder { return true; } + public void setHeaderPx(int headerPx) { + if (this.headerPx != headerPx) { + synchronized (this.updateLock) { + this.headerPx = headerPx; + } + } + } + public void setFooterPx(int footerPx) { if (this.footerPx != footerPx) { synchronized (this.updateLock) { @@ -288,4 +312,8 @@ public class ModuleViewListBuilder { } } } + + public void updateInsets() { + this.updateInsets.run(); + } } 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 50144d1..2a91d7b 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.java +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.java @@ -165,13 +165,17 @@ public class AndroidacyWebAPI { /** * Show action bar if not visible, the action bar is only visible by default on notes. + * Optional title param to set action bar title. */ @JavascriptInterface - public void showActionBar() { + public void showActionBar(final String title) { if (this.consumedAction) return; this.consumedAction = true; this.activity.runOnUiThread(() -> { this.activity.showActionBar(); + if (title != null && !title.isEmpty()) { + this.activity.setTitle(title); + } this.consumedAction = false; }); } diff --git a/app/src/main/java/com/fox2code/mmm/compat/CompatActivity.java b/app/src/main/java/com/fox2code/mmm/compat/CompatActivity.java index 2f13c25..4783397 100644 --- a/app/src/main/java/com/fox2code/mmm/compat/CompatActivity.java +++ b/app/src/main/java/com/fox2code/mmm/compat/CompatActivity.java @@ -6,6 +6,7 @@ import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; import android.content.res.Resources; +import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; import android.util.Log; @@ -198,6 +199,35 @@ public class CompatActivity extends AppCompatActivity { } } + public void setActionBarBackground(Drawable drawable) { + androidx.appcompat.app.ActionBar compatActionBar; + try { + compatActionBar = this.getSupportActionBar(); + } catch (Exception e) { + Log.e(TAG, "Failed to call getSupportActionBar", e); + compatActionBar = null; // Allow fallback to builtin actionBar. + } + if (compatActionBar != null) { + compatActionBar.setBackgroundDrawable(drawable); + } else { + android.app.ActionBar actionBar = this.getActionBar(); + if (actionBar != null) + actionBar.setBackgroundDrawable(drawable); + } + } + + @Dimension @Px + public int getStatusBarHeight() { // How to improve this? + int height = WindowInsetsCompat.CONSUMED.getInsets( + WindowInsetsCompat.Type.statusBars()).top; + if (height == 0) { // Fallback to system resources + int id = Resources.getSystem().getIdentifier( + "status_bar_height", "dimen", "android"); + if (id > 0) return Resources.getSystem().getDimensionPixelSize(id); + } + return height; + } + public int getNavigationBarHeight() { // How to improve this? int height = WindowInsetsCompat.CONSUMED.getInsets( WindowInsetsCompat.Type.navigationBars()).bottom; diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index c4a1fc4..8165540 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -2,9 +2,10 @@ + + + + + app:layout_constraintTop_toBottomOf="@+id/action_bar_blur" /> + android:layout_height="wrap_content" + android:background="@null" /> diff --git a/app/src/main/res/layout/module_entry.xml b/app/src/main/res/layout/module_entry.xml index a2c8d55..b718051 100644 --- a/app/src/main/res/layout/module_entry.xml +++ b/app/src/main/res/layout/module_entry.xml @@ -17,6 +17,7 @@ android:layout_height="wrap_content" app:cardCornerRadius="@dimen/card_corner_radius" app:strokeColor="@android:color/transparent" + app:cardPreventCornerOverlap="true" app:strokeWidth="0dp"> #FF018786 #EF6C00 #FFA726 + #80000000 + #80FFFFFF #FF000000 #FFFFFFFF \ No newline at end of file