Make it possible to sort the home screen (#7048)

This commit is contained in:
Fredrik Wallén 2024-04-05 20:45:26 +02:00 committed by GitHub
parent 687db0f5ed
commit 00d6df6261
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 444 additions and 74 deletions

View File

@ -6,22 +6,26 @@ import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentContainerView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.databinding.HomeFragmentBinding;
import de.danoeh.antennapod.event.FeedListUpdateEvent;
import de.danoeh.antennapod.event.FeedUpdateRunningEvent;
import de.danoeh.antennapod.model.feed.FeedItemFilter;
import de.danoeh.antennapod.net.download.serviceinterface.FeedUpdateManager;
import de.danoeh.antennapod.storage.database.DBReader;
import de.danoeh.antennapod.ui.echo.EchoConfig;
import de.danoeh.antennapod.ui.screen.SearchFragment;
import de.danoeh.antennapod.ui.screen.home.sections.AllowNotificationsSection;
import de.danoeh.antennapod.ui.screen.home.sections.DownloadsSection;
import de.danoeh.antennapod.ui.screen.home.sections.EchoSection;
@ -29,27 +33,19 @@ import de.danoeh.antennapod.ui.screen.home.sections.EpisodesSurpriseSection;
import de.danoeh.antennapod.ui.screen.home.sections.InboxSection;
import de.danoeh.antennapod.ui.screen.home.sections.QueueSection;
import de.danoeh.antennapod.ui.screen.home.sections.SubscriptionsSection;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.storage.database.DBReader;
import de.danoeh.antennapod.databinding.HomeFragmentBinding;
import de.danoeh.antennapod.event.FeedListUpdateEvent;
import de.danoeh.antennapod.event.FeedUpdateRunningEvent;
import de.danoeh.antennapod.ui.screen.SearchFragment;
import de.danoeh.antennapod.ui.screen.home.settingsdialog.HomePreferences;
import de.danoeh.antennapod.ui.screen.home.settingsdialog.HomeSectionsSettingsDialog;
import de.danoeh.antennapod.ui.view.LiftOnScrollListener;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.util.Calendar;
import java.util.List;
/**
* Shows unread or recently published episodes
@ -58,7 +54,6 @@ public class HomeFragment extends Fragment implements Toolbar.OnMenuItemClickLis
public static final String TAG = "HomeFragment";
public static final String PREF_NAME = "PrefHomeFragment";
public static final String PREF_HIDDEN_SECTIONS = "PrefHomeSectionsString";
public static final String PREF_DISABLE_NOTIFICATION_PERMISSION_NAG = "DisableNotificationPermissionNag";
public static final String PREF_HIDE_ECHO = "HideEcho";
@ -106,12 +101,8 @@ public class HomeFragment extends Fragment implements Toolbar.OnMenuItemClickLis
addSection(new EchoSection());
}
List<String> hiddenSections = getHiddenSections(getContext());
String[] sectionTags = getResources().getStringArray(R.array.home_section_tags);
List<String> sectionTags = HomePreferences.getSortedSectionTags(getContext());
for (String sectionTag : sectionTags) {
if (hiddenSections.contains(sectionTag)) {
continue;
}
addSection(getSection(sectionTag));
}
}
@ -140,12 +131,6 @@ public class HomeFragment extends Fragment implements Toolbar.OnMenuItemClickLis
}
}
public static List<String> getHiddenSections(Context context) {
SharedPreferences prefs = context.getSharedPreferences(HomeFragment.PREF_NAME, Context.MODE_PRIVATE);
String hiddenSectionsString = prefs.getString(HomeFragment.PREF_HIDDEN_SECTIONS, "");
return new ArrayList<>(Arrays.asList(TextUtils.split(hiddenSectionsString, ",")));
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEventMainThread(FeedUpdateRunningEvent event) {
viewBinding.swipeRefresh.setRefreshing(event.isFeedUpdateRunning);
@ -154,7 +139,7 @@ public class HomeFragment extends Fragment implements Toolbar.OnMenuItemClickLis
@Override
public boolean onMenuItemClick(MenuItem item) {
if (item.getItemId() == R.id.homesettings_items) {
HomeSectionsSettingsDialog.open(getContext(), (dialogInterface, i) -> populateSectionList());
HomeSectionsSettingsDialog.open(getContext(), this::populateSectionList);
return true;
} else if (item.getItemId() == R.id.refresh_item) {
FeedUpdateManager.getInstance().runOnceOrAsk(requireContext());

View File

@ -1,42 +0,0 @@
package de.danoeh.antennapod.ui.screen.home;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.text.TextUtils;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import de.danoeh.antennapod.R;
import java.util.List;
public class HomeSectionsSettingsDialog {
public static void open(Context context, DialogInterface.OnClickListener onSettingsChanged) {
final List<String> hiddenSections = HomeFragment.getHiddenSections(context);
String[] sectionLabels = context.getResources().getStringArray(R.array.home_section_titles);
String[] sectionTags = context.getResources().getStringArray(R.array.home_section_tags);
final boolean[] checked = new boolean[sectionLabels.length];
for (int i = 0; i < sectionLabels.length; i++) {
String tag = sectionTags[i];
if (!hiddenSections.contains(tag)) {
checked[i] = true;
}
}
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
builder.setTitle(R.string.configure_home);
builder.setMultiChoiceItems(sectionLabels, checked, (dialog, which, isChecked) -> {
if (isChecked) {
hiddenSections.remove(sectionTags[which]);
} else {
hiddenSections.add(sectionTags[which]);
}
});
builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> {
SharedPreferences prefs = context.getSharedPreferences(HomeFragment.PREF_NAME, Context.MODE_PRIVATE);
prefs.edit().putString(HomeFragment.PREF_HIDDEN_SECTIONS, TextUtils.join(",", hiddenSections)).apply();
onSettingsChanged.onClick(dialog, which);
});
builder.setNegativeButton(R.string.cancel_label, null);
builder.create().show();
}
}

View File

@ -0,0 +1,85 @@
package de.danoeh.antennapod.ui.screen.home.settingsdialog;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.text.TextUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.ui.screen.home.HomeFragment;
public class HomePreferences {
private static final String PREF_HIDDEN_SECTIONS = "PrefHomeSectionsString";
private static final String PREF_SECTION_ORDER = "PrefHomeSectionOrder";
private static HashMap<String, String> sectionTagToName;
public static String getNameFromTag(Context context, String sectionTag) {
if (sectionTagToName == null) {
initializeMap(context);
}
return sectionTagToName.get(sectionTag);
}
private static void initializeMap(Context context) {
Resources resources = context.getResources();
String[] sectionLabels = resources.getStringArray(R.array.home_section_titles);
String[] sectionTags = resources.getStringArray(R.array.home_section_tags);
sectionTagToName = new HashMap<>(sectionTags.length);
for (int i = 0; i < sectionLabels.length; i++) {
String label = sectionLabels[i];
String tag = sectionTags[i];
sectionTagToName.put(tag, label);
}
}
public static List<String> getHiddenSectionTags(Context context) {
return getListPreference(context, PREF_HIDDEN_SECTIONS);
}
public static List<String> getSortedSectionTags(Context context) {
List<String> sectionTagOrder = getListPreference(context, PREF_SECTION_ORDER);
List<String> hiddenSectionTags = getHiddenSectionTags(context);
String[] sectionTags = context.getResources().getStringArray(R.array.home_section_tags);
Arrays.sort(sectionTags, (String a, String b) -> Integer.signum(
indexOfOrMaxValue(sectionTagOrder, a) - indexOfOrMaxValue(sectionTagOrder, b)));
List<String> finalSectionTags = new ArrayList<>();
for (String sectionTag: sectionTags) {
if (hiddenSectionTags.contains(sectionTag)) {
continue;
}
finalSectionTags.add(sectionTag);
}
return finalSectionTags;
}
private static List<String> getListPreference(Context context, String preferenceKey) {
SharedPreferences prefs = context.getSharedPreferences(HomeFragment.PREF_NAME, Context.MODE_PRIVATE);
String hiddenSectionsString = prefs.getString(preferenceKey, "");
return new ArrayList<>(Arrays.asList(TextUtils.split(hiddenSectionsString, ",")));
}
private static int indexOfOrMaxValue(List<String> haystack, String needle) {
int index = haystack.indexOf(needle);
return index == -1 ? Integer.MAX_VALUE : index;
}
public static void saveChanges(Context context, List<String> hiddenSections, List<String> sectionOrder) {
SharedPreferences prefs = context.getSharedPreferences(HomeFragment.PREF_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor edit = prefs.edit();
edit.putString(PREF_HIDDEN_SECTIONS, TextUtils.join(",", hiddenSections));
edit.putString(PREF_SECTION_ORDER, TextUtils.join(",", sectionOrder));
edit.apply();
}
}

View File

@ -0,0 +1,148 @@
package de.danoeh.antennapod.ui.screen.home.settingsdialog;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.Consumer;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import de.danoeh.antennapod.databinding.ChooseHomeScreenOrderDialogEntryBinding;
import de.danoeh.antennapod.databinding.ChooseHomeScreenOrderDialogHeaderBinding;
class HomeScreenSettingDialogAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int HEADER_VIEW = 0;
private static final int ITEM_VIEW = 1;
private final List<HomeScreenSettingsDialogItem> settingsDialogItems;
@Nullable private Consumer<ItemViewHolder> dragListener;
public HomeScreenSettingDialogAdapter(@NonNull List<HomeScreenSettingsDialogItem> dialogItems) {
settingsDialogItems = dialogItems;
}
public void setDragListener(@Nullable Consumer<ItemViewHolder> dragListener) {
this.dragListener = dragListener;
}
@NonNull
public List<String> getOrderedSectionTags() {
List<String> orderedSectionTags = new ArrayList<>();
for (HomeScreenSettingsDialogItem item: settingsDialogItems) {
if (item.getViewType() == HomeScreenSettingsDialogItem.ViewType.Header) {
continue;
}
orderedSectionTags.add(item.getTitle());
}
return orderedSectionTags;
}
public List<String> getHiddenSectionTags() {
List<String> hiddenSections = new ArrayList<>();
for (int i = settingsDialogItems.size() - 1; i >= 0; i--) {
HomeScreenSettingsDialogItem item = settingsDialogItems.get(i);
if (item.getViewType() == HomeScreenSettingsDialogItem.ViewType.Header) {
return hiddenSections;
}
hiddenSections.add(item.getTitle());
}
Collections.reverse(hiddenSections);
return hiddenSections;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
if (viewType == HEADER_VIEW) {
ChooseHomeScreenOrderDialogHeaderBinding binding = ChooseHomeScreenOrderDialogHeaderBinding.inflate(
inflater, parent, false);
return new HeaderViewHolder(binding.getRoot(), binding.headerLabel);
}
ChooseHomeScreenOrderDialogEntryBinding binding = ChooseHomeScreenOrderDialogEntryBinding.inflate(
inflater, parent, false);
return new ItemViewHolder(binding.getRoot(), binding.sectionLabel, binding.dragHandle);
}
@Override
public int getItemViewType(int position) {
HomeScreenSettingsDialogItem.ViewType viewType = settingsDialogItems.get(position).getViewType();
boolean isHeader = viewType == HomeScreenSettingsDialogItem.ViewType.Header;
return isHeader ? HEADER_VIEW : ITEM_VIEW;
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
String title = settingsDialogItems.get(position).getTitle();
if (holder instanceof HeaderViewHolder) {
HeaderViewHolder headerViewHolder = (HeaderViewHolder) holder;
headerViewHolder.categoryLabel.setText(title);
} else if (holder instanceof ItemViewHolder) {
ItemViewHolder itemViewHolder = (ItemViewHolder) holder;
String sectionName = HomePreferences.getNameFromTag(itemViewHolder.nameLabel.getContext(), title);
itemViewHolder.nameLabel.setText(sectionName);
itemViewHolder.dragImage.setOnTouchListener((view, motionEvent) -> {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
if (dragListener != null) {
dragListener.accept(itemViewHolder);
}
}
return true;
});
}
}
@Override
public int getItemCount() {
return settingsDialogItems.size();
}
public boolean onItemMove(int fromPosition, int toPosition) {
if (fromPosition < toPosition) {
for (int i = fromPosition; i < toPosition; i++) {
Collections.swap(settingsDialogItems, i, i + 1);
}
} else {
for (int i = fromPosition; i > toPosition; i--) {
Collections.swap(settingsDialogItems, i, i - 1);
}
}
notifyItemMoved(fromPosition, toPosition);
return true;
}
static class ItemViewHolder extends RecyclerView.ViewHolder {
private final TextView nameLabel;
private final ImageView dragImage;
ItemViewHolder(@NonNull View itemView, TextView nameLabel, ImageView dragImage) {
super(itemView);
this.nameLabel = nameLabel;
this.dragImage = dragImage;
}
}
static class HeaderViewHolder extends RecyclerView.ViewHolder {
private final TextView categoryLabel;
HeaderViewHolder(@NonNull View itemView, @NonNull TextView categoryLabel) {
super(itemView);
this.categoryLabel = categoryLabel;
}
}
}

View File

@ -0,0 +1,24 @@
package de.danoeh.antennapod.ui.screen.home.settingsdialog;
final class HomeScreenSettingsDialogItem {
enum ViewType {
Section,
Header
}
private final ViewType viewType;
private final String title;
HomeScreenSettingsDialogItem(ViewType viewType, String title) {
this.viewType = viewType;
this.title = title;
}
public ViewType getViewType() {
return viewType;
}
public String getTitle() {
return title;
}
}

View File

@ -0,0 +1,82 @@
package de.danoeh.antennapod.ui.screen.home.settingsdialog;
import android.content.Context;
import android.view.LayoutInflater;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.databinding.ChooseHomeScreenOrderDialogBinding;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class HomeSectionsSettingsDialog {
public static void open(Context context, Runnable onSettingsChanged) {
LayoutInflater layoutInflater = LayoutInflater.from(context);
ChooseHomeScreenOrderDialogBinding viewBinding = ChooseHomeScreenOrderDialogBinding.inflate(layoutInflater);
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
builder.setTitle(R.string.configure_home);
builder.setView(viewBinding.getRoot());
RecyclerView recyclerView = viewBinding.recyclerView;
List<HomeScreenSettingsDialogItem> dialogItems = initialItemsSettingsDialog(context);
HomeScreenSettingDialogAdapter adapter = new HomeScreenSettingDialogAdapter(dialogItems);
configureRecyclerView(recyclerView, adapter, context);
builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> {
final List<String> sectionOrder = adapter.getOrderedSectionTags();
final List<String> hiddenSections = adapter.getHiddenSectionTags();
HomePreferences.saveChanges(context, hiddenSections, sectionOrder);
onSettingsChanged.run();
});
builder.setNegativeButton(R.string.cancel_label, null);
builder.setNeutralButton(R.string.reset, (dialog, which) -> {
HomePreferences.saveChanges(context, Collections.emptyList(), Collections.emptyList());
onSettingsChanged.run();
});
builder.show();
}
private static void configureRecyclerView(RecyclerView recyclerView,
HomeScreenSettingDialogAdapter adapter, Context context) {
ItemTouchCallback itemMoveCallback = new ItemTouchCallback() {
@Override
protected boolean onItemMove(int fromPosition, int toPosition) {
return adapter.onItemMove(fromPosition, toPosition);
}
};
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemMoveCallback);
itemTouchHelper.attachToRecyclerView(recyclerView);
adapter.setDragListener(itemTouchHelper::startDrag);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
recyclerView.setAdapter(adapter);
}
@NonNull
private static List<HomeScreenSettingsDialogItem> initialItemsSettingsDialog(@NonNull Context context) {
final List<String> sectionTags = HomePreferences.getSortedSectionTags(context);
final List<String> hiddenSectionTags = HomePreferences.getHiddenSectionTags(context);
ArrayList<HomeScreenSettingsDialogItem> settingsDialogItems = new ArrayList<>();
for (String sectionTag: sectionTags) {
settingsDialogItems.add(new HomeScreenSettingsDialogItem(
HomeScreenSettingsDialogItem.ViewType.Section, sectionTag));
}
String hiddenText = context.getString(R.string.section_hidden);
settingsDialogItems.add(new HomeScreenSettingsDialogItem(
HomeScreenSettingsDialogItem.ViewType.Header, hiddenText));
for (String sectionTag: hiddenSectionTags) {
settingsDialogItems.add(new HomeScreenSettingsDialogItem(
HomeScreenSettingsDialogItem.ViewType.Section, sectionTag));
}
return settingsDialogItems;
}
}

View File

@ -0,0 +1,32 @@
package de.danoeh.antennapod.ui.screen.home.settingsdialog;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
public abstract class ItemTouchCallback extends ItemTouchHelper.Callback {
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
final int swipeFlags = 0;
return makeMovementFlags(dragFlags, swipeFlags);
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
return onItemMove(viewHolder.getBindingAdapterPosition(), target.getBindingAdapterPosition());
}
protected abstract boolean onItemMove(int fromPosition, int toPosition);
@Override
public boolean isItemViewSwipeEnabled() {
return false;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
}
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="16sp" />

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/dragHandle"
android:layout_width="48dp"
android:layout_height="48dp"
android:paddingStart="16dp"
android:paddingEnd="0dp"
android:paddingVertical="8dp"
android:layout_gravity="center_vertical"
android:contentDescription="@string/sort"
android:src="?attr/dragview_background" />
<TextView
android:id="@+id/sectionLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:layout_gravity="center_vertical"
android:textColor="?android:attr/textColorPrimary"
android:textSize="18sp" />
</LinearLayout>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp">
<TextView
android:id="@+id/headerLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="18sp" />
</LinearLayout>

View File

@ -69,6 +69,7 @@
<string name="deny_label">Deny</string>
<string name="open_settings">Open settings</string>
<string name="configure_home">Configure Home Screen</string>
<string name="section_hidden">Hidden</string>
<!-- Download Statistics fragment -->
<string name="total_size_downloaded_podcasts">Total size of episodes on the device</string>