aboutsummaryrefslogtreecommitdiff
path: root/app/src
diff options
context:
space:
mode:
Diffstat (limited to 'app/src')
-rw-r--r--app/src/main/java/github/daneren2005/dsub/adapter/ArtistAdapter.java14
-rw-r--r--app/src/main/java/github/daneren2005/dsub/adapter/PlaylistAdapter.java14
-rw-r--r--app/src/main/java/github/daneren2005/dsub/adapter/PodcastChannelAdapter.java14
-rw-r--r--app/src/main/java/github/daneren2005/dsub/fragments/SelectRecyclerFragment.java11
-rw-r--r--app/src/main/java/github/daneren2005/dsub/view/FastScroller.java208
-rw-r--r--app/src/main/res/drawable/fast_scroller_bubble.xml16
-rw-r--r--app/src/main/res/drawable/fast_scroller_handle.xml26
-rw-r--r--app/src/main/res/layout/abstract_recycler_fragment.xml19
-rw-r--r--app/src/main/res/layout/fast_scroller.xml25
9 files changed, 339 insertions, 8 deletions
diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/ArtistAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/ArtistAdapter.java
index 7461af69..9116e587 100644
--- a/app/src/main/java/github/daneren2005/dsub/adapter/ArtistAdapter.java
+++ b/app/src/main/java/github/daneren2005/dsub/adapter/ArtistAdapter.java
@@ -30,9 +30,10 @@ import github.daneren2005.dsub.domain.Artist;
import github.daneren2005.dsub.domain.MusicFolder;
import github.daneren2005.dsub.util.Util;
import github.daneren2005.dsub.view.ArtistView;
+import github.daneren2005.dsub.view.FastScroller;
import github.daneren2005.dsub.view.UpdateView;
-public class ArtistAdapter extends SectionAdapter<Artist> {
+public class ArtistAdapter extends SectionAdapter<Artist> implements FastScroller.BubbleTextGetter {
public static int VIEW_TYPE_ARTIST = 4;
private List<MusicFolder> musicFolders;
@@ -122,6 +123,17 @@ public class ArtistAdapter extends SectionAdapter<Artist> {
return VIEW_TYPE_ARTIST;
}
+ @Override
+ public String getTextToShowInBubble(int position) {
+ Artist artist = getItemForPosition(position);
+
+ if(artist == null) {
+ return "";
+ } else {
+ return artist.getName().substring(0, 1);
+ }
+ }
+
public interface OnMusicFolderChanged {
void onMusicFolderChanged(MusicFolder musicFolder);
}
diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/PlaylistAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/PlaylistAdapter.java
index 4221677e..d7a9e8d7 100644
--- a/app/src/main/java/github/daneren2005/dsub/adapter/PlaylistAdapter.java
+++ b/app/src/main/java/github/daneren2005/dsub/adapter/PlaylistAdapter.java
@@ -21,10 +21,11 @@ import java.util.List;
import android.view.ViewGroup;
import github.daneren2005.dsub.domain.Playlist;
import github.daneren2005.dsub.util.ImageLoader;
+import github.daneren2005.dsub.view.FastScroller;
import github.daneren2005.dsub.view.PlaylistView;
import github.daneren2005.dsub.view.UpdateView;
-public class PlaylistAdapter extends SectionAdapter<Playlist> {
+public class PlaylistAdapter extends SectionAdapter<Playlist> implements FastScroller.BubbleTextGetter {
public static int VIEW_TYPE_PLAYLIST = 1;
private ImageLoader imageLoader;
@@ -58,4 +59,15 @@ public class PlaylistAdapter extends SectionAdapter<Playlist> {
public int getItemViewType(Playlist playlist) {
return VIEW_TYPE_PLAYLIST;
}
+
+ @Override
+ public String getTextToShowInBubble(int position) {
+ Playlist playlist = getItemForPosition(position);
+
+ if(playlist == null) {
+ return "";
+ } else {
+ return playlist.getName().substring(0, 1);
+ }
+ }
}
diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/PodcastChannelAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/PodcastChannelAdapter.java
index dc94178d..c1b132c7 100644
--- a/app/src/main/java/github/daneren2005/dsub/adapter/PodcastChannelAdapter.java
+++ b/app/src/main/java/github/daneren2005/dsub/adapter/PodcastChannelAdapter.java
@@ -17,12 +17,13 @@ package github.daneren2005.dsub.adapter;
import android.content.Context;
import android.view.ViewGroup;
import github.daneren2005.dsub.domain.PodcastChannel;
+import github.daneren2005.dsub.view.FastScroller;
import github.daneren2005.dsub.view.PodcastChannelView;
import github.daneren2005.dsub.view.UpdateView;
import java.util.List;
-public class PodcastChannelAdapter extends SectionAdapter<PodcastChannel>{
+public class PodcastChannelAdapter extends SectionAdapter<PodcastChannel> implements FastScroller.BubbleTextGetter {
public static int VIEW_TYPE_PODCAST = 1;
public PodcastChannelAdapter(Context context, List<PodcastChannel> podcasts, OnItemClickedListener listener) {
@@ -44,4 +45,15 @@ public class PodcastChannelAdapter extends SectionAdapter<PodcastChannel>{
public int getItemViewType(PodcastChannel item) {
return VIEW_TYPE_PODCAST;
}
+
+ @Override
+ public String getTextToShowInBubble(int position) {
+ PodcastChannel podcast = getItemForPosition(position);
+
+ if(podcast == null) {
+ return "";
+ } else {
+ return podcast.getName().substring(0, 1);
+ }
+ }
}
diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SelectRecyclerFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SelectRecyclerFragment.java
index 9dc3e363..adf22484 100644
--- a/app/src/main/java/github/daneren2005/dsub/fragments/SelectRecyclerFragment.java
+++ b/app/src/main/java/github/daneren2005/dsub/fragments/SelectRecyclerFragment.java
@@ -15,7 +15,6 @@
package github.daneren2005.dsub.fragments;
-import android.content.Context;
import android.os.Bundle;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.RecyclerView;
@@ -38,10 +37,12 @@ import github.daneren2005.dsub.service.MusicServiceFactory;
import github.daneren2005.dsub.util.Constants;
import github.daneren2005.dsub.util.ProgressListener;
import github.daneren2005.dsub.util.TabBackgroundTask;
+import github.daneren2005.dsub.view.FastScroller;
public abstract class SelectRecyclerFragment<T> extends SubsonicFragment implements SectionAdapter.OnItemClickedListener<T> {
private static final String TAG = SelectRecyclerFragment.class.getSimpleName();
protected RecyclerView recyclerView;
+ protected FastScroller fastScroller;
protected SectionAdapter<T> adapter;
protected UpdateTask currentTask;
protected List<T> objects;
@@ -77,6 +78,7 @@ public abstract class SelectRecyclerFragment<T> extends SubsonicFragment impleme
refreshLayout.setOnRefreshListener(this);
recyclerView = (RecyclerView) rootView.findViewById(R.id.fragment_recycler);
+ fastScroller = (FastScroller) rootView.findViewById(R.id.fragment_fast_scroller);
setupLayoutManager();
if(pullToRefresh) {
@@ -184,6 +186,13 @@ public abstract class SelectRecyclerFragment<T> extends SubsonicFragment impleme
public void done(List<T> result) {
if (result != null && !result.isEmpty()) {
recyclerView.setAdapter(adapter = getAdapter(result));
+ if(adapter instanceof FastScroller.BubbleTextGetter) {
+ if(!fastScroller.isAttached()) {
+ fastScroller.attachRecyclerView(recyclerView);
+ }
+ } else if(fastScroller.isAttached()) {
+ fastScroller.detachRecyclerView();
+ }
onFinishRefresh();
recyclerView.setVisibility(View.VISIBLE);
diff --git a/app/src/main/java/github/daneren2005/dsub/view/FastScroller.java b/app/src/main/java/github/daneren2005/dsub/view/FastScroller.java
new file mode 100644
index 00000000..ee7aef4b
--- /dev/null
+++ b/app/src/main/java/github/daneren2005/dsub/view/FastScroller.java
@@ -0,0 +1,208 @@
+/*
+ This file is part of Subsonic.
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+ Subsonic 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 General Public License for more details.
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+ Copyright 2015 (C) Scott Jackson
+*/
+
+package github.daneren2005.dsub.view;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import github.daneren2005.dsub.R;
+
+import static android.support.v7.widget.RecyclerView.OnScrollListener;
+
+public class FastScroller extends LinearLayout {
+ private static final int BUBBLE_ANIMATION_DURATION = 100;
+ private static final int TRACK_SNAP_RANGE = 5;
+
+ private TextView bubble;
+ private View handle;
+ private RecyclerView recyclerView;
+ private final ScrollListener scrollListener = new ScrollListener();
+ private int height;
+
+ private ObjectAnimator currentAnimator = null;
+
+ public FastScroller(final Context context,final AttributeSet attrs,final int defStyleAttr) {
+ super(context,attrs,defStyleAttr);
+ initialise(context);
+ }
+
+ public FastScroller(final Context context) {
+ super(context);
+ initialise(context);
+ }
+
+ public FastScroller(final Context context,final AttributeSet attrs) {
+ super(context,attrs);
+ initialise(context);
+ }
+
+ private void initialise(Context context) {
+ setOrientation(HORIZONTAL);
+ setClipChildren(false);
+ LayoutInflater inflater = LayoutInflater.from(context);
+ inflater.inflate(R.layout.fast_scroller,this,true);
+ bubble = (TextView)findViewById(R.id.fastscroller_bubble);
+ handle = findViewById(R.id.fastscroller_handle);
+ bubble.setVisibility(INVISIBLE);
+ setVisibility(GONE);
+ }
+
+ @Override
+ protected void onSizeChanged(int w,int h,int oldw,int oldh) {
+ super.onSizeChanged(w,h,oldw,oldh);
+ height = h;
+ }
+
+ @Override
+ public boolean onTouchEvent(@NonNull MotionEvent event) {
+ final int action = event.getAction();
+ switch(action)
+ {
+ case MotionEvent.ACTION_DOWN:
+ if(event.getX()<handle.getX())
+ return false;
+ if(currentAnimator != null)
+ currentAnimator.cancel();
+ if(bubble.getVisibility() == INVISIBLE)
+ showBubble();
+ handle.setSelected(true);
+ case MotionEvent.ACTION_MOVE:
+ final float y = event.getY();
+ setBubbleAndHandlePosition(y);
+ setRecyclerViewPosition(y);
+ return true;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ handle.setSelected(false);
+ hideBubble();
+ return true;
+ }
+ return super.onTouchEvent(event);
+ }
+
+ public void attachRecyclerView(RecyclerView recyclerView) {
+ this.recyclerView = recyclerView;
+ recyclerView.addOnScrollListener(scrollListener);
+ recyclerView.setVerticalScrollBarEnabled(false);
+ setVisibility(View.VISIBLE);
+ }
+ public void detachRecyclerView() {
+ recyclerView.removeOnScrollListener(scrollListener);
+ recyclerView.setVerticalScrollBarEnabled(true);
+ recyclerView = null;
+ setVisibility(View.GONE);
+ }
+ public boolean isAttached() {
+ return recyclerView != null;
+ }
+
+ private void setRecyclerViewPosition(float y) {
+ if(recyclerView != null)
+ {
+ int itemCount = recyclerView.getAdapter().getItemCount();
+ float proportion;
+ if(handle.getY() == 0)
+ proportion = 0f;
+ else if(handle.getY()+handle.getHeight()>=height-TRACK_SNAP_RANGE)
+ proportion = 1f;
+ else
+ proportion = y/(float)height;
+ int targetPos = getValueInRange(0,itemCount-1,(int)(proportion*(float)itemCount));
+ ((LinearLayoutManager)recyclerView.getLayoutManager()).scrollToPositionWithOffset(targetPos,0);
+ String bubbleText = ((BubbleTextGetter)recyclerView.getAdapter()).getTextToShowInBubble(targetPos);
+ bubble.setText(bubbleText);
+ }
+ }
+
+ private int getValueInRange(int min,int max,int value) {
+ int minimum = Math.max(min,value);
+ return Math.min(minimum,max);
+ }
+
+ private void setBubbleAndHandlePosition(float y) {
+ int bubbleHeight = bubble.getHeight();
+ int handleHeight = handle.getHeight();
+ handle.setY(getValueInRange(0,height-handleHeight,(int)(y-handleHeight/2)));
+ bubble.setY(getValueInRange(0,height-bubbleHeight-handleHeight/2,(int)(y-bubbleHeight)));
+ }
+
+ private void showBubble() {
+ AnimatorSet animatorSet = new AnimatorSet();
+ bubble.setVisibility(VISIBLE);
+ if(currentAnimator != null)
+ currentAnimator.cancel();
+ currentAnimator = ObjectAnimator.ofFloat(bubble,"alpha",0f,1f).setDuration(BUBBLE_ANIMATION_DURATION);
+ currentAnimator.start();
+ }
+
+ private void hideBubble() {
+ if(currentAnimator != null)
+ currentAnimator.cancel();
+ currentAnimator = ObjectAnimator.ofFloat(bubble,"alpha",1f,0f).setDuration(BUBBLE_ANIMATION_DURATION);
+ currentAnimator.addListener(new AnimatorListenerAdapter(){
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ bubble.setVisibility(INVISIBLE);
+ currentAnimator = null;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ super.onAnimationCancel(animation);
+ bubble.setVisibility(INVISIBLE);
+ currentAnimator = null;
+ }
+ });
+ currentAnimator.start();
+ }
+
+ private class ScrollListener extends OnScrollListener {
+ @Override
+ public void onScrolled(RecyclerView rv,int dx,int dy) {
+ View firstVisibleView = recyclerView.getChildAt(0);
+ int firstVisiblePosition = recyclerView.getChildPosition(firstVisibleView);
+ int visibleRange = recyclerView.getChildCount();
+ int lastVisiblePosition = firstVisiblePosition+visibleRange;
+ int itemCount = recyclerView.getAdapter().getItemCount();
+ int position;
+ if(firstVisiblePosition == 0)
+ position = 0;
+ else if(lastVisiblePosition == itemCount)
+ position = itemCount;
+ else
+ position = (int)(((float)firstVisiblePosition/(((float)itemCount-(float)visibleRange)))*(float)itemCount);
+ float proportion = (float)position/(float)itemCount;
+ setBubbleAndHandlePosition(height*proportion);
+ }
+ }
+
+ public interface BubbleTextGetter {
+ String getTextToShowInBubble(int position);
+ }
+}
diff --git a/app/src/main/res/drawable/fast_scroller_bubble.xml b/app/src/main/res/drawable/fast_scroller_bubble.xml
new file mode 100644
index 00000000..02dfee5b
--- /dev/null
+++ b/app/src/main/res/drawable/fast_scroller_bubble.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+
+ <corners
+ android:topLeftRadius="44dp"
+ android:topRightRadius="44dp"
+ android:bottomLeftRadius="44dp"
+ android:bottomRightRadius="0px"/>
+
+ <solid android:color="#FF0288D1"/>
+
+ <size
+ android:height="88dp"
+ android:width="88dp"/>
+</shape> \ No newline at end of file
diff --git a/app/src/main/res/drawable/fast_scroller_handle.xml b/app/src/main/res/drawable/fast_scroller_handle.xml
new file mode 100644
index 00000000..e1744ceb
--- /dev/null
+++ b/app/src/main/res/drawable/fast_scroller_handle.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_selected="true">
+ <shape android:shape="rectangle">
+ <corners android:radius="2dp"/>
+
+ <solid android:color="#FF0288D1"/>
+
+ <size
+ android:height="32dp"
+ android:width="4dp"/>
+ </shape>
+ </item>
+
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="2dp"/>
+
+ <solid android:color="#FF737373"/>
+
+ <size
+ android:height="32dp"
+ android:width="4dp"/>
+ </shape>
+ </item>
+</selector> \ No newline at end of file
diff --git a/app/src/main/res/layout/abstract_recycler_fragment.xml b/app/src/main/res/layout/abstract_recycler_fragment.xml
index 47141450..0e0c87f4 100644
--- a/app/src/main/res/layout/abstract_recycler_fragment.xml
+++ b/app/src/main/res/layout/abstract_recycler_fragment.xml
@@ -10,12 +10,23 @@
android:layout_height="fill_parent"
android:orientation="vertical" >
- <android.support.v7.widget.RecyclerView
- android:id="@+id/fragment_recycler"
+ <RelativeLayout
android:layout_width="fill_parent"
android:layout_height="0dip"
- android:layout_weight="1.0"
- android:scrollbars="vertical"/>
+ android:layout_weight="1.0">
+
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/fragment_recycler"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:scrollbars="vertical"/>
+
+ <github.daneren2005.dsub.view.FastScroller
+ android:id="@+id/fragment_fast_scroller"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:layout_alignParentRight="true"/>
+ </RelativeLayout>
<include layout="@layout/tab_progress" />
</LinearLayout>
diff --git a/app/src/main/res/layout/fast_scroller.xml b/app/src/main/res/layout/fast_scroller.xml
new file mode 100644
index 00000000..b2e244e3
--- /dev/null
+++ b/app/src/main/res/layout/fast_scroller.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:id="@+id/fastscroller_bubble"
+ android:layout_gravity="right|end"
+ android:gravity="center"
+ android:textSize="48sp" tools:text="A"
+ android:layout_width="wrap_content"
+ android:textColor="#FFffffff"
+ android:layout_height="wrap_content"
+ android:background="@drawable/fast_scroller_bubble"
+ android:visibility="visible"/>
+
+ <ImageView
+ android:id="@+id/fastscroller_handle"
+ android:layout_width="wrap_content"
+ android:layout_marginRight="8dp"
+ android:layout_marginLeft="8dp"
+ android:layout_height="wrap_content"
+ android:src="@drawable/fast_scroller_handle"/>
+</merge> \ No newline at end of file