Grid layout for local lists

This commit is contained in:
Vasily 2018-08-31 14:34:32 +03:00
parent 8aef24be1e
commit ee4942dfd7
10 changed files with 257 additions and 13 deletions

View File

@ -1,8 +1,13 @@
package org.schabi.newpipe.local; package org.schabi.newpipe.local;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBar;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.util.Log; import android.util.Log;
@ -25,7 +30,7 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView;
* called and is memory efficient when in backstack. * called and is memory efficient when in backstack.
* */ * */
public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I> public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
implements ListViewContract<I, N> { implements ListViewContract<I, N>, SharedPreferences.OnSharedPreferenceChangeListener {
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Views // Views
@ -36,6 +41,9 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
protected LocalItemListAdapter itemListAdapter; protected LocalItemListAdapter itemListAdapter;
protected RecyclerView itemsList; protected RecyclerView itemsList;
private int updateFlags = 0;
private static final int LIST_MODE_UPDATE_FLAG = 0x32;
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Lifecycle - Creation // Lifecycle - Creation
@ -45,6 +53,29 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setHasOptionsMenu(true); setHasOptionsMenu(true);
PreferenceManager.getDefaultSharedPreferences(activity)
.registerOnSharedPreferenceChangeListener(this);
}
@Override
public void onDestroy() {
super.onDestroy();
PreferenceManager.getDefaultSharedPreferences(activity)
.unregisterOnSharedPreferenceChangeListener(this);
}
@Override
public void onResume() {
super.onResume();
if (updateFlags != 0) {
if ((updateFlags & LIST_MODE_UPDATE_FLAG) != 0) {
final boolean useGrid = isGridLayout();
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
itemListAdapter.setGridItemVariants(useGrid);
itemListAdapter.notifyDataSetChanged();
}
updateFlags = 0;
}
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -59,6 +90,16 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
return activity.getLayoutInflater().inflate(R.layout.pignate_footer, itemsList, false); return activity.getLayoutInflater().inflate(R.layout.pignate_footer, itemsList, false);
} }
protected RecyclerView.LayoutManager getGridLayoutManager() {
final Resources resources = activity.getResources();
int width = resources.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width);
width += (24 * resources.getDisplayMetrics().density);
final int spanCount = (int) Math.floor(resources.getDisplayMetrics().widthPixels / (double)width);
final GridLayoutManager lm = new GridLayoutManager(activity, spanCount);
lm.setSpanSizeLookup(itemListAdapter.getSpanSizeLookup(spanCount));
return lm;
}
protected RecyclerView.LayoutManager getListLayoutManager() { protected RecyclerView.LayoutManager getListLayoutManager() {
return new LinearLayoutManager(activity); return new LinearLayoutManager(activity);
} }
@ -67,10 +108,13 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
protected void initViews(View rootView, Bundle savedInstanceState) { protected void initViews(View rootView, Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState); super.initViews(rootView, savedInstanceState);
itemsList = rootView.findViewById(R.id.items_list);
itemsList.setLayoutManager(getListLayoutManager());
itemListAdapter = new LocalItemListAdapter(activity); itemListAdapter = new LocalItemListAdapter(activity);
final boolean useGrid = isGridLayout();
itemsList = rootView.findViewById(R.id.items_list);
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
itemListAdapter.setGridItemVariants(useGrid);
itemListAdapter.setHeader(headerRootView = getListHeader()); itemListAdapter.setHeader(headerRootView = getListHeader());
itemListAdapter.setFooter(footerRootView = getListFooter()); itemListAdapter.setFooter(footerRootView = getListFooter());
@ -174,4 +218,22 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
resetFragment(); resetFragment();
return super.onError(exception); return super.onError(exception);
} }
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals(getString(R.string.list_view_mode_key))) {
updateFlags |= LIST_MODE_UPDATE_FLAG;
}
}
protected boolean isGridLayout() {
final String list_mode = PreferenceManager.getDefaultSharedPreferences(activity).getString(getString(R.string.list_view_mode_key), getString(R.string.list_view_mode_value));
if ("auto".equals(list_mode)) {
final Configuration configuration = getResources().getConfiguration();
return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
&& configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE);
} else {
return "grid".equals(list_mode);
}
}
} }

View File

