refactor: extracted common toolbar to better handle two flavors and minimized file duplication

This commit is contained in:
antonio 2023-07-01 18:11:44 +02:00
parent a3fe0de233
commit c711b387bb
29 changed files with 308 additions and 2097 deletions

View file

@ -7,11 +7,8 @@ android {
buildToolsVersion '33.0.0'
defaultConfig {
applicationId 'com.cappielloantonio.tempo'
minSdkVersion 24
targetSdkVersion 34
versionCode 11
versionName '3.4.4'
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
@ -25,6 +22,24 @@ android {
}
}
flavorDimensions "default"
productFlavors {
tempo {
dimension "default"
applicationId 'com.cappielloantonio.tempo'
versionCode 11
versionName '3.4.4'
}
notquitemy {
dimension "default"
applicationId "com.cappielloantonio.notquitemy.tempo"
versionCode 1
versionName "1.0.0"
}
}
buildTypes {
release {
shrinkResources true
@ -68,7 +83,7 @@ dependencies {
implementation 'androidx.core:core-splashscreen:1.0.1'
// Google GMS
implementation 'com.google.android.gms:play-services-cast-framework:21.3.0'
tempoImplementation 'com.google.android.gms:play-services-cast-framework:21.3.0'
// Android Material
implementation 'com.google.android.material:material:1.9.0'
@ -82,7 +97,7 @@ dependencies {
implementation 'androidx.media3:media3-common:1.0.2'
implementation 'androidx.media3:media3-exoplayer:1.0.2'
implementation 'androidx.media3:media3-ui:1.0.2'
implementation 'androidx.media3:media3-cast:1.0.2'
tempoImplementation 'androidx.media3:media3-cast:1.0.2'
annotationProcessor 'com.github.bumptech.glide:compiler:4.15.1'
annotationProcessor 'androidx.room:room-compiler:2.5.2'

View file

@ -1,316 +0,0 @@
package com.cappielloantonio.tempo.service
import android.annotation.SuppressLint
import android.app.PendingIntent.FLAG_IMMUTABLE
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.app.TaskStackBuilder
import android.content.Intent
import android.os.Bundle
import androidx.media3.cast.CastPlayer
import androidx.media3.cast.SessionAvailabilityListener
import androidx.media3.common.*
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import androidx.media3.session.*
import androidx.media3.session.MediaSession.ControllerInfo
import com.cappielloantonio.tempo.R
import com.cappielloantonio.tempo.ui.activity.MainActivity
import com.cappielloantonio.tempo.util.Constants
import com.cappielloantonio.tempo.util.DownloadUtil
import com.cappielloantonio.tempo.util.UIUtil
import com.google.android.gms.cast.framework.CastContext
import com.google.common.collect.ImmutableList
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
@UnstableApi
class MediaService : MediaLibraryService(), SessionAvailabilityListener {
private val librarySessionCallback = CustomMediaLibrarySessionCallback()
private lateinit var player: ExoPlayer
private lateinit var castPlayer: CastPlayer
private lateinit var mediaLibrarySession: MediaLibrarySession
private lateinit var customCommands: List<CommandButton>
private var customLayout = ImmutableList.of<CommandButton>()
companion object {
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON =
"android.media3.session.demo.SHUFFLE_ON"
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF =
"android.media3.session.demo.SHUFFLE_OFF"
}
override fun onCreate() {
super.onCreate()
initializeCustomCommands()
initializePlayer()
initializeCastPlayer()
initializeMediaLibrarySession()
initializePlayerListener()
setPlayer(
null,
if (this::castPlayer.isInitialized && castPlayer.isCastSessionAvailable) castPlayer else player
)
}
override fun onGetSession(controllerInfo: ControllerInfo): MediaLibrarySession {
return mediaLibrarySession
}
override fun onDestroy() {
releasePlayer()
super.onDestroy()
}
private inner class CustomMediaLibrarySessionCallback : MediaLibrarySession.Callback {
override fun onConnect(
session: MediaSession,
controller: ControllerInfo
): MediaSession.ConnectionResult {
val connectionResult = super.onConnect(session, controller)
val availableSessionCommands = connectionResult.availableSessionCommands.buildUpon()
customCommands.forEach { commandButton ->
// TODO: Aggiungere i comandi personalizzati
// commandButton.sessionCommand?.let { availableSessionCommands.add(it) }
}
return MediaSession.ConnectionResult.accept(
availableSessionCommands.build(),
connectionResult.availablePlayerCommands
)
}
override fun onPostConnect(session: MediaSession, controller: ControllerInfo) {
if (!customLayout.isEmpty() && controller.controllerVersion != 0) {
ignoreFuture(mediaLibrarySession.setCustomLayout(controller, customLayout))
}
}
override fun onCustomCommand(
session: MediaSession,
controller: ControllerInfo,
customCommand: SessionCommand,
args: Bundle
): ListenableFuture<SessionResult> {
if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON == customCommand.customAction) {
player.shuffleModeEnabled = true
customLayout = ImmutableList.of(customCommands[1])
session.setCustomLayout(customLayout)
} else if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF == customCommand.customAction) {
player.shuffleModeEnabled = false
customLayout = ImmutableList.of(customCommands[0])
session.setCustomLayout(customLayout)
}
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
}
/* override fun onGetLibraryRoot(
session: MediaLibrarySession,
browser: ControllerInfo,
params: LibraryParams?
): ListenableFuture<LibraryResult<MediaItem>> {
return Futures.immediateFuture(LibraryResult.ofItem(MediaItemTree.getRootItem(), params))
}
override fun onGetItem(
session: MediaLibrarySession,
browser: ControllerInfo,
mediaId: String
): ListenableFuture<LibraryResult<MediaItem>> {
val item =
MediaItemTree.getItem(mediaId)
?: return Futures.immediateFuture(
LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE)
)
return Futures.immediateFuture(LibraryResult.ofItem(item, /* params= */ null))
}
override fun onSubscribe(
session: MediaLibrarySession,
browser: ControllerInfo,
parentId: String,
params: LibraryParams?
): ListenableFuture<LibraryResult<Void>> {
val children =
MediaItemTree.getChildren(parentId)
?: return Futures.immediateFuture(
LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE)
)
session.notifyChildrenChanged(browser, parentId, children.size, params)
return Futures.immediateFuture(LibraryResult.ofVoid())
}
override fun onGetChildren(
session: MediaLibrarySession,
browser: ControllerInfo,
parentId: String,
page: Int,
pageSize: Int,
params: LibraryParams?
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
val children =
MediaItemTree.getChildren(parentId)
?: return Futures.immediateFuture(
LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE)
)
return Futures.immediateFuture(LibraryResult.ofItemList(children, params))
}*/
override fun onAddMediaItems(
mediaSession: MediaSession,
controller: ControllerInfo,
mediaItems: List<MediaItem>
): ListenableFuture<List<MediaItem>> {
val updatedMediaItems = mediaItems.map {
it.buildUpon()
.setUri(it.requestMetadata.mediaUri)
.setMediaMetadata(it.mediaMetadata)
.setMimeType(MimeTypes.BASE_TYPE_AUDIO)
.build()
}
return Futures.immediateFuture(updatedMediaItems)
}
}
private fun initializeCustomCommands() {
customCommands =
listOf(
getShuffleCommandButton(
SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON, Bundle.EMPTY)
),
getShuffleCommandButton(
SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF, Bundle.EMPTY)
)
)
customLayout = ImmutableList.of(customCommands[0])
}
private fun initializePlayer() {
player = ExoPlayer.Builder(this)
.setRenderersFactory(getRenderersFactory())
.setMediaSourceFactory(getMediaSourceFactory())
.setAudioAttributes(AudioAttributes.DEFAULT, true)
.setHandleAudioBecomingNoisy(true)
.setWakeMode(C.WAKE_MODE_NETWORK)
.build()
}
private fun initializeCastPlayer() {
if (UIUtil.isCastApiAvailable(this)) {
castPlayer = CastPlayer(CastContext.getSharedInstance(this))
castPlayer.setSessionAvailabilityListener(this)
}
}
private fun initializeMediaLibrarySession() {
val sessionActivityPendingIntent =
TaskStackBuilder.create(this).run {
addNextIntent(Intent(this@MediaService, MainActivity::class.java))
getPendingIntent(0, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)
}
mediaLibrarySession =
MediaLibrarySession.Builder(this, player, librarySessionCallback)
.setSessionActivity(sessionActivityPendingIntent)
.build()
if (!customLayout.isEmpty()) {
mediaLibrarySession.setCustomLayout(customLayout)
}
}
private fun initializePlayerListener() {
player.addListener(object : Player.Listener {
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
if (mediaItem == null) return
if(reason == Player.MEDIA_ITEM_TRANSITION_REASON_SEEK || reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO) {
MediaManager.setLastPlayedTimestamp(mediaItem)
}
}
override fun onIsPlayingChanged(isPlaying: Boolean) {
if (!isPlaying) {
MediaManager.setPlayingPausedTimestamp(
player.currentMediaItem,
player.currentPosition
)
}
}
override fun onPositionDiscontinuity(
oldPosition: Player.PositionInfo,
newPosition: Player.PositionInfo,
reason: Int
) {
super.onPositionDiscontinuity(oldPosition, newPosition, reason)
if(reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) {
if (oldPosition.mediaItem?.mediaMetadata?.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC) {
MediaManager.scrobble(oldPosition.mediaItem)
MediaManager.saveChronology(oldPosition.mediaItem)
}
if (newPosition.mediaItem?.mediaMetadata?.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC) {
MediaManager.setLastPlayedTimestamp(newPosition.mediaItem)
}
}
}
})
}
private fun setPlayer(oldPlayer: Player?, newPlayer: Player) {
if (oldPlayer === newPlayer) return
oldPlayer?.stop()
mediaLibrarySession.player = newPlayer
}
private fun releasePlayer() {
if (this::castPlayer.isInitialized) castPlayer.setSessionAvailabilityListener(null)
if (this::castPlayer.isInitialized) castPlayer.release()
player.release()
mediaLibrarySession.release()
}
@SuppressLint("PrivateResource")
private fun getShuffleCommandButton(sessionCommand: SessionCommand): CommandButton {
val isOn = sessionCommand.customAction == CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON
return CommandButton.Builder()
.setDisplayName(
getString(
if (isOn) R.string.exo_controls_shuffle_on_description
else R.string.exo_controls_shuffle_off_description
)
)
.setSessionCommand(sessionCommand)
.setIconResId(if (isOn) R.drawable.exo_icon_shuffle_off else R.drawable.exo_icon_shuffle_on)
.build()
}
private fun ignoreFuture(customLayout: ListenableFuture<SessionResult>) {
/* Do nothing. */
}
private fun getRenderersFactory() = DownloadUtil.buildRenderersFactory(this, false)
private fun getMediaSourceFactory() =
DefaultMediaSourceFactory(this).setDataSourceFactory(DownloadUtil.getDataSourceFactory(this))
override fun onCastSessionAvailable() {
setPlayer(player, castPlayer)
}
override fun onCastSessionUnavailable() {
setPlayer(castPlayer, player)
}
}

