diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a017b00..6e35c44f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,20 @@ # Changelog ## What's Changed +## [4.12.4](https://github.com/eddyizm/tempo/releases/tag/v4.12.4) (2026-03-01) +* feat: advertise existing long press to refresh per section on library page by @tvillega in https://github.com/eddyizm/tempus/pull/467 +* fix: playlist filter returns properly filtered list and reset correctly by @eddyizm in https://github.com/eddyizm/tempus/pull/476 +* feat: toggle player bitrate visibility on touch by @tvillega in https://github.com/eddyizm/tempus/pull/466 + +**Full Changelog**: https://github.com/eddyizm/tempus/compare/v4.12.0...v4.12.3 + +## What's Changed +## [4.12.0](https://github.com/eddyizm/tempo/releases/tag/v4.12.0) (2026-02-28) * chore(i18n): Update Polish translation by @skajmer in https://github.com/eddyizm/tempus/pull/441 * feat: radio logos support for AndroidAuto by @dmachard in https://github.com/eddyizm/tempus/pull/435 * feat: Port remove song of playlist from tempus ng by @tvillega in https://github.com/eddyizm/tempus/pull/457 * fix: artist sort by name case sensitive by @tvillega in https://github.com/eddyizm/tempus/pull/462 -* feat: enhance navigation by @tvillega in https://github.com/eddyizm/tempus/pull/450 +* feat: added slide out enhanced navigation for tab mode and optionally portrait mode by @tvillega in https://github.com/eddyizm/tempus/pull/450 * feat: Android Auto: improve media service browsing by @MaFo-28 in https://github.com/eddyizm/tempus/pull/437 * feat: Support specifying a client certificate for mTLS auth by @tinsukE in https://github.com/eddyizm/tempus/pull/458 diff --git a/app/build.gradle b/app/build.gradle index 8ab28cbd..cc66453b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { minSdkVersion 24 targetSdk 35 - versionCode 20 - versionName '4.12.3-rc' + versionCode 22 + versionName '4.12.4' testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' javaCompileOptions { @@ -101,6 +101,7 @@ dependencies { implementation 'androidx.room:room-runtime:2.6.1' implementation 'androidx.core:core-splashscreen:1.0.1' implementation 'androidx.appcompat:appcompat:1.7.0' + implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.2.0" // Android Material implementation 'com.google.android.material:material:1.10.0' diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/PlaylistHorizontalAdapter.java b/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/PlaylistHorizontalAdapter.java index baa4005e..763e39e7 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/PlaylistHorizontalAdapter.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/PlaylistHorizontalAdapter.java @@ -47,6 +47,7 @@ public class PlaylistHorizontalAdapter extends RecyclerView.Adapter 0) playlists.addAll((List) results.values); + if (results.values != null) { + playlists.addAll((List) results.values); + } notifyDataSetChanged(); } }; diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/LibraryFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/LibraryFragment.java index daa5d412..b10e6402 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/LibraryFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/LibraryFragment.java @@ -9,6 +9,8 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.ViewModelProvider; import androidx.media3.common.util.UnstableApi; import androidx.media3.session.MediaBrowser; @@ -16,8 +18,11 @@ import androidx.media3.session.SessionToken; import androidx.navigation.Navigation; import android.content.ComponentName; +import android.widget.Toast; + import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import com.cappielloantonio.tempo.R; import com.cappielloantonio.tempo.databinding.FragmentLibraryBinding; @@ -43,6 +48,7 @@ import java.util.Objects; @UnstableApi public class LibraryFragment extends Fragment implements ClickCallback { private static final String TAG = "LibraryFragment"; + private static final String TOAST_MSG = "Long press to refresh" ; private FragmentLibraryBinding bind; private MainActivity activity; @@ -81,6 +87,7 @@ public class LibraryFragment extends Fragment implements ClickCallback { initArtistView(); initGenreView(); initPlaylistView(); + initSwipeToRefresh(); } @Override @@ -112,22 +119,41 @@ public class LibraryFragment extends Fragment implements ClickCallback { activity.navController.navigate(R.id.action_libraryFragment_to_playlistCatalogueFragment, bundle); }); + // Album bind.albumCatalogueSampleTextViewRefreshable.setOnLongClickListener(view -> { libraryViewModel.refreshAlbumSample(getViewLifecycleOwner()); return true; }); + bind.albumCatalogueSampleTextViewRefreshable.setOnClickListener( v -> + Toast.makeText(requireContext(), TOAST_MSG, Toast.LENGTH_SHORT).show() + ); + + // Artist bind.artistCatalogueSampleTextViewRefreshable.setOnLongClickListener(view -> { libraryViewModel.refreshArtistSample(getViewLifecycleOwner()); return true; }); + bind.artistCatalogueSampleTextViewRefreshable.setOnClickListener( v -> + Toast.makeText(requireContext(), TOAST_MSG, Toast.LENGTH_SHORT).show() + ); + + // Genre bind.genreCatalogueSampleTextViewRefreshable.setOnLongClickListener(view -> { libraryViewModel.refreshGenreSample(getViewLifecycleOwner()); return true; }); + bind.genreCatalogueSampleTextViewRefreshable.setOnClickListener(v -> + Toast.makeText(requireContext(), TOAST_MSG, Toast.LENGTH_SHORT).show() + ); + + // Playlist bind.playlistCatalogueSampleTextViewRefreshable.setOnLongClickListener(view -> { libraryViewModel.refreshPlaylistSample(getViewLifecycleOwner()); return true; }); + bind.playlistCatalogueSampleTextViewRefreshable.setOnClickListener( v -> + Toast.makeText(requireContext(), TOAST_MSG, Toast.LENGTH_SHORT).show() + ); } private void initAppBar() { @@ -304,4 +330,20 @@ public class LibraryFragment extends Fragment implements ClickCallback { private void initializeMediaBrowser() { mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync(); } + + public void initSwipeToRefresh() { + bind.swipeLibraryToRefresh.setOnRefreshListener(() -> { + pullToRefresh(); + bind.swipeLibraryToRefresh.setRefreshing(false); + }); + } + + private void pullToRefresh() { + LifecycleOwner lifecycleOwner = getViewLifecycleOwner(); + libraryViewModel.refreshAlbumSample(lifecycleOwner); + libraryViewModel.refreshGenreSample(lifecycleOwner); + libraryViewModel.refreshArtistSample(lifecycleOwner); + libraryViewModel.refreshPlaylistSample(lifecycleOwner); + + } } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerControllerFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerControllerFragment.java index 3bba6183..3c14788b 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerControllerFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerControllerFragment.java @@ -8,9 +8,11 @@ import android.os.Bundle; import android.os.IBinder; import android.text.TextUtils; import android.util.Log; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.Button; import android.widget.ImageButton; import android.widget.LinearLayout; @@ -33,6 +35,10 @@ import androidx.media3.session.SessionToken; import androidx.navigation.NavController; import androidx.navigation.NavOptions; import androidx.navigation.fragment.NavHostFragment; +import androidx.transition.ChangeBounds; +import androidx.transition.Slide; +import androidx.transition.TransitionManager; +import androidx.transition.TransitionSet; import androidx.viewpager2.widget.ViewPager2; import com.cappielloantonio.tempo.R; @@ -56,7 +62,6 @@ import com.google.android.material.elevation.SurfaceColors; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; -import java.text.DecimalFormat; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -314,7 +319,7 @@ public class PlayerControllerFragment extends Fragment { if (!samplingRate.trim().isEmpty()) items.add(samplingRate); String mediaQuality = TextUtils.join(" • ", items); - playerMediaBitrate.setVisibility(View.VISIBLE); + playerMediaBitrate.setVisibility(Preferences.getBitrateVisible() ? View.VISIBLE : View.GONE); playerMediaBitrate.setText(isLocal ? mediaQuality : mediaQuality); } } @@ -335,7 +340,25 @@ public class PlayerControllerFragment extends Fragment { TrackInfoDialog dialog = new TrackInfoDialog(mediaMetadata); dialog.show(activity.getSupportFragmentManager(), null); }); + + playerMediaExtension.setOnClickListener( v -> toggleBitrateVisibility() ); + playerMediaBitrate.setOnClickListener(v -> toggleBitrateVisibility() ); } + + private void toggleBitrateVisibility() { + ViewGroup parent = (ViewGroup) playerMediaBitrate.getParent(); + + TransitionSet transition = new TransitionSet() + .addTransition(new Slide(Gravity.START)) + .addTransition(new ChangeBounds()) + .setDuration(500) + .setInterpolator(new AccelerateDecelerateInterpolator()); + TransitionManager.beginDelayedTransition(parent, transition); + + playerMediaBitrate.setVisibility(Preferences.getBitrateVisible() ? View.GONE : View.VISIBLE); + Preferences.setBitrateVisible(!Preferences.getBitrateVisible()); + } + private void updateAssetLinkChips(MediaMetadata mediaMetadata) { if (assetLinkChipGroup == null) return; String mediaType = mediaMetadata.extras != null ? mediaMetadata.extras.getString("type", Constants.MEDIA_TYPE_MUSIC) : Constants.MEDIA_TYPE_MUSIC; diff --git a/app/src/main/java/com/cappielloantonio/tempo/util/Preferences.kt b/app/src/main/java/com/cappielloantonio/tempo/util/Preferences.kt index 1edc914e..099a471d 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/util/Preferences.kt +++ b/app/src/main/java/com/cappielloantonio/tempo/util/Preferences.kt @@ -25,6 +25,7 @@ object Preferences { private const val IN_USE_SERVER_ADDRESS = "in_use_server_address" private const val NEXT_SERVER_SWITCH = "next_server_switch" private const val PLAYBACK_SPEED = "playback_speed" + private const val BITRATE_VISIBLE = "bitrate_visible" private const val SKIP_SILENCE = "skip_silence" private const val SHUFFLE_MODE = "shuffle_mode" private const val REPEAT_MODE = "repeat_mode" @@ -292,6 +293,16 @@ object Preferences { App.getInstance().preferences.edit().putFloat(PLAYBACK_SPEED, playbackSpeed).apply() } + @JvmStatic + fun getBitrateVisible(): Boolean { + return App.getInstance().preferences.getBoolean(BITRATE_VISIBLE, true) + } + + @JvmStatic + fun setBitrateVisible(bitrateVisible: Boolean) { + App.getInstance().preferences.edit().putBoolean(BITRATE_VISIBLE, bitrateVisible).apply() + } + @JvmStatic fun isSkipSilenceMode(): Boolean { return App.getInstance().preferences.getBoolean(SKIP_SILENCE, false) diff --git a/app/src/main/res/layout-land/inner_fragment_player_controller_layout.xml b/app/src/main/res/layout-land/inner_fragment_player_controller_layout.xml index 435e3648..b337e6fd 100644 --- a/app/src/main/res/layout-land/inner_fragment_player_controller_layout.xml +++ b/app/src/main/res/layout-land/inner_fragment_player_controller_layout.xml @@ -24,30 +24,42 @@ app:layout_constraintStart_toEndOf="@+id/vertical_guideline" app:layout_constraintTop_toTopOf="parent"> - + app:layout_constraintEnd_toEndOf="parent"> - + + + + + diff --git a/app/src/main/res/layout/fragment_library.xml b/app/src/main/res/layout/fragment_library.xml index c96fadaf..9c5f0419 100644 --- a/app/src/main/res/layout/fragment_library.xml +++ b/app/src/main/res/layout/fragment_library.xml @@ -11,6 +11,12 @@ android:layout_width="match_parent" android:layout_height="wrap_content" /> + + - + + + + + + + + + + android:layout_height="0dp" + android:layout_weight="1" /> @@ -130,22 +156,41 @@ android:paddingEnd="8dp" android:paddingBottom="8dp"> - + + android:layout_width="wrap_content" + android:layout_height="wrap_content" > + + + + + + + @@ -184,25 +229,45 @@ android:paddingEnd="8dp" android:paddingBottom="8dp"> - + + android:layout_width="wrap_content" + android:layout_height="wrap_content" > + + + + + + + + - + + + + + + + + + + android:layout_height="0dp" + android:layout_weight="1" /> @@ -270,4 +355,5 @@ + \ No newline at end of file diff --git a/app/src/main/res/layout/inner_fragment_player_controller_layout.xml b/app/src/main/res/layout/inner_fragment_player_controller_layout.xml index 8fab7c84..05bfe253 100644 --- a/app/src/main/res/layout/inner_fragment_player_controller_layout.xml +++ b/app/src/main/res/layout/inner_fragment_player_controller_layout.xml @@ -33,30 +33,42 @@ app:layout_constraintBottom_toBottomOf="parent" app:tint="?attr/colorOnPrimaryContainer" /> - + app:layout_constraintEnd_toEndOf="parent"> - + + + + + diff --git a/fastlane/metadata/android/en-US/changelogs/21.txt b/fastlane/metadata/android/en-US/changelogs/21.txt new file mode 100644 index 00000000..793e84c5 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/21.txt @@ -0,0 +1,7 @@ +chore(i18n): Update Polish translation +feat: radio logos support for AndroidAuto +feat: Port remove song of playlist from tempus ng +fix: artist sort by name case sensitive +feat: added slide out enhanced navigation for tab mode and optionally portrait mode +feat: Android Auto: improve media service browsing +feat: Support specifying a client certificate for mTLS auth \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/22.txt b/fastlane/metadata/android/en-US/changelogs/22.txt new file mode 100644 index 00000000..98006a09 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/22.txt @@ -0,0 +1,3 @@ +feat: advertise existing long press to refresh per section on library page +fix: playlist filter returns properly filtered list and reset correctly +feat: toggle player bitrate visibility on touch