Add recycler view item long press with read state actions (set read/unread)
This commit is contained in:
parent
da13d487e4
commit
1e8195b906
@ -6,6 +6,7 @@ import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.widget.DrawerLayout;
|
||||
import android.support.v4.widget.SwipeRefreshLayout;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.DividerItemDecoration;
|
||||
@ -14,6 +15,7 @@ import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.support.v7.widget.helper.ItemTouchHelper;
|
||||
import android.util.Log;
|
||||
import android.view.ActionMode;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
@ -46,6 +48,7 @@ import org.apache.commons.collections4.CollectionUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ -87,6 +90,8 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou
|
||||
private boolean showReadItems;
|
||||
private ListSortType sortType;
|
||||
|
||||
private ActionMode actionMode;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@ -221,31 +226,105 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou
|
||||
|
||||
ViewPreloadSizeProvider preloadSizeProvider = new ViewPreloadSizeProvider();
|
||||
adapter = new MainItemListAdapter(GlideApp.with(this), preloadSizeProvider);
|
||||
adapter.setOnItemClickListener((itemWithFeed, position) -> {
|
||||
Intent intent = new Intent(this, ItemActivity.class);
|
||||
adapter.setOnItemClickListener(new MainItemListAdapter.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(ItemWithFeed itemWithFeed, int position) {
|
||||
if (actionMode == null) {
|
||||
Intent intent = new Intent(getApplicationContext(), ItemActivity.class);
|
||||
|
||||
intent.putExtra(ItemActivity.ITEM_ID, itemWithFeed.getItem().getId());
|
||||
intent.putExtra(ItemActivity.IMAGE_URL, itemWithFeed.getItem().getImageLink());
|
||||
startActivityForResult(intent, ITEM_REQUEST);
|
||||
intent.putExtra(ItemActivity.ITEM_ID, itemWithFeed.getItem().getId());
|
||||
intent.putExtra(ItemActivity.IMAGE_URL, itemWithFeed.getItem().getImageLink());
|
||||
startActivityForResult(intent, ITEM_REQUEST);
|
||||
|
||||
viewModel.setItemRead(itemWithFeed.getItem().getId())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new DisposableCompletableObserver() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
viewModel.setItemReadState(itemWithFeed.getItem().getId(), true)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new DisposableCompletableObserver() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
itemWithFeed.getItem().setRead(true);
|
||||
adapter.notifyItemChanged(position, itemWithFeed);
|
||||
updateDrawerFeeds();
|
||||
} else {
|
||||
adapter.toggleSelection(position);
|
||||
|
||||
if (adapter.getSelection().isEmpty())
|
||||
actionMode.finish();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemLongClick(ItemWithFeed itemWithFeed, int position) {
|
||||
if (actionMode != null)
|
||||
return;
|
||||
|
||||
adapter.toggleSelection(position);
|
||||
|
||||
actionMode = startActionMode(new ActionMode.Callback() {
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
|
||||
drawer.getDrawerLayout().setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
|
||||
refreshLayout.setEnabled(false);
|
||||
actionMode.getMenuInflater().inflate(R.menu.item_list_contextual_menu, menu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
|
||||
menu.findItem(R.id.item_mark_read).setVisible(!itemWithFeed.getItem().isRead());
|
||||
menu.findItem(R.id.item_mark_unread).setVisible(itemWithFeed.getItem().isRead());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
|
||||
switch (menuItem.getItemId()) {
|
||||
case R.id.item_mark_read:
|
||||
viewModel.setItemsReadState(getIdsFromPositions(adapter.getSelection()), true)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe();
|
||||
adapter.updateSelection(true);
|
||||
|
||||
break;
|
||||
case R.id.item_mark_unread:
|
||||
viewModel.setItemsReadState(getIdsFromPositions(adapter.getSelection()), false)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe();
|
||||
adapter.updateSelection(false);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
actionMode.finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public void onDestroyActionMode(ActionMode mode) {
|
||||
mode.finish();
|
||||
actionMode = null;
|
||||
|
||||
itemWithFeed.getItem().setRead(true);
|
||||
adapter.notifyItemChanged(position, itemWithFeed);
|
||||
updateDrawerFeeds();
|
||||
drawer.getDrawerLayout().setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
|
||||
refreshLayout.setEnabled(true);
|
||||
|
||||
adapter.clearSelection();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
RecyclerViewPreloader<String> preloader = new RecyclerViewPreloader<String>(Glide.with(this), adapter, preloadSizeProvider, 10);
|
||||
@ -421,7 +500,6 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou
|
||||
showReadItems = true;
|
||||
SharedPreferencesManager.writeValue(this,
|
||||
SharedPreferencesManager.SharedPrefKey.SHOW_READ_ARTICLES, true);
|
||||
|
||||
}
|
||||
|
||||
filterItems(filterFeedId);
|
||||
@ -457,8 +535,20 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou
|
||||
.show();
|
||||
}
|
||||
|
||||
private List<Integer> getIdsFromPositions(LinkedHashSet<Integer> positions) {
|
||||
List<Integer> ids = new ArrayList<>();
|
||||
|
||||
for (int position : positions) {
|
||||
ids.add((int)adapter.getItemId(position));
|
||||
}
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
public enum ListSortType {
|
||||
NEWEST_TO_OLDEST,
|
||||
OLDEST_TO_NEWEST
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -32,8 +32,13 @@ public interface ItemDao {
|
||||
@Insert
|
||||
void insertAll(List<Item> items);
|
||||
|
||||
@Query("Update Item set read = 1 Where id = :itemId")
|
||||
void setRead(int itemId);
|
||||
/**
|
||||
* Set an item read or unread
|
||||
* @param itemId if of the item to update
|
||||
* @param readState 1 for read, 0 for unread
|
||||
*/
|
||||
@Query("Update Item set read = :readState Where id = :itemId")
|
||||
void setReadState(int itemId, int readState);
|
||||
|
||||
@Query("Select count(*) From Item Where feed_id = :feedId And read = 0")
|
||||
int getUnreadCount(int feedId);
|
||||
|
@ -10,9 +10,8 @@ import com.readrops.app.database.entities.Feed;
|
||||
import com.readrops.app.database.entities.Folder;
|
||||
import com.readrops.app.database.pojo.ItemWithFeed;
|
||||
import com.readrops.app.repositories.LocalFeedRepository;
|
||||
import com.readrops.app.utils.ParsingResult;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
@ -67,10 +66,20 @@ public class MainViewModel extends AndroidViewModel {
|
||||
});
|
||||
}
|
||||
|
||||
public Completable setItemRead(int itemId) {
|
||||
public Completable setItemReadState(int itemId, boolean read) {
|
||||
return Completable.create(emitter -> {
|
||||
db.itemDao().setRead(itemId);
|
||||
db.itemDao().setReadState(itemId, read ? 1 : 0);
|
||||
emitter.onComplete();
|
||||
});
|
||||
}
|
||||
|
||||
public Completable setItemsReadState(List<Integer> ids, boolean read) {
|
||||
List<Completable> completableList = new ArrayList<>();
|
||||
|
||||
for (int id : ids) {
|
||||
completableList.add(setItemReadState(id, read));
|
||||
}
|
||||
|
||||
return Completable.concat(completableList);
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,19 @@
|
||||
package com.readrops.app.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.content.res.AppCompatResources;
|
||||
import android.support.v7.recyclerview.extensions.ListAdapter;
|
||||
import android.support.v7.util.DiffUtil;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@ -32,6 +38,7 @@ import com.readrops.app.utils.GlideRequests;
|
||||
import com.readrops.app.utils.Utils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
||||
import static com.bumptech.glide.load.resource.bitmap.BitmapTransitionOptions.withCrossFade;
|
||||
@ -42,11 +49,14 @@ public class MainItemListAdapter extends ListAdapter<ItemWithFeed, MainItemListA
|
||||
private OnItemClickListener listener;
|
||||
private ViewPreloadSizeProvider preloadSizeProvider;
|
||||
|
||||
private LinkedHashSet<Integer> selection;
|
||||
|
||||
public MainItemListAdapter(GlideRequests glideRequests, ViewPreloadSizeProvider preloadSizeProvider) {
|
||||
super(DIFF_CALLBACK);
|
||||
|
||||
this.glideRequests = glideRequests;
|
||||
this.preloadSizeProvider = preloadSizeProvider;
|
||||
selection = new LinkedHashSet<>();
|
||||
}
|
||||
|
||||
private static final DiffUtil.ItemCallback<ItemWithFeed> DIFF_CALLBACK = new DiffUtil.ItemCallback<ItemWithFeed>() {
|
||||
@ -87,8 +97,8 @@ public class MainItemListAdapter extends ListAdapter<ItemWithFeed, MainItemListA
|
||||
if (payloads.size() > 0) {
|
||||
ItemWithFeed itemWithFeed = (ItemWithFeed) payloads.get(0);
|
||||
|
||||
float alpha = itemWithFeed.getItem().isRead() ? 0.5f : 1.0f;
|
||||
holder.itemView.setAlpha(alpha);
|
||||
holder.setReadState(itemWithFeed.getItem().isRead());
|
||||
holder.setSelected(selection.contains(position));
|
||||
} else
|
||||
onBindViewHolder(holder, position);
|
||||
}
|
||||
@ -98,17 +108,6 @@ public class MainItemListAdapter extends ListAdapter<ItemWithFeed, MainItemListA
|
||||
ItemWithFeed itemWithFeed = getItem(i);
|
||||
viewHolder.bind(itemWithFeed);
|
||||
|
||||
View[] alphaViews = new View[] {
|
||||
viewHolder.dateLayout,
|
||||
viewHolder.itemFolderName,
|
||||
viewHolder.feedIcon,
|
||||
viewHolder.feedName,
|
||||
viewHolder.itemDescription,
|
||||
viewHolder.itemTitle,
|
||||
viewHolder.itemImage,
|
||||
viewHolder.itemReadTime,
|
||||
};
|
||||
|
||||
if (itemWithFeed.getItem().hasImage()) {
|
||||
viewHolder.itemImage.setVisibility(View.VISIBLE);
|
||||
|
||||
@ -156,18 +155,47 @@ public class MainItemListAdapter extends ListAdapter<ItemWithFeed, MainItemListA
|
||||
else
|
||||
viewHolder.itemFolderName.setText(resources.getString(R.string.no_folder));
|
||||
|
||||
float alpha = itemWithFeed.getItem().isRead() ? 0.5f : 1.0f;
|
||||
for (View view : alphaViews) {
|
||||
view.setAlpha(alpha);
|
||||
}
|
||||
|
||||
viewHolder.setReadState(itemWithFeed.getItem().isRead());
|
||||
viewHolder.setSelected(selection.contains(viewHolder.getAdapterPosition()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return getItem(position).getItem().getId();
|
||||
}
|
||||
|
||||
public void toggleSelection(int position) {
|
||||
if (selection.contains(position))
|
||||
selection.remove(position);
|
||||
else
|
||||
selection.add(position);
|
||||
|
||||
notifyItemChanged(position);
|
||||
}
|
||||
|
||||
public void clearSelection() {
|
||||
selection.clear();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public LinkedHashSet<Integer> getSelection() {
|
||||
return selection;
|
||||
}
|
||||
|
||||
public void updateSelection(boolean read) {
|
||||
for (int position : selection) {
|
||||
ItemWithFeed itemWithFeed = getItem(position);
|
||||
itemWithFeed.getItem().setRead(read);
|
||||
notifyItemChanged(position, itemWithFeed);
|
||||
}
|
||||
}
|
||||
|
||||
public ItemWithFeed getItemWithFeed(int i) {
|
||||
return getItem(i);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<String> getPreloadItems(int position) {
|
||||
@ -177,7 +205,6 @@ public class MainItemListAdapter extends ListAdapter<ItemWithFeed, MainItemListA
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@ -192,6 +219,7 @@ public class MainItemListAdapter extends ListAdapter<ItemWithFeed, MainItemListA
|
||||
|
||||
public interface OnItemClickListener {
|
||||
void onItemClick(ItemWithFeed itemWithFeed, int position);
|
||||
void onItemLongClick(ItemWithFeed itemWithFeed, int position);
|
||||
}
|
||||
|
||||
public void setOnItemClickListener(OnItemClickListener listener) {
|
||||
@ -210,6 +238,8 @@ public class MainItemListAdapter extends ListAdapter<ItemWithFeed, MainItemListA
|
||||
private TextView itemFolderName;
|
||||
private RelativeLayout dateLayout;
|
||||
|
||||
View[] alphaViews;
|
||||
|
||||
ItemViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
|
||||
@ -220,6 +250,15 @@ public class MainItemListAdapter extends ListAdapter<ItemWithFeed, MainItemListA
|
||||
listener.onItemClick(getItem(position), position);
|
||||
}));
|
||||
|
||||
itemView.setOnLongClickListener(v -> {
|
||||
int position = getAdapterPosition();
|
||||
|
||||
if (listener != null && position != RecyclerView.NO_POSITION)
|
||||
listener.onItemLongClick(getItem(position), position);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
itemTitle = itemView.findViewById(R.id.item_title);
|
||||
date = itemView.findViewById(R.id.item_date);
|
||||
feedName = itemView.findViewById(R.id.item_feed_title);
|
||||
@ -229,6 +268,17 @@ public class MainItemListAdapter extends ListAdapter<ItemWithFeed, MainItemListA
|
||||
itemReadTime = itemView.findViewById(R.id.item_readtime);
|
||||
itemFolderName = itemView.findViewById(R.id.item_folder_name);
|
||||
dateLayout = itemView.findViewById(R.id.item_date_layout);
|
||||
|
||||
alphaViews = new View[] {
|
||||
dateLayout,
|
||||
itemFolderName,
|
||||
feedIcon,
|
||||
feedName,
|
||||
itemDescription,
|
||||
itemTitle,
|
||||
itemImage,
|
||||
itemReadTime
|
||||
};
|
||||
}
|
||||
|
||||
private void bind(ItemWithFeed itemWithFeed) {
|
||||
@ -245,6 +295,27 @@ public class MainItemListAdapter extends ListAdapter<ItemWithFeed, MainItemListA
|
||||
itemDescription.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void setReadState(boolean isRead) {
|
||||
float alpha = isRead ? 0.5f : 1.0f;
|
||||
for (View view : alphaViews) {
|
||||
view.setAlpha(alpha);
|
||||
}
|
||||
}
|
||||
|
||||
private void setSelected(boolean selected) {
|
||||
Context context = itemView.getContext();
|
||||
|
||||
if (selected)
|
||||
itemView.setBackground(new ColorDrawable(ContextCompat.getColor(context, R.color.selected_background)));
|
||||
else {
|
||||
TypedValue outValue = new TypedValue();
|
||||
context.getTheme().resolveAttribute(
|
||||
android.R.attr.selectableItemBackground, outValue, true);
|
||||
|
||||
itemView.setBackgroundResource(outValue.resourceId);
|
||||
}
|
||||
}
|
||||
|
||||
public ImageView getItemImage() {
|
||||
return itemImage;
|
||||
}
|
||||
|
5
app/src/main/res/drawable/ic_read.xml
Normal file
5
app/src/main/res/drawable/ic_read.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M19,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.11,0 2,-0.9 2,-2L21,5c0,-1.1 -0.89,-2 -2,-2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z"/>
|
||||
</vector>
|
5
app/src/main/res/drawable/ic_unread.xml
Normal file
5
app/src/main/res/drawable/ic_unread.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M19,5v14H5V5h14m0,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z"/>
|
||||
</vector>
|
17
app/src/main/res/menu/item_list_contextual_menu.xml
Normal file
17
app/src/main/res/menu/item_list_contextual_menu.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/item_mark_unread"
|
||||
android:title="@string/unread"
|
||||
android:icon="@drawable/ic_unread"
|
||||
app:showAsAction="ifRoom"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/item_mark_read"
|
||||
android:title="@string/read"
|
||||
android:icon="@drawable/ic_read"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
</menu>
|
@ -47,5 +47,7 @@
|
||||
<item>Du plus récent au plus ancien</item>
|
||||
<item>Du plus ancien au plus récent</item>
|
||||
</string-array>
|
||||
<string name="unread">Marquer comme non lu</string>
|
||||
<string name="read">Marquer comme lu</string>
|
||||
|
||||
</resources>
|
@ -6,4 +6,6 @@
|
||||
<color name="colorControlNormal">#d7d7d7</color>
|
||||
<color name="colorBackground">#fafafa</color>
|
||||
<color name="textColorPrimary">#000000</color>
|
||||
|
||||
<color name="selected_background">#E0E0E0</color>
|
||||
</resources>
|
||||
|
@ -49,4 +49,6 @@
|
||||
<item>Newest to oldest</item>
|
||||
<item>Oldest to newsest</item>
|
||||
</string-array>
|
||||
<string name="unread">Mark as non read</string>
|
||||
<string name="read">Mark as read</string>
|
||||
</resources>
|
||||
|
@ -14,6 +14,7 @@
|
||||
<style name="AppTheme.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="windowActionModeOverlay">true</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.Design.CollapsingToolbar.Expanded.Custom" parent="TextAppearance.Design.CollapsingToolbar.Expanded">
|
||||
|
Loading…
x
Reference in New Issue
Block a user