View file

@ -19,9 +19,8 @@ import androidx.media3.session.SessionToken;
import com.cappielloantonio.tempo.service.DownloaderService;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.dialog.BatteryOptimizationDialog;
import com.cappielloantonio.tempo.util.Flavors;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.util.UIUtil;
import com.google.android.gms.cast.framework.CastContext;
import com.google.android.material.elevation.SurfaceColors;
import com.google.common.util.concurrent.ListenableFuture;
@ -34,7 +33,7 @@ public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initializeCastContext();
Flavors.initializeCastContext(this);
initializeDownloader();
checkBatteryOptimization();
checkPermission();
@ -54,7 +53,7 @@ public class BaseActivity extends AppCompatActivity {
}
private void checkBatteryOptimization() {
if (detectBatteryOptimization() && Preferences.askForOptimization()) {
if (detectBatteryOptimization() && Boolean.TRUE.equals(Preferences.askForOptimization())) {
showBatteryOptimizationDialog();
}
}
@ -98,10 +97,6 @@ public class BaseActivity extends AppCompatActivity {
}
}
private void initializeCastContext() {
if (UIUtil.isCastApiAvailable(this)) CastContext.getSharedInstance(this);
}
private void setNavigationBarColor() {
getWindow().setNavigationBarColor(SurfaceColors.getColorForElevation(this, 10));
}

View file