@ -1,18 +1,21 @@
package org.schabi.newpipe.local; package org.schabi.newpipe.local;
import android.app.Activity; import android.app.Activity;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.local.HeaderFooterHolder;
import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.local.holder.LocalItemHolder; import org.schabi.newpipe.local.holder.LocalItemHolder;
import org.schabi.newpipe.local.holder.LocalPlaylistGridItemHolder;
import org.schabi.newpipe.local.holder.LocalPlaylistItemHolder; import org.schabi.newpipe.local.holder.LocalPlaylistItemHolder;
import org.schabi.newpipe.local.holder.LocalPlaylistStreamGridItemHolder;
import org.schabi.newpipe.local.holder.LocalPlaylistStreamItemHolder; import org.schabi.newpipe.local.holder.LocalPlaylistStreamItemHolder;
import org.schabi.newpipe.local.holder.LocalStatisticStreamGridItemHolder;
import org.schabi.newpipe.local.holder.LocalStatisticStreamItemHolder; import org.schabi.newpipe.local.holder.LocalStatisticStreamItemHolder;
import org.schabi.newpipe.local.holder.RemotePlaylistGridItemHolder;
import org.schabi.newpipe.local.holder.RemotePlaylistItemHolder; import org.schabi.newpipe.local.holder.RemotePlaylistItemHolder;
import org.schabi.newpipe.util.FallbackViewHolder; import org.schabi.newpipe.util.FallbackViewHolder;
import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.Localization;
@ -52,14 +55,19 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
private static final int STREAM_STATISTICS_HOLDER_TYPE = 0x1000; private static final int STREAM_STATISTICS_HOLDER_TYPE = 0x1000;
private static final int STREAM_PLAYLIST_HOLDER_TYPE = 0x1001; private static final int STREAM_PLAYLIST_HOLDER_TYPE = 0x1001;
private static final int STREAM_STATISTICS_GRID_HOLDER_TYPE = 0x1002;
private static final int STREAM_PLAYLIST_GRID_HOLDER_TYPE = 0x1004;
private static final int LOCAL_PLAYLIST_HOLDER_TYPE = 0x2000; private static final int LOCAL_PLAYLIST_HOLDER_TYPE = 0x2000;
private static final int REMOTE_PLAYLIST_HOLDER_TYPE = 0x2001; private static final int REMOTE_PLAYLIST_HOLDER_TYPE = 0x2001;
private static final int LOCAL_PLAYLIST_GRID_HOLDER_TYPE = 0x2002;
private static final int REMOTE_PLAYLIST_GRID_HOLDER_TYPE = 0x2004;
private final LocalItemBuilder localItemBuilder; private final LocalItemBuilder localItemBuilder;
private final ArrayList<LocalItem> localItems; private final ArrayList<LocalItem> localItems;
private final DateFormat dateFormat; private final DateFormat dateFormat;
private boolean showFooter = false; private boolean showFooter = false;
private boolean useGridVariant = false;
private View header = null; private View header = null;
private View footer = null; private View footer = null;
@ -134,6 +142,10 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
notifyDataSetChanged(); notifyDataSetChanged();
} }
public void setGridItemVariants(boolean useGridVariant) {
this.useGridVariant = useGridVariant;
}
public void setHeader(View header) { public void setHeader(View header) {
boolean changed = header != this.header; boolean changed = header != this.header;
this.header = header; this.header = header;
@ -195,11 +207,11 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
final LocalItem item = localItems.get(position); final LocalItem item = localItems.get(position);
switch (item.getLocalItemType()) { switch (item.getLocalItemType()) {
case PLAYLIST_LOCAL_ITEM: return LOCAL_PLAYLIST_HOLDER_TYPE; case PLAYLIST_LOCAL_ITEM: return useGridVariant ? LOCAL_PLAYLIST_GRID_HOLDER_TYPE : LOCAL_PLAYLIST_HOLDER_TYPE;
case PLAYLIST_REMOTE_ITEM: return REMOTE_PLAYLIST_HOLDER_TYPE; case PLAYLIST_REMOTE_ITEM: return useGridVariant ? REMOTE_PLAYLIST_GRID_HOLDER_TYPE : REMOTE_PLAYLIST_HOLDER_TYPE;
case PLAYLIST_STREAM_ITEM: return STREAM_PLAYLIST_HOLDER_TYPE; case PLAYLIST_STREAM_ITEM: return useGridVariant ? STREAM_PLAYLIST_GRID_HOLDER_TYPE : STREAM_PLAYLIST_HOLDER_TYPE;
case STATISTIC_STREAM_ITEM: return STREAM_STATISTICS_HOLDER_TYPE; case STATISTIC_STREAM_ITEM: return useGridVariant ? STREAM_STATISTICS_GRID_HOLDER_TYPE : STREAM_STATISTICS_HOLDER_TYPE;
default: default:
Log.e(TAG, "No holder type has been considered for item: [" + Log.e(TAG, "No holder type has been considered for item: [" +
item.getLocalItemType() + "]"); item.getLocalItemType() + "]");
@ -218,12 +230,20 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
return new HeaderFooterHolder(footer); return new HeaderFooterHolder(footer);
case LOCAL_PLAYLIST_HOLDER_TYPE: case LOCAL_PLAYLIST_HOLDER_TYPE:
return new LocalPlaylistItemHolder(localItemBuilder, parent); return new LocalPlaylistItemHolder(localItemBuilder, parent);
case LOCAL_PLAYLIST_GRID_HOLDER_TYPE:
return new LocalPlaylistGridItemHolder(localItemBuilder, parent);
case REMOTE_PLAYLIST_HOLDER_TYPE: case REMOTE_PLAYLIST_HOLDER_TYPE:
return new RemotePlaylistItemHolder(localItemBuilder, parent); return new RemotePlaylistItemHolder(localItemBuilder, parent);
case REMOTE_PLAYLIST_GRID_HOLDER_TYPE:
return new RemotePlaylistGridItemHolder(localItemBuilder, parent);
case STREAM_PLAYLIST_HOLDER_TYPE: case STREAM_PLAYLIST_HOLDER_TYPE:
return new LocalPlaylistStreamItemHolder(localItemBuilder, parent); return new LocalPlaylistStreamItemHolder(localItemBuilder, parent);
case STREAM_PLAYLIST_GRID_HOLDER_TYPE:
return new LocalPlaylistStreamGridItemHolder(localItemBuilder, parent);
case STREAM_STATISTICS_HOLDER_TYPE: case STREAM_STATISTICS_HOLDER_TYPE:
return new LocalStatisticStreamItemHolder(localItemBuilder, parent); return new LocalStatisticStreamItemHolder(localItemBuilder, parent);
case STREAM_STATISTICS_GRID_HOLDER_TYPE:
return new LocalStatisticStreamGridItemHolder(localItemBuilder, parent);
default: default:
Log.e(TAG, "No view type has been considered for holder: [" + type + "]"); Log.e(TAG, "No view type has been considered for holder: [" + type + "]");
return new FallbackViewHolder(new View(parent.getContext())); return new FallbackViewHolder(new View(parent.getContext()));
@ -247,4 +267,14 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
((HeaderFooterHolder) holder).view = footer; ((HeaderFooterHolder) holder).view = footer;
} }
} }
public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final int spanCount) {
return new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
final int type = getItemViewType(position);
return type == HEADER_TYPE || type == FOOTER_TYPE ? spanCount : 1;
}
};
}
} }

