diff --git a/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java b/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java index 46d246244e..ecfab8d4e5 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java +++ b/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java @@ -95,14 +95,18 @@ import android.content.Context; import android.content.SharedPreferences; import android.graphics.Color; +import android.graphics.Outline; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.LayerDrawable; +import android.graphics.drawable.StateListDrawable; import android.os.Build; import android.os.Handler; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.ViewOutlineProvider; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.PopupMenu; @@ -134,7 +138,10 @@ public class RecyclerAdapter extends RecyclerView.Adapter= getItemCount()) { + return false; + } + final int type = getItemViewType(position); + return type == TYPE_ITEM || type == TYPE_BACK; + } + + /** + * Builds the rounded "section card" background for a list row. Consecutive rows between two + * headers share one continuous surface; only the first row rounds its top corners and only the + * last row rounds its bottom corners, so the run reads as a single card. The selected/pressed + * state keeps the row's rounded shape while tinting the surface. + */ + private Drawable createListCardBackground(int position) { + final float radius = context.getResources().getDimension(R.dimen.list_card_corner_radius); + final boolean roundTop = !isCardGroupMember(position - 1); + final boolean roundBottom = !isCardGroupMember(position + 1); + + final float[] radii = new float[8]; + if (roundTop) { + radii[0] = radii[1] = radii[2] = radii[3] = radius; + } + if (roundBottom) { + radii[4] = radii[5] = radii[6] = radii[7] = radius; + } + + final int surfaceColor; + final int selectedColor; + final AppTheme theme = utilsProvider.getAppTheme(); + if (theme.equals(AppTheme.LIGHT)) { + surfaceColor = Utils.getColor(context, R.color.card_surface_light); + selectedColor = Utils.getColor(context, R.color.card_selected_light); + } else if (theme.equals(AppTheme.BLACK)) { + surfaceColor = Utils.getColor(context, R.color.card_surface_black); + selectedColor = Utils.getColor(context, R.color.card_selected_black); + } else { + surfaceColor = Utils.getColor(context, R.color.card_surface_dark); + selectedColor = Utils.getColor(context, R.color.card_selected_dark); + } + + final GradientDrawable normal = new GradientDrawable(); + normal.setColor(surfaceColor); + normal.setCornerRadii(radii); + + final GradientDrawable selected = new GradientDrawable(); + selected.setColor(selectedColor); + selected.setCornerRadii(radii.clone()); + + final StateListDrawable background = new StateListDrawable(); + background.addState(new int[] {android.R.attr.state_selected}, selected); + background.addState(new int[] {android.R.attr.state_pressed}, selected); + background.addState(new int[] {android.R.attr.state_activated}, selected); + background.addState(new int[] {android.R.attr.state_focused}, selected); + background.addState(new int[] {}, normal); + return background; + } + + /** + * Binds a folder shown in grid view. Folders render as a compact icon + name + date cell (like a + * list row) that sits flush inside the rounded section container built by {@link + * #createGridFolderBackground}. + */ + private void bindViewHolderGridFolder(@NonNull final ItemViewHolder holder, int position) { + final LayoutElementParcelable rowItem = + getItemsDigested().get(position).layoutElementParcelable; + + holder.baseItemView.setOnLongClickListener( + p1 -> { + if (hasPendingPasteOperation()) return false; + if (dragAndDropPreference == PreferencesConstants.PREFERENCE_DRAG_DEFAULT + || (dragAndDropPreference == PreferencesConstants.PREFERENCE_DRAG_TO_MOVE_COPY + && getItemsDigested().get(holder.getAdapterPosition()).getChecked() + != ListItem.CHECKED)) { + mainFragment.registerListItemChecked( + holder.getAdapterPosition(), holder.checkImageViewGrid); + } + initDragListener(position, p1, holder); + return true; + }); + holder.baseItemView.setOnClickListener( + v -> + mainFragment.onListItemClicked( + false, holder.getAdapterPosition(), rowItem, holder.checkImageViewGrid)); + + Glide.with(mainFragment).clear(holder.genericIcon); + holder.genericIcon.setVisibility(View.VISIBLE); + modelProvider.getPreloadRequestBuilder(rowItem.iconData).into(holder.genericIcon); + // Folders use a plain grey glyph with no coloured circle (matches the redesign). + final GradientDrawable iconBackground = (GradientDrawable) holder.genericIcon.getBackground(); + if (iconBackground != null) { + iconBackground.setColor(Color.TRANSPARENT); + } + holder.genericIcon.setColorFilter(getFolderIconColor()); + + holder.txtTitle.setText(rowItem.title); + holder.txtDesc.setText(""); + if (getBoolean(PREFERENCE_SHOW_LAST_MODIFIED)) { + holder.date.setText(rowItem.dateModification); + } else { + holder.date.setText(""); + } + + holder.about.setVisibility(View.VISIBLE); + if (utilsProvider.getAppTheme().equals(AppTheme.LIGHT)) { + holder.about.setColorFilter(grey_color); + } + holder.about.setOnClickListener(v -> showPopup(v, rowItem)); + + final boolean checked = getItemsDigested().get(position).getChecked() == ListItem.CHECKED; + holder.checkImageViewGrid.setColorFilter(accentColor); + holder.checkImageViewGrid.setVisibility(checked ? View.VISIBLE : View.INVISIBLE); + + holder.baseItemView.setBackground( + createGridSectionBackground(position, checked, TYPE_ITEM_GRID_FOLDER)); + } + + /** Plain grey tint used for folder icons (no coloured circle), keyed to the theme. */ + private int getFolderIconColor() { + if (utilsProvider.getAppTheme().equals(AppTheme.LIGHT)) { + return Utils.getColor(context, R.color.folder_icon_light); + } + return Utils.getColor(context, R.color.folder_icon_dark); + } + + /** Clips a grid tile's thumbnail/icon frame to rounded corners. */ + private void roundThumbnailCorners(final View iconFrame) { + if (iconFrame == null) { + return; + } + final float radius = context.getResources().getDimension(R.dimen.grid_thumb_corner_radius); + iconFrame.setClipToOutline(true); + iconFrame.setOutlineProvider( + new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), radius); + } + }); + } + + /** + * Builds the unified "section container" background for a grid cell. Consecutive cells of the + * same {@code sectionType} (folders or files) between two headers share one rounded panel: only + * the four outer corners of the block are rounded, and hairline dividers separate adjacent cells. + */ + private Drawable createGridSectionBackground(int position, boolean selected, int sectionType) { + final Integer columnsPref = mainFragment.getMainFragmentViewModel().getColumns(); + final int columnCount = (columnsPref == null || columnsPref <= 0) ? 3 : columnsPref; + + int start = position; + while (start - 1 >= 0 && getItemViewType(start - 1) == sectionType) { + start--; + } + int end = position; + while (end + 1 < getItemCount() && getItemViewType(end + 1) == sectionType) { + end++; + } + final int index = position - start; + final int count = end - start + 1; + final int col = index % columnCount; + final int row = index / columnCount; + final int lastRow = (count - 1) / columnCount; + + final float radius = context.getResources().getDimension(R.dimen.list_card_corner_radius); + final float[] radii = new float[8]; + if (index == 0) { + radii[0] = radii[1] = radius; // top-left + } + if (index == Math.min(columnCount - 1, count - 1)) { + radii[2] = radii[3] = radius; // top-right + } + if (index == count - 1) { + radii[4] = radii[5] = radius; // bottom-right + } + if (index == lastRow * columnCount) { + radii[6] = radii[7] = radius; // bottom-left + } + + final int surfaceColor; + final int selectedColor; + final int dividerColor; + final AppTheme theme = utilsProvider.getAppTheme(); + if (theme.equals(AppTheme.LIGHT)) { + surfaceColor = Utils.getColor(context, R.color.card_surface_light); + selectedColor = Utils.getColor(context, R.color.card_selected_light); + dividerColor = Utils.getColor(context, R.color.grid_divider_light); + } else if (theme.equals(AppTheme.BLACK)) { + surfaceColor = Utils.getColor(context, R.color.card_surface_black); + selectedColor = Utils.getColor(context, R.color.card_selected_black); + dividerColor = Utils.getColor(context, R.color.grid_divider_black); + } else { + surfaceColor = Utils.getColor(context, R.color.card_surface_dark); + selectedColor = Utils.getColor(context, R.color.card_selected_dark); + dividerColor = Utils.getColor(context, R.color.grid_divider_dark); + } + + final GradientDrawable surface = new GradientDrawable(); + surface.setColor(selected ? selectedColor : surfaceColor); + surface.setCornerRadii(radii); + + final boolean dividerRight = + col < columnCount - 1 && index < count - 1 && ((index + 1) / columnCount == row); + final boolean dividerBottom = row < lastRow; + if (!dividerRight && !dividerBottom) { + return surface; + } + + // A single physical pixel: a hairline that blends with the surface rather than standing out. + final int thickness = 1; + final GradientDrawable dividerLayer = new GradientDrawable(); + dividerLayer.setColor(dividerColor); + dividerLayer.setCornerRadii(radii.clone()); + final LayerDrawable background = new LayerDrawable(new Drawable[] {dividerLayer, surface}); + background.setLayerInset(1, 0, 0, dividerRight ? thickness : 0, dividerBottom ? thickness : 0); + return background; + } + private void bindViewHolderList(@NonNull final ItemViewHolder holder, int position) { final boolean isBackButton = getItemsDigested().get(position).specialType == TYPE_BACK; @Nullable @@ -948,11 +1207,7 @@ && getItemsDigested().get(holder.getAdapterPosition()).getChecked() break; } - if (utilsProvider.getAppTheme().equals(AppTheme.LIGHT)) { - holder.baseItemView.setBackgroundResource(R.drawable.safr_ripple_white); - } else { - holder.baseItemView.setBackgroundResource(R.drawable.safr_ripple_black); - } + holder.baseItemView.setBackground(createListCardBackground(position)); holder.baseItemView.setSelected(false); if (getItemsDigested().get(position).getChecked() == ListItem.CHECKED) { @@ -968,6 +1223,8 @@ && getItemsDigested().get(holder.getAdapterPosition()).getChecked() holder.apkIcon.setVisibility(View.GONE); holder.pictureIcon.setVisibility(View.GONE); holder.genericIcon.setVisibility(View.VISIBLE); + // Reset any folder grey tint so the selected item shows the standard grey-circle highlight. + holder.genericIcon.clearColorFilter(); GradientDrawable gradientDrawable = (GradientDrawable) holder.genericIcon.getBackground(); gradientDrawable.setColor(goBackColor); } @@ -982,18 +1239,20 @@ && getBoolean(PREFERENCE_SHOW_THUMB))) { holder.genericIcon.setVisibility(View.VISIBLE); GradientDrawable gradientDrawable = (GradientDrawable) holder.genericIcon.getBackground(); - if (getBoolean(PREFERENCE_COLORIZE_ICONS)) { - if (rowItem.isDirectory) { - gradientDrawable.setColor(iconSkinColor); - } else { + if (rowItem.isDirectory && !isBackButton) { + // Folders use a plain grey glyph with no coloured circle (matches the redesign). + gradientDrawable.setColor(Color.TRANSPARENT); + holder.genericIcon.setColorFilter(getFolderIconColor()); + } else { + holder.genericIcon.clearColorFilter(); + if (getBoolean(PREFERENCE_COLORIZE_ICONS) && !rowItem.isDirectory) { ColorUtils.colorizeIcons(context, rowItem.filetype, gradientDrawable, iconSkinColor); + } else { + gradientDrawable.setColor(iconSkinColor); + } + if (isBackButton) { + gradientDrawable.setColor(goBackColor); } - } else { - gradientDrawable.setColor(iconSkinColor); - } - - if (isBackButton) { - gradientDrawable.setColor(goBackColor); } } } @@ -1414,10 +1673,18 @@ public boolean onResourceReady( private void showPopup(@NonNull View view, @NonNull final LayoutElementParcelable rowItem) { if (hasPendingPasteOperation()) return; - Context currentContext = this.context; - if (mainFragment.getMainActivity().getAppTheme() == AppTheme.BLACK) { - currentContext = new ContextThemeWrapper(context, R.style.overflow_black); + // Wrap the context with the rounded popup theme overlay so the per-item menu gets the same + // rounded surface as the toolbar overflow, in every theme. + final AppTheme appTheme = mainFragment.getMainActivity().getAppTheme(); + final int popupThemeOverlay; + if (appTheme == AppTheme.LIGHT) { + popupThemeOverlay = R.style.PopupThemeOverlayLight; + } else if (appTheme == AppTheme.BLACK) { + popupThemeOverlay = R.style.PopupThemeOverlayBlack; + } else { + popupThemeOverlay = R.style.PopupThemeOverlayDark; } + Context currentContext = new ContextThemeWrapper(context, popupThemeOverlay); PopupMenu popupMenu = new ItemPopupMenu( currentContext, diff --git a/app/src/main/java/com/amaze/filemanager/adapters/holders/SpecialViewHolder.kt b/app/src/main/java/com/amaze/filemanager/adapters/holders/SpecialViewHolder.kt index f329e9685a..ff0436c163 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/holders/SpecialViewHolder.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/holders/SpecialViewHolder.kt @@ -60,9 +60,9 @@ class SpecialViewHolder( // if(utilsProvider.getAppTheme().equals(AppTheme.DARK)) // view.setBackgroundResource(R.color.holo_dark_background); if (utilsProvider.appTheme == AppTheme.LIGHT) { - txtTitle.setTextColor(Utils.getColor(c, R.color.text_light)) + txtTitle.setTextColor(Utils.getColor(c, R.color.section_header_light)) } else { - txtTitle.setTextColor(Utils.getColor(c, R.color.text_dark)) + txtTitle.setTextColor(Utils.getColor(c, R.color.section_header_dark)) } } } diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java index 2154ee2d43..2594eadf2c 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java @@ -170,7 +170,9 @@ import android.content.res.Configuration; import android.database.Cursor; import android.graphics.Color; +import android.graphics.PorterDuff; import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; import android.hardware.usb.UsbManager; import android.media.RingtoneManager; import android.net.Uri; @@ -197,6 +199,7 @@ import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.StringRes; +import androidx.appcompat.widget.Toolbar; import androidx.arch.core.util.Function; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.content.ContextCompat; @@ -1077,6 +1080,8 @@ public void goToMain(String path, boolean hideFab) { public boolean onCreateOptionsMenu(Menu menu) { MenuInflater menuInflater = getMenuInflater(); menuInflater.inflate(R.menu.activity_extra, menu); + // Tint the search / view / overflow icons to sit on the app bar surface. + tintMenuIcons(menu, getOnAppBarColor()); /* SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); SearchView searchView = (SearchView) menu.findItem(R.id.search).getActionView(); @@ -1832,6 +1837,76 @@ public void updateViews(ColorDrawable colorDrawable) { } } + /** + * Recolours the top app bar chrome (toolbar, status bar, path breadcrumb) to a surface colour — + * white in light mode, an elevated grey in dark/black — instead of the primary colour, and lifts + * it with a small elevation so it reads as sitting above the file listing. The drawer header + * keeps the primary colour. Re-applied after action mode exits so selection mode can still + * recolour the bar. Scoped to this activity's shared toolbar; other activities are unaffected. + */ + public void applyAppBarSurface() { + if (appbar == null) { + return; + } + final int surface = getAppBarSurfaceColor(); + final int onSurface = getOnAppBarColor(); + final Toolbar toolbar = getAppbar().getToolbar(); + final AppBarLayout appBarLayout = getAppbar().getAppbarLayout(); + + appBarLayout.setBackgroundColor(surface); + toolbar.setBackgroundColor(surface); + if (SDK_INT >= LOLLIPOP) { + // Constant elevation (drop the scroll-driven lift) so the bar always casts a shadow. + appBarLayout.setStateListAnimator(null); + appBarLayout.setElevation(getResources().getDimension(R.dimen.app_bar_elevation)); + } + + toolbar.setTitleTextColor(onSurface); + toolbar.setSubtitleTextColor(onSurface); + tintDrawable(toolbar.getNavigationIcon(), onSurface); + tintDrawable(toolbar.getOverflowIcon(), onSurface); + tintMenuIcons(toolbar.getMenu(), onSurface); + + getAppbar().getBottomBar().setBackgroundColor(surface); + getAppbar().getBottomBar().setPathTextColor(onSurface); + + if (SDK_INT >= LOLLIPOP) { + getWindow().setStatusBarColor(surface); + } + } + + /** App bar surface colour for the current theme (white / elevated grey / near-black). */ + public int getAppBarSurfaceColor() { + if (getAppTheme().equals(AppTheme.LIGHT)) { + return Utils.getColor(this, R.color.app_bar_light); + } else if (getAppTheme().equals(AppTheme.BLACK)) { + return Utils.getColor(this, R.color.app_bar_black); + } + return Utils.getColor(this, R.color.app_bar_dark); + } + + /** Title / icon / path colour that reads on {@link #getAppBarSurfaceColor()}. */ + public int getOnAppBarColor() { + return getAppTheme().equals(AppTheme.LIGHT) + ? Utils.getColor(this, R.color.on_app_bar_light) + : Utils.getColor(this, R.color.on_app_bar_dark); + } + + private void tintDrawable(@Nullable Drawable drawable, int color) { + if (drawable != null) { + drawable.mutate().setColorFilter(color, PorterDuff.Mode.SRC_ATOP); + } + } + + void tintMenuIcons(@Nullable Menu menu, int color) { + if (menu == null) { + return; + } + for (int i = 0; i < menu.size(); i++) { + tintDrawable(menu.getItem(i).getIcon(), color); + } + } + void initialiseFab() { int colorAccent = getAccent(); diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java b/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java index 97d68b8c1e..8cd57f85b1 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java @@ -108,6 +108,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; +import android.widget.ImageView; import android.widget.Toast; import androidx.activity.result.ActivityResultLauncher; @@ -121,6 +122,7 @@ import androidx.core.content.ContextCompat; import androidx.core.content.pm.ShortcutInfoCompat; import androidx.core.content.pm.ShortcutManagerCompat; +import androidx.core.graphics.ColorUtils; import androidx.core.graphics.drawable.IconCompat; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; @@ -253,11 +255,9 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat mSwipeRefreshLayout.setOnRefreshListener(() -> updateList(true)); - // String itemsstring = res.getString(R.string.items);// TODO: 23/5/2017 use or delete - mToolbarContainer.setBackgroundColor( - MainActivity.currentTab == 1 - ? mainFragmentViewModel.getPrimaryTwoColor() - : mainFragmentViewModel.getPrimaryColor()); + // The app bar is a white/grey surface that sits above the listing rather than the primary + // colour; MainActivity owns the recolour so the shared toolbar stays consistent. + requireMainActivity().applyAppBarSurface(); // listView.setPadding(listView.getPaddingLeft(), paddingTop, listView.getPaddingRight(), // listView.getPaddingBottom()); @@ -268,11 +268,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat f.generateMode(getActivity()); getMainActivity().getAppbar().getBottomBar().setClickListener(); - if (utilsProvider.getAppTheme().equals(AppTheme.LIGHT) && !mainFragmentViewModel.isList()) { - listView.setBackgroundColor(Utils.getColor(getContext(), R.color.grid_background_light)); - } else { - listView.setBackgroundDrawable(null); - } + applyListViewBackground(); listView.setHasFixedSize(true); if (mainFragmentViewModel.isList()) { mLayoutManager = new CustomScrollLinearLayoutManager(getContext()); @@ -291,7 +287,11 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat // 23/5/2017 use or delete dividerItemDecoration = new DividerItemDecoration(requireActivity(), false, getBoolean(PREFERENCE_SHOW_DIVIDERS)); - listView.addItemDecoration(dividerItemDecoration); + // Only list view uses row dividers; grid folders carry their own dividers inside the unified + // section container, so keep the decoration off the grid. + if (mainFragmentViewModel.isList()) { + listView.addItemDecoration(dividerItemDecoration); + } mSwipeRefreshLayout.setColorSchemeColors(mainFragmentViewModel.getAccentColor()); DefaultItemAnimator animator = new DefaultItemAnimator(); listView.setItemAnimator(animator); @@ -339,14 +339,61 @@ public int getSpanSize(int position) { }); } - void switchToGrid() { - mainFragmentViewModel.setList(false); + /** + * Applies the file-listing page background that sits behind the section cards, honouring the + * current app theme. Centralised so the initial load and list/grid switches stay consistent, and + * so future background work (patterned "graffiti" surface) has a single hook. + */ + private void applyListViewBackground() { + final AppTheme theme = utilsProvider.getAppTheme(); + final int pageColor; + final int patternRes; + // Tint strength: darker themes need a touch more of the colour to read as a tint. + final float tintFraction; + if (theme.equals(AppTheme.LIGHT)) { + pageColor = Utils.getColor(getContext(), R.color.page_background_light); + patternRes = R.drawable.bg_file_pattern_light; + tintFraction = 0.12f; + } else if (theme.equals(AppTheme.BLACK)) { + pageColor = Color.BLACK; + patternRes = R.drawable.bg_file_pattern_dark; + tintFraction = 0.08f; + } else { + pageColor = Utils.getColor(getContext(), R.color.page_background_dark); + patternRes = R.drawable.bg_file_pattern_dark; + tintFraction = 0.08f; + } - if (utilsProvider.getAppTheme().equals(AppTheme.LIGHT)) { + // Mirrors the drawer banner: a wash of the user's primary (app bar) colour over the page + // surface, with the tilted, translucent graffiti pattern on top. The list itself is transparent + // so this shows through the gaps around the section cards. + final int primaryColor = + MainActivity.currentTab == 1 + ? mainFragmentViewModel.getPrimaryTwoColor() + : mainFragmentViewModel.getPrimaryColor(); + final int tintedBase = ColorUtils.blendARGB(pageColor, primaryColor, tintFraction); - // will always be grid, set alternate white background - listView.setBackgroundColor(Utils.getColor(getContext(), R.color.grid_background_light)); + final View listBackground = rootView.findViewById(R.id.list_background); + if (listBackground != null) { + listBackground.setBackgroundColor(tintedBase); + } + final ImageView listBackgroundPattern = rootView.findViewById(R.id.list_background_pattern); + if (listBackgroundPattern != null) { + listBackgroundPattern.setImageResource(patternRes); } + listView.setBackgroundColor(Color.TRANSPARENT); + + // Left/right gutter so the section cards float with breathing room and the patterned surface + // shows through the edges. Applied on the list rather than per-row so folders/files stay flush. + final int gutter = (int) getResources().getDimension(R.dimen.list_card_horizontal_margin); + listView.setPadding(gutter, listView.getPaddingTop(), gutter, listView.getPaddingBottom()); + listView.setClipToPadding(false); + } + + void switchToGrid() { + mainFragmentViewModel.setList(false); + + applyListViewBackground(); if (mLayoutManagerGrid == null) if (mainFragmentViewModel.getColumns() == -1 || mainFragmentViewModel.getColumns() == 0) @@ -365,10 +412,7 @@ void switchToGrid() { void switchToList() { mainFragmentViewModel.setList(true); - if (utilsProvider.getAppTheme().equals(AppTheme.LIGHT)) { - - listView.setBackgroundDrawable(null); - } + applyListViewBackground(); if (mLayoutManager == null) mLayoutManager = new CustomScrollLinearLayoutManager(getActivity()); listView.setLayoutManager(mLayoutManager); diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/TabFragment.java b/app/src/main/java/com/amaze/filemanager/ui/fragments/TabFragment.java index d5177276fd..1abcdfbca7 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/TabFragment.java +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/TabFragment.java @@ -291,8 +291,9 @@ public void onPageScrolled(int position, float positionOffset, int positionOffse @ColorInt int color = (int) evaluator.evaluate(position + positionOffset, startColor, endColor); - colorDrawable.setColor(color); - requireMainActivity().updateViews(colorDrawable); + // The app bar is a fixed white/grey surface now; only the drawer header follows the per-tab + // colour as panes are swiped, so the toolbar no longer flashes back to the primary colour. + requireMainActivity().getDrawer().setBackgroundColor(color); } @Override diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/data/MainFragmentViewModel.kt b/app/src/main/java/com/amaze/filemanager/ui/fragments/data/MainFragmentViewModel.kt index 69b61e42cb..596b1ee8be 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/data/MainFragmentViewModel.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/data/MainFragmentViewModel.kt @@ -21,6 +21,7 @@ package com.amaze.filemanager.ui.fragments.data import android.content.SharedPreferences +import android.content.res.Resources import android.os.Bundle import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel @@ -155,7 +156,22 @@ class MainFragmentViewModel : ViewModel() { PREFERENCE_GRID_COLUMNS_DEFAULT, ) Objects.requireNonNull(columnPreference) - columns = columnPreference?.toInt() + // Clamp to what the device can practically show, so grid cells never become too narrow to + // read (e.g. the default "3" on a phone, or a "6" saved before the cap existed). + columns = columnPreference?.toInt()?.coerceAtMost(maxGridColumns()) + } + + companion object { + /** + * Maximum number of grid columns that is practical on this device: phones cap at 2, tablets + * scale up to 4 based on the smallest screen width. Used to clamp both the stored preference + * and the options offered in settings. + */ + @JvmStatic + fun maxGridColumns(): Int { + val smallestWidthDp = Resources.getSystem().configuration.smallestScreenWidthDp + return (smallestWidthDp / 200).coerceIn(2, 4) + } } /** diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/preferencefragments/AppearancePrefsFragment.kt b/app/src/main/java/com/amaze/filemanager/ui/fragments/preferencefragments/AppearancePrefsFragment.kt index 6626ef853b..26d603ebfb 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/preferencefragments/AppearancePrefsFragment.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/preferencefragments/AppearancePrefsFragment.kt @@ -24,26 +24,14 @@ import android.os.Bundle import androidx.preference.Preference import com.afollestad.materialdialogs.MaterialDialog import com.amaze.filemanager.R +import com.amaze.filemanager.ui.fragments.data.MainFragmentViewModel import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_GRID_COLUMNS import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_GRID_COLUMNS_DEFAULT import com.amaze.filemanager.ui.theme.AppThemePreference -import java.util.Objects class AppearancePrefsFragment : BasePrefsFragment() { override val title = R.string.appearance - /** - * The actual value saved for the preference, to see the localized strings see [R.array.columns] - */ - private val savedPreferenceValues = - listOf( - PREFERENCE_GRID_COLUMNS_DEFAULT, - "2", - "3", - "4", - "5", - "6", - ) private var currentTheme = 0 private var gridColumnPref: Preference? = null @@ -73,41 +61,35 @@ class AppearancePrefsFragment : BasePrefsFragment() { private val onClickGridColumn = Preference.OnPreferenceClickListener { - val dialog = - MaterialDialog.Builder(activity).also { builder -> + // Offer only the column counts this device can practically show: "Automatic" plus + // 2..maxGridColumns (2 on phones, up to 4 on tablets). + val maxColumns = MainFragmentViewModel.maxGridColumns() + val savedValues = + listOf(PREFERENCE_GRID_COLUMNS_DEFAULT) + (2..maxColumns).map { it.toString() } + val labels: List = + listOf(getString(R.string.default_string)) + (2..maxColumns).map { it.toString() } + + val saved = + activity.prefs.getString(PREFERENCE_GRID_COLUMNS, PREFERENCE_GRID_COLUMNS_DEFAULT) + val current = savedValues.indexOf(saved).coerceAtLeast(0) + + MaterialDialog.Builder(activity) + .also { builder -> builder.theme(activity.utilsProvider.appTheme.getMaterialDialogTheme()) builder.title(R.string.gridcolumnno) - val columnsPreference = - activity - .prefs - .getString(PREFERENCE_GRID_COLUMNS, PREFERENCE_GRID_COLUMNS_DEFAULT) - - Objects.requireNonNull(columnsPreference) - val current = - when (columnsPreference) { - null -> { - PREFERENCE_GRID_COLUMNS_DEFAULT.toInt() - } - else -> { - columnsPreference.toInt() - 1 - } - } - builder - .items(R.array.columns) + .items(labels) .itemsCallbackSingleChoice(current) { dialog, _, which, _ -> - val editor = activity.prefs.edit() - editor.putString( - PREFERENCE_GRID_COLUMNS, - savedPreferenceValues[which], - ) - editor.apply() + activity.prefs + .edit() + .putString(PREFERENCE_GRID_COLUMNS, savedValues[which]) + .apply() dialog.dismiss() updateGridColumnSummary() true } }.build() - dialog.show() + .show() true } @@ -176,6 +158,10 @@ class AppearancePrefsFragment : BasePrefsFragment() { PREFERENCE_GRID_COLUMNS, PREFERENCE_GRID_COLUMNS_DEFAULT, ) - gridColumnPref?.summary = preferenceColumns + // Show the effective (device-clamped) column count so it matches what the grid renders. + val effective = + (preferenceColumns?.toIntOrNull() ?: PREFERENCE_GRID_COLUMNS_DEFAULT.toInt()) + .coerceAtMost(MainFragmentViewModel.maxGridColumns()) + gridColumnPref?.summary = effective.toString() } } diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/DividerItemDecoration.java b/app/src/main/java/com/amaze/filemanager/ui/views/DividerItemDecoration.java index 6f132e35d6..8ff3d16897 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/DividerItemDecoration.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/DividerItemDecoration.java @@ -80,6 +80,15 @@ private void drawVertical(Canvas c, RecyclerView parent) { continue; } + // Only draw a divider between two consecutive rows of the same section. When the following + // row is a header or the trailing empty spacer, this row is the last of its section, so a + // divider below it would dangle with nothing beneath it. + final View next = parent.getChildAt(i + 1); + final int nextViewType = parent.getChildViewHolder(next).getItemViewType(); + if (nextViewType != RecyclerAdapter.TYPE_ITEM && nextViewType != RecyclerAdapter.TYPE_BACK) { + continue; + } + final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); final int top = child.getBottom() + params.bottomMargin; final int bottom = top + mDivider.getIntrinsicHeight(); diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/BottomBar.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/BottomBar.java index 38ebd25519..60d2b9cad0 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/BottomBar.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/BottomBar.java @@ -95,6 +95,8 @@ public class BottomBar implements View.OnTouchListener { private int lastUsedFolderButton = 0; private Drawable arrow; + @ColorInt private int pathTextColor = Color.WHITE; + private CountDownTimer timer; private GestureDetector gestureDetector; @@ -335,7 +337,7 @@ private AppCompatButton createFolderButton(String text) { if (lastUsedFolderButton >= folderButtons.size()) { button = new AppCompatButton(mainActivity); - button.setTextColor(Utils.getColor(mainActivity, android.R.color.white)); + button.setTextColor(pathTextColor); button.setTextSize(13); button.setLayoutParams(buttonParams); button.setBackgroundResource(0); @@ -355,6 +357,29 @@ public void setBackgroundColor(@ColorInt int color) { frame.setBackgroundColor(color); } + /** + * Colours the path breadcrumb (text, folder buttons, storage/root icons and arrows) so it stays + * legible on the app bar surface. Applied by {@link MainActivity#applyAppBarSurface()}. + */ + public void setPathTextColor(@ColorInt int color) { + pathTextColor = color; + pathText.setTextColor(color); + fullPathText.setTextColor(color); + fullPathAnim.setTextColor(color); + for (AppCompatButton folderButton : folderButtons) { + folderButton.setTextColor(color); + } + for (AppCompatImageView arrowButton : arrowButtons) { + arrowButton.setColorFilter(color); + } + if (buttonRoot != null) { + buttonRoot.setColorFilter(color); + } + if (buttonStorage != null) { + buttonStorage.setColorFilter(color); + } + } + public void setVisibility(int visibility) { frame.setVisibility(visibility); } diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java index 9240a5e426..6126592244 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java @@ -78,6 +78,7 @@ import android.content.res.ColorStateList; import android.content.res.Configuration; import android.graphics.Color; +import android.graphics.drawable.GradientDrawable; import android.os.Build; import android.text.SpannableString; import android.text.TextUtils; @@ -101,6 +102,7 @@ import androidx.appcompat.widget.AppCompatImageButton; import androidx.appcompat.widget.AppCompatImageView; import androidx.appcompat.widget.AppCompatTextView; +import androidx.core.graphics.ColorUtils; import androidx.drawerlayout.widget.DrawerLayout; import androidx.fragment.app.FragmentTransaction; import androidx.legacy.app.ActionBarDrawerToggle; @@ -224,7 +226,8 @@ public Drawer(MainActivity mainActivity) { // mDrawerLayout.setStatusBarBackgroundColor(Color.parseColor((currentTab==1 ? skinTwo : // skin))); drawerHeaderView.setBackgroundResource(R.drawable.amaze_header); - // drawerHeaderParent.setBackgroundColor(Color.parseColor((currentTab==1 ? skinTwo : skin))); + // Colour the banner with the user's theme colour on load (kept in sync per-tab elsewhere). + setBackgroundColor(mainActivity.getPrimary()); if (mainActivity.findViewById(R.id.tab_frame) != null) { isOnTablet = true; mDrawerLayout.setScrimColor(Color.TRANSPARENT); @@ -949,7 +952,13 @@ public int getDrawerSelectedItem() { public void setBackgroundColor(@ColorInt int color) { mDrawerLayout.setStatusBarBackgroundColor(color); - drawerHeaderParent.setBackgroundColor(color); + // Banner follows the user's theme colour as a subtle diagonal gradient; the translucent Amaze + // header (logo + mountains) sits on top. + GradientDrawable bannerGradient = + new GradientDrawable( + GradientDrawable.Orientation.TL_BR, + new int[] {color, ColorUtils.blendARGB(color, Color.BLACK, 0.25f)}); + drawerHeaderParent.setBackground(bannerGradient); } public void resetPendingPath() { diff --git a/app/src/main/java/com/amaze/filemanager/utils/MainActivityActionMode.kt b/app/src/main/java/com/amaze/filemanager/utils/MainActivityActionMode.kt index 4af20b454a..2cbb3f7a20 100644 --- a/app/src/main/java/com/amaze/filemanager/utils/MainActivityActionMode.kt +++ b/app/src/main/java/com/amaze/filemanager/utils/MainActivityActionMode.kt @@ -436,6 +436,9 @@ class MainActivityActionMode(private val mainActivityReference: WeakReference + + + + + + diff --git a/app/src/main/res/layout/grid_header.xml b/app/src/main/res/layout/grid_header.xml index 4688dbe6b6..c33ca95ef2 100644 --- a/app/src/main/res/layout/grid_header.xml +++ b/app/src/main/res/layout/grid_header.xml @@ -12,8 +12,12 @@ android:layout_height="wrap_content" android:layout_width="match_parent" android:minHeight="?android:attr/listPreferredItemHeightSmall" - android:paddingLeft="@dimen/material_generic" - android:paddingRight="@dimen/material_generic" + android:fontFamily="sans-serif-medium" + android:letterSpacing="0.02" + android:paddingLeft="@dimen/section_header_padding_start" + android:paddingStart="@dimen/section_header_padding_start" + android:paddingRight="@dimen/section_header_padding_start" + android:paddingEnd="@dimen/section_header_padding_start" android:textSize="@dimen/list_header_textsize"> diff --git a/app/src/main/res/layout/griditem.xml b/app/src/main/res/layout/griditem.xml index 1655cf7d7d..36a392986f 100644 --- a/app/src/main/res/layout/griditem.xml +++ b/app/src/main/res/layout/griditem.xml @@ -3,9 +3,7 @@ android:id="@+id/second" android:layout_width="match_parent" android:layout_height="170dp" - android:layout_margin="2dp" - android:background="@drawable/item_doc_grid" - android:foreground="@drawable/item_backgrund" + android:foreground="?android:attr/selectableItemBackground" android:focusable="true" android:nextFocusDown="@id/properties"> @@ -13,6 +11,11 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" + android:paddingTop="@dimen/grid_thumb_inset" + android:paddingStart="@dimen/grid_thumb_inset" + android:paddingLeft="@dimen/grid_thumb_inset" + android:paddingEnd="@dimen/grid_thumb_inset" + android:paddingRight="@dimen/grid_thumb_inset" android:paddingBottom="6dp"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/list_header.xml b/app/src/main/res/layout/list_header.xml index a8d4bffd3d..c33ca95ef2 100644 --- a/app/src/main/res/layout/list_header.xml +++ b/app/src/main/res/layout/list_header.xml @@ -12,8 +12,12 @@ android:layout_height="wrap_content" android:layout_width="match_parent" android:minHeight="?android:attr/listPreferredItemHeightSmall" - android:paddingLeft="@dimen/icon_width" - android:paddingRight="@dimen/icon_width" + android:fontFamily="sans-serif-medium" + android:letterSpacing="0.02" + android:paddingLeft="@dimen/section_header_padding_start" + android:paddingStart="@dimen/section_header_padding_start" + android:paddingRight="@dimen/section_header_padding_start" + android:paddingEnd="@dimen/section_header_padding_start" android:textSize="@dimen/list_header_textsize"> diff --git a/app/src/main/res/layout/main_frag.xml b/app/src/main/res/layout/main_frag.xml index 92f0b96603..29b173883e 100644 --- a/app/src/main/res/layout/main_frag.xml +++ b/app/src/main/res/layout/main_frag.xml @@ -25,6 +25,24 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + + + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 0d246815be..c9cb02d0ec 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -130,4 +130,38 @@ #262a3e + + + #F4F4F6 + #121212 + + #FFFFFF + #1E1E1E + #161616 + + #E4E5EA + #2E2E2E + #242424 + + #8A8A8E + #9AA0A6 + + #5F6368 + #C4C7C5 + + #ECECEC + #2A2A2A + #202020 + + #FFFFFF + #242424 + #0D0D0D + + #202124 + #FFFFFF + + #F1F1F1 + #2E2E2E + #1C1C1C + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index c9d87daca1..01eebe3a35 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -47,7 +47,7 @@ 10dp 48dp - 16sp + 13sp 22sp @@ -62,4 +62,19 @@ 18sp 24sp 8dp + + + 16dp + 12dp + 12dp + 12dp + 56dp + 32dp + 12dp + 8dp + 4dp + 18dp + + 18dp diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index e324453fe7..e63c64e5b2 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -24,6 +24,42 @@ #ffffff + + + + + + + + + @@ -86,6 +134,9 @@ #ffffff @style/custom_action_mode_dark @style/custom_action_mode_dark + @color/popup_surface_dark + @style/OverflowMenuRounded + @drawable/popup_menu_background