@ -3,9 +3,6 @@ package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
@ -28,7 +25,7 @@ import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.DownloadHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.DownloadViewModel;
import com.google.android.gms.cast.framework.CastButtonFactory;
import com.google.android.material.appbar.MaterialToolbar;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Objects;
@ -43,18 +40,7 @@ public class DownloadFragment extends Fragment implements ClickCallback {
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.main_page_menu, menu);
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.media_route_menu_item);
}
private MaterialToolbar materialToolbar;
@Nullable
@Override
@ -97,22 +83,11 @@ public class DownloadFragment extends Fragment implements ClickCallback {
bind = null;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_search) {
activity.navController.navigate(R.id.action_downloadFragment_to_searchFragment);
return true;
} else if (item.getItemId() == R.id.action_settings) {
activity.navController.navigate(R.id.action_downloadFragment_to_settingsFragment);
return true;
}
return false;
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
Objects.requireNonNull(bind.toolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
materialToolbar = bind.getRoot().findViewById(R.id.toolbar);
activity.setSupportActionBar(materialToolbar);
Objects.requireNonNull(materialToolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
}
private void initDownloadedSongView() {

View file

@ -2,9 +2,6 @@ package com.cappielloantonio.tempo.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
@ -18,7 +15,9 @@ import com.cappielloantonio.tempo.databinding.FragmentHomeBinding;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.fragment.pager.HomePager;
import com.cappielloantonio.tempo.util.Preferences;
import com.google.android.gms.cast.framework.CastButtonFactory;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.MaterialToolbar;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import java.util.Objects;
@ -30,18 +29,9 @@ public class HomeFragment extends Fragment {
private FragmentHomeBinding bind;
private MainActivity activity;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.main_page_menu, menu);
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.media_route_menu_item);
}
private MaterialToolbar materialToolbar;
private AppBarLayout appBarLayout;
private TabLayout tabLayout;
@Nullable
@Override
@ -73,22 +63,18 @@ public class HomeFragment extends Fragment {
bind = null;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_search) {
activity.navController.navigate(R.id.action_homeFragment_to_searchFragment);
return true;
} else if (item.getItemId() == R.id.action_settings) {
activity.navController.navigate(R.id.action_homeFragment_to_settingsFragment);
return true;
}
return false;
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
Objects.requireNonNull(bind.toolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
appBarLayout = bind.getRoot().findViewById(R.id.toolbar_fragment);
materialToolbar = bind.getRoot().findViewById(R.id.toolbar);
activity.setSupportActionBar(materialToolbar);
Objects.requireNonNull(materialToolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
tabLayout = new TabLayout(requireContext());
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
tabLayout.setTabMode(TabLayout.MODE_FIXED);
appBarLayout.addView(tabLayout);
}
private void initHomePager() {
@ -106,13 +92,13 @@ public class HomeFragment extends Fragment {
bind.homeViewPager.setOffscreenPageLimit(3);
bind.homeViewPager.setUserInputEnabled(false);
new TabLayoutMediator(bind.homeTabLayout, bind.homeViewPager,
new TabLayoutMediator(tabLayout, bind.homeViewPager,
(tab, position) -> {
tab.setText(pager.getPageTitle(position));
// tab.setIcon(pager.getPageIcon(position));
}
).attach();
bind.homeTabLayout.setVisibility(Preferences.isPodcastSectionVisible() || Preferences.isRadioSectionVisible() ? View.VISIBLE : View.GONE);
tabLayout.setVisibility(Preferences.isPodcastSectionVisible() || Preferences.isRadioSectionVisible() ? View.VISIBLE : View.GONE);
}
}

View file

@ -2,9 +2,6 @@ package com.cappielloantonio.tempo.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
@ -30,7 +27,7 @@ import com.cappielloantonio.tempo.ui.adapter.PlaylistHorizontalAdapter;
import com.cappielloantonio.tempo.ui.dialog.PlaylistEditorDialog;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.LibraryViewModel;
import com.google.android.gms.cast.framework.CastButtonFactory;
import com.google.android.material.appbar.MaterialToolbar;
import java.util.Objects;
@ -46,21 +43,9 @@ public class LibraryFragment extends Fragment implements ClickCallback {
private AlbumAdapter albumAdapter;
private ArtistAdapter artistAdapter;
private GenreAdapter genreAdapter;
private PlaylistHorizontalAdapter playlistHorizontalAdapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.main_page_menu, menu);
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.media_route_menu_item);
}
private MaterialToolbar materialToolbar;
@Nullable
@Override
@ -100,19 +85,6 @@ public class LibraryFragment extends Fragment implements ClickCallback {
bind = null;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_search) {
activity.navController.navigate(R.id.action_libraryFragment_to_searchFragment);
return true;
} else if (item.getItemId() == R.id.action_settings) {
activity.navController.navigate(R.id.action_libraryFragment_to_settingsFragment);
return true;
}
return false;
}
private void init() {
bind.albumCatalogueTextViewClickable.setOnClickListener(v -> activity.navController.navigate(R.id.action_libraryFragment_to_albumCatalogueFragment));
bind.artistCatalogueTextViewClickable.setOnClickListener(v -> activity.navController.navigate(R.id.action_libraryFragment_to_artistCatalogueFragment));
@ -142,8 +114,10 @@ public class LibraryFragment extends Fragment implements ClickCallback {
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
Objects.requireNonNull(bind.toolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
materialToolbar = bind.getRoot().findViewById(R.id.toolbar);
activity.setSupportActionBar(materialToolbar);
Objects.requireNonNull(materialToolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
}
private void initMusicFolderView() {

View file

@ -7,9 +7,6 @@ import android.graphics.drawable.InsetDrawable;
import androidx.recyclerview.widget.DividerItemDecoration;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
public class UIUtil {
public static int getSpanCount(int itemCount, int maxSpan) {
int itemSize = itemCount == 0 ? 1 : itemCount;
@ -21,10 +18,6 @@ public class UIUtil {
}
}
public static boolean isCastApiAvailable(Context context) {
return GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS;
}
public static DividerItemDecoration getDividerItemDecoration(Context context) {
int[] ATTRS = new int[]{android.R.attr.listDivider};

View file

@ -4,40 +4,11 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
<fragment
android:id="@+id/toolbar_fragment"
android:name="com.cappielloantonio.tempo.ui.fragment.ToolbarFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorSurface"
app:layout_scrollFlags="scroll|enterAlways|snap">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="8dp"
android:background="@drawable/ic_toolbar_tempo" />
<TextView
style="@style/HeadlineMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:text="@string/app_name" />
</LinearLayout>
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.appbar.AppBarLayout>
android:layout_height="wrap_content" />
<ProgressBar
android:id="@+id/loading_progress_bar"

View file

@ -6,47 +6,11 @@
android:layout_height="match_parent"
tools:context=".ui.fragment.HomeFragment">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
<fragment
android:id="@+id/toolbar_fragment"
android:name="com.cappielloantonio.tempo.ui.fragment.ToolbarFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorSurface"
app:layout_scrollFlags="scroll|enterAlways|snap">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="8dp"
android:background="@drawable/ic_toolbar_tempo" />
<TextView
style="@style/HeadlineMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:text="@string/app_name" />
</LinearLayout>
</com.google.android.material.appbar.MaterialToolbar>
<com.google.android.material.tabs.TabLayout
android:id="@+id/homeTabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabGravity="fill"
app:tabMode="fixed" />
</com.google.android.material.appbar.AppBarLayout>
android:layout_height="wrap_content" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/homeViewPager"

View file

@ -4,40 +4,11 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
<fragment
android:id="@+id/toolbar_fragment"
android:name="com.cappielloantonio.tempo.ui.fragment.ToolbarFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorSurface"
app:layout_scrollFlags="scroll|enterAlways|snap">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="8dp"
android:background="@drawable/ic_toolbar_tempo" />
<TextView
style="@style/HeadlineMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:text="@string/app_name" />
</LinearLayout>
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.appbar.AppBarLayout>
android:layout_height="wrap_content" />
<androidx.core.widget.NestedScrollView
android:id="@+id/fragment_library_nested_scroll_view"

View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.appbar.AppBarLayout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorSurface"
app:layout_scrollFlags="scroll|enterAlways|snap">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="8dp"
android:background="@drawable/ic_toolbar_tempo" />
<TextView
style="@style/HeadlineMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:text="@string/app_name" />
</LinearLayout>
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.appbar.AppBarLayout>

View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.appbar.AppBarLayout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorSurface"
app:layout_scrollFlags="scroll|enterAlways|snap">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="8dp"
android:background="@drawable/ic_toolbar_tempo" />
<TextView
style="@style/HeadlineMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:text="@string/app_name" />
</LinearLayout>
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.appbar.AppBarLayout>

View file

@ -256,4 +256,6 @@
<string name="menu_sort_name">Name</string>
<string name="menu_sort_random">Random</string>
<string name="description_empty_title">No description available</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
</resources>

View file

@ -6,8 +6,6 @@ import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.app.TaskStackBuilder
import android.content.Intent
import android.os.Bundle
import androidx.media3.cast.CastPlayer
import androidx.media3.cast.SessionAvailabilityListener
import androidx.media3.common.*
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
@ -18,19 +16,16 @@ import com.cappielloantonio.tempo.R
import com.cappielloantonio.tempo.ui.activity.MainActivity
import com.cappielloantonio.tempo.util.Constants
import com.cappielloantonio.tempo.util.DownloadUtil
import com.cappielloantonio.tempo.util.UIUtil
import com.google.android.gms.cast.framework.CastContext
import com.google.common.collect.ImmutableList
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
@UnstableApi
class MediaService : MediaLibraryService(), SessionAvailabilityListener {
class MediaService : MediaLibraryService() {
private val librarySessionCallback = CustomMediaLibrarySessionCallback()
private lateinit var player: ExoPlayer
private lateinit var castPlayer: CastPlayer
private lateinit var mediaLibrarySession: MediaLibrarySession
private lateinit var customCommands: List<CommandButton>
@ -48,14 +43,10 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
initializeCustomCommands()
initializePlayer()
initializeCastPlayer()
initializeMediaLibrarySession()
initializePlayerListener()
setPlayer(
null,
if (this::castPlayer.isInitialized && castPlayer.isCastSessionAvailable) castPlayer else player
)
setPlayer(player)
}
override fun onGetSession(controllerInfo: ControllerInfo): MediaLibrarySession {
@ -112,59 +103,6 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
}
/* override fun onGetLibraryRoot(
session: MediaLibrarySession,
browser: ControllerInfo,
params: LibraryParams?
): ListenableFuture<LibraryResult<MediaItem>> {
return Futures.immediateFuture(LibraryResult.ofItem(MediaItemTree.getRootItem(), params))
}
override fun onGetItem(
session: MediaLibrarySession,
browser: ControllerInfo,
mediaId: String
): ListenableFuture<LibraryResult<MediaItem>> {
val item =
MediaItemTree.getItem(mediaId)
?: return Futures.immediateFuture(
LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE)
)
return Futures.immediateFuture(LibraryResult.ofItem(item, /* params= */ null))
}
override fun onSubscribe(
session: MediaLibrarySession,
browser: ControllerInfo,
parentId: String,
params: LibraryParams?
): ListenableFuture<LibraryResult<Void>> {
val children =
MediaItemTree.getChildren(parentId)
?: return Futures.immediateFuture(
LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE)
)
session.notifyChildrenChanged(browser, parentId, children.size, params)
return Futures.immediateFuture(LibraryResult.ofVoid())
}
override fun onGetChildren(
session: MediaLibrarySession,
browser: ControllerInfo,
parentId: String,
page: Int,
pageSize: Int,
params: LibraryParams?
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
val children =
MediaItemTree.getChildren(parentId)
?: return Futures.immediateFuture(
LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE)
)
return Futures.immediateFuture(LibraryResult.ofItemList(children, params))
}*/
override fun onAddMediaItems(
mediaSession: MediaSession,
controller: ControllerInfo,
@ -205,13 +143,6 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
.build()
}
private fun initializeCastPlayer() {
if (UIUtil.isCastApiAvailable(this)) {
castPlayer = CastPlayer(CastContext.getSharedInstance(this))
castPlayer.setSessionAvailabilityListener(this)
}
}
private fun initializeMediaLibrarySession() {
val sessionActivityPendingIntent =
TaskStackBuilder.create(this).run {
@ -269,15 +200,11 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
})
}
private fun setPlayer(oldPlayer: Player?, newPlayer: Player) {
if (oldPlayer === newPlayer) return
oldPlayer?.stop()
mediaLibrarySession.player = newPlayer
private fun setPlayer(player: Player) {
mediaLibrarySession.player = player
}
private fun releasePlayer() {
if (this::castPlayer.isInitialized) castPlayer.setSessionAvailabilityListener(null)
if (this::castPlayer.isInitialized) castPlayer.release()
player.release()
mediaLibrarySession.release()
}
@ -306,11 +233,5 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
private fun getMediaSourceFactory() =
DefaultMediaSourceFactory(this).setDataSourceFactory(DownloadUtil.getDataSourceFactory(this))
override fun onCastSessionAvailable() {
setPlayer(player, castPlayer)
}
override fun onCastSessionUnavailable() {
setPlayer(castPlayer, player)
}
}

View file

@ -1,101 +0,0 @@
package com.cappielloantonio.tempo.ui.activity.base;
import android.Manifest;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.PowerManager;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.offline.DownloadService;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import com.cappielloantonio.tempo.service.DownloaderService;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.dialog.BatteryOptimizationDialog;
import com.cappielloantonio.tempo.util.Preferences;
import com.google.android.material.elevation.SurfaceColors;
import com.google.common.util.concurrent.ListenableFuture;
@UnstableApi
public class BaseActivity extends AppCompatActivity {
private static final String TAG = "BaseActivity";
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initializeDownloader();
checkBatteryOptimization();
checkPermission();
}
@Override
protected void onStart() {
super.onStart();
setNavigationBarColor();
initializeBrowser();
}
@Override
protected void onStop() {
releaseBrowser();
super.onStop();
}
private void checkBatteryOptimization() {
if (detectBatteryOptimization() && Preferences.askForOptimization()) {
showBatteryOptimizationDialog();
}
}
private void checkPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.POST_NOTIFICATIONS}, 101);
}
}
}
private boolean detectBatteryOptimization() {
String packageName = getPackageName();
PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
return !powerManager.isIgnoringBatteryOptimizations(packageName);
}
private void showBatteryOptimizationDialog() {
BatteryOptimizationDialog dialog = new BatteryOptimizationDialog();
dialog.show(getSupportFragmentManager(), null);
}
private void initializeBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(this, new SessionToken(this, new ComponentName(this, MediaService.class))).buildAsync();
}
private void releaseBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
public ListenableFuture<MediaBrowser> getMediaBrowserListenableFuture() {
return mediaBrowserListenableFuture;
}
private void initializeDownloader() {
try {
DownloadService.start(this, DownloaderService.class);
} catch (IllegalStateException e) {
DownloadService.startForeground(this, DownloaderService.class);
}
}
private void setNavigationBarColor() {
getWindow().setNavigationBarColor(SurfaceColors.getColorForElevation(this, 10));
}
}

