77 | * Animates hiding the view, with the view sliding down and out of the screen. 78 | * After the view has disappeared, its visibility will change to GONE. 79 | * 80 | * @param view The quick return view 81 | */ 82 | private void hide(final View view) { 83 | view.animate().cancel(); 84 | 85 | view.animate() 86 | .translationY(view.getHeight()) 87 | .setInterpolator(INTERPOLATOR) 88 | .setDuration(200) 89 | .setListener(new AnimatorListenerAdapter() { 90 | private boolean isCanceled = false; 91 | 92 | @Override 93 | public void onAnimationStart(Animator animation) { 94 | animState = ANIM_STATE_HIDING; 95 | isCanceled = false; 96 | view.setVisibility(View.VISIBLE); 97 | } 98 | 99 | @Override 100 | public void onAnimationCancel(Animator animation) { 101 | isCanceled = true; 102 | } 103 | 104 | @Override 105 | public void onAnimationEnd(Animator animation) { 106 | animState = ANIM_STATE_NONE; 107 | if (!isCanceled) { 108 | view.setVisibility(View.INVISIBLE); 109 | } 110 | } 111 | }); 112 | } 113 | 114 | /** 115 | * Show the quick return view. 116 | *
117 | * Animates showing the view, with the view sliding up from the bottom of the screen.
118 | * After the view has reappeared, its visibility will change to VISIBLE.
119 | *
120 | * @param view The quick return view
121 | */
122 | private void show(final View view) {
123 | view.animate().cancel();
124 |
125 | view.animate()
126 | .translationY(0f)
127 | .setInterpolator(INTERPOLATOR)
128 | .setDuration(200)
129 | .setListener(new AnimatorListenerAdapter() {
130 | @Override
131 | public void onAnimationStart(Animator animator) {
132 | animState = ANIM_STATE_SHOWING;
133 | view.setVisibility(View.VISIBLE);
134 | }
135 |
136 | @Override
137 | public void onAnimationEnd(Animator animator) {
138 | animState = ANIM_STATE_NONE;
139 | }
140 | });
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tanuj/nowplayinghistory/callbacks/FavoritesItemTouchCallback.java:
--------------------------------------------------------------------------------
1 | package com.tanuj.nowplayinghistory.callbacks;
2 |
3 | import android.graphics.Canvas;
4 | import com.google.android.material.snackbar.Snackbar;
5 | import androidx.recyclerview.widget.RecyclerView;
6 | import androidx.recyclerview.widget.ItemTouchHelper;
7 | import android.view.View;
8 |
9 | import com.tanuj.nowplayinghistory.App;
10 | import com.tanuj.nowplayinghistory.SwipeAction;
11 | import com.tanuj.nowplayinghistory.Utils;
12 | import com.tanuj.nowplayinghistory.adapters.SongsPagedListAdapter;
13 | import com.tanuj.nowplayinghistory.persistence.FavSong;
14 | import com.tanuj.nowplayinghistory.persistence.Song;
15 |
16 | public class FavoritesItemTouchCallback extends ItemTouchHelper.SimpleCallback {
17 |
18 | private final SwipeAction removeSwipeAction = new SwipeAction(SwipeAction.Dir.LEFT, "Remove");
19 |
20 | private final View anchor;
21 | private final SongsPagedListAdapter adapter;
22 |
23 | public FavoritesItemTouchCallback(View anchor, SongsPagedListAdapter adapter) {
24 | super(0, ItemTouchHelper.LEFT);
25 | this.anchor = anchor;
26 | this.adapter = adapter;
27 | }
28 |
29 | @Override
30 | public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
31 | return false;
32 | }
33 |
34 | @Override
35 | public void onSwiped(final RecyclerView.ViewHolder viewHolder, int swipeDir) {
36 | final int position = viewHolder.getAdapterPosition();
37 | Song song = adapter.getItem(position);
38 | final FavSong favSong = new FavSong(song);
39 |
40 | if (swipeDir == ItemTouchHelper.LEFT) {
41 | Utils.executeAsync(() -> App.getDb().songDao().delete(favSong));
42 |
43 | String message = "Removed " + favSong.getSongText() + " from favorites";
44 | Snackbar.make(anchor, message, Snackbar.LENGTH_SHORT)
45 | .setAction("Undo", (view) -> Utils.executeAsync(() -> App.getDb().songDao().insert(favSong)))
46 | .show();
47 | }
48 | }
49 |
50 | @Override
51 | public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
52 | View itemView = viewHolder.itemView;
53 | if (dX < 0) {
54 | removeSwipeAction.draw(c, itemView, dX);
55 | }
56 |
57 | super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tanuj/nowplayinghistory/callbacks/RecentsItemTouchCallback.java:
--------------------------------------------------------------------------------
1 | package com.tanuj.nowplayinghistory.callbacks;
2 |
3 | import android.database.sqlite.SQLiteConstraintException;
4 | import android.graphics.Canvas;
5 | import com.google.android.material.snackbar.Snackbar;
6 | import androidx.recyclerview.widget.RecyclerView;
7 | import androidx.recyclerview.widget.ItemTouchHelper;
8 | import android.view.View;
9 |
10 | import com.tanuj.nowplayinghistory.App;
11 | import com.tanuj.nowplayinghistory.SwipeAction;
12 | import com.tanuj.nowplayinghistory.Utils;
13 | import com.tanuj.nowplayinghistory.adapters.SongsPagedListAdapter;
14 | import com.tanuj.nowplayinghistory.persistence.FavSong;
15 | import com.tanuj.nowplayinghistory.persistence.Song;
16 |
17 | public class RecentsItemTouchCallback extends ItemTouchHelper.SimpleCallback {
18 |
19 | private final SwipeAction deleteSwipeAction = new SwipeAction(SwipeAction.Dir.LEFT, "Delete");
20 | private final SwipeAction favoriteSwipeAction = new SwipeAction(SwipeAction.Dir.RIGHT, "Favorite");
21 |
22 | private final View anchor;
23 | private final SongsPagedListAdapter adapter;
24 |
25 | public RecentsItemTouchCallback(View anchor, SongsPagedListAdapter adapter) {
26 | super(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
27 | this.anchor = anchor;
28 | this.adapter = adapter;
29 | }
30 |
31 | @Override
32 | public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
33 | return false;
34 | }
35 |
36 | @Override
37 | public void onSwiped(final RecyclerView.ViewHolder viewHolder, int swipeDir) {
38 | final int position = viewHolder.getAdapterPosition();
39 | final Song song = adapter.getItem(position);
40 |
41 | if (swipeDir == ItemTouchHelper.LEFT) {
42 | Utils.executeAsync(() -> App.getDb().songDao().delete(song));
43 |
44 | String message = "Deleted " + song.getSongText();
45 | Snackbar.make(anchor, message, Snackbar.LENGTH_SHORT)
46 | .setAction("Undo", (view) -> Utils.executeAsync(() -> App.getDb().songDao().insert(song)))
47 | .show();
48 | } else {
49 | final FavSong favSong = new FavSong(song);
50 | Utils.executeAsync(() -> {
51 | try {
52 | App.getDb().songDao().insert(favSong);
53 | } catch (SQLiteConstraintException e) {
54 | }
55 | }, () -> adapter.notifyItemChanged(position));
56 |
57 | String message = "Added " + song.getSongText() + " to favorites";
58 | Snackbar.make(anchor, message, Snackbar.LENGTH_SHORT)
59 | .setAction("Undo", (view) -> Utils.executeAsync(() -> App.getDb().songDao().delete(favSong)))
60 | .show();
61 | }
62 | }
63 |
64 | @Override
65 | public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
66 | View itemView = viewHolder.itemView;
67 | if (dX < 0) {
68 | deleteSwipeAction.draw(c, itemView, dX);
69 | } else {
70 | favoriteSwipeAction.draw(c, itemView, dX);
71 | }
72 |
73 | super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tanuj/nowplayinghistory/fragments/ClusterDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.tanuj.nowplayinghistory.fragments;
2 |
3 | import android.os.Bundle;
4 | import androidx.annotation.NonNull;
5 | import androidx.annotation.Nullable;
6 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
7 | import androidx.recyclerview.widget.LinearLayoutManager;
8 | import androidx.recyclerview.widget.RecyclerView;
9 | import android.view.LayoutInflater;
10 | import android.view.View;
11 | import android.view.ViewGroup;
12 |
13 | import com.google.android.gms.maps.CameraUpdateFactory;
14 | import com.google.android.gms.maps.GoogleMap;
15 | import com.google.android.gms.maps.MapView;
16 | import com.google.android.gms.maps.OnMapReadyCallback;
17 | import com.google.android.gms.maps.model.LatLng;
18 | import com.google.android.gms.maps.model.MarkerOptions;
19 | import com.google.maps.android.clustering.Cluster;
20 | import com.tanuj.nowplayinghistory.MapItem;
21 | import com.tanuj.nowplayinghistory.R;
22 | import com.tanuj.nowplayinghistory.Utils;
23 | import com.tanuj.nowplayinghistory.adapters.SongsListAdapter;
24 | import com.tanuj.nowplayinghistory.persistence.Song;
25 |
26 | import java.util.ArrayList;
27 | import java.util.List;
28 |
29 | public class ClusterDialogFragment extends BottomSheetDialogFragment implements OnMapReadyCallback {
30 |
31 | private static final String EXTRA_CLUSTER_ITEMS = "cluster";
32 | private static final String EXTRA_CLUSTER_POSITION = "cluster-position";
33 |
34 | private MapView mapView;
35 | private RecyclerView recyclerView;
36 | private ArrayList> loadAllSongs(double minLat, double maxLat, double minLon1, double maxLon1, double minLon2, double maxLon2, long minTimestamp);
32 |
33 | @Query("SELECT * FROM songs WHERE timestamp = (SELECT MAX(timestamp) FROM songs)")
34 | public Song loadLatestSong();
35 |
36 | // FavSong
37 | @Insert
38 | public void insert(FavSong... favSongs);
39 |
40 | @Delete
41 | public void delete(FavSong... favSongs);
42 |
43 | @Query("DELETE FROM fav_songs WHERE timestamp < :maxTimestamp")
44 | public void deleteAllFavSongs(long maxTimestamp);
45 |
46 | @Query("SELECT * FROM fav_songs WHERE timestamp > :minTimestamp ORDER BY timestamp DESC")
47 | public DataSource.Factory
> loadAllFavSongs(double minLat, double maxLat, double minLon1, double maxLon1, double minLon2, double maxLon2, long minTimestamp);
51 | }
52 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tanuj/nowplayinghistory/tasks/InitClusterManagerTask.java:
--------------------------------------------------------------------------------
1 | package com.tanuj.nowplayinghistory.tasks;
2 |
3 | import androidx.lifecycle.LiveData;
4 | import androidx.lifecycle.Observer;
5 | import android.location.Location;
6 | import android.os.AsyncTask;
7 | import androidx.annotation.Nullable;
8 |
9 | import com.google.android.gms.maps.CameraUpdateFactory;
10 | import com.google.android.gms.maps.GoogleMap;
11 | import com.google.android.gms.maps.model.LatLng;
12 | import com.google.android.gms.maps.model.LatLngBounds;
13 | import com.google.maps.android.clustering.ClusterManager;
14 | import com.tanuj.nowplayinghistory.App;
15 | import com.tanuj.nowplayinghistory.MapItem;
16 | import com.tanuj.nowplayinghistory.Utils;
17 | import com.tanuj.nowplayinghistory.persistence.Song;
18 | import com.tanuj.nowplayinghistory.persistence.SongDao;
19 |
20 | import java.util.List;
21 |
22 | public class InitClusterManagerTask extends AsyncTask
> {
23 |
24 | private final GoogleMap map;
25 | private final boolean showFavorites;
26 | private final long minTimestamp;
27 | private final ClusterManager
> prevData;
30 | private boolean skipReload;
31 |
32 | public InitClusterManagerTask(GoogleMap map, boolean showFavorites, long minTimestamp) {
33 | this.map = map;
34 | this.showFavorites = showFavorites;
35 | this.minTimestamp = minTimestamp;
36 | this.clusterManager = new ClusterManager<>(App.getContext(), map);
37 |
38 | clusterManager.setAnimation(false);
39 | clusterManager.setOnClusterItemClickListener(mapItem -> {
40 | skipReload = true;
41 | return false;
42 | });
43 | }
44 |
45 | public void setOnClusterItemInfoWindowClickListener(ClusterManager.OnClusterItemInfoWindowClickListener
> getData(SongDao songDao, LatLngBounds bounds, long minTimestamp, boolean favs) {
117 | // [southwest.latitude, northeast.latitude]
118 | double minLat = bounds.southwest.latitude;
119 | double maxLat = bounds.northeast.latitude;
120 |
121 | double minLon1;
122 | double maxLon1;
123 | double minLon2;
124 | double maxLon2;
125 | if (bounds.southwest.longitude <= bounds.northeast.longitude) {
126 | // [southwest.longitude, northeast.longitude]
127 | minLon1 = bounds.southwest.longitude;
128 | maxLon1 = bounds.northeast.longitude;
129 | minLon2 = 0;
130 | maxLon2 = 0;
131 | } else {
132 | // [southwest.longitude, 180) ∪ [-180, northeast.longitude]
133 | minLon1 = bounds.southwest.longitude;
134 | maxLon1 = 180;
135 | minLon2 = -180;
136 | maxLon2 = bounds.northeast.longitude;
137 | }
138 |
139 | if (favs) {
140 | return songDao.loadAllFavSongs(minLat, maxLat, minLon1, maxLon1, minLon2, maxLon2, minTimestamp);
141 | } else {
142 | return songDao.loadAllSongs(minLat, maxLat, minLon1, maxLon1, minLon2, maxLon2, minTimestamp);
143 | }
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tanuj/nowplayinghistory/viewmodels/SongsListViewModel.java:
--------------------------------------------------------------------------------
1 | package com.tanuj.nowplayinghistory.viewmodels;
2 |
3 | import androidx.lifecycle.LiveData;
4 | import androidx.lifecycle.ViewModel;
5 | import androidx.paging.LivePagedListBuilder;
6 | import androidx.paging.PagedList;
7 |
8 | import com.tanuj.nowplayinghistory.persistence.Song;
9 | import com.tanuj.nowplayinghistory.persistence.SongDao;
10 |
11 | public class SongsListViewModel extends ViewModel {
12 | private LiveData