/**
* Android ownCloud News
*
* @author David Luhmer
* @copyright 2013 David Luhmer david-dev@live.de
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
package de.luhmer.owncloudnewsreader.ListView;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.preference.PreferenceManager;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.ViewUtils;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import org.apache.commons.lang3.time.StopWatch;
import java.util.ArrayList;
import java.util.List;
import butterknife.Bind;
import butterknife.ButterKnife;
import de.luhmer.owncloudnewsreader.R;
import de.luhmer.owncloudnewsreader.SettingsActivity;
import de.luhmer.owncloudnewsreader.database.DatabaseConnectionOrm;
import de.luhmer.owncloudnewsreader.database.model.Feed;
import de.luhmer.owncloudnewsreader.database.model.Folder;
import de.luhmer.owncloudnewsreader.helper.FavIconHandler;
import de.luhmer.owncloudnewsreader.helper.ThemeChooser;
import de.luhmer.owncloudnewsreader.interfaces.ExpListTextClicked;
import de.luhmer.owncloudnewsreader.model.AbstractItem;
import de.luhmer.owncloudnewsreader.model.ConcreteFeedItem;
import de.luhmer.owncloudnewsreader.model.FolderSubscribtionItem;
import de.luhmer.owncloudnewsreader.model.Tuple;
import static de.luhmer.owncloudnewsreader.ListView.SubscriptionExpandableListAdapter.SPECIAL_FOLDERS.ALL_STARRED_ITEMS;
import static de.luhmer.owncloudnewsreader.ListView.SubscriptionExpandableListAdapter.SPECIAL_FOLDERS.ALL_UNREAD_ITEMS;
import static de.luhmer.owncloudnewsreader.ListView.SubscriptionExpandableListAdapter.SPECIAL_FOLDERS.ITEMS_WITHOUT_FOLDER;
public class SubscriptionExpandableListAdapter extends BaseExpandableListAdapter {
private final String TAG = getClass().getCanonicalName();
private Context mContext;
private DatabaseConnectionOrm dbConn;
ListView listView;
ExpListTextClicked eListTextClickHandler;
private ArrayList<AbstractItem> mCategoriesArrayList;
private SparseArray<ArrayList<ConcreteFeedItem>> mItemsArrayList;
private boolean showOnlyUnread = false;
public enum SPECIAL_FOLDERS {
ALL_UNREAD_ITEMS(-10), ALL_STARRED_ITEMS(-11), ALL_ITEMS(-12), ITEMS_WITHOUT_FOLDER(-22);
private int id;
SPECIAL_FOLDERS(int id) {
this.id = id;
}
public int getValue() {
return id;
}
public String getValueString() {
return String.valueOf(id);
}
@Override
public String toString() {
return getValueString();
}
}
private int mTextColorLightTheme;
private FavIconHandler favIconHandler;
LayoutInflater inflater;
public SubscriptionExpandableListAdapter(Context mContext, DatabaseConnectionOrm dbConn, ListView listView)
{
favIconHandler = new FavIconHandler(mContext);
this.inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.mContext = mContext;
this.dbConn = dbConn;
mTextColorLightTheme = ContextCompat.getColor(mContext, R.color.slider_listview_text_color_light_theme);
unreadCountFeeds = new SparseArray<>();
unreadCountFolders = new SparseArray<>();
starredCountFeeds = new SparseArray<>();
mCategoriesArrayList = new ArrayList<>();
mItemsArrayList = new SparseArray<>();
this.listView = listView;
}
@Override
public Object getChild(int groupPosition, int childPosition) {
int parent_id = (int)getGroupId(groupPosition);
return mItemsArrayList.get(parent_id).get(childPosition);
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return ((ConcreteFeedItem)(getChild(groupPosition, childPosition))).id_database;
}
@Override
public View getChildView(int groupPosition, int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
final ConcreteFeedItem item = (ConcreteFeedItem)getChild(groupPosition, childPosition);
final ChildHolder viewHolder;
if (convertView != null) {
viewHolder = (ChildHolder) convertView.getTag();
} else {
LinearLayout view = new LinearLayout(mContext);
convertView = inflater.inflate(R.layout.subscription_list_sub_item, view, true);
viewHolder = new ChildHolder(convertView);
convertView.setTag(viewHolder);
}
if(item != null)
{
String headerText = (item.header != null) ? item.header : "";
viewHolder.tV_HeaderText.setText(headerText);
String unreadCount;
if(item.idFolder == ALL_STARRED_ITEMS.getValue()) {
unreadCount = starredCountFeeds.get((int) item.id_database);
} else {
unreadCount = unreadCountFeeds.get((int) item.id_database);
}
if(unreadCount != null)
viewHolder.tV_UnreadCount.setText(unreadCount);
else
viewHolder.tV_UnreadCount.setText("");
favIconHandler.loadFavIconForFeed(item.favIcon, viewHolder.imgView_FavIcon);
}
else
{
viewHolder.tV_HeaderText.setText(mContext.getString(R.string.login_dialog_text_something_went_wrong));
viewHolder.tV_UnreadCount.setText("");
viewHolder.imgView_FavIcon.setImageDrawable(null);
}
return convertView;
}
static class ChildHolder {
@Bind(R.id.list_item_layout) View listItemLayout;
@Bind(R.id.summary) TextView tV_HeaderText;
@Bind(R.id.tv_unreadCount) TextView tV_UnreadCount;
@Bind(R.id.iVFavicon) ImageView imgView_FavIcon;
public ChildHolder(View view) {
ButterKnife.bind(this, view);
}
}
@Override
public int getChildrenCount(int groupPosition) {
int parent_id = (int)getGroupId(groupPosition);
return (mItemsArrayList.get(parent_id) != null) ? mItemsArrayList.get(parent_id).size() : 0;
}
@Override
public Object getGroup(int groupPosition) {
return mCategoriesArrayList.get(groupPosition);
}
@Override
public int getGroupCount() {
return mCategoriesArrayList.size();
}
@Override
public long getGroupId(int groupPosition) {
return ((AbstractItem)getGroup(groupPosition)).id_database;
}
private enum GroupViewType { FOLDER, FEED }
@Override
public int getGroupType(int groupPosition) {
AbstractItem ai = mCategoriesArrayList.get(groupPosition);
if(ai instanceof FolderSubscribtionItem)
return GroupViewType.FOLDER.ordinal();
else
return GroupViewType.FEED.ordinal();
}
@Override
public int getGroupTypeCount() {
return GroupViewType.values().length;
}
@Override
public View getGroupView(final int groupPosition, final boolean isExpanded,
View convertView, ViewGroup parent) {
GroupHolder viewHolder;
final AbstractItem group = (AbstractItem) getGroup(groupPosition);
if (convertView == null) {
LinearLayout view = new LinearLayout(mContext);
convertView = inflater.inflate(R.layout.subscription_list_item, view, true);
viewHolder = new GroupHolder(convertView);
view.setTag(viewHolder);
} else {
viewHolder = (GroupHolder) convertView.getTag();
}
viewHolder.txt_Summary.setText(group.header);
viewHolder.listItemLayout.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
long idFeed = group.id_database;
boolean skipFireEvent = false;
if (group instanceof ConcreteFeedItem) {
fireListTextClicked(idFeed, false, (long) ITEMS_WITHOUT_FOLDER.getValue());
skipFireEvent = true;
}
if (!skipFireEvent)
fireListTextClicked(idFeed, true, ((FolderSubscribtionItem) group).idFolder);
}
});
viewHolder.listItemLayout.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
long idFeed = group.id_database;
if (group instanceof ConcreteFeedItem) {
fireListTextLongClicked(idFeed, false, (long) ITEMS_WITHOUT_FOLDER.getValue());
} else {
fireListTextLongClicked(idFeed, true, ((FolderSubscribtionItem) group).idFolder);
}
return true; //consume event
}
});
viewHolder.txt_UnreadCount.setText("");
boolean skipGetUnread = false;
if(group.idFolder != null && group.idFolder == ITEMS_WITHOUT_FOLDER.getValue()) {
String unreadCount = unreadCountFeeds.get((int) group.id_database);
if(unreadCount != null) {
viewHolder.txt_UnreadCount.setText(unreadCount);
}
skipGetUnread = true;
}
if(!skipGetUnread) {
String unreadCount = unreadCountFolders.get((int) group.id_database);
if(unreadCount != null)
viewHolder.txt_UnreadCount.setText(unreadCount);
}
int rotation = 0;
int contentDescriptionId = R.string.content_desc_none;
if(group.idFolder != null)
{
viewHolder.imgView.setVisibility(View.GONE);
if(group.idFolder == ITEMS_WITHOUT_FOLDER.getValue())
{
ConcreteFeedItem concreteFeedItem = ((ConcreteFeedItem) group);
favIconHandler.loadFavIconForFeed(concreteFeedItem.favIcon, viewHolder.faviconView);
}
} else {
if(group.id_database == ALL_STARRED_ITEMS.getValue()) {
viewHolder.imgView.setVisibility(View.GONE);
viewHolder.faviconView.setVisibility(View.VISIBLE);
rotation = 0;
viewHolder.faviconView.setImageResource(getBtn_rating_star_off_normal_holo_light());
} else if (getChildrenCount( groupPosition ) == 0 ) {
viewHolder.imgView.setVisibility(View.GONE);
viewHolder.faviconView.setVisibility(View.INVISIBLE);
} else {
viewHolder.imgView.setVisibility(View.VISIBLE);
viewHolder.faviconView.setVisibility(View.INVISIBLE);
viewHolder.imgView.setImageResource(R.drawable.ic_action_expand_less);
if(isExpanded) {
rotation = 180;
contentDescriptionId = R.string.content_desc_collapse;
} else {
if (ViewUtils.isLayoutRtl(listView)) {
rotation = -90; // mirror for rtl layout
} else {
rotation = 90;
}
contentDescriptionId = R.string.content_desc_expand;
}
viewHolder.imgView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if(isExpanded)
((ExpandableListView)listView).collapseGroup(groupPosition);
else
((ExpandableListView)listView).expandGroup(groupPosition);
}
});
}
}
viewHolder.imgView.setRotation(rotation);
viewHolder.imgView.setContentDescription(viewHolder.imgView.getContext().getString(contentDescriptionId));
return convertView;
}
private Integer btn_rating_star_off_normal_holo_light;
private int getBtn_rating_star_off_normal_holo_light() {
if(btn_rating_star_off_normal_holo_light == null) {
if(ThemeChooser.isDarkTheme(mContext)) {
btn_rating_star_off_normal_holo_light = R.drawable.ic_action_star_border_dark;
} else {
btn_rating_star_off_normal_holo_light = R.drawable.ic_action_star_border_light;
}
}
return btn_rating_star_off_normal_holo_light;
}
static class GroupHolder
{
@Bind(R.id.list_item_layout) View listItemLayout;
@Bind(R.id.summary) TextView txt_Summary;
@Bind(R.id.tV_feedsCount) TextView txt_UnreadCount;
@Bind(R.id.img_View_expandable_indicator) ImageButton imgView;
@Bind(R.id.img_view_favicon) ImageView faviconView;
public GroupHolder(View view) {
ButterKnife.bind(this, view);
}
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
SparseArray<String> starredCountFeeds;
SparseArray<String> unreadCountFolders;
SparseArray<String> unreadCountFeeds;
SparseArray<String> urlsToFavIcons;
public void NotifyDataSetChangedAsync() {
new NotifyDataSetChangedAsyncTask().execute((Void) null);
}
private class NotifyDataSetChangedAsyncTask extends AsyncTask<Void, Void, Void> {
SparseArray<String> starredCountFeedsTemp;
SparseArray<String> unreadCountFoldersTemp;
SparseArray<String> unreadCountFeedsTemp;
SparseArray<String> urlsToFavIconsTemp;
@Override
protected Void doInBackground(Void... voids) {
StopWatch stopwatch = new StopWatch();
stopwatch.start();
SparseArray<String>[] temp = dbConn.getUnreadItemCountFeedFolder();
unreadCountFoldersTemp = temp[0];// dbConn.getUnreadItemCountForFolder();
unreadCountFeedsTemp = temp[1]; // dbConn.getUnreadItemCountForFeed();
starredCountFeedsTemp = dbConn.getStarredItemCount();
urlsToFavIconsTemp = dbConn.getUrlsToFavIcons();
stopwatch.stop();
Log.v(TAG, "Fetched folder/feed counts in " + stopwatch.toString());
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
if(showOnlyUnread) {
for (int i = 0; i < mCategoriesArrayList.size(); i++) {
AbstractItem item = mCategoriesArrayList.get(i);
if(item instanceof FolderSubscribtionItem &&
unreadCountFoldersTemp.get(((Long) item.id_database).intValue()) == null) {
Log.v(TAG, "Remove folder item!!!");
mCategoriesArrayList.remove(i);
i--;
} else if(item instanceof ConcreteFeedItem &&
unreadCountFeedsTemp.get(((Long) item.id_database).intValue()) == null) {
Log.v(TAG, "Remove feed item!!!");
mCategoriesArrayList.remove(i);
i--;
} else {
//Log.v(TAG, "Keep.. " + unreadCountFoldersTemp.get(((Long) item.id_database).intValue()));
}
}
for (int i = 0; i < mItemsArrayList.size(); i++) {
ArrayList<ConcreteFeedItem> item = mItemsArrayList.valueAt(i);
for (int x = 0; x < item.size(); x++) {
if (unreadCountFeedsTemp.get((int) item.get(x).id_database) == null) {
item.remove(x);
x--;
Log.v(TAG, "Remove sub feed!!");
}
}
}
}
notifyCountDataSetChanged(unreadCountFoldersTemp, unreadCountFeedsTemp, urlsToFavIconsTemp, starredCountFeedsTemp);
super.onPostExecute(aVoid);
}
}
public void ReloadAdapterAsync() {
new ReloadAdapterAsyncTask().execute((Void) null);
}
private class ReloadAdapterAsyncTask extends AsyncTask<Void, Void, Tuple<ArrayList<AbstractItem>, SparseArray<ArrayList<ConcreteFeedItem>>>> {
public ReloadAdapterAsyncTask() {
}
@Override
protected Tuple<ArrayList<AbstractItem>, SparseArray<ArrayList<ConcreteFeedItem>>> doInBackground(Void... voids) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Tuple<ArrayList<AbstractItem>, SparseArray<ArrayList<ConcreteFeedItem>>> ad = ReloadAdapter();
//return ReloadAdapter();
stopWatch.stop();
Log.v(TAG, "Reload Adapter - time taken: " + stopWatch.toString());
return ad;
}
@Override
protected void onPostExecute(Tuple<ArrayList<AbstractItem>, SparseArray<ArrayList<ConcreteFeedItem>>> arrayListSparseArrayTuple) {
mCategoriesArrayList = arrayListSparseArrayTuple.key;
mItemsArrayList = arrayListSparseArrayTuple.value;
notifyDataSetChanged();
NotifyDataSetChangedAsync();
super.onPostExecute(arrayListSparseArrayTuple);
}
}
public Tuple<ArrayList<AbstractItem>, SparseArray<ArrayList<ConcreteFeedItem>>> ReloadAdapter()
{
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
showOnlyUnread = mPrefs.getBoolean(SettingsActivity.CB_SHOWONLYUNREAD_STRING, false);
ArrayList<AbstractItem> mCategoriesArrayListAsync = new ArrayList<>();
mCategoriesArrayListAsync.add(new FolderSubscribtionItem(mContext.getString(R.string.allUnreadFeeds), null, ALL_UNREAD_ITEMS.getValue()));
mCategoriesArrayListAsync.add(new FolderSubscribtionItem(mContext.getString(R.string.starredFeeds), null, ALL_STARRED_ITEMS.getValue()));
StopWatch sw = new StopWatch();
sw.start();
List<Folder> folderList;
//if(showOnlyUnread) {
// folderList = dbConn.getListOfFoldersWithUnreadItems();
//} else {
folderList = dbConn.getListOfFolders();
//}
sw.stop();
Log.v(TAG, "Time needed (fetch folder list): " + sw.toString());
for(Folder folder : folderList) {
mCategoriesArrayListAsync.add(new FolderSubscribtionItem(folder.getLabel(), null, folder.getId()));
}
for(Feed feed : dbConn.getListOfFeedsWithoutFolders(showOnlyUnread)) {
mCategoriesArrayListAsync.add(new ConcreteFeedItem(feed.getFeedTitle(), (long) ITEMS_WITHOUT_FOLDER.getValue(), feed.getId(), feed.getFaviconUrl(), feed.getId()));
}
SparseArray<ArrayList<ConcreteFeedItem>> mItemsArrayListAsync = new SparseArray<>();
for(int groupPosition = 0; groupPosition < mCategoriesArrayListAsync.size(); groupPosition++) {
//int parent_id = (int)getGroupId(groupPosition);
int parent_id = (int) mCategoriesArrayListAsync.get(groupPosition).id_database;
mItemsArrayListAsync.append(parent_id, new ArrayList<ConcreteFeedItem>());
List<Feed> feedItemList = null;
if(parent_id == ALL_UNREAD_ITEMS.getValue()) {
feedItemList = dbConn.getAllFeedsWithUnreadRssItems();
} else if(parent_id == ALL_STARRED_ITEMS.getValue()) {
feedItemList = dbConn.getAllFeedsWithStarredRssItems();
} else {
for(Folder folder : folderList) {//Find the current selected folder
if (folder.getId() == parent_id) {//Current item
feedItemList = dbConn.getAllFeedsWithUnreadRssItemsForFolder(folder.getId());
break;
}
}
}
if(feedItemList != null) {
for (Feed feed : feedItemList) {
ConcreteFeedItem newItem = new ConcreteFeedItem(feed.getFeedTitle(), (long) parent_id, feed.getId(), feed.getFaviconUrl(), feed.getId());
mItemsArrayListAsync.get(parent_id).add(newItem);
}
}
}
return new Tuple<>(mCategoriesArrayListAsync, mItemsArrayListAsync);
}
@SuppressLint("NewApi") // wrongly reports setSelectionFromTop is only available in lollipop
public void notifyCountDataSetChanged(SparseArray<String> unreadCountFolders, SparseArray<String> unreadCountFeeds, SparseArray<String> urlsToFavIcons, SparseArray<String> starredCountFeeds) {
this.unreadCountFolders = unreadCountFolders;
this.unreadCountFeeds = unreadCountFeeds;
this.urlsToFavIcons = urlsToFavIcons;
this.starredCountFeeds = starredCountFeeds;
BlockingExpandableListView bView = (BlockingExpandableListView) listView;
int firstVisPos = bView.getFirstVisiblePosition();
View firstVisView = bView.getChildAt(0);
int top = firstVisView != null ? firstVisView.getTop() : 0;
// Number of items added before the first visible item
int itemsAddedBeforeFirstVisible = 0;
bView.setBlockLayoutChildren(true);
notifyDataSetChanged();
bView.setBlockLayoutChildren(false);
// Call setSelectionFromTop to change the ListView position
if(bView.getCount() >= firstVisPos + itemsAddedBeforeFirstVisible)
bView.setSelectionFromTop(firstVisPos + itemsAddedBeforeFirstVisible, top);
}
public void setHandlerListener(ExpListTextClicked listener)
{
eListTextClickHandler = listener;
}
protected void fireListTextClicked(long idFeed, boolean isFolder, Long optional_folder_id)
{
if(eListTextClickHandler != null)
eListTextClickHandler.onTextClicked(idFeed, isFolder, optional_folder_id);
}
protected void fireListTextLongClicked(long idFeed, boolean isFolder, Long optional_folder_id)
{
if(eListTextClickHandler != null)
eListTextClickHandler.onTextLongClicked(idFeed, isFolder, optional_folder_id);
}
}