View file

@ -1,168 +0,0 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentDownloadBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.DownloadHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.DownloadViewModel;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Objects;
@UnstableApi
public class DownloadFragment extends Fragment implements ClickCallback {
private FragmentDownloadBinding bind;
private MainActivity activity;
private DownloadViewModel downloadViewModel;
private DownloadHorizontalAdapter downloadHorizontalAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.main_page_menu, menu);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentDownloadBinding.inflate(inflater, container, false);
View view = bind.getRoot();
downloadViewModel = new ViewModelProvider(requireActivity()).get(DownloadViewModel.class);
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initAppBar();
initDownloadedSongView();
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
activity.setBottomNavigationBarVisibility(true);
activity.setBottomSheetVisibility(true);
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_search) {
activity.navController.navigate(R.id.action_downloadFragment_to_searchFragment);
return true;
} else if (item.getItemId() == R.id.action_settings) {
activity.navController.navigate(R.id.action_downloadFragment_to_settingsFragment);
return true;
}
return false;
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
Objects.requireNonNull(bind.toolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
}
private void initDownloadedSongView() {
bind.downloadedTracksRecyclerView.setHasFixedSize(true);
downloadHorizontalAdapter = new DownloadHorizontalAdapter(this);
bind.downloadedTracksRecyclerView.setAdapter(downloadHorizontalAdapter);
downloadViewModel.getDownloadedTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
if (songs != null) {
if (songs.isEmpty()) {
if (bind != null) {
bind.emptyDownloadLayout.setVisibility(View.VISIBLE);
bind.fragmentDownloadNestedScrollView.setVisibility(View.GONE);
bind.downloadDownloadedTracksPlaceholder.placeholder.setVisibility(View.VISIBLE);
bind.downloadDownloadedTracksSector.setVisibility(View.GONE);
}
} else {
if (bind != null) {
bind.emptyDownloadLayout.setVisibility(View.GONE);
bind.fragmentDownloadNestedScrollView.setVisibility(View.VISIBLE);
bind.downloadDownloadedTracksPlaceholder.placeholder.setVisibility(View.GONE);
bind.downloadDownloadedTracksSector.setVisibility(View.VISIBLE);
bind.downloadedTracksRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
downloadHorizontalAdapter.setItems(songs);
}
}
if (bind != null) bind.loadingProgressBar.setVisibility(View.GONE);
}
});
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
@Override
public void onMediaClick(Bundle bundle) {
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
activity.setBottomSheetInPeek(true);
}
@Override
public void onMediaLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
}
}