View File

@ -0,0 +1,13 @@
package org.schabi.newpipe.local.holder;
import android.view.ViewGroup;
import org.schabi.newpipe.R;
import org.schabi.newpipe.local.LocalItemBuilder;
public class LocalPlaylistGridItemHolder extends LocalPlaylistItemHolder {
public LocalPlaylistGridItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) {
super(infoItemBuilder, R.layout.list_playlist_grid_item, parent);
}
}

View File

@ -16,6 +16,10 @@ public class LocalPlaylistItemHolder extends PlaylistItemHolder {
super(infoItemBuilder, parent); super(infoItemBuilder, parent);
} }
LocalPlaylistItemHolder(LocalItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
super(infoItemBuilder, layoutId, parent);
}
@Override @Override
public void updateFromItem(final LocalItem localItem, final DateFormat dateFormat) { public void updateFromItem(final LocalItem localItem, final DateFormat dateFormat) {
if (!(localItem instanceof PlaylistMetadataEntry)) return; if (!(localItem instanceof PlaylistMetadataEntry)) return;

View File

@ -0,0 +1,13 @@
package org.schabi.newpipe.local.holder;
import android.view.ViewGroup;
import org.schabi.newpipe.R;
import org.schabi.newpipe.local.LocalItemBuilder;
public class LocalPlaylistStreamGridItemHolder extends LocalPlaylistStreamItemHolder {
public LocalPlaylistStreamGridItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) {
super(infoItemBuilder, R.layout.list_stream_playlist_grid_item, parent); //TODO
}
}

View File

@ -0,0 +1,13 @@
package org.schabi.newpipe.local.holder;
import android.view.ViewGroup;
import org.schabi.newpipe.R;
import org.schabi.newpipe.local.LocalItemBuilder;
public class LocalStatisticStreamGridItemHolder extends LocalStatisticStreamItemHolder {
public LocalStatisticStreamGridItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) {
super(infoItemBuilder, R.layout.list_stream_grid_item, parent);
}
}

View File

