Collapsible navigation drawer groups

pull/996/head
Alex Baker 4 years ago
parent a946b8e216
commit fba0f1cfec

File diff suppressed because it is too large Load Diff

@ -17,14 +17,19 @@ import android.view.ViewGroup;
import android.widget.BaseAdapter;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.FilterListItem;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import org.tasks.LocalBroadcastManager;
import org.tasks.billing.Inventory;
import org.tasks.data.CaldavDao;
import org.tasks.data.GoogleTaskDao;
import org.tasks.filters.NavigationDrawerSubheader;
import org.tasks.locale.Locale;
import org.tasks.preferences.Preferences;
import org.tasks.themes.ColorProvider;
import org.tasks.themes.Theme;
import org.tasks.themes.ThemeAccent;
@ -39,6 +44,10 @@ public class FilterAdapter extends BaseAdapter {
private final Locale locale;
private final Inventory inventory;
private final ColorProvider colorProvider;
private final Preferences preferences;
private final GoogleTaskDao googleTaskDao;
private final CaldavDao caldavDao;
private final LocalBroadcastManager localBroadcastManager;
private final LayoutInflater inflater;
private Filter selected = null;
private List<FilterListItem> items = new ArrayList<>();
@ -49,12 +58,20 @@ public class FilterAdapter extends BaseAdapter {
Theme theme,
Locale locale,
Inventory inventory,
ColorProvider colorProvider) {
ColorProvider colorProvider,
Preferences preferences,
GoogleTaskDao googleTaskDao,
CaldavDao caldavDao,
LocalBroadcastManager localBroadcastManager) {
this.activity = activity;
this.accent = theme.getThemeAccent();
this.locale = locale;
this.inventory = inventory;
this.colorProvider = colorProvider;
this.preferences = preferences;
this.googleTaskDao = googleTaskDao;
this.caldavDao = caldavDao;
this.localBroadcastManager = localBroadcastManager;
this.inflater = theme.getLayoutInflater(activity);
}
@ -69,13 +86,9 @@ public class FilterAdapter extends BaseAdapter {
}
public void setData(List<FilterListItem> items, @Nullable Filter selected) {
setData(items, selected, -1);
}
public void setData(List<FilterListItem> items, @Nullable Filter selected, int defaultIndex) {
assertMainThread();
this.items = items;
this.selected = defaultIndex >= 0 ? getFilter(indexOf(selected, defaultIndex)) : selected;
this.selected = selected;
notifyDataSetChanged();
}
@ -91,11 +104,6 @@ public class FilterAdapter extends BaseAdapter {
return items.get(position);
}
private Filter getFilter(int position) {
FilterListItem item = getItem(position);
return item instanceof Filter ? (Filter) item : null;
}
@Override
public long getItemId(int position) {
return position;
@ -105,7 +113,7 @@ public class FilterAdapter extends BaseAdapter {
private View newView(View convertView, ViewGroup parent, FilterListItem.Type viewType) {
if (convertView == null) {
convertView = inflater.inflate(viewType.layout, parent, false);
FilterViewHolder viewHolder;
ViewHolder viewHolder;
switch (viewType) {
case ITEM:
viewHolder =
@ -116,7 +124,14 @@ public class FilterAdapter extends BaseAdapter {
viewHolder = new FilterViewHolder(convertView);
break;
case SUBHEADER:
viewHolder = new FilterViewHolder(convertView, activity);
viewHolder =
new SubheaderViewHolder(
convertView,
activity,
preferences,
googleTaskDao,
caldavDao,
localBroadcastManager);
break;
default:
throw new RuntimeException();
@ -142,13 +157,13 @@ public class FilterAdapter extends BaseAdapter {
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
FilterListItem item = getItem(position);
convertView = newView(convertView, parent, item.getItemType());
FilterViewHolder viewHolder = (FilterViewHolder) convertView.getTag();
ViewHolder viewHolder = (ViewHolder) convertView.getTag();
switch (item.getItemType()) {
case ITEM:
viewHolder.bind(item, item.equals(selected), 0);
((FilterViewHolder) viewHolder).bind(item, item.equals(selected), 0);
break;
case SUBHEADER:
viewHolder.bind((NavigationDrawerSubheader) item);
((SubheaderViewHolder) viewHolder).bind((NavigationDrawerSubheader) item);
break;
case SEPARATOR:
break;

@ -25,7 +25,6 @@ import com.todoroo.astrid.api.GtasksFilter;
import com.todoroo.astrid.api.TagFilter;
import org.tasks.R;
import org.tasks.billing.Inventory;
import org.tasks.filters.NavigationDrawerSubheader;
import org.tasks.locale.Locale;
import org.tasks.preferences.SyncPreferences;
import org.tasks.themes.ColorProvider;
@ -164,11 +163,6 @@ public class FilterViewHolder extends RecyclerView.ViewHolder {
}
}
public void bind(NavigationDrawerSubheader filter) {
text.setText(filter.listingTitle);
icon.setVisibility(filter.error ? View.VISIBLE : View.GONE);
}
public interface OnClick {
void onClick(@Nullable FilterListItem item);
}

@ -20,6 +20,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.DiffUtil.ItemCallback;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.todoroo.astrid.adapter.FilterViewHolder.OnClick;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.FilterListItem;
@ -27,14 +28,18 @@ import com.todoroo.astrid.api.FilterListItem.Type;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
import org.tasks.LocalBroadcastManager;
import org.tasks.billing.Inventory;
import org.tasks.data.CaldavDao;
import org.tasks.data.GoogleTaskDao;
import org.tasks.filters.NavigationDrawerSubheader;
import org.tasks.locale.Locale;
import org.tasks.preferences.Preferences;
import org.tasks.themes.ColorProvider;
import org.tasks.themes.Theme;
import org.tasks.themes.ThemeAccent;
public class NavigationDrawerAdapter extends ListAdapter<FilterListItem, FilterViewHolder> {
public class NavigationDrawerAdapter extends ListAdapter<FilterListItem, ViewHolder> {
private static final String TOKEN_SELECTED = "token_selected";
private final Activity activity;
@ -42,6 +47,10 @@ public class NavigationDrawerAdapter extends ListAdapter<FilterListItem, FilterV
private final Locale locale;
private final Inventory inventory;
private final ColorProvider colorProvider;
private final Preferences preferences;
private final GoogleTaskDao googleTaskDao;
private final CaldavDao caldavDao;
private final LocalBroadcastManager localBroadcastManager;
private final LayoutInflater inflater;
private OnClick onClick;
private Filter selected = null;
@ -53,13 +62,21 @@ public class NavigationDrawerAdapter extends ListAdapter<FilterListItem, FilterV
Theme theme,
Locale locale,
Inventory inventory,
ColorProvider colorProvider) {
ColorProvider colorProvider,
Preferences preferences,
GoogleTaskDao googleTaskDao,
CaldavDao caldavDao,
LocalBroadcastManager localBroadcastManager) {
super(new DiffCallback());
this.activity = activity;
this.accent = theme.getThemeAccent();
this.locale = locale;
this.inventory = inventory;
this.colorProvider = colorProvider;
this.preferences = preferences;
this.googleTaskDao = googleTaskDao;
this.caldavDao = caldavDao;
this.localBroadcastManager = localBroadcastManager;
this.inflater = theme.getLayoutInflater(activity);
}
@ -97,14 +114,15 @@ public class NavigationDrawerAdapter extends ListAdapter<FilterListItem, FilterV
@NonNull
@Override
public FilterViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
Type type = Type.values()[viewType];
View view = inflater.inflate(type.layout, parent, false);
if (type == ITEM) {
return new FilterViewHolder(
view, accent, true, locale, activity, inventory, colorProvider, this::onClickFilter);
} else if (type == SUBHEADER) {
return new FilterViewHolder(view, activity);
return new SubheaderViewHolder(
view, activity, preferences, googleTaskDao, caldavDao, localBroadcastManager);
} else {
return new FilterViewHolder(view);
}
@ -115,13 +133,14 @@ public class NavigationDrawerAdapter extends ListAdapter<FilterListItem, FilterV
}
@Override
public void onBindViewHolder(@NonNull FilterViewHolder holder, int position) {
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
FilterListItem item = getItem(position);
Type type = item.getItemType();
if (type == ITEM) {
holder.bind(item, item.equals(selected), item.count >= 0 ? item.count : counts.get(item));
((FilterViewHolder) holder)
.bind(item, item.equals(selected), item.count >= 0 ? item.count : counts.get(item));
} else if (type == SUBHEADER) {
holder.bind((NavigationDrawerSubheader) item);
((SubheaderViewHolder) holder).bind((NavigationDrawerSubheader) item);
}
}

@ -0,0 +1,83 @@
package com.todoroo.astrid.adapter;
import android.app.Activity;
import android.content.Intent;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import org.tasks.LocalBroadcastManager;
import org.tasks.R;
import org.tasks.data.CaldavDao;
import org.tasks.data.GoogleTaskDao;
import org.tasks.filters.NavigationDrawerSubheader;
import org.tasks.preferences.Preferences;
import org.tasks.preferences.SyncPreferences;
import org.tasks.themes.DrawableUtil;
public class SubheaderViewHolder extends RecyclerView.ViewHolder {
private final Preferences preferences;
private final GoogleTaskDao googleTaskDao;
private final CaldavDao caldavDao;
private final LocalBroadcastManager localBroadcastManager;
@BindView(R.id.text)
TextView text;
@BindView(R.id.icon_error)
ImageView errorIcon;
private NavigationDrawerSubheader subheader;
SubheaderViewHolder(
@NonNull View itemView,
Activity activity,
Preferences preferences,
GoogleTaskDao googleTaskDao,
CaldavDao caldavDao,
LocalBroadcastManager localBroadcastManager) {
super(itemView);
this.preferences = preferences;
this.googleTaskDao = googleTaskDao;
this.caldavDao = caldavDao;
this.localBroadcastManager = localBroadcastManager;
ButterKnife.bind(this, itemView);
errorIcon.setOnClickListener(
v -> activity.startActivity(new Intent(activity, SyncPreferences.class)));
}
@OnClick(R.id.subheader_row)
public void onClick() {
boolean collapsed = !subheader.isCollapsed();
switch (subheader.getSubheaderType()) {
case PREFERENCE:
preferences.setBoolean((int) subheader.getId(), collapsed);
break;
case GOOGLE_TASKS:
googleTaskDao.setCollapsed(subheader.getId(), collapsed);
break;
case CALDAV:
caldavDao.setCollapsed(subheader.getId(), collapsed);
break;
}
localBroadcastManager.broadcastRefreshList();
}
public void bind(NavigationDrawerSubheader subheader) {
this.subheader = subheader;
text.setText(subheader.listingTitle);
errorIcon.setVisibility(subheader.error ? View.VISIBLE : View.GONE);
DrawableUtil.setRightDrawable(
itemView.getContext(),
text,
subheader.isCollapsed()
? R.drawable.ic_keyboard_arrow_up_black_18dp
: R.drawable.ic_keyboard_arrow_down_black_18dp);
}
}

@ -58,7 +58,7 @@ import org.tasks.notifications.NotificationDao;
CaldavAccount.class,
GoogleTaskAccount.class
},
version = 71)
version = 72)
public abstract class Database extends RoomDatabase {
public static final String NAME = "database";

@ -0,0 +1,5 @@
package org.tasks;
public interface Function<T> {
T call();
}

@ -1,5 +1,7 @@
package org.tasks.activities;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import com.todoroo.andlib.utility.AndroidUtilities;
@ -10,6 +12,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import javax.inject.Inject;
import org.tasks.LocalBroadcastManager;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.filters.FilterProvider;
import org.tasks.injection.ActivityComponent;
@ -26,9 +29,16 @@ public class FilterSelectionActivity extends InjectingAppCompatActivity {
@Inject DialogBuilder dialogBuilder;
@Inject FilterAdapter filterAdapter;
@Inject FilterProvider filterProvider;
@Inject LocalBroadcastManager localBroadcastManager;
private CompositeDisposable disposables;
private Filter selected;
private BroadcastReceiver refreshReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
refresh();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -72,17 +82,18 @@ public class FilterSelectionActivity extends InjectingAppCompatActivity {
super.onResume();
disposables = new CompositeDisposable();
disposables.add(
Single.fromCallable(() -> filterProvider.getItems(false))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(items -> filterAdapter.setData(items, selected)));
localBroadcastManager.registerRefreshListReceiver(refreshReceiver);
refresh();
}
@Override
protected void onPause() {
super.onPause();
localBroadcastManager.unregisterReceiver(refreshReceiver);
disposables.dispose();
}
@ -93,6 +104,14 @@ public class FilterSelectionActivity extends InjectingAppCompatActivity {
filterAdapter.save(outState);
}
private void refresh() {
disposables.add(
Single.fromCallable(() -> filterProvider.getItems(false))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(items -> filterAdapter.setData(items, selected)));
}
@Override
public void inject(ActivityComponent component) {
component.inject(this);

@ -2,6 +2,8 @@ package org.tasks.activities;
import android.app.Activity;
import android.app.Dialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.NonNull;
@ -17,6 +19,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import javax.inject.Inject;
import org.tasks.LocalBroadcastManager;
import org.tasks.R;
import org.tasks.dialogs.AlertDialogBuilder;
import org.tasks.dialogs.DialogBuilder;
@ -37,8 +40,15 @@ public class RemoteListPicker extends InjectingDialogFragment
@Inject FilterAdapter filterAdapter;
@Inject FilterProvider filterProvider;
@Inject SyncAdapters syncAdapters;
@Inject LocalBroadcastManager localBroadcastManager;
private CompositeDisposable disposables;
private BroadcastReceiver refreshReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
refresh();
}
};
public static RemoteListPicker newRemoteListSupportPicker(
Filter selected, Fragment targetFragment, int requestCode) {
@ -103,22 +113,19 @@ public class RemoteListPicker extends InjectingDialogFragment
public void onResume() {
super.onResume();
Bundle arguments = getArguments();
boolean noSelection = arguments.getBoolean(EXTRA_NO_SELECTION, false);
Filter selected = noSelection ? null : arguments.getParcelable(EXTRA_SELECTED_FILTER);
disposables = new CompositeDisposable();
disposables =
new CompositeDisposable(
Single.fromCallable(filterProvider::getRemoteListPickerItems)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(items -> filterAdapter.setData(items, selected, noSelection ? -1 : 0)));
localBroadcastManager.registerRefreshListReceiver(refreshReceiver);
refresh();
}
@Override
public void onPause() {
super.onPause();
localBroadcastManager.unregisterReceiver(refreshReceiver);
disposables.dispose();
}
@ -147,4 +154,15 @@ public class RemoteListPicker extends InjectingDialogFragment
Activity.RESULT_OK,
new Intent().putExtra(EXTRA_SELECTED_FILTER, filter));
}
private void refresh() {
Bundle arguments = getArguments();
boolean noSelection = arguments.getBoolean(EXTRA_NO_SELECTION, false);
Filter selected = noSelection ? null : arguments.getParcelable(EXTRA_SELECTED_FILTER);
disposables.add(Single.fromCallable(filterProvider::getRemoteListPickerItems)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(items -> filterAdapter.setData(items, selected)));
}
}

@ -61,6 +61,9 @@ public class CaldavAccount implements Parcelable {
@ColumnInfo(name = "cda_account_type")
private int accountType;
@ColumnInfo(name = "cda_collapsed")
private boolean collapsed;
public CaldavAccount() {}
@Ignore
@ -75,6 +78,7 @@ public class CaldavAccount implements Parcelable {
suppressRepeatingTasks = ParcelCompat.readBoolean(source);
accountType = source.readInt();
encryptionKey = source.readString();
collapsed = ParcelCompat.readBoolean(source);
}
public long getId() {
@ -173,6 +177,14 @@ public class CaldavAccount implements Parcelable {
return accountType == TYPE_ETESYNC;
}
public boolean isCollapsed() {
return collapsed;
}
public void setCollapsed(boolean collapsed) {
this.collapsed = collapsed;
}
@Override
public String toString() {
return "CaldavAccount{"
@ -203,6 +215,8 @@ public class CaldavAccount implements Parcelable {
+ '\''
+ ", accountType="
+ accountType
+ ", collapsed="
+ collapsed
+ '}';
}
@ -226,6 +240,9 @@ public class CaldavAccount implements Parcelable {
if (accountType != that.accountType) {
return false;
}
if (collapsed != that.collapsed) {
return false;
}
if (uuid != null ? !uuid.equals(that.uuid) : that.uuid != null) {
return false;
}
@ -261,6 +278,7 @@ public class CaldavAccount implements Parcelable {
result = 31 * result + (suppressRepeatingTasks ? 1 : 0);
result = 31 * result + (encryptionKey != null ? encryptionKey.hashCode() : 0);
result = 31 * result + accountType;
result = 31 * result + (collapsed ? 1 : 0);
return result;
}
@ -281,5 +299,6 @@ public class CaldavAccount implements Parcelable {
ParcelCompat.writeBoolean(dest, suppressRepeatingTasks);
dest.writeInt(accountType);
dest.writeString(encryptionKey);
ParcelCompat.writeBoolean(dest, collapsed);
}
}

@ -30,6 +30,9 @@ public abstract class CaldavDao {
@Query("SELECT * FROM caldav_accounts ORDER BY UPPER(cda_name) ASC")
public abstract List<CaldavAccount> getAccounts();
@Query("UPDATE caldav_accounts SET cda_collapsed = :collapsed WHERE cda_id = :id")
public abstract void setCollapsed(long id, boolean collapsed);
@Insert
public abstract long insert(CaldavAccount caldavAccount);

@ -2,6 +2,7 @@ package org.tasks.data;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.core.os.ParcelCompat;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
@ -35,6 +36,9 @@ public class GoogleTaskAccount implements Parcelable {
@ColumnInfo(name = "gta_etag")
private String etag;
@ColumnInfo(name = "gta_collapsed")
private boolean collapsed;
public GoogleTaskAccount() {}
@Ignore
@ -43,6 +47,7 @@ public class GoogleTaskAccount implements Parcelable {
account = source.readString();
error = source.readString();
etag = source.readString();
collapsed = ParcelCompat.readBoolean(source);
}
@Ignore
@ -82,12 +87,20 @@ public class GoogleTaskAccount implements Parcelable {
this.etag = etag;
}
public boolean isCollapsed() {
return collapsed;
}
public void setCollapsed(boolean collapsed) {
this.collapsed = collapsed;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
if (!(o instanceof GoogleTaskAccount)) {
return false;
}
@ -96,6 +109,9 @@ public class GoogleTaskAccount implements Parcelable {
if (id != that.id) {
return false;
}
if (collapsed != that.collapsed) {
return false;
}
if (account != null ? !account.equals(that.account) : that.account != null) {
return false;
}
@ -111,6 +127,7 @@ public class GoogleTaskAccount implements Parcelable {
result = 31 * result + (account != null ? account.hashCode() : 0);
result = 31 * result + (error != null ? error.hashCode() : 0);
result = 31 * result + (etag != null ? etag.hashCode() : 0);
result = 31 * result + (collapsed ? 1 : 0);
return result;
}
@ -128,6 +145,8 @@ public class GoogleTaskAccount implements Parcelable {
+ ", etag='"
+ etag
+ '\''
+ ", collapsed="
+ collapsed
+ '}';
}
@ -142,5 +161,6 @@ public class GoogleTaskAccount implements Parcelable {
dest.writeString(account);
dest.writeString(error);
dest.writeString(etag);
ParcelCompat.writeBoolean(dest, collapsed);
}
}

@ -67,6 +67,9 @@ public abstract class GoogleTaskDao {
update(task);
}
@Query("UPDATE google_task_accounts SET gta_collapsed = :collapsed WHERE gta_id = :id")
public abstract void setCollapsed(long id, boolean collapsed);
@Query("SELECT * FROM google_tasks WHERE gt_task = :taskId AND gt_deleted = 0 LIMIT 1")
public abstract GoogleTask getByTaskId(long taskId);

@ -391,6 +391,17 @@ public class Migrations {
}
};
private static final Migration MIGRATION_71_72 =
new Migration(71, 72) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL(
"ALTER TABLE `caldav_accounts` ADD COLUMN `cda_collapsed` INTEGER NOT NULL DEFAULT 0");
database.execSQL(
"ALTER TABLE `google_task_accounts` ADD COLUMN `gta_collapsed` INTEGER NOT NULL DEFAULT 0");
}
};
public static final Migration[] MIGRATIONS =
new Migration[] {
MIGRATION_35_36,
@ -419,7 +430,8 @@ public class Migrations {
MIGRATION_67_68,
MIGRATION_68_69,
MIGRATION_69_70,
MIGRATION_70_71
MIGRATION_70_71,
MIGRATION_71_72
};
private static Migration NOOP(int from, int to) {

@ -21,12 +21,14 @@ import com.todoroo.astrid.gtasks.GtasksFilterExposer;
import com.todoroo.astrid.tags.TagFilterExposer;
import com.todoroo.astrid.timers.TimerFilterExposer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.inject.Inject;
import org.tasks.BuildConfig;
import org.tasks.Function;
import org.tasks.R;
import org.tasks.activities.GoogleTaskListSettingsActivity;
import org.tasks.activities.TagSettingsActivity;
@ -36,9 +38,11 @@ import org.tasks.caldav.CaldavFilterExposer;
import org.tasks.data.CaldavAccount;
import org.tasks.data.GoogleTaskAccount;
import org.tasks.etesync.EteSyncCalendarSettingsActivity;
import org.tasks.filters.NavigationDrawerSubheader.SubheaderType;
import org.tasks.injection.ForApplication;
import org.tasks.preferences.HelpAndFeedback;
import org.tasks.preferences.MainPreferences;
import org.tasks.preferences.Preferences;
import org.tasks.ui.NavigationDrawerFragment;
public class FilterProvider {
@ -51,6 +55,7 @@ public class FilterProvider {
private final TagFilterExposer tagFilterExposer;
private final GtasksFilterExposer gtasksFilterExposer;
private final CaldavFilterExposer caldavFilterExposer;
private final Preferences preferences;
@Inject
public FilterProvider(
@ -61,7 +66,8 @@ public class FilterProvider {
CustomFilterExposer customFilterExposer,
TagFilterExposer tagFilterExposer,
GtasksFilterExposer gtasksFilterExposer,
CaldavFilterExposer caldavFilterExposer) {
CaldavFilterExposer caldavFilterExposer,
Preferences preferences) {
this.context = context;
this.inventory = inventory;
this.builtInFilterExposer = builtInFilterExposer;
@ -70,6 +76,7 @@ public class FilterProvider {
this.tagFilterExposer = tagFilterExposer;
this.gtasksFilterExposer = gtasksFilterExposer;
this.caldavFilterExposer = caldavFilterExposer;
this.preferences = preferences;
}
public List<FilterListItem> getRemoteListPickerItems() {
@ -85,14 +92,26 @@ public class FilterProvider {
GoogleTaskAccount account = filters.getKey();
items.addAll(
getSubmenu(
account.getAccount(), !isNullOrEmpty(account.getError()), filters.getValue(), true));
account.getAccount(),
!isNullOrEmpty(account.getError()),
account.isCollapsed() ? Collections.emptyList() : filters.getValue(),
true,
account.isCollapsed(),
SubheaderType.GOOGLE_TASKS,
account.getId()));
}
for (Map.Entry<CaldavAccount, List<Filter>> filters : getCaldavFilters()) {
CaldavAccount account = filters.getKey();
items.addAll(
getSubmenu(
account.getName(), !isNullOrEmpty(account.getError()), filters.getValue(), true));
account.getName(),
!isNullOrEmpty(account.getError()),
account.isCollapsed() ? Collections.emptyList() : filters.getValue(),
true,
account.isCollapsed(),
SubheaderType.CALDAV,
account.getId()));
}
return items;
@ -105,9 +124,9 @@ public class FilterProvider {
items.add(builtInFilterExposer.getMyTasksFilter());
items.addAll(getSubmenu(R.string.filters, getFilters()));
items.addAll(getSubmenu(R.string.filters, R.string.p_collapse_filters, this::getFilters));
if (navigationDrawer) {
if (navigationDrawer && !preferences.getBoolean(R.string.p_collapse_filters, false)) {
items.add(
new NavigationDrawerAction(
context.getString(R.string.FLA_new_filter),
@ -116,9 +135,10 @@ public class FilterProvider {
NavigationDrawerFragment.REQUEST_NEW_LIST));
}
items.addAll(getSubmenu(R.string.tags, tagFilterExposer.getFilters()));
items.addAll(
getSubmenu(R.string.tags, R.string.p_collapse_tags, tagFilterExposer::getFilters));
if (navigationDrawer) {
if (navigationDrawer && !preferences.getBoolean(R.string.p_collapse_tags, false)) {
items.add(
new NavigationDrawerAction(
context.getString(R.string.new_tag),
@ -133,10 +153,13 @@ public class FilterProvider {
getSubmenu(
account.getAccount(),
!isNullOrEmpty(account.getError()),
filters.getValue(),
!navigationDrawer));
account.isCollapsed() ? Collections.emptyList() : filters.getValue(),
!navigationDrawer,
account.isCollapsed(),
SubheaderType.GOOGLE_TASKS,
account.getId()));
if (navigationDrawer) {
if (navigationDrawer && !account.isCollapsed()) {
items.add(
new NavigationDrawerAction(
context.getString(R.string.new_list),
@ -153,10 +176,13 @@ public class FilterProvider {
getSubmenu(
account.getName(),
!isNullOrEmpty(account.getError()),
filters.getValue(),
!navigationDrawer));
account.isCollapsed() ? Collections.emptyList() : filters.getValue(),
!navigationDrawer,
account.isCollapsed(),
SubheaderType.CALDAV,
account.getId()));
if (navigationDrawer) {
if (navigationDrawer && !account.isCollapsed()) {
items.add(
new NavigationDrawerAction(
context.getString(R.string.new_list),
@ -223,15 +249,29 @@ public class FilterProvider {
return caldavFilterExposer.getFilters().entrySet();
}
private List<FilterListItem> getSubmenu(int title, List<Filter> filters) {
return getSubmenu(context.getString(title), false, filters, false);
private List<FilterListItem> getSubmenu(int title, int prefId, Function<List<Filter>> getFilters) {
boolean collapsed = preferences.getBoolean(prefId, false);
return newArrayList(
concat(
ImmutableList.of(
new NavigationDrawerSubheader(
context.getString(title), false, collapsed, SubheaderType.PREFERENCE, prefId)),
collapsed ? Collections.emptyList() : getFilters.call()));
}
private List<FilterListItem> getSubmenu(
String title, boolean error, List<Filter> filters, boolean hideIfEmpty) {
return hideIfEmpty && filters.isEmpty()
String title,
boolean error,
List<Filter> filters,
boolean hideIfEmpty,
boolean collapsed,
SubheaderType type,
long id) {
return hideIfEmpty && filters.isEmpty() && !collapsed
? ImmutableList.of()
: newArrayList(
concat(ImmutableList.of(new NavigationDrawerSubheader(title, error)), filters));
concat(
ImmutableList.of(new NavigationDrawerSubheader(title, error, collapsed, type, id)),
filters));
}
}

@ -3,6 +3,7 @@ package org.tasks.filters;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.core.os.ParcelCompat;
import com.todoroo.astrid.api.FilterListItem;
public class NavigationDrawerSubheader extends FilterListItem {
@ -24,40 +25,104 @@ public class NavigationDrawerSubheader extends FilterListItem {
return new NavigationDrawerSubheader[size];
}
};
public boolean error;
private boolean collapsed;
private SubheaderType subheaderType;
private long id;
private NavigationDrawerSubheader() {}
public NavigationDrawerSubheader(String listingTitle, boolean error) {
public NavigationDrawerSubheader(
String listingTitle, boolean error, boolean collapsed, SubheaderType subheaderType, long id) {
this.error = error;
this.collapsed = collapsed;
this.subheaderType = subheaderType;
this.id = id;
this.listingTitle = listingTitle;
}
public long getId() {
return id;
}
public boolean isCollapsed() {
return collapsed;
}
public SubheaderType getSubheaderType() {
return subheaderType;
}
@Override
protected void readFromParcel(Parcel source) {
super.readFromParcel(source);
error = source.readInt() == 1;
error = ParcelCompat.readBoolean(source);
collapsed = ParcelCompat.readBoolean(source);
subheaderType = (SubheaderType) source.readSerializable();
id = source.readLong();
}
@Override
public boolean areItemsTheSame(@NonNull FilterListItem other) {
return other instanceof NavigationDrawerSubheader && listingTitle.equals(other.listingTitle);
return other instanceof NavigationDrawerSubheader
&& subheaderType == ((NavigationDrawerSubheader) other).getSubheaderType()
&& id == ((NavigationDrawerSubheader) other).getId();
}
@Override
public boolean areContentsTheSame(@NonNull FilterListItem other) {
return super.areContentsTheSame(other) && error == ((NavigationDrawerSubheader) other).error;
return this.equals(other);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof NavigationDrawerSubheader)) {
return false;
}
NavigationDrawerSubheader that = (NavigationDrawerSubheader) o;
if (error != that.error) {
return false;
}
if (collapsed != that.collapsed) {
return false;
}
if (id != that.id) {
return false;
}
return subheaderType == that.subheaderType;
}
@Override
public int hashCode() {
int result = (error ? 1 : 0);
result = 31 * result + (collapsed ? 1 : 0);
result = 31 * result + (subheaderType != null ? subheaderType.hashCode() : 0);
result = 31 * result + (int) (id ^ (id >>> 32));
return result;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(error ? 1 : 0);
ParcelCompat.writeBoolean(dest, error);
ParcelCompat.writeBoolean(dest, collapsed);
dest.writeSerializable(subheaderType);
dest.writeLong(id);
}
@Override
public Type getItemType() {
return Type.SUBHEADER;
}
public enum SubheaderType {
PREFERENCE,
GOOGLE_TASKS,
CALDAV
}
}

@ -25,6 +25,15 @@ public class DrawableUtil {
}
}
public static void setRightDrawable(Context context, TextView tv, @DrawableRes int resId) {
Drawable wrapped = getWrapped(context, resId);
if (atLeastJellybeanMR1()) {
tv.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, wrapped, null);
} else {
tv.setCompoundDrawablesWithIntrinsicBounds(null, null, wrapped, null);
}
}
public static Drawable getLeftDrawable(TextView tv) {
return atLeastJellybeanMR1()
? tv.getCompoundDrawablesRelative()[0]

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="18dp"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="18dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M7.41,7.84L12,12.42l4.59,-4.58L18,9.25l-6,6 -6,-6z"/>
</vector>

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="18dp"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="18dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z"/>
</vector>

@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/row"
android:background="@drawable/drawer_background_selector"
android:foreground="?attr/selectableItemBackground"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingTop="12dp"

@ -2,12 +2,14 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:background="@drawable/drawer_background_selector"
android:focusable="false"
android:orientation="vertical">
<View style="@style/horizontal_divider"/>
<View
android:id="@+id/divider"
android:paddingBottom="4dp"
style="@style/horizontal_divider"
android:layout_gravity="top" />
</LinearLayout>

@ -2,58 +2,62 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="48dp"
android:paddingTop="8dp"
android:background="@drawable/drawer_background_selector"
android:clickable="true"
android:id="@+id/subheader_row"
android:background="?attr/selectableItemBackground"
android:focusable="true"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:id="@+id/divider"
style="@style/horizontal_divider"
android:layout_gravity="top"/>
<View
android:id="@+id/divider"
android:paddingBottom="4dp"
style="@style/horizontal_divider"
android:layout_gravity="top" />
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_below="@id/divider"
android:paddingTop="12dp"
android:paddingStart="0dp"
android:paddingEnd="@dimen/keyline_first"
android:paddingLeft="0dp"
android:paddingRight="@dimen/keyline_first"
android:alpha="@dimen/alpha_secondary"
android:clickable="true"
android:focusable="true"
android:scaleType="center"
android:src="@drawable/ic_outline_sync_problem_24px"
android:tint="@color/icon_tint"
android:visibility="gone"/>
<ImageView
android:id="@+id/icon_error"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignBottom="@id/text"
android:layout_below="@id/divider"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:paddingStart="0dp"
android:paddingEnd="@dimen/keyline_first"
android:paddingLeft="0dp"
android:paddingRight="@dimen/keyline_first"
android:alpha="@dimen/alpha_secondary"
android:clickable="true"
android:focusable="true"
android:scaleType="center"
android:src="@drawable/ic_outline_sync_problem_24px"
android:tint="@color/overdue"
android:visibility="gone" />
<CheckedTextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@id/divider"
android:layout_toLeftOf="@id/icon"
android:layout_toStartOf="@id/icon"
android:paddingTop="12dp"
android:paddingStart="@dimen/keyline_first"
android:paddingEnd="@dimen/keyline_first"
android:paddingLeft="@dimen/keyline_first"
android:paddingRight="@dimen/keyline_first"
android:alpha="0.54"
android:clickable="false"
android:ellipsize="end"
android:fontFamily="@string/font_fontFamily_medium"
android:gravity="start|center_vertical"
android:singleLine="true"
android:textAlignment="viewStart"
android:textSize="14sp"
tools:ignore="UnusedAttribute"/>
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:textColor="@color/text_secondary"
android:fontFamily="@string/font_fontFamily_medium"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@id/divider"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:gravity="start|center_vertical"
android:drawableTint="@color/icon_tint_with_alpha"
android:singleLine="true"
android:textAlignment="viewStart"
android:textSize="14sp"
android:paddingStart="@dimen/keyline_first"
android:paddingLeft="@dimen/keyline_first"
android:paddingEnd="@dimen/keyline_first"
android:paddingRight="@dimen/keyline_first"
tools:ignore="UnusedAttribute" />
</RelativeLayout>

@ -291,4 +291,6 @@
<string name="p_chip_style">chip_style</string>
<string name="p_chip_appearance">chip_appearance</string>
<string name="p_desaturate_colors">desaturate_colors</string>
<string name="p_collapse_filters">collapse_filters</string>
<string name="p_collapse_tags">collapse_tags</string>
</resources>

Loading…
Cancel
Save