View file

@ -1,116 +0,0 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.media3.common.util.UnstableApi;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentHomeBinding;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.fragment.pager.HomePager;
import com.cappielloantonio.tempo.util.Preferences;
import com.google.android.material.tabs.TabLayoutMediator;
import java.util.Objects;
@UnstableApi
public class HomeFragment extends Fragment {
private static final String TAG = "HomeFragment";
private FragmentHomeBinding bind;
private MainActivity activity;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.main_page_menu, menu);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentHomeBinding.inflate(inflater, container, false);
return bind.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initAppBar();
initHomePager();
}
@Override
public void onStart() {
super.onStart();
activity.setBottomNavigationBarVisibility(true);
activity.setBottomSheetVisibility(true);
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_search) {
activity.navController.navigate(R.id.action_homeFragment_to_searchFragment);
return true;
} else if (item.getItemId() == R.id.action_settings) {
activity.navController.navigate(R.id.action_homeFragment_to_settingsFragment);
return true;
}
return false;
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
Objects.requireNonNull(bind.toolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
}
private void initHomePager() {
HomePager pager = new HomePager(this);
pager.addFragment(new HomeTabMusicFragment(), "Music", R.drawable.ic_home);
if (Preferences.isPodcastSectionVisible())
pager.addFragment(new HomeTabPodcastFragment(), "Podcast", R.drawable.ic_graphic_eq);
if (Preferences.isRadioSectionVisible())
pager.addFragment(new HomeTabRadioFragment(), "Radio", R.drawable.ic_play_for_work);
bind.homeViewPager.setAdapter(pager);
bind.homeViewPager.setOffscreenPageLimit(3);
bind.homeViewPager.setUserInputEnabled(false);
new TabLayoutMediator(bind.homeTabLayout, bind.homeViewPager,
(tab, position) -> {
tab.setText(pager.getPageTitle(position));
// tab.setIcon(pager.getPageIcon(position));
}
).attach();
bind.homeTabLayout.setVisibility(Preferences.isPodcastSectionVisible() || Preferences.isRadioSectionVisible() ? View.VISIBLE : View.GONE);
}
}

View file

@ -1,306 +0,0 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentLibraryBinding;
import com.cappielloantonio.tempo.helper.recyclerview.CustomLinearSnapHelper;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter;
import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter;
import com.cappielloantonio.tempo.ui.adapter.GenreAdapter;
import com.cappielloantonio.tempo.ui.adapter.MusicFolderAdapter;
import com.cappielloantonio.tempo.ui.adapter.PlaylistHorizontalAdapter;
import com.cappielloantonio.tempo.ui.dialog.PlaylistEditorDialog;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.LibraryViewModel;
import java.util.Objects;
@UnstableApi
public class LibraryFragment extends Fragment implements ClickCallback {
private static final String TAG = "LibraryFragment";
private FragmentLibraryBinding bind;
private MainActivity activity;
private LibraryViewModel libraryViewModel;
private MusicFolderAdapter musicFolderAdapter;
private AlbumAdapter albumAdapter;
private ArtistAdapter artistAdapter;
private GenreAdapter genreAdapter;
private PlaylistHorizontalAdapter playlistHorizontalAdapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.main_page_menu, menu);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentLibraryBinding.inflate(inflater, container, false);
View view = bind.getRoot();
libraryViewModel = new ViewModelProvider(requireActivity()).get(LibraryViewModel.class);
init();
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initAppBar();
initMusicFolderView();
initAlbumView();
initArtistView();
initGenreView();
initPlaylistSlideView();
}
@Override
public void onStart() {
super.onStart();
activity.setBottomNavigationBarVisibility(true);
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_search) {
activity.navController.navigate(R.id.action_libraryFragment_to_searchFragment);
return true;
} else if (item.getItemId() == R.id.action_settings) {
activity.navController.navigate(R.id.action_libraryFragment_to_settingsFragment);
return true;
}
return false;
}
private void init() {
bind.albumCatalogueTextViewClickable.setOnClickListener(v -> activity.navController.navigate(R.id.action_libraryFragment_to_albumCatalogueFragment));
bind.artistCatalogueTextViewClickable.setOnClickListener(v -> activity.navController.navigate(R.id.action_libraryFragment_to_artistCatalogueFragment));
bind.genreCatalogueTextViewClickable.setOnClickListener(v -> activity.navController.navigate(R.id.action_libraryFragment_to_genreCatalogueFragment));
bind.playlistCatalogueTextViewClickable.setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putString(Constants.PLAYLIST_ALL, Constants.PLAYLIST_ALL);
activity.navController.navigate(R.id.action_libraryFragment_to_playlistCatalogueFragment, bundle);
});
bind.albumCatalogueSampleTextViewRefreshable.setOnLongClickListener(view -> {
libraryViewModel.refreshAlbumSample(getViewLifecycleOwner());
return true;
});
bind.artistCatalogueSampleTextViewRefreshable.setOnLongClickListener(view -> {
libraryViewModel.refreshArtistSample(getViewLifecycleOwner());
return true;
});
bind.genreCatalogueSampleTextViewRefreshable.setOnLongClickListener(view -> {
libraryViewModel.refreshGenreSample(getViewLifecycleOwner());
return true;
});
bind.playlistCatalogueSampleTextViewRefreshable.setOnLongClickListener(view -> {
libraryViewModel.refreshPlaylistSample(getViewLifecycleOwner());
return true;
});
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
Objects.requireNonNull(bind.toolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
}
private void initMusicFolderView() {
bind.musicFolderRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.musicFolderRecyclerView.setHasFixedSize(true);
musicFolderAdapter = new MusicFolderAdapter(this);
bind.musicFolderRecyclerView.setAdapter(musicFolderAdapter);
libraryViewModel.getMusicFolders(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), musicFolders -> {
if (musicFolders == null) {
if (bind != null)
bind.libraryMusicFolderPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryMusicFolderSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.libraryMusicFolderPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryMusicFolderSector.setVisibility(!musicFolders.isEmpty() ? View.VISIBLE : View.GONE);
musicFolderAdapter.setItems(musicFolders);
}
});
}
private void initAlbumView() {
bind.albumRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.albumRecyclerView.setHasFixedSize(true);
albumAdapter = new AlbumAdapter(this);
bind.albumRecyclerView.setAdapter(albumAdapter);
libraryViewModel.getAlbumSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
if (bind != null)
bind.libraryAlbumPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryAlbumSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.libraryAlbumPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryAlbumSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
albumAdapter.setItems(albums);
}
});
CustomLinearSnapHelper albumSnapHelper = new CustomLinearSnapHelper();
albumSnapHelper.attachToRecyclerView(bind.albumRecyclerView);
}
private void initArtistView() {
bind.artistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.artistRecyclerView.setHasFixedSize(true);
artistAdapter = new ArtistAdapter(this, false, false);
bind.artistRecyclerView.setAdapter(artistAdapter);
libraryViewModel.getArtistSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> {
if (artists == null) {
if (bind != null)
bind.libraryArtistPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryArtistSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.libraryArtistPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryArtistSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
artistAdapter.setItems(artists);
}
});
CustomLinearSnapHelper artistSnapHelper = new CustomLinearSnapHelper();
artistSnapHelper.attachToRecyclerView(bind.artistRecyclerView);
}
private void initGenreView() {
bind.genreRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 3, GridLayoutManager.HORIZONTAL, false));
bind.genreRecyclerView.setHasFixedSize(true);
genreAdapter = new GenreAdapter(this);
bind.genreRecyclerView.setAdapter(genreAdapter);
libraryViewModel.getGenreSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), genres -> {
if (genres == null) {
if (bind != null)
bind.libraryGenrePlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryGenresSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.libraryGenrePlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryGenresSector.setVisibility(!genres.isEmpty() ? View.VISIBLE : View.GONE);
genreAdapter.setItems(genres);
}
});
CustomLinearSnapHelper genreSnapHelper = new CustomLinearSnapHelper();
genreSnapHelper.attachToRecyclerView(bind.genreRecyclerView);
}
private void initPlaylistSlideView() {
bind.playlistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.playlistRecyclerView.setHasFixedSize(true);
playlistHorizontalAdapter = new PlaylistHorizontalAdapter(this);
bind.playlistRecyclerView.setAdapter(playlistHorizontalAdapter);
libraryViewModel.getPlaylistSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), playlists -> {
if (playlists == null) {
if (bind != null)
bind.libraryPlaylistPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryPlaylistSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.libraryPlaylistPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryPlaylistSector.setVisibility(!playlists.isEmpty() ? View.VISIBLE : View.GONE);
playlistHorizontalAdapter.setItems(playlists);
}
});
}
@Override
public void onAlbumClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumPageFragment, bundle);
}
@Override
public void onAlbumLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumBottomSheetDialog, bundle);
}
@Override
public void onArtistClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.artistPageFragment, bundle);
}
@Override
public void onArtistLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.artistBottomSheetDialog, bundle);
}
@Override
public void onGenreClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songListPageFragment, bundle);
}
@Override
public void onPlaylistClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.playlistPageFragment, bundle);
}
@Override
public void onPlaylistLongClick(Bundle bundle) {
PlaylistEditorDialog dialog = new PlaylistEditorDialog();
dialog.setArguments(bundle);
dialog.show(activity.getSupportFragmentManager(), null);
}
@Override
public void onMusicFolderClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.indexFragment, bundle);
}
}

