diff --git a/app/build.gradle b/app/build.gradle index 06af95952c..f865620f57 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,6 +42,10 @@ android { "room.expandProjection": "true"] } } + + //For Dimezis/BlurView + renderscriptTargetApi 29 //must match target sdk and build tools + renderscriptSupportModeEnabled true } signingConfigs { @@ -124,6 +128,7 @@ dependencies { implementation 'androidx.vectordrawable:vectordrawable-animated:1.1.0' implementation 'androidx.legacy:legacy-support-v13:1.0.0' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation "com.google.android.material:material:$androidMaterialVersion" implementation "androidx.fragment:fragment-ktx:$androidXFragmentVersion" implementation "androidx.appcompat:appcompat:$androidXAppCompatVersion" @@ -131,6 +136,8 @@ dependencies { implementation 'androidx.cardview:cardview:1.0.0' implementation "androidx.room:room-runtime:$roomVersion" implementation "com.android.billingclient:billing:$androidBillingVersion" + implementation "androidx.lifecycle:lifecycle-livedata-ktx:$androidXLifecycleVersion" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$androidXLifecycleVersion" kapt "androidx.room:room-compiler:$roomVersion" // RxJava support for Room implementation "androidx.room:room-rxjava2:$roomVersion" @@ -250,6 +257,10 @@ dependencies { implementation "androidx.core:core-ktx:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "ch.acra:acra-core:5.7.0" + + //For blurring quickview background + implementation 'com.eightbitlab:blurview:1.6.6' + implementation 'com.eightbitlab:supportrenderscriptblur:1.0.2' } configurations.all { 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 0d05686905..81cbe9495b 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java +++ b/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java @@ -33,6 +33,7 @@ import java.util.ArrayList; import java.util.List; +import com.amaze.filemanager.BuildConfig; import com.amaze.filemanager.GlideApp; import com.amaze.filemanager.R; import com.amaze.filemanager.adapters.data.IconDataParcelable; @@ -50,6 +51,7 @@ import com.amaze.filemanager.ui.drag.RecyclerAdapterDragListener; import com.amaze.filemanager.ui.fragments.MainFragment; import com.amaze.filemanager.ui.fragments.preference_fragments.PreferencesConstants; +import com.amaze.filemanager.ui.fragments.quickview.HasQuickView; import com.amaze.filemanager.ui.icons.Icons; import com.amaze.filemanager.ui.icons.MimeTypes; import com.amaze.filemanager.ui.provider.UtilitiesProvider; @@ -733,6 +735,12 @@ public void onBindViewHolder(final RecyclerView.ViewHolder vholder, int p) { return true; }); + if (HasQuickView.hasQuickView(rowItem.filetype) && BuildConfig.DEBUG) { + holder.quickView.setVisibility(View.VISIBLE); + final View.OnClickListener listener = v -> mainFrag.onQuickViewClicked(rowItem); + holder.quickView.setOnClickListener(listener); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { holder.checkImageView.setBackground( new CircleGradientDrawable( @@ -910,6 +918,11 @@ && getBoolean(PREFERENCE_SHOW_THUMB))) { mainFrag.onListItemClicked( isBackButton, vholder.getAdapterPosition(), rowItem, holder.checkImageViewGrid); }); + if (HasQuickView.hasQuickView(rowItem.filetype) && BuildConfig.DEBUG) { + holder.quickView.setVisibility(View.VISIBLE); + final View.OnClickListener listener = v -> mainFrag.onQuickViewClicked(rowItem); + holder.quickView.setOnClickListener(listener); + } holder.txtTitle.setText(rowItem.title); holder.imageView1.setVisibility(View.INVISIBLE); holder.genericIcon.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/com/amaze/filemanager/adapters/data/IconDataParcelable.java b/app/src/main/java/com/amaze/filemanager/adapters/data/IconDataParcelable.java index 1761b7a3aa..889d5331e3 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/data/IconDataParcelable.java +++ b/app/src/main/java/com/amaze/filemanager/adapters/data/IconDataParcelable.java @@ -20,10 +20,16 @@ package com.amaze.filemanager.adapters.data; +import com.amaze.filemanager.GlideRequests; +import com.bumptech.glide.RequestBuilder; +import com.bumptech.glide.load.engine.DiskCacheStrategy; + +import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; /** * Saves data on what should be loaded as an icon for LayoutElementParcelable @@ -64,6 +70,22 @@ public void setImageBroken(boolean imageBroken) { isImageBroken = imageBroken; } + /** + * This is hack to load this icon to a request: The problem is that cloud icons cannot be cached. + */ + @NonNull + public RequestBuilder getPreloadRequestBuilder(@NonNull GlideRequests request) { + if (type == IconDataParcelable.IMAGE_FROMFILE) { + return request.load(path); + } + + if (type == IconDataParcelable.IMAGE_FROMCLOUD) { + return request.load(path).diskCacheStrategy(DiskCacheStrategy.NONE); + } + + return request.load(image); + } + @Override public int describeContents() { return 0; diff --git a/app/src/main/java/com/amaze/filemanager/adapters/glide/RecyclerPreloadModelProvider.java b/app/src/main/java/com/amaze/filemanager/adapters/glide/RecyclerPreloadModelProvider.java index 089d5eb06b..9a73a79651 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/glide/RecyclerPreloadModelProvider.java +++ b/app/src/main/java/com/amaze/filemanager/adapters/glide/RecyclerPreloadModelProvider.java @@ -24,11 +24,11 @@ import java.util.List; import com.amaze.filemanager.GlideApp; -import com.amaze.filemanager.GlideRequest; +import com.amaze.filemanager.GlideRequests; import com.amaze.filemanager.adapters.data.IconDataParcelable; import com.bumptech.glide.ListPreloader; import com.bumptech.glide.RequestBuilder; -import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.request.RequestOptions; import android.graphics.drawable.Drawable; @@ -41,17 +41,18 @@ public class RecyclerPreloadModelProvider implements ListPreloader.PreloadModelProvider { private final List urisToLoad; - private final GlideRequest request; + private final GlideRequests requests; + private final RequestOptions cropOptions; public RecyclerPreloadModelProvider( @NonNull Fragment fragment, @NonNull List uris, boolean isCircled) { urisToLoad = uris; - GlideRequest incompleteRequest = GlideApp.with(fragment).asDrawable(); + requests = GlideApp.with(fragment); if (isCircled) { - request = incompleteRequest.circleCrop(); + cropOptions = new RequestOptions().circleCrop(); } else { - request = incompleteRequest.centerCrop(); + cropOptions = new RequestOptions().centerCrop(); } } @@ -66,14 +67,6 @@ public List getPreloadItems(int position) { @Override @Nullable public RequestBuilder getPreloadRequestBuilder(IconDataParcelable iconData) { - RequestBuilder requestBuilder; - if (iconData.type == IconDataParcelable.IMAGE_FROMFILE) { - requestBuilder = request.load(iconData.path); - } else if (iconData.type == IconDataParcelable.IMAGE_FROMCLOUD) { - requestBuilder = request.load(iconData.path).diskCacheStrategy(DiskCacheStrategy.NONE); - } else { - requestBuilder = request.load(iconData.image); - } - return requestBuilder; + return iconData.getPreloadRequestBuilder(requests).apply(cropOptions); } } diff --git a/app/src/main/java/com/amaze/filemanager/adapters/holders/ItemViewHolder.kt b/app/src/main/java/com/amaze/filemanager/adapters/holders/ItemViewHolder.kt index 0100dd9998..c1b507000f 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/holders/ItemViewHolder.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/holders/ItemViewHolder.kt @@ -68,6 +68,9 @@ class ItemViewHolder(view: View) : RecyclerView.ViewHolder(view) { @JvmField val about: ImageButton = view.findViewById(R.id.properties) + @JvmField + val quickView: ImageButton = view.findViewById(R.id.quickView) + @JvmField val checkImageView: ImageView? = view.findViewById(R.id.check_icon) 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 574d6b4c51..52c8831828 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 @@ -114,6 +114,7 @@ import com.amaze.filemanager.ui.fragments.SearchWorkerFragment; import com.amaze.filemanager.ui.fragments.TabFragment; import com.amaze.filemanager.ui.fragments.preference_fragments.PreferencesConstants; +import com.amaze.filemanager.ui.fragments.quickview.QuickViewFragment; import com.amaze.filemanager.ui.strings.StorageNamingHelper; import com.amaze.filemanager.ui.views.CustomZoomFocusChange; import com.amaze.filemanager.ui.views.appbar.AppBar; @@ -203,6 +204,7 @@ public class MainActivity extends PermissionsActivity public static final Pattern DIR_SEPARATOR = Pattern.compile("/"); public static final String TAG_ASYNC_HELPER = "async_helper"; + public static final String TAG_QUICK_VIEW_FRAGMENT = "quick_view_fragment"; private DataUtils dataUtils; @@ -829,6 +831,14 @@ public synchronized ArrayList getStorageDirectoriesL @Override public void onBackPressed() { + Fragment quickViewFragment = + getSupportFragmentManager().findFragmentById(R.id.quickViewContainer); + + if (quickViewFragment != null) { + ((QuickViewFragment) quickViewFragment).onBackPressed(); + return; + } + if (!drawer.isLocked() && drawer.isOpen()) { drawer.close(); return; 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 3ce1ef58ed..4dbb868ba0 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 @@ -40,6 +40,7 @@ import com.afollestad.materialdialogs.MaterialDialog; import com.amaze.filemanager.R; import com.amaze.filemanager.adapters.RecyclerAdapter; +import com.amaze.filemanager.adapters.data.IconDataParcelable; import com.amaze.filemanager.adapters.data.LayoutElementParcelable; import com.amaze.filemanager.adapters.holders.ItemViewHolder; import com.amaze.filemanager.application.AppConfig; @@ -67,6 +68,9 @@ import com.amaze.filemanager.ui.drag.RecyclerAdapterDragListener; import com.amaze.filemanager.ui.drag.TabFragmentBottomDragListener; import com.amaze.filemanager.ui.fragments.data.MainFragmentViewModel; +import com.amaze.filemanager.ui.fragments.quickview.QuickViewFragment; +import com.amaze.filemanager.ui.fragments.quickview.types.QuickViewImage; +import com.amaze.filemanager.ui.fragments.quickview.types.QuickViewType; import com.amaze.filemanager.ui.icons.MimeTypes; import com.amaze.filemanager.ui.provider.UtilitiesProvider; import com.amaze.filemanager.ui.selection.SelectionPopupMenu; @@ -558,6 +562,8 @@ public boolean onPrepareActionMode(ActionMode mode, Menu menu) { // called when the user selects a contextual menu item public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + assureNoQuickView(); + computeScroll(); ArrayList checkedItems = adapter.getCheckedItems(); switch (item.getItemId()) { @@ -736,6 +742,28 @@ public void onDestroyActionMode(ActionMode mode) { } }; + /** When TabFragment detects this fragment is in view it calls this method */ + public void onWorkspaceSelected() { + if (getCurrentPath() != null) { + getMainActivity().getDrawer().selectCorrectDrawerItemForPath(getCurrentPath()); + } + + final QuickViewFragment quickViewFragment = getCurrentQuickView(); + + if (quickViewFragment != null) { + quickViewFragment.runOpenVisibilityChanges(); + } + } + + /** When TabFragment detects this fragment is not in view it calls this method */ + public void onWorkspaceUnselected() { + final QuickViewFragment quickViewFragment = getCurrentQuickView(); + + if (quickViewFragment != null) { + quickViewFragment.runExitVisibilityChanges(); + } + } + private BroadcastReceiver receiver2 = new BroadcastReceiver() { @@ -908,6 +936,33 @@ public void onListItemClicked( } } + public void onQuickViewClicked(LayoutElementParcelable layoutElementParcelable) { + final IconDataParcelable iconData = layoutElementParcelable.iconData; + + final QuickViewType quickViewType = new QuickViewImage(iconData, layoutElementParcelable.title); + final QuickViewFragment fragment = QuickViewFragment.newInstance(quickViewType); + + getChildFragmentManager() + .beginTransaction() + .replace(R.id.quickViewContainer, fragment, MainActivity.TAG_QUICK_VIEW_FRAGMENT) + .commit(); + } + + private void assureNoQuickView() { + QuickViewFragment fragment = getCurrentQuickView(); + + if (fragment == null) { + return; + } + + fragment.exit(); + } + + @Nullable + private QuickViewFragment getCurrentQuickView() { + return (QuickViewFragment) getChildFragmentManager().findFragmentById(R.id.quickViewContainer); + } + public void updateTabWithDb(Tab tab) { mainFragmentViewModel.setCurrentPath(tab.path); mainFragmentViewModel.setHome(tab.home); 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 b588a5b9be..23b36a8b83 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 @@ -64,7 +64,7 @@ /** Created by Arpit on 15-12-2014. */ public class TabFragment extends Fragment implements ViewPager.OnPageChangeListener { - public List fragments = new ArrayList<>(); + public List fragments = new ArrayList<>(2); public ScreenSlidePagerAdapter mSectionsPagerAdapter; public DisablableViewPager mViewPager; @@ -265,7 +265,7 @@ public void onPageScrolled(int position, float positionOffset, int positionOffse } @Override - public void onPageSelected(int p1) { + public void onPageSelected(int selectedTabId) { mainActivity .getAppbar() .getAppbarLayout() @@ -274,7 +274,7 @@ public void onPageSelected(int p1) { .setInterpolator(new DecelerateInterpolator(2)) .start(); - MainActivity.currentTab = p1; + MainActivity.currentTab = selectedTabId; if (sharedPrefs != null) { sharedPrefs @@ -283,19 +283,25 @@ public void onPageSelected(int p1) { .apply(); } - // Log.d(getClass().getSimpleName(), "Page Selected: " + MainActivity.currentTab, new - // Exception()); + Fragment notInView = fragments.get(selectedTabId == 0 ? 1 : 0); + if (notInView instanceof MainFragment) { + ((MainFragment) notInView).onWorkspaceUnselected(); + } + + Fragment inView = fragments.get(selectedTabId); + if (inView instanceof MainFragment) { + MainFragment mainFragment = (MainFragment) inView; - Fragment fragment = fragments.get(p1); - if (fragment != null && fragment instanceof MainFragment) { - MainFragment ma = (MainFragment) fragment; - if (ma.getCurrentPath() != null) { - mainActivity.getDrawer().selectCorrectDrawerItemForPath(ma.getCurrentPath()); - updateBottomBar(ma); + if (mainFragment.getCurrentPath() != null) { + updateBottomBar(mainFragment); } + + ((MainFragment) inView).onWorkspaceSelected(); } - if (circleDrawable1 != null && circleDrawable2 != null) updateIndicator(p1); + if (circleDrawable1 != null && circleDrawable2 != null) { + updateIndicator(selectedTabId); + } } @Override diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/quickview/HasQuickView.kt b/app/src/main/java/com/amaze/filemanager/ui/fragments/quickview/HasQuickView.kt new file mode 100644 index 0000000000..a8a65b8a42 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/quickview/HasQuickView.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2014-2021 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.ui.fragments.quickview + +import com.amaze.filemanager.ui.icons.Icons +import com.amaze.filemanager.ui.icons.Icons.IMAGE + +object HasQuickView { + /** + * Check if a filetype given by [Icons.getTypeOfFile] is openable in a [QuickViewFragment] + */ + @JvmStatic + fun hasQuickView(filetype: Int): Boolean { + return filetype == IMAGE + } +} diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/quickview/QuickViewFragment.kt b/app/src/main/java/com/amaze/filemanager/ui/fragments/quickview/QuickViewFragment.kt new file mode 100644 index 0000000000..f3280eee76 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/quickview/QuickViewFragment.kt @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2014-2021 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.ui.fragments.quickview + +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Drawable +import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.ShapeDrawable +import android.os.Build +import android.os.Bundle +import android.view.* +import android.view.View.VISIBLE +import android.widget.ImageView +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.fragment.app.Fragment +import com.amaze.filemanager.GlideApp +import com.amaze.filemanager.R +import com.amaze.filemanager.ui.activities.MainActivity +import com.amaze.filemanager.ui.activities.superclasses.ThemedActivity +import com.amaze.filemanager.ui.fragments.MainFragment +import com.amaze.filemanager.ui.fragments.quickview.types.QuickViewImage +import com.amaze.filemanager.ui.fragments.quickview.types.QuickViewType +import com.amaze.filemanager.ui.views.ContrastingTextView +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.engine.GlideException +import com.bumptech.glide.request.RequestListener +import com.bumptech.glide.request.target.Target +import com.eightbitlab.supportrenderscriptblur.SupportRenderScriptBlur +import eightbitlab.com.blurview.BlurView + +class QuickViewFragment : Fragment() { + + companion object { + private const val VIEW_TYPE_ARGUMENT = "QuickViewFragment.viewTypeArgument" + + /** + * Creates a new instance of [QuickViewFragment] + * + * [viewType] is the [QuickViewType] that will be shown + */ + @JvmStatic + fun newInstance(viewType: QuickViewType): QuickViewFragment { + val arguments = Bundle().also { + it.putParcelable(VIEW_TYPE_ARGUMENT, viewType) + } + + return QuickViewFragment().also { + it.arguments = arguments + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setHasOptionsMenu(true) // HACK its needed to get a onPrepareOptionsMenu() callback + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return inflater.inflate(R.layout.quick_view_fragment, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + runOpenVisibilityChanges() + + val constraintLayout = view.findViewById(R.id.frameLayout) + constraintLayout.setOnClickListener { + exit() + } + // TODO correctly deal with BottomBar visibility + + blur(view) + + val quickViewType = requireArguments().getParcelable(VIEW_TYPE_ARGUMENT) + + when (quickViewType) { + is QuickViewImage -> showImage(quickViewType, constraintLayout) + else -> TODO() + } + } + + private fun blur(view: View) { + val activity = requireActivity() as MainActivity + val decorView: View = activity.window.decorView + val rootView = activity.currentMainFragment!!.requireView() as ViewGroup + // Set drawable to draw in the beginning of each blurred frame (Optional). + // Can be used in case your layout has a lot of transparent space and your content + // gets kinda lost after after blur is applied. + val windowBackground = decorView.background + + val blurView = view.findViewById(R.id.blurView) + blurView.setupWith(rootView) + .setFrameClearDrawable(windowBackground) + .setBlurAlgorithm(SupportRenderScriptBlur(requireContext())) + .setBlurRadius(5f) + // This is a hack to show a blurred FAB in the background of the QuickView + .setBlurAutoUpdate(false) + .setHasFixedTransformationMatrix(true) + } + + private fun showImage(quickViewType: QuickViewImage, constraintLayout: ConstraintLayout) { + val imageViewContainer = constraintLayout + .findViewById(R.id.imageViewContainer) + val containerColor = applyContainerColor(imageViewContainer) + + val textView = constraintLayout.findViewById(R.id.textView) + textView.text = quickViewType.name + ContrastingTextView.setIntelligentTextColor(requireContext(), textView, containerColor) + + runAfterSizeComputed(constraintLayout) { + val imageView = constraintLayout.findViewById(R.id.imageView) + + val incompleteRequest = GlideApp.with(this) + + val requestBuilder = quickViewType.reference.getPreloadRequestBuilder(incompleteRequest) + + val requestListener = object : RequestListener { + override fun onLoadFailed( + e: GlideException?, + model: Any?, + target: Target?, + isFirstResource: Boolean + ): Boolean { + exit() + return true + } + + override fun onResourceReady( + resource: Drawable?, + model: Any?, + target: Target?, + dataSource: DataSource?, + isFirstResource: Boolean + ): Boolean { + imageViewContainer.visibility = VISIBLE + return false + } + } + + val width = (constraintLayout.height * 0.90f).toInt() + val height = (constraintLayout.width * 0.90f).toInt() + + requestBuilder + .override(width, height) + .listener(requestListener) + .into(imageView) + } + } + + private fun runAfterSizeComputed(view: View, f: () -> Unit) { + view.viewTreeObserver.addOnGlobalLayoutListener( + object : ViewTreeObserver.OnGlobalLayoutListener { + override fun onGlobalLayout() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + view.viewTreeObserver + .removeOnGlobalLayoutListener(this) + } else { + view.viewTreeObserver + .removeGlobalOnLayoutListener(this) + } + + f() + } + }) + } + + private fun applyContainerColor(container: View): Int { + val colorPrimary = (requireActivity() as ThemedActivity).primary + + val background: Drawable = container.background + when (background) { + is ShapeDrawable -> { + background.paint.color = colorPrimary + } + is GradientDrawable -> { + background.mutate() + background.setColor(colorPrimary) + } + is ColorDrawable -> { + background.color = colorPrimary + } + } + + return colorPrimary + } + + override fun onPrepareOptionsMenu(menu: Menu) { + super.onPrepareOptionsMenu(menu) + + (requireActivity() as MainActivity).appbar.toolbar.menu + .setGroupVisible(0, false) + + (requireActivity() as MainActivity).appbar.bottomBar + .setIsClickEnabled(false) + } + + /** + * Called by MainActivity when a back press is detected, so the fragment can react + */ + fun onBackPressed() { + exit() + } + + /** + * Exits the fragment undoing all that was changed when [MainFragment] creates this fragment, + * see [MainFragment.onQuickViewClicked] + */ + fun exit() { + parentFragmentManager + .beginTransaction() + .remove(this) + .commit() + + runExitVisibilityChanges() + } + + /** + * Run the visibility changes needed for this fragment to look good + */ + fun runOpenVisibilityChanges() { + (requireActivity() as MainActivity) + .appbar.toolbar.menu.setGroupVisible(0, false) + + (requireActivity() as MainActivity) + .appbar.bottomBar.setIsClickEnabled(false) + + (requireActivity() as MainActivity) + .fab.mainFab.visibility = View.GONE + } + + /** + * Undo the visibility changes needed for this fragment to look good + */ + fun runExitVisibilityChanges() { + (requireActivity() as MainActivity) + .appbar.toolbar.menu.setGroupVisible(0, true) + + (requireActivity() as MainActivity).appbar.bottomBar + .setIsClickEnabled(true) + + (requireActivity() as MainActivity) + .fab.mainFab.visibility = VISIBLE + } +} diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/quickview/QuickViewViewModel.kt b/app/src/main/java/com/amaze/filemanager/ui/fragments/quickview/QuickViewViewModel.kt new file mode 100644 index 0000000000..f7b88cac48 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/quickview/QuickViewViewModel.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2014-2021 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.ui.fragments.quickview + +import androidx.lifecycle.ViewModel + +class QuickViewViewModel : ViewModel() { + // TODO: Implement the ViewModel +} diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/quickview/types/QuickViewType.kt b/app/src/main/java/com/amaze/filemanager/ui/fragments/quickview/types/QuickViewType.kt new file mode 100644 index 0000000000..e32bbec5e6 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/quickview/types/QuickViewType.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2014-2021 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.ui.fragments.quickview.types + +import android.os.Parcelable +import com.amaze.filemanager.adapters.data.IconDataParcelable +import com.amaze.filemanager.ui.fragments.quickview.HasQuickView +import com.amaze.filemanager.ui.fragments.quickview.QuickViewFragment +import kotlinx.parcelize.Parcelize + +/** + * This class represents any filetype that is openable in a [QuickViewFragment] + * and contains all information to show it in one + * + * Check [HasQuickView] to see if the filetype is openable in a [QuickViewFragment] + */ +@Parcelize +open class QuickViewType(open val name: String) : Parcelable + +data class QuickViewImage( + val reference: IconDataParcelable, + override val name: String +) : QuickViewType(name) diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/ContrastingTextView.kt b/app/src/main/java/com/amaze/filemanager/ui/views/ContrastingTextView.kt new file mode 100644 index 0000000000..5a2a825962 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/ui/views/ContrastingTextView.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2014-2021 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.ui.views + +import android.content.Context +import android.graphics.Color +import android.widget.TextView +import androidx.annotation.FloatRange +import com.amaze.filemanager.utils.Utils +import java.lang.Math.pow +import kotlin.math.pow + +object ContrastingTextView { + /** + * Correctly sets text color based on a given background color so that the + * user can see the text correctly + */ + @JvmStatic + fun setIntelligentTextColor(context: Context, textView: TextView, backgroundColor: Int) { + val red = Color.red(backgroundColor) / 255.0 + val green = Color.green(backgroundColor) / 255.0 + val blue = Color.blue(backgroundColor) / 255.0 + + val linearRed = computeLinearValueForChannel(red) + val linearGreen = computeLinearValueForChannel(green) + val linearBlue = computeLinearValueForChannel(blue) + + val luminance = (0.2126 * linearRed + 0.7152 * linearGreen + 0.0722 * linearBlue) + + val perceivedLuminance = computePerceivedLuminance(luminance) + + val color = if (perceivedLuminance > 50) android.R.color.black else android.R.color.white + + textView.setTextColor(Utils.getColor(context, color)) + } + + @FloatRange(from = 0.0, to = 1.0) + private fun computeLinearValueForChannel(value: Double): Double { + return if (value <= 0.04045) { + value / 12.92 + } else { + ((value + 0.055) / 1.055).pow(2.4) + } + } + + @FloatRange(from = 0.0, to = 100.0) + private fun computePerceivedLuminance(luminance: Double): Double { + return if (luminance <= 216.0 / 24389.0) { + // The CIE standard states 0.008856 but 216/24389 is the intent for 0.008856451679036 + luminance * (24389.0 / 27.0) + // The CIE standard states 903.3, but 24389/27 is the intent, making 903.296296296296296 + } else { + luminance.pow(1.0 / 3.0) * 116 - 16 + } + } +} diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/ThemedTextView.java b/app/src/main/java/com/amaze/filemanager/ui/views/ThemedTextView.java deleted file mode 100644 index 88010426e4..0000000000 --- a/app/src/main/java/com/amaze/filemanager/ui/views/ThemedTextView.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , - * Emmanuel Messulam, Raymond Lai and Contributors. - * - * This file is part of Amaze File Manager. - * - * Amaze File Manager is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.amaze.filemanager.ui.views; - -import org.jetbrains.annotations.NotNull; - -import com.amaze.filemanager.ui.activities.MainActivity; -import com.amaze.filemanager.ui.theme.AppTheme; -import com.amaze.filemanager.utils.Utils; - -import android.content.Context; -import android.util.AttributeSet; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.appcompat.widget.AppCompatTextView; - -/** - * Created by vishal on 18/1/17. - * - *