@ -1,5 +1,6 @@
package org.schabi.newpipe.local.holder; package org.schabi.newpipe.local.holder;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -42,10 +43,15 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
public final TextView itemVideoTitleView; public final TextView itemVideoTitleView;
public final TextView itemUploaderView; public final TextView itemUploaderView;
public final TextView itemDurationView; public final TextView itemDurationView;
@Nullable
public final TextView itemAdditionalDetails; public final TextView itemAdditionalDetails;
public LocalStatisticStreamItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) { public LocalStatisticStreamItemHolder(LocalItemBuilder itemBuilder, ViewGroup parent) {
super(infoItemBuilder, R.layout.list_stream_item, parent); this(itemBuilder, R.layout.list_stream_item, parent);
}
LocalStatisticStreamItemHolder(LocalItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
super(infoItemBuilder, layoutId, parent);
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView); itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
itemVideoTitleView = itemView.findViewById(R.id.itemVideoTitleView); itemVideoTitleView = itemView.findViewById(R.id.itemVideoTitleView);
@ -80,7 +86,9 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
itemDurationView.setVisibility(View.GONE); itemDurationView.setVisibility(View.GONE);
} }
itemAdditionalDetails.setText(getStreamInfoDetailLine(item, dateFormat)); if (itemAdditionalDetails != null) {
itemAdditionalDetails.setText(getStreamInfoDetailLine(item, dateFormat));
}
// Default thumbnail is shown on error, while loading and if the url is empty // Default thumbnail is shown on error, while loading and if the url is empty
itemBuilder.displayImage(item.thumbnailUrl, itemThumbnailView, itemBuilder.displayImage(item.thumbnailUrl, itemThumbnailView,

View File

@ -0,0 +1,13 @@
package org.schabi.newpipe.local.holder;
import android.view.ViewGroup;
import org.schabi.newpipe.R;
import org.schabi.newpipe.local.LocalItemBuilder;
public class RemotePlaylistGridItemHolder extends RemotePlaylistItemHolder {
public RemotePlaylistGridItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) {
super(infoItemBuilder, R.layout.list_playlist_grid_item, parent);
}
}

View File

@ -16,6 +16,10 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder {
super(infoItemBuilder, parent); super(infoItemBuilder, parent);
} }
RemotePlaylistItemHolder(LocalItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
super(infoItemBuilder, layoutId, parent);
}
@Override @Override
public void updateFromItem(final LocalItem localItem, final DateFormat dateFormat) { public void updateFromItem(final LocalItem localItem, final DateFormat dateFormat) {
if (!(localItem instanceof PlaylistRemoteEntity)) return; if (!(localItem instanceof PlaylistRemoteEntity)) return;

View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/itemRoot"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:padding="@dimen/video_item_search_padding">
<ImageView
android:id="@+id/itemThumbnailView"
android:layout_width="@dimen/video_item_grid_thumbnail_image_width"
android:layout_height="@dimen/video_item_grid_thumbnail_image_height"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginRight="@dimen/video_item_search_image_right_margin"
android:contentDescription="@string/list_thumbnail_view_description"
android:scaleType="centerCrop"
android:src="@drawable/dummy_thumbnail"
tools:ignore="RtlHardcoded" />
<TextView
android:id="@+id/itemDurationView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/itemThumbnailView"
android:layout_alignRight="@id/itemThumbnailView"
android:layout_marginBottom="@dimen/video_item_search_duration_margin"
android:layout_marginRight="@dimen/video_item_search_duration_margin"
android:background="@color/duration_background_color"
android:paddingBottom="@dimen/video_item_search_duration_vertical_padding"
android:paddingLeft="@dimen/video_item_search_duration_horizontal_padding"
android:paddingRight="@dimen/video_item_search_duration_horizontal_padding"
android:paddingTop="@dimen/video_item_search_duration_vertical_padding"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="@color/duration_text_color"
android:textSize="@dimen/video_item_search_duration_text_size"
tools:ignore="RtlHardcoded"
tools:text="1:09:10" />
<ImageView
android:id="@+id/itemHandle"
android:layout_width="wrap_content"
android:layout_height="55dp"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:contentDescription="@string/detail_drag_description"
android:paddingLeft="@dimen/video_item_search_image_right_margin"
android:scaleType="center"
android:src="?attr/drag_handle"
tools:ignore="RtlHardcoded,RtlSymmetry" />
<TextView
android:id="@+id/itemVideoTitleView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignEnd="@id/itemThumbnailView"
android:layout_alignLeft="@id/itemThumbnailView"
android:layout_alignRight="@id/itemThumbnailView"
android:layout_alignStart="@id/itemThumbnailView"
android:layout_below="@id/itemThumbnailView"
android:ellipsize="end"
android:maxLines="2"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_search_title_text_size"
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique..." />
<TextView
android:id="@+id/itemAdditionalDetails"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignEnd="@id/itemThumbnailView"
android:layout_alignLeft="@id/itemThumbnailView"
android:layout_alignRight="@id/itemThumbnailView"
android:layout_alignStart="@id/itemThumbnailView"
android:layout_below="@+id/itemVideoTitleView"
android:lines="1"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="@dimen/video_item_search_uploader_text_size"
tools:text="Uploader" />
</RelativeLayout>