View file

@ -0,0 +1,65 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.media3.common.util.UnstableApi;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentToolbarBinding;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
@UnstableApi
public class ToolbarFragment extends Fragment {
private static final String TAG = "ToolbarFragment";
private FragmentToolbarBinding bind;
private MainActivity activity;
public ToolbarFragment() {
// Required empty public constructor
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.main_page_menu, menu);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentToolbarBinding.inflate(inflater, container, false);
View view = bind.getRoot();
return view;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_search) {
activity.navController.navigate(R.id.searchFragment);
return true;
} else if (item.getItemId() == R.id.action_settings) {
activity.navController.navigate(R.id.settingsFragment);
return true;
}
return false;
}
}

View file

@ -0,0 +1,9 @@
package com.cappielloantonio.tempo.util;
import android.content.Context;
public class Flavors {
public static void initializeCastContext(Context context) {
}
}

View file

@ -1,34 +0,0 @@
package com.cappielloantonio.tempo.util;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.InsetDrawable;
import androidx.recyclerview.widget.DividerItemDecoration;
public class UIUtil {
public static int getSpanCount(int itemCount, int maxSpan) {
int itemSize = itemCount == 0 ? 1 : itemCount;
if (itemSize / maxSpan > 0) {
return maxSpan;
} else {
return itemSize % maxSpan;
}
}
public static DividerItemDecoration getDividerItemDecoration(Context context) {
int[] ATTRS = new int[]{android.R.attr.listDivider};
TypedArray a = context.obtainStyledAttributes(ATTRS);
Drawable divider = a.getDrawable(0);
InsetDrawable insetDivider = new InsetDrawable(divider, 42, 0, 42, 42);
a.recycle();
DividerItemDecoration itemDecoration = new DividerItemDecoration(context, DividerItemDecoration.VERTICAL);
itemDecoration.setDrawable(insetDivider);
return itemDecoration;
}
}

View file

@ -18,8 +18,9 @@ import com.cappielloantonio.tempo.R
import com.cappielloantonio.tempo.ui.activity.MainActivity
import com.cappielloantonio.tempo.util.Constants
import com.cappielloantonio.tempo.util.DownloadUtil
import com.cappielloantonio.tempo.util.UIUtil
import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
import com.google.common.collect.ImmutableList
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
@ -206,7 +207,9 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
}
private fun initializeCastPlayer() {
if (UIUtil.isCastApiAvailable(this)) {
if (GoogleApiAvailability.getInstance()
.isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS
) {
castPlayer = CastPlayer(CastContext.getSharedInstance(this))
castPlayer.setSessionAvailabilityListener(this)
}

View file

@ -1,108 +0,0 @@
package com.cappielloantonio.tempo.ui.activity.base;
import android.Manifest;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.PowerManager;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.offline.DownloadService;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import com.cappielloantonio.tempo.service.DownloaderService;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.dialog.BatteryOptimizationDialog;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.util.UIUtil;
import com.google.android.gms.cast.framework.CastContext;
import com.google.android.material.elevation.SurfaceColors;
import com.google.common.util.concurrent.ListenableFuture;
@UnstableApi
public class BaseActivity extends AppCompatActivity {
private static final String TAG = "BaseActivity";
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initializeCastContext();
initializeDownloader();
checkBatteryOptimization();
checkPermission();
}
@Override
protected void onStart() {
super.onStart();
setNavigationBarColor();
initializeBrowser();
}
@Override
protected void onStop() {
releaseBrowser();
super.onStop();
}
private void checkBatteryOptimization() {
if (detectBatteryOptimization() && Preferences.askForOptimization()) {
showBatteryOptimizationDialog();
}
}
private void checkPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.POST_NOTIFICATIONS}, 101);
}
}
}
private boolean detectBatteryOptimization() {
String packageName = getPackageName();
PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
return !powerManager.isIgnoringBatteryOptimizations(packageName);
}
private void showBatteryOptimizationDialog() {
BatteryOptimizationDialog dialog = new BatteryOptimizationDialog();
dialog.show(getSupportFragmentManager(), null);
}
private void initializeBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(this, new SessionToken(this, new ComponentName(this, MediaService.class))).buildAsync();
}
private void releaseBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
public ListenableFuture<MediaBrowser> getMediaBrowserListenableFuture() {
return mediaBrowserListenableFuture;
}
private void initializeDownloader() {
try {
DownloadService.start(this, DownloaderService.class);
} catch (IllegalStateException e) {
DownloadService.startForeground(this, DownloaderService.class);
}
}
private void initializeCastContext() {
if (UIUtil.isCastApiAvailable(this)) CastContext.getSharedInstance(this);
}
private void setNavigationBarColor() {
getWindow().setNavigationBarColor(SurfaceColors.getColorForElevation(this, 10));
}
}

