diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/ShareHorizontalAdapter.java b/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/ShareHorizontalAdapter.java new file mode 100644 index 00000000..e72774d7 --- /dev/null +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/ShareHorizontalAdapter.java @@ -0,0 +1,99 @@ +package com.cappielloantonio.tempo.ui.adapter; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.cappielloantonio.tempo.R; +import com.cappielloantonio.tempo.databinding.ItemHorizontalShareBinding; +import com.cappielloantonio.tempo.glide.CustomGlideRequest; +import com.cappielloantonio.tempo.interfaces.ClickCallback; +import com.cappielloantonio.tempo.subsonic.models.Share; +import com.cappielloantonio.tempo.util.Constants; +import com.cappielloantonio.tempo.util.MusicUtil; +import com.cappielloantonio.tempo.util.UIUtil; + +import java.util.Collections; +import java.util.List; + +public class ShareHorizontalAdapter extends RecyclerView.Adapter { + private final ClickCallback click; + + private List shares; + + public ShareHorizontalAdapter(ClickCallback click) { + this.click = click; + this.shares = Collections.emptyList(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + ItemHorizontalShareBinding view = ItemHorizontalShareBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + Share share = shares.get(position); + + holder.item.shareTitleTextView.setText(MusicUtil.getReadableString(share.getDescription())); + holder.item.shareSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.share_subtitle_item, UIUtil.getReadableDate(share.getExpires()))); + + if (share.getEntries() != null && !share.getEntries().isEmpty()) CustomGlideRequest.Builder + .from(holder.itemView.getContext(), share.getEntries().get(0).getCoverArtId(), CustomGlideRequest.ResourceType.Album) + .build() + .into(holder.item.shareCoverImageView); + } + + @Override + public int getItemCount() { + return shares.size(); + } + + public void setItems(List shares) { + this.shares = shares; + notifyDataSetChanged(); + } + + public Share getItem(int id) { + return shares.get(id); + } + + public class ViewHolder extends RecyclerView.ViewHolder { + ItemHorizontalShareBinding item; + + ViewHolder(ItemHorizontalShareBinding item) { + super(item.getRoot()); + + this.item = item; + + item.shareTitleTextView.setSelected(true); + item.shareSubtitleTextView.setSelected(true); + + itemView.setOnClickListener(v -> onClick()); + itemView.setOnLongClickListener(v -> onLongClick()); + + item.shareButton.setOnClickListener(v -> onLongClick()); + } + + private void onClick() { + Bundle bundle = new Bundle(); + bundle.putParcelable(Constants.SHARE_OBJECT, shares.get(getBindingAdapterPosition())); + + click.onShareClick(bundle); + } + + private boolean onLongClick() { + Bundle bundle = new Bundle(); + bundle.putParcelable(Constants.SHARE_OBJECT, shares.get(getBindingAdapterPosition())); + + click.onShareLongClick(bundle); + + return true; + } + } +} diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeTabMusicFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeTabMusicFragment.java index 5edf3659..31f04973 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeTabMusicFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeTabMusicFragment.java @@ -1,7 +1,10 @@ package com.cappielloantonio.tempo.ui.fragment; import android.content.ComponentName; +import android.content.Intent; +import android.net.Uri; import android.os.Bundle; +import android.os.Handler; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -33,6 +36,7 @@ import com.cappielloantonio.tempo.service.DownloaderManager; import com.cappielloantonio.tempo.service.MediaManager; import com.cappielloantonio.tempo.service.MediaService; import com.cappielloantonio.tempo.subsonic.models.Child; +import com.cappielloantonio.tempo.subsonic.models.Share; import com.cappielloantonio.tempo.ui.activity.MainActivity; import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter; import com.cappielloantonio.tempo.ui.adapter.AlbumHorizontalAdapter; @@ -40,6 +44,7 @@ import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter; import com.cappielloantonio.tempo.ui.adapter.ArtistHorizontalAdapter; import com.cappielloantonio.tempo.ui.adapter.DiscoverSongAdapter; import com.cappielloantonio.tempo.ui.adapter.GridTrackAdapter; +import com.cappielloantonio.tempo.ui.adapter.ShareHorizontalAdapter; import com.cappielloantonio.tempo.ui.adapter.SimilarTrackAdapter; import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter; import com.cappielloantonio.tempo.ui.adapter.YearAdapter; @@ -76,6 +81,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback { private AlbumHorizontalAdapter newReleasesAlbumAdapter; private YearAdapter yearAdapter; private GridTrackAdapter gridTrackAdapter; + private ShareHorizontalAdapter shareHorizontalAdapter; private ListenableFuture mediaBrowserListenableFuture; @@ -111,6 +117,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback { initYearSongView(); initRecentAddedAlbumView(); initGridView(); + initSharesView(); } @Override @@ -120,6 +127,12 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback { initializeMediaBrowser(); } + @Override + public void onResume() { + super.onResume(); + refreshSharesView(); + } + @Override public void onStop() { releaseMediaBrowser(); @@ -227,6 +240,11 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback { homeViewModel.refreshMostRecentlyAddedAlbums(getViewLifecycleOwner()); return true; }); + + bind.sharesTextViewRefreshable.setOnLongClickListener(v -> { + homeViewModel.refreshShares(getViewLifecycleOwner()); + return true; + }); } private void initSyncStarredView() { @@ -649,6 +667,45 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback { recentAddedAlbumSnapHelper.attachToRecyclerView(bind.recentlyAddedAlbumsRecyclerView); } + private void initSharesView() { + bind.sharesRecyclerView.setHasFixedSize(true); + + shareHorizontalAdapter = new ShareHorizontalAdapter(this); + bind.sharesRecyclerView.setAdapter(shareHorizontalAdapter); + homeViewModel.getShares(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), shares -> { + if (shares == null) { + if (bind != null) bind.sharesPlaceholder.placeholder.setVisibility(View.VISIBLE); + if (bind != null) bind.sharesSector.setVisibility(View.GONE); + } else { + if (bind != null) bind.sharesPlaceholder.placeholder.setVisibility(View.GONE); + if (bind != null) + bind.sharesSector.setVisibility(!shares.isEmpty() ? View.VISIBLE : View.GONE); + if (bind != null) + bind.sharesRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(shares.size(), 10), GridLayoutManager.HORIZONTAL, false)); + + shareHorizontalAdapter.setItems(shares); + } + }); + + SnapHelper starredTrackSnapHelper = new PagerSnapHelper(); + starredTrackSnapHelper.attachToRecyclerView(bind.sharesRecyclerView); + + bind.sharesRecyclerView.addItemDecoration( + new DotsIndicatorDecoration( + getResources().getDimensionPixelSize(R.dimen.radius), + getResources().getDimensionPixelSize(R.dimen.radius) * 4, + getResources().getDimensionPixelSize(R.dimen.dots_height), + requireContext().getResources().getColor(R.color.titleTextColor, null), + requireContext().getResources().getColor(R.color.titleTextColor, null)) + ); + } + + private void refreshSharesView() { + final Handler handler = new Handler(); + final Runnable runnable = () -> homeViewModel.refreshShares(getViewLifecycleOwner()); + handler.postDelayed(runnable, 100); + } + private void setSlideViewOffset(ViewPager2 viewPager, float pageOffset, float pageMargin) { viewPager.setPageTransformer((page, position) -> { float myOffset = position * -(2 * pageOffset + pageMargin); @@ -748,4 +805,16 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback { public void onYearClick(Bundle bundle) { Navigation.findNavController(requireView()).navigate(R.id.songListPageFragment, bundle); } + + @Override + public void onShareClick(Bundle bundle) { + Share share = bundle.getParcelable(Constants.SHARE_OBJECT); + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(share.getUrl())).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + + @Override + public void onShareLongClick(Bundle bundle) { + Navigation.findNavController(requireView()).navigate(R.id.shareBottomSheetDialog, bundle); + } } diff --git a/app/src/main/java/com/cappielloantonio/tempo/util/UIUtil.java b/app/src/main/java/com/cappielloantonio/tempo/util/UIUtil.java index 23609420..bbbdf988 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/util/UIUtil.java +++ b/app/src/main/java/com/cappielloantonio/tempo/util/UIUtil.java @@ -14,7 +14,9 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -84,4 +86,9 @@ public class UIUtil { return map; } + + public static String getReadableDate(Date date) { + SimpleDateFormat formatter = new SimpleDateFormat("dd MMM, yyyy", Locale.getDefault()); + return formatter.format(date); + } } diff --git a/app/src/main/java/com/cappielloantonio/tempo/viewmodel/HomeViewModel.java b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/HomeViewModel.java index 0d8b1280..25c48055 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/viewmodel/HomeViewModel.java +++ b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/HomeViewModel.java @@ -15,10 +15,12 @@ import com.cappielloantonio.tempo.repository.AlbumRepository; import com.cappielloantonio.tempo.repository.ArtistRepository; import com.cappielloantonio.tempo.repository.ChronologyRepository; import com.cappielloantonio.tempo.repository.FavoriteRepository; +import com.cappielloantonio.tempo.repository.SharingRepository; import com.cappielloantonio.tempo.repository.SongRepository; import com.cappielloantonio.tempo.subsonic.models.AlbumID3; import com.cappielloantonio.tempo.subsonic.models.ArtistID3; import com.cappielloantonio.tempo.subsonic.models.Child; +import com.cappielloantonio.tempo.subsonic.models.Share; import com.cappielloantonio.tempo.util.Preferences; import java.util.ArrayList; @@ -36,6 +38,7 @@ public class HomeViewModel extends AndroidViewModel { private final ArtistRepository artistRepository; private final ChronologyRepository chronologyRepository; private final FavoriteRepository favoriteRepository; + private final SharingRepository sharingRepository; private final MutableLiveData> dicoverSongSample = new MutableLiveData<>(null); private final MutableLiveData> newReleasedAlbum = new MutableLiveData<>(null); @@ -54,6 +57,8 @@ public class HomeViewModel extends AndroidViewModel { private final MutableLiveData> mediaInstantMix = new MutableLiveData<>(null); private final MutableLiveData> artistInstantMix = new MutableLiveData<>(null); private final MutableLiveData> artistBestOf = new MutableLiveData<>(null); + private final MutableLiveData> shares = new MutableLiveData<>(null); + public HomeViewModel(@NonNull Application application) { super(application); @@ -63,6 +68,7 @@ public class HomeViewModel extends AndroidViewModel { artistRepository = new ArtistRepository(); chronologyRepository = new ChronologyRepository(); favoriteRepository = new FavoriteRepository(); + sharingRepository = new SharingRepository(); setOfflineFavorite(); } @@ -204,6 +210,14 @@ public class HomeViewModel extends AndroidViewModel { return artistBestOf; } + public LiveData> getShares(LifecycleOwner owner) { + if (shares.getValue() == null) { + sharingRepository.getShares().observe(owner, shares::postValue); + } + + return shares; + } + public LiveData> getAllStarredTracks() { return songRepository.getStarredSongs(false, -1); } @@ -248,6 +262,10 @@ public class HomeViewModel extends AndroidViewModel { albumRepository.getAlbums("recent", 20, null, null).observe(owner, recentlyPlayedAlbumSample::postValue); } + public void refreshShares(LifecycleOwner owner) { + sharingRepository.getShares().observe(owner, this.shares::postValue); + } + public void setOfflineFavorite() { ArrayList favorites = getFavorites(); ArrayList favoritesToSave = getFavoritesToSave(favorites); diff --git a/app/src/main/res/layout/bottom_sheet_share_dialog.xml b/app/src/main/res/layout/bottom_sheet_share_dialog.xml new file mode 100644 index 00000000..318aa6cb --- /dev/null +++ b/app/src/main/res/layout/bottom_sheet_share_dialog.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home_tab_music.xml b/app/src/main/res/layout/fragment_home_tab_music.xml index 5f97c2db..83d47899 100644 --- a/app/src/main/res/layout/fragment_home_tab_music.xml +++ b/app/src/main/res/layout/fragment_home_tab_music.xml @@ -753,6 +753,39 @@ android:id="@+id/home_recently_added_albums_placeholder" layout="@layout/item_placeholder_album" android:visibility="gone" /> + + + + + + + + + + diff --git a/app/src/main/res/layout/item_horizontal_share.xml b/app/src/main/res/layout/item_horizontal_share.xml new file mode 100644 index 00000000..f48b05cb --- /dev/null +++ b/app/src/main/res/layout/item_horizontal_share.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + \ No newline at end of file