Class sets text color based on current theme, without explicit method call in app lifecycle To - * be used only under themed activity context - */ -public class ThemedTextView extends AppCompatTextView { - - public ThemedTextView(Context context, AttributeSet attrs) { - super(context, attrs); - setTextViewColor(this, context); - } - - public static void setTextViewColor(@NotNull TextView textView, @NonNull Context context) { - if (((MainActivity) context).getAppTheme().equals(AppTheme.LIGHT)) { - textView.setTextColor(Utils.getColor(context, android.R.color.black)); - } else if (((MainActivity) context).getAppTheme().equals(AppTheme.DARK) - || ((MainActivity) context).getAppTheme().equals(AppTheme.BLACK)) { - textView.setTextColor(Utils.getColor(context, android.R.color.white)); - } - } -} diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/ThemedTextView.kt b/app/src/main/java/com/amaze/filemanager/ui/views/ThemedTextView.kt new file mode 100644 index 0000000000..f0818d71fd --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/ui/views/ThemedTextView.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2014-2021 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.ui.views + +import android.R +import android.content.Context +import android.util.AttributeSet +import android.widget.TextView +import androidx.appcompat.widget.AppCompatTextView +import com.amaze.filemanager.ui.activities.MainActivity +import com.amaze.filemanager.ui.theme.AppTheme +import com.amaze.filemanager.utils.Utils + +/** + * Class sets text color based on current theme, without explicit method call in app lifecycle To + * be used only under themed activity context + * + * @deprecated Use [ContrastingTextView.setIntelligentTextColor] + */ +class ThemedTextView(context: Context, attrs: AttributeSet?) : AppCompatTextView(context, attrs) { + companion object { + /** + * This updates a [TextView] in [MainActivity] with the correct color based on theme + * + * @deprecated Use [ContrastingTextView.setIntelligentTextColor] + */ + @JvmStatic + fun setTextViewColor(textView: TextView, context: Context) { + if ((context as MainActivity).appTheme == AppTheme.LIGHT) { + textView.setTextColor(Utils.getColor(context, R.color.black)) + } else if (context.appTheme == AppTheme.DARK || context.appTheme == AppTheme.BLACK) { + textView.setTextColor(Utils.getColor(context, R.color.white)) + } + } + } + + init { + setTextViewColor(this, context) + } +} 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 73e22635ed..8ff6f10865 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 @@ -98,6 +98,8 @@ public class BottomBar implements View.OnTouchListener { private CountDownTimer timer; private GestureDetector gestureDetector; + private boolean isEnabledClick = true; + public BottomBar(AppBar appbar, MainActivity a) { mainActivity = a; this.appbar = appbar; @@ -240,6 +242,10 @@ public boolean areButtonsShowing() { return buttons.getVisibility() == View.VISIBLE; } + public void setIsClickEnabled(boolean isEnabled) { + isEnabledClick = isEnabled; + } + public void showButtons(final BottomBarButtonPath buttonPathInterface) { final String path = buttonPathInterface.getPath(); if (buttons.getVisibility() == View.VISIBLE) { @@ -588,6 +594,10 @@ private void sendScroll(final HorizontalScrollView scrollView) { @Override public boolean onTouch(View v, MotionEvent event) { + if (!isEnabledClick) { + return true; + } + return gestureDetector.onTouchEvent(event); } } diff --git a/app/src/main/res/drawable/ic_baseline_remove_red_eye_24.xml b/app/src/main/res/drawable/ic_baseline_remove_red_eye_24.xml new file mode 100644 index 0000000000..18f157cc9a --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_remove_red_eye_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/rounded_corners.xml b/app/src/main/res/drawable/rounded_corners.xml new file mode 100644 index 0000000000..fe4e696d0b --- /dev/null +++ b/app/src/main/res/drawable/rounded_corners.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/griditem.xml b/app/src/main/res/layout/griditem.xml index e9825b8301..380c70bb26 100644 --- a/app/src/main/res/layout/griditem.xml +++ b/app/src/main/res/layout/griditem.xml @@ -1,4 +1,5 @@ + + @@ -124,13 +139,12 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/line1" - android:layout_toStartOf="@+id/properties" - android:layout_toLeftOf="@id/properties" + android:layout_toStartOf="@+id/quickView" + android:layout_toLeftOf="@id/quickView" android:baselineAligned="false" android:gravity="center_vertical" android:orientation="horizontal"> - + + diff --git a/app/src/main/res/layout/quick_view_fragment.xml b/app/src/main/res/layout/quick_view_fragment.xml new file mode 100644 index 0000000000..77263dac9e --- /dev/null +++ b/app/src/main/res/layout/quick_view_fragment.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/rowlayout.xml b/app/src/main/res/layout/rowlayout.xml index f168515e8b..151f516cf6 100644 --- a/app/src/main/res/layout/rowlayout.xml +++ b/app/src/main/res/layout/rowlayout.xml @@ -18,6 +18,7 @@ --> + + diff --git a/app/src/main/res/layout/view_quickview_base.xml b/app/src/main/res/layout/view_quickview_base.xml new file mode 100644 index 0000000000..93082ef72e --- /dev/null +++ b/app/src/main/res/layout/view_quickview_base.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 4d30142fc6..4977e5c23d 100644 --- a/build.gradle +++ b/build.gradle @@ -13,6 +13,7 @@ buildscript { awaitilityVersion = "3.1.6" androidMaterialVersion = "1.3.0" androidXFragmentVersion = "1.3.0" + androidXLifecycleVersion = "2.3.1" androidXAppCompatVersion = "1.2.0" androidXTestVersion = "1.3.0" androidXTestExtVersion = "1.1.2"