View file

@ -1,170 +0,0 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentDownloadBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.DownloadHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.DownloadViewModel;
import com.google.android.gms.cast.framework.CastButtonFactory;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Objects;
@UnstableApi
public class DownloadFragment extends Fragment implements ClickCallback {
private FragmentDownloadBinding bind;
private MainActivity activity;
private DownloadViewModel downloadViewModel;
private DownloadHorizontalAdapter downloadHorizontalAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.main_page_menu, menu);
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.media_route_menu_item);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentDownloadBinding.inflate(inflater, container, false);
View view = bind.getRoot();
downloadViewModel = new ViewModelProvider(requireActivity()).get(DownloadViewModel.class);
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initAppBar();
initDownloadedSongView();
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
activity.setBottomNavigationBarVisibility(true);
activity.setBottomSheetVisibility(true);
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_search) {
activity.navController.navigate(R.id.action_downloadFragment_to_searchFragment);
return true;
} else if (item.getItemId() == R.id.action_settings) {
activity.navController.navigate(R.id.action_downloadFragment_to_settingsFragment);
return true;
}
return false;
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
Objects.requireNonNull(bind.toolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
}
private void initDownloadedSongView() {
bind.downloadedTracksRecyclerView.setHasFixedSize(true);
downloadHorizontalAdapter = new DownloadHorizontalAdapter(this);
bind.downloadedTracksRecyclerView.setAdapter(downloadHorizontalAdapter);
downloadViewModel.getDownloadedTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
if (songs != null) {
if (songs.isEmpty()) {
if (bind != null) {
bind.emptyDownloadLayout.setVisibility(View.VISIBLE);
bind.fragmentDownloadNestedScrollView.setVisibility(View.GONE);
bind.downloadDownloadedTracksPlaceholder.placeholder.setVisibility(View.VISIBLE);
bind.downloadDownloadedTracksSector.setVisibility(View.GONE);
}
} else {
if (bind != null) {
bind.emptyDownloadLayout.setVisibility(View.GONE);
bind.fragmentDownloadNestedScrollView.setVisibility(View.VISIBLE);
bind.downloadDownloadedTracksPlaceholder.placeholder.setVisibility(View.GONE);
bind.downloadDownloadedTracksSector.setVisibility(View.VISIBLE);
bind.downloadedTracksRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
downloadHorizontalAdapter.setItems(songs);
}
}
if (bind != null) bind.loadingProgressBar.setVisibility(View.GONE);
}
});
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
@Override
public void onMediaClick(Bundle bundle) {
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
activity.setBottomSheetInPeek(true);
}
@Override
public void onMediaLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
}
}

View file

@ -1,118 +0,0 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.media3.common.util.UnstableApi;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentHomeBinding;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.fragment.pager.HomePager;
import com.cappielloantonio.tempo.util.Preferences;
import com.google.android.gms.cast.framework.CastButtonFactory;
import com.google.android.material.tabs.TabLayoutMediator;
import java.util.Objects;
@UnstableApi
public class HomeFragment extends Fragment {
private static final String TAG = "HomeFragment";
private FragmentHomeBinding bind;
private MainActivity activity;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.main_page_menu, menu);
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.media_route_menu_item);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentHomeBinding.inflate(inflater, container, false);
return bind.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initAppBar();
initHomePager();
}
@Override
public void onStart() {
super.onStart();
activity.setBottomNavigationBarVisibility(true);
activity.setBottomSheetVisibility(true);
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_search) {
activity.navController.navigate(R.id.action_homeFragment_to_searchFragment);
return true;
} else if (item.getItemId() == R.id.action_settings) {
activity.navController.navigate(R.id.action_homeFragment_to_settingsFragment);
return true;
}
return false;
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
Objects.requireNonNull(bind.toolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
}
private void initHomePager() {
HomePager pager = new HomePager(this);
pager.addFragment(new HomeTabMusicFragment(), "Music", R.drawable.ic_home);
if (Preferences.isPodcastSectionVisible())
pager.addFragment(new HomeTabPodcastFragment(), "Podcast", R.drawable.ic_graphic_eq);
if (Preferences.isRadioSectionVisible())
pager.addFragment(new HomeTabRadioFragment(), "Radio", R.drawable.ic_play_for_work);
bind.homeViewPager.setAdapter(pager);
bind.homeViewPager.setOffscreenPageLimit(3);
bind.homeViewPager.setUserInputEnabled(false);
new TabLayoutMediator(bind.homeTabLayout, bind.homeViewPager,
(tab, position) -> {
tab.setText(pager.getPageTitle(position));
// tab.setIcon(pager.getPageIcon(position));
}
).attach();
bind.homeTabLayout.setVisibility(Preferences.isPodcastSectionVisible() || Preferences.isRadioSectionVisible() ? View.VISIBLE : View.GONE);
}
}

View file

