From d45edf9cae7830e44a9c302bc22b4c96cbed314f Mon Sep 17 00:00:00 2001 From: aikrq <95296093+aikrq@users.noreply.github.com> Date: Thu, 2 Jan 2025 16:56:17 +0700 Subject: [PATCH] feat: Add toggleable items to delete & use MaterialSwitch instead --- .../manage/ManageLocalLibraryActivity.java | 287 +++++++++++++----- .../main/res/layout/manage_locallibraries.xml | 20 ++ .../main/res/layout/view_item_local_lib.xml | 37 +-- .../res/layout/view_item_local_lib_search.xml | 26 +- .../menu/menu_expanded_local_libraries.xml | 17 ++ 5 files changed, 270 insertions(+), 117 deletions(-) create mode 100644 app/src/main/res/menu/menu_expanded_local_libraries.xml diff --git a/app/src/main/java/dev/aldi/sayuti/editor/manage/ManageLocalLibraryActivity.java b/app/src/main/java/dev/aldi/sayuti/editor/manage/ManageLocalLibraryActivity.java index 6091ee6897..bbb1df9686 100644 --- a/app/src/main/java/dev/aldi/sayuti/editor/manage/ManageLocalLibraryActivity.java +++ b/app/src/main/java/dev/aldi/sayuti/editor/manage/ManageLocalLibraryActivity.java @@ -1,5 +1,8 @@ package dev.aldi.sayuti.editor.manage; +import static pro.sketchware.utility.FileUtil.formatFileSize; +import static pro.sketchware.utility.FileUtil.getFileSize; + import android.annotation.SuppressLint; import android.net.Uri; import android.os.Bundle; @@ -14,12 +17,14 @@ import androidx.activity.EdgeToEdge; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.besome.sketch.lib.base.BaseAppCompatActivity; +import com.google.android.material.card.MaterialCardView; import com.google.gson.Gson; import java.io.File; @@ -53,12 +58,17 @@ public class ManageLocalLibraryActivity extends BaseAppCompatActivity { private static String localLibsPath; private boolean notAssociatedWithProject; + private boolean searchBarExpanded; private String localLibFile; private BuildSettings buildSettings; private ManageLocallibrariesBinding binding; private LibraryAdapter adapter = new LibraryAdapter(); private SearchAdapter searchAdapter = new SearchAdapter(); + private interface OnLocalLibrarySelectedStateChangedListener { + void onLocalLibrarySelectedStateChanged(LocalLibrary library); + } + static { localLibsPath = FileUtil.getExternalStorageDir().concat("/.sketchware/libs/local_libs/"); } @@ -72,6 +82,12 @@ public void onCreate(Bundle savedInstanceState) { ViewCompat.setOnApplyWindowInsetsListener(binding.downloadLibraryButton, new AddMarginOnApplyWindowInsetsListener(WindowInsetsCompat.Type.navigationBars(), WindowInsetsCompat.CONSUMED)); + ViewCompat.setOnApplyWindowInsetsListener(binding.contextualToolbarContainer, (v, windowInsets) -> { + var insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); + v.setPadding(insets.left, insets.top, insets.right, 0); + return windowInsets; + }); + if (getIntent().hasExtra("sc_id")) { String scId = Objects.requireNonNull(getIntent().getStringExtra("sc_id")); buildSettings = new BuildSettings(scId); @@ -79,6 +95,17 @@ public void onCreate(Bundle savedInstanceState) { localLibFile = FileUtil.getExternalStorageDir().concat("/.sketchware/data/").concat(scId.concat("/local_library")); } + adapter.setOnLocalLibrarySelectedStateChangedListener(item -> { + long selectedItemCount = getSelectedLocalLibrariesCount(); + if (selectedItemCount > 0 && adapter.isSelectionModeEnabled) { + binding.contextualToolbar.setTitle(String.valueOf(selectedItemCount)); + expandContextualToolbar(); + } else { + adapter.isSelectionModeEnabled = false; + collapseContextualToolbar(); + } + }); + binding.librariesList.setAdapter(adapter); binding.searchList.setAdapter(searchAdapter); @@ -88,6 +115,31 @@ public void onCreate(Bundle savedInstanceState) { } }); + binding.contextualToolbar.setNavigationOnClickListener(v -> hideContextualToolbarAndClearSelection()); + binding.contextualToolbar.setOnMenuItemClickListener(item -> { + int id = item.getItemId(); + if (id == R.id.action_select_all) { + setLocalLibrariesSelected(true); + binding.contextualToolbar.setTitle(String.valueOf(getSelectedLocalLibrariesCount())); + return true; + } else if (id == R.id.action_delete_selected_local_libraries) { + new Thread(() -> { + for (LocalLibrary library : getSelectedLocalLibraries()) { + FileUtil.deleteFile(localLibsPath.concat(library.getName())); + } + + runOnUiThread(() -> { + SketchwareUtil.toast("Deleted successfully"); + runLoadLocalLibrariesTask(); + adapter.isSelectionModeEnabled = false; + collapseContextualToolbar(); + }); + }).start(); + return true; + } + return false; + }); + binding.downloadLibraryButton.setOnClickListener(v -> { if (getSupportFragmentManager().findFragmentByTag("library_downloader_dialog") != null) { return; @@ -112,7 +164,7 @@ public void beforeTextChanged(CharSequence s, int start, int count, int after) { @Override public void afterTextChanged(Editable s) { String value = s.toString().trim(); - searchAdapter.filter(adapter.getLibraryFiles(), value); + searchAdapter.filter(adapter.getLocalLibraries(), value); } @Override @@ -123,6 +175,44 @@ public void onTextChanged(CharSequence newText, int start, int before, int count runLoadLocalLibrariesTask(); } + private void hideContextualToolbarAndClearSelection() { + adapter.isSelectionModeEnabled = false; + if (collapseContextualToolbar()) { + setLocalLibrariesSelected(false); + } + } + + public void setLocalLibrariesSelected(boolean selected) { + for (LocalLibrary library : adapter.getLocalLibraries()) { + library.setSelected(selected); + } + adapter.notifyDataSetChanged(); + } + + private void expandContextualToolbar() { + searchBarExpanded = true; + binding.searchBar.expand(binding.contextualToolbarContainer, binding.appBarLayout); + } + + private boolean collapseContextualToolbar() { + searchBarExpanded = false; + return binding.searchBar.collapse(binding.contextualToolbarContainer, binding.appBarLayout); + } + + private long getSelectedLocalLibrariesCount() { + return getSelectedLocalLibraries().isEmpty() ? 0 : getSelectedLocalLibraries().size(); + } + + private List getSelectedLocalLibraries() { + List selectedLocalLibraries = new LinkedList<>(); + for (LocalLibrary library : adapter.getLocalLibraries()) { + if (library.isSelected()) { + selectedLocalLibraries.add(library); + } + } + return selectedLocalLibraries; + } + private void runLoadLocalLibrariesTask() { k(); new Handler().postDelayed(() -> { @@ -132,7 +222,9 @@ private void runLoadLocalLibrariesTask() { @Override public void onBackPressed() { - if (binding.searchView.isShowing()) { + if (searchBarExpanded) { + hideContextualToolbarAndClearSelection(); + } else if (binding.searchView.isShowing()) { binding.searchView.hide(); } else { super.onBackPressed(); @@ -155,16 +247,16 @@ private void loadLibraries() { FileUtil.listDirAsFile(localLibsPath, localLibraryFiles); localLibraryFiles.sort(new LocalLibrariesComparator()); - List libraryFiles = new LinkedList<>(); + List localLibraries = new LinkedList<>(); for (File libraryFile : localLibraryFiles) { if (libraryFile.isDirectory()) { - libraryFiles.add(libraryFile); + localLibraries.add(LocalLibrary.fromFile(libraryFile)); } } runOnUiThread(() -> { - adapter.setLibraryFiles(libraryFiles); - binding.noContentLayout.setVisibility(libraryFiles.isEmpty() ? View.VISIBLE : View.GONE); + adapter.setLocalLibraries(localLibraries); + binding.noContentLayout.setVisibility(localLibraries.isEmpty() ? View.VISIBLE : View.GONE); }); } @@ -206,6 +298,37 @@ public static HashMap createLibraryMap(String name, String depen return localLibrary; } + private static class LocalLibrary { + private final String name; + private final String size; + private boolean isSelected; + + private LocalLibrary(String name, String size) { + this.name = name; + this.size = size; + } + + public String getName() { + return name; + } + + public String getSize() { + return size; + } + + public boolean isSelected() { + return isSelected; + } + + public void setSelected(boolean isSelected) { + this.isSelected = isSelected; + } + + public static LocalLibrary fromFile(File file) { + return new LocalLibrary(file.getName(), formatFileSize(getFileSize(file))); + } + } + private static class LoadLocalLibrariesTask extends MA { private final WeakReference activity; @@ -236,7 +359,9 @@ public void b() { } public class LibraryAdapter extends RecyclerView.Adapter { - private final List libraryFiles = new ArrayList<>(); + private final List localLibraries = new ArrayList<>(); + public boolean isSelectionModeEnabled; + private @Nullable OnLocalLibrarySelectedStateChangedListener onLocalLibrarySelectedStateChangedListener; @NonNull @Override @@ -246,52 +371,75 @@ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { @Override public void onBindViewHolder(ViewHolder holder, final int position) { - var binding = holder.binding; + final var binding = holder.binding; + final var library = localLibraries.get(position); - final File libraryFile = libraryFiles.get(position); - final String librarySize = FileUtil.formatFileSize(FileUtil.getFileSize(libraryFile)); - binding.libraryName.setText(libraryFile.getName()); - binding.librarySize.setText(librarySize); + binding.libraryName.setText(library.getName()); + binding.librarySize.setText(library.getSize()); binding.libraryName.setSelected(true); + bindSelectedState(binding.card, library); + + binding.card.setOnClickListener(v -> { + if (isSelectionModeEnabled) { + toggleLocalLibrary(binding.card, library, onLocalLibrarySelectedStateChangedListener); + } else { + binding.materialSwitch.performClick(); + } + }); + + binding.card.setOnLongClickListener(v -> { + if (isSelectionModeEnabled) { + return false; + } - binding.card.setOnClickListener(v -> binding.checkbox.performClick()); - binding.checkbox.setOnClickListener(v -> onItemClicked(binding)); + isSelectionModeEnabled = true; + toggleLocalLibrary(binding.card, library, onLocalLibrarySelectedStateChangedListener); + return true; + }); - binding.checkbox.setChecked(false); + binding.materialSwitch.setOnClickListener(v -> { + onItemClicked(binding, library.getName()); + }); + + binding.materialSwitch.setChecked(false); if (!notAssociatedWithProject) { lookupList = new Gson().fromJson(FileUtil.readFile(localLibFile), Helper.TYPE_MAP_LIST); for (HashMap localLibrary : lookupList) { - if (binding.libraryName.getText().toString().equals(Objects.requireNonNull(localLibrary.get("name")).toString())) { - binding.checkbox.setChecked(true); + if (library.getName().equals(Objects.requireNonNull(localLibrary.get("name")).toString())) { + binding.materialSwitch.setChecked(true); } } } else { - binding.checkbox.setEnabled(false); + binding.materialSwitch.setEnabled(false); } - - binding.imgDelete.setOnClickListener(v -> { - PopupMenu popupMenu = new PopupMenu(ManageLocalLibraryActivity.this, v); - popupMenu.getMenu().add(Menu.NONE, Menu.NONE, Menu.NONE, "Delete"); - popupMenu.setOnMenuItemClickListener(menuItem -> { - FileUtil.deleteFile(localLibsPath.concat(binding.libraryName.getText().toString())); - SketchwareUtil.toast("Deleted successfully"); - runLoadLocalLibrariesTask(); - return true; - }); - popupMenu.show(); - }); } @Override public int getItemCount() { - return libraryFiles.isEmpty() ? 0 : libraryFiles.size(); + return localLibraries.isEmpty() ? 0 : localLibraries.size(); + } + + public void setOnLocalLibrarySelectedStateChangedListener( + @Nullable OnLocalLibrarySelectedStateChangedListener onLocalLibrarySelectedStateChangedListener) { + this.onLocalLibrarySelectedStateChangedListener = onLocalLibrarySelectedStateChangedListener; + } + + private void toggleLocalLibrary(MaterialCardView card, LocalLibrary library, + @Nullable OnLocalLibrarySelectedStateChangedListener onLocalLibrarySelectedStateChangedListener) { + library.setSelected(!library.isSelected()); + bindSelectedState(card, library); + if (onLocalLibrarySelectedStateChangedListener != null) { + onLocalLibrarySelectedStateChangedListener.onLocalLibrarySelectedStateChanged(library); + } } - private void onItemClicked(ViewItemLocalLibBinding binding) { - String name = binding.libraryName.getText().toString(); + private void bindSelectedState(MaterialCardView card, LocalLibrary library) { + card.setChecked(library.isSelected()); + } + private void onItemClicked(ViewItemLocalLibBinding binding, String name) { HashMap localLibrary; - if (!binding.checkbox.isChecked()) { + if (!binding.materialSwitch.isChecked()) { // Remove the library from the list int indexToRemove = -1; for (int i = 0; i < projectUsedLibs.size(); i++) { @@ -320,14 +468,14 @@ private void onItemClicked(ViewItemLocalLibBinding binding) { FileUtil.writeFile(localLibFile, new Gson().toJson(projectUsedLibs)); } - public void setLibraryFiles(List libraryFiles) { - this.libraryFiles.clear(); - this.libraryFiles.addAll(libraryFiles); + public void setLocalLibraries(List localLibraries) { + this.localLibraries.clear(); + this.localLibraries.addAll(localLibraries); notifyDataSetChanged(); } - public List getLibraryFiles() { - return libraryFiles; + public List getLocalLibraries() { + return localLibraries; } static class ViewHolder extends RecyclerView.ViewHolder { @@ -341,7 +489,7 @@ public ViewHolder(@NonNull ViewItemLocalLibBinding binding) { } public class SearchAdapter extends RecyclerView.Adapter { - private final List filteredLibraryFiles = new ArrayList<>(); + private final List filteredLocalLibraries = new ArrayList<>(); @NonNull @Override @@ -352,52 +500,43 @@ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { @Override public void onBindViewHolder(ViewHolder holder, final int position) { - var binding = holder.binding; + final var binding = holder.binding; + final var library = filteredLocalLibraries.get(position); - final File libraryFile = filteredLibraryFiles.get(position); - final String librarySize = FileUtil.formatFileSize(FileUtil.getFileSize(libraryFile)); - binding.libraryName.setText(libraryFile.getName()); - binding.librarySize.setText(librarySize); + binding.libraryName.setText(library.getName()); + binding.librarySize.setText(library.getSize()); binding.libraryName.setSelected(true); - binding.getRoot().setOnClickListener(v -> binding.checkbox.performClick()); - binding.checkbox.setOnClickListener(v -> onItemClicked(binding)); + binding.getRoot().setOnClickListener(v -> { + binding.materialSwitch.performClick(); + }); + + binding.materialSwitch.setOnClickListener(v -> { + onItemClicked(binding, library.getName()); + adapter.notifyItemChanged(position); + }); - binding.checkbox.setChecked(false); + binding.materialSwitch.setChecked(false); if (!notAssociatedWithProject) { lookupList = new Gson().fromJson(FileUtil.readFile(localLibFile), Helper.TYPE_MAP_LIST); for (HashMap localLibrary : lookupList) { - if (binding.libraryName.getText().toString().equals(Objects.requireNonNull(localLibrary.get("name")).toString())) { - binding.checkbox.setChecked(true); + if (library.getName().equals(Objects.requireNonNull(localLibrary.get("name")).toString())) { + binding.materialSwitch.setChecked(true); } } } else { - binding.checkbox.setEnabled(false); + binding.materialSwitch.setEnabled(false); } - - binding.imgDelete.setOnClickListener(v -> { - PopupMenu popupMenu = new PopupMenu(ManageLocalLibraryActivity.this, v); - popupMenu.getMenu().add(Menu.NONE, Menu.NONE, Menu.NONE, "Delete"); - popupMenu.setOnMenuItemClickListener(menuItem -> { - FileUtil.deleteFile(localLibsPath.concat(binding.libraryName.getText().toString())); - SketchwareUtil.toast("Deleted successfully"); - runLoadLocalLibrariesTask(); - return true; - }); - popupMenu.show(); - }); } @Override public int getItemCount() { - return filteredLibraryFiles.isEmpty() ? 0 : filteredLibraryFiles.size(); + return filteredLocalLibraries.isEmpty() ? 0 : filteredLocalLibraries.size(); } - private void onItemClicked(ViewItemLocalLibSearchBinding binding) { - String name = binding.libraryName.getText().toString(); - + private void onItemClicked(ViewItemLocalLibSearchBinding binding, String name) { HashMap localLibrary; - if (!binding.checkbox.isChecked()) { + if (!binding.materialSwitch.isChecked()) { // Remove the library from the list int indexToRemove = -1; for (int i = 0; i < projectUsedLibs.size(); i++) { @@ -426,14 +565,14 @@ private void onItemClicked(ViewItemLocalLibSearchBinding binding) { FileUtil.writeFile(localLibFile, new Gson().toJson(projectUsedLibs)); } - public void filter(List libraryFiles, String query) { - filteredLibraryFiles.clear(); + public void filter(List localLibraries, String query) { + filteredLocalLibraries.clear(); if (query.isEmpty()) { - filteredLibraryFiles.addAll(libraryFiles); + filteredLocalLibraries.addAll(localLibraries); } else { - for (File file : libraryFiles) { - if (file.getName().toLowerCase().contains(query.toLowerCase())) { - filteredLibraryFiles.add(file); + for (LocalLibrary library : localLibraries) { + if (library.getName().toLowerCase().contains(query.toLowerCase())) { + filteredLocalLibraries.add(library); } } } diff --git a/app/src/main/res/layout/manage_locallibraries.xml b/app/src/main/res/layout/manage_locallibraries.xml index 53b4dda8af..74caa482da 100644 --- a/app/src/main/res/layout/manage_locallibraries.xml +++ b/app/src/main/res/layout/manage_locallibraries.xml @@ -33,6 +33,26 @@ + + + + + + + + app:cardCornerRadius="24dp" + app:checkedIcon="@null"> + android:paddingVertical="8dp"> - - - + app:layout_constraintTop_toTopOf="parent" /> diff --git a/app/src/main/res/layout/view_item_local_lib_search.xml b/app/src/main/res/layout/view_item_local_lib_search.xml index 25e1c01a23..a664aeaf96 100644 --- a/app/src/main/res/layout/view_item_local_lib_search.xml +++ b/app/src/main/res/layout/view_item_local_lib_search.xml @@ -6,7 +6,7 @@ android:background="?attr/selectableItemBackground" android:clickable="true" android:focusable="true" - android:paddingEnd="16dp" + android:paddingEnd="20dp" android:paddingStart="24dp" android:paddingVertical="16dp"> @@ -18,7 +18,7 @@ android:singleLine="true" android:textAppearance="?attr/textAppearanceBodyLarge" android:textColor="?attr/colorOnSurface" - app:layout_constraintEnd_toStartOf="@id/checkbox" + app:layout_constraintEnd_toStartOf="@id/material_switch" app:layout_constraintStart_toStartOf="parent" app:layout_constraintBottom_toTopOf="@id/library_size" tools:text="appcompat-v1.7.0" /> @@ -30,31 +30,17 @@ android:layout_marginTop="2dp" android:textAppearance="?attr/textAppearanceBodyMedium" android:textColor="?attr/colorOnSurfaceVariant" - app:layout_constraintEnd_toStartOf="@id/checkbox" + app:layout_constraintEnd_toStartOf="@id/material_switch" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/library_name" tools:text="3.4 KB" /> - - - + app:layout_constraintTop_toTopOf="parent" /> diff --git a/app/src/main/res/menu/menu_expanded_local_libraries.xml b/app/src/main/res/menu/menu_expanded_local_libraries.xml new file mode 100644 index 0000000000..74a7a6e587 --- /dev/null +++ b/app/src/main/res/menu/menu_expanded_local_libraries.xml @@ -0,0 +1,17 @@ + + + + + + + +