@ -1,308 +0,0 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentLibraryBinding;
import com.cappielloantonio.tempo.helper.recyclerview.CustomLinearSnapHelper;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter;
import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter;
import com.cappielloantonio.tempo.ui.adapter.GenreAdapter;
import com.cappielloantonio.tempo.ui.adapter.MusicFolderAdapter;
import com.cappielloantonio.tempo.ui.adapter.PlaylistHorizontalAdapter;
import com.cappielloantonio.tempo.ui.dialog.PlaylistEditorDialog;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.LibraryViewModel;
import com.google.android.gms.cast.framework.CastButtonFactory;
import java.util.Objects;
@UnstableApi
public class LibraryFragment extends Fragment implements ClickCallback {
private static final String TAG = "LibraryFragment";
private FragmentLibraryBinding bind;
private MainActivity activity;
private LibraryViewModel libraryViewModel;
private MusicFolderAdapter musicFolderAdapter;
private AlbumAdapter albumAdapter;
private ArtistAdapter artistAdapter;
private GenreAdapter genreAdapter;
private PlaylistHorizontalAdapter playlistHorizontalAdapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.main_page_menu, menu);
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.media_route_menu_item);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentLibraryBinding.inflate(inflater, container, false);
View view = bind.getRoot();
libraryViewModel = new ViewModelProvider(requireActivity()).get(LibraryViewModel.class);
init();
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initAppBar();
initMusicFolderView();
initAlbumView();
initArtistView();
initGenreView();
initPlaylistSlideView();
}
@Override
public void onStart() {
super.onStart();
activity.setBottomNavigationBarVisibility(true);
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_search) {
activity.navController.navigate(R.id.action_libraryFragment_to_searchFragment);
return true;
} else if (item.getItemId() == R.id.action_settings) {
activity.navController.navigate(R.id.action_libraryFragment_to_settingsFragment);
return true;
}
return false;
}
private void init() {
bind.albumCatalogueTextViewClickable.setOnClickListener(v -> activity.navController.navigate(R.id.action_libraryFragment_to_albumCatalogueFragment));
bind.artistCatalogueTextViewClickable.setOnClickListener(v -> activity.navController.navigate(R.id.action_libraryFragment_to_artistCatalogueFragment));
bind.genreCatalogueTextViewClickable.setOnClickListener(v -> activity.navController.navigate(R.id.action_libraryFragment_to_genreCatalogueFragment));
bind.playlistCatalogueTextViewClickable.setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putString(Constants.PLAYLIST_ALL, Constants.PLAYLIST_ALL);
activity.navController.navigate(R.id.action_libraryFragment_to_playlistCatalogueFragment, bundle);
});
bind.albumCatalogueSampleTextViewRefreshable.setOnLongClickListener(view -> {
libraryViewModel.refreshAlbumSample(getViewLifecycleOwner());
return true;
});
bind.artistCatalogueSampleTextViewRefreshable.setOnLongClickListener(view -> {
libraryViewModel.refreshArtistSample(getViewLifecycleOwner());
return true;
});
bind.genreCatalogueSampleTextViewRefreshable.setOnLongClickListener(view -> {
libraryViewModel.refreshGenreSample(getViewLifecycleOwner());
return true;
});
bind.playlistCatalogueSampleTextViewRefreshable.setOnLongClickListener(view -> {
libraryViewModel.refreshPlaylistSample(getViewLifecycleOwner());
return true;
});
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
Objects.requireNonNull(bind.toolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
}
private void initMusicFolderView() {
bind.musicFolderRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.musicFolderRecyclerView.setHasFixedSize(true);
musicFolderAdapter = new MusicFolderAdapter(this);
bind.musicFolderRecyclerView.setAdapter(musicFolderAdapter);
libraryViewModel.getMusicFolders(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), musicFolders -> {
if (musicFolders == null) {
if (bind != null)
bind.libraryMusicFolderPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryMusicFolderSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.libraryMusicFolderPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryMusicFolderSector.setVisibility(!musicFolders.isEmpty() ? View.VISIBLE : View.GONE);
musicFolderAdapter.setItems(musicFolders);
}
});
}
private void initAlbumView() {
bind.albumRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.albumRecyclerView.setHasFixedSize(true);
albumAdapter = new AlbumAdapter(this);
bind.albumRecyclerView.setAdapter(albumAdapter);
libraryViewModel.getAlbumSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
if (bind != null)
bind.libraryAlbumPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryAlbumSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.libraryAlbumPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryAlbumSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
albumAdapter.setItems(albums);
}
});
CustomLinearSnapHelper albumSnapHelper = new CustomLinearSnapHelper();
albumSnapHelper.attachToRecyclerView(bind.albumRecyclerView);
}
private void initArtistView() {
bind.artistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.artistRecyclerView.setHasFixedSize(true);
artistAdapter = new ArtistAdapter(this, false, false);
bind.artistRecyclerView.setAdapter(artistAdapter);
libraryViewModel.getArtistSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> {
if (artists == null) {
if (bind != null)
bind.libraryArtistPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryArtistSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.libraryArtistPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryArtistSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
artistAdapter.setItems(artists);
}
});
CustomLinearSnapHelper artistSnapHelper = new CustomLinearSnapHelper();
artistSnapHelper.attachToRecyclerView(bind.artistRecyclerView);
}
private void initGenreView() {
bind.genreRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 3, GridLayoutManager.HORIZONTAL, false));
bind.genreRecyclerView.setHasFixedSize(true);
genreAdapter = new GenreAdapter(this);
bind.genreRecyclerView.setAdapter(genreAdapter);
libraryViewModel.getGenreSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), genres -> {
if (genres == null) {
if (bind != null)
bind.libraryGenrePlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryGenresSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.libraryGenrePlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryGenresSector.setVisibility(!genres.isEmpty() ? View.VISIBLE : View.GONE);
genreAdapter.setItems(genres);
}
});
CustomLinearSnapHelper genreSnapHelper = new CustomLinearSnapHelper();
genreSnapHelper.attachToRecyclerView(bind.genreRecyclerView);
}
private void initPlaylistSlideView() {
bind.playlistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.playlistRecyclerView.setHasFixedSize(true);
playlistHorizontalAdapter = new PlaylistHorizontalAdapter(this);
bind.playlistRecyclerView.setAdapter(playlistHorizontalAdapter);
libraryViewModel.getPlaylistSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), playlists -> {
if (playlists == null) {
if (bind != null)
bind.libraryPlaylistPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryPlaylistSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.libraryPlaylistPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryPlaylistSector.setVisibility(!playlists.isEmpty() ? View.VISIBLE : View.GONE);
playlistHorizontalAdapter.setItems(playlists);
}
});
}
@Override
public void onAlbumClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumPageFragment, bundle);
}
@Override
public void onAlbumLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumBottomSheetDialog, bundle);
}
@Override
public void onArtistClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.artistPageFragment, bundle);
}
@Override
public void onArtistLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.artistBottomSheetDialog, bundle);
}
@Override
public void onGenreClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songListPageFragment, bundle);
}
@Override
public void onPlaylistClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.playlistPageFragment, bundle);
}
@Override
public void onPlaylistLongClick(Bundle bundle) {
PlaylistEditorDialog dialog = new PlaylistEditorDialog();
dialog.setArguments(bundle);
dialog.show(activity.getSupportFragmentManager(), null);
}
@Override
public void onMusicFolderClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.indexFragment, bundle);
}
}

View file

@ -0,0 +1,67 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.media3.common.util.UnstableApi;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentToolbarBinding;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.google.android.gms.cast.framework.CastButtonFactory;
@UnstableApi
public class ToolbarFragment extends Fragment {
private static final String TAG = "ToolbarFragment";
private FragmentToolbarBinding bind;
private MainActivity activity;
public ToolbarFragment() {
// Required empty public constructor
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.main_page_menu, menu);
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.media_route_menu_item);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentToolbarBinding.inflate(inflater, container, false);
View view = bind.getRoot();
return view;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_search) {
activity.navController.navigate(R.id.searchFragment);
return true;
} else if (item.getItemId() == R.id.action_settings) {
activity.navController.navigate(R.id.settingsFragment);
return true;
}
return false;
}
}

View file

@ -0,0 +1,14 @@
package com.cappielloantonio.tempo.util;
import android.content.Context;
import com.google.android.gms.cast.framework.CastContext;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
public class Flavors {
public static void initializeCastContext(Context context) {
if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS)
CastContext.getSharedInstance(context);
}
}

View file

@ -1,41 +0,0 @@
package com.cappielloantonio.tempo.util;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.InsetDrawable;
import androidx.recyclerview.widget.DividerItemDecoration;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
public class UIUtil {
public static int getSpanCount(int itemCount, int maxSpan) {
int itemSize = itemCount == 0 ? 1 : itemCount;
if (itemSize / maxSpan > 0) {
return maxSpan;
} else {
return itemSize % maxSpan;
}
}
public static boolean isCastApiAvailable(Context context) {
return GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS;
}
public static DividerItemDecoration getDividerItemDecoration(Context context) {
int[] ATTRS = new int[]{android.R.attr.listDivider};
TypedArray a = context.obtainStyledAttributes(ATTRS);
Drawable divider = a.getDrawable(0);
InsetDrawable insetDivider = new InsetDrawable(divider, 42, 0, 42, 42);
a.recycle();
DividerItemDecoration itemDecoration = new DividerItemDecoration(context, DividerItemDecoration.VERTICAL);
itemDecoration.setDrawable(insetDivider);
return itemDecoration;
}
}