4 Commits

Author SHA1 Message Date
eddyizm
918bf6928e chore: bumped version and change log for release 2026-03-01 19:59:28 -08:00
Tom
c9cf86acb5 feat: toggle player bitrate visibility on touch (#466)
* feat: touch player chip to toggle bitrate visibility

* feat: player bitrate visibility is remembered

* fix: player landscape layout not grouping chip with textview

* feat: touch bitrate to toggle its visibility

This catches the edge case where the the chip is not reachable due to insuficient horizontal space

---------

Co-authored-by: eddyizm <eddyizm@gmail.com>
2026-03-01 19:48:15 -08:00
eddyizm
0487f3bb9b fix: returns filtered list and reset correctly (#476) 2026-03-01 19:36:48 -08:00
Tom
c7f2524085 feat: feat: advertise existing long press to refresh per section (#467)
* feat: advertise existing long press to refresh per section

---------

Co-authored-by: eddyizm <eddyizm@gmail.com>
2026-03-01 19:36:03 -08:00
10 changed files with 274 additions and 73 deletions

View File

@@ -1,5 +1,13 @@
# Changelog
## What's Changed
## [4.12.3](https://github.com/eddyizm/tempo/releases/tag/v4.12.3) (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

View File

@@ -10,8 +10,8 @@ android {
minSdkVersion 24
targetSdk 35
versionCode 21
versionName '4.12.0'
versionCode 22
versionName '4.12.3'
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'

View File

@@ -47,6 +47,7 @@ public class PlaylistHorizontalAdapter extends RecyclerView.Adapter<PlaylistHori
FilterResults results = new FilterResults();
results.values = filteredList;
results.count = filteredList.size();
return results;
}
@@ -54,7 +55,9 @@ public class PlaylistHorizontalAdapter extends RecyclerView.Adapter<PlaylistHori
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
playlists.clear();
if (results.count > 0) playlists.addAll((List) results.values);
if (results.values != null) {
playlists.addAll((List<Playlist>) results.values);
}
notifyDataSetChanged();
}
};

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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)

View File

@@ -24,30 +24,42 @@
app:layout_constraintStart_toEndOf="@+id/vertical_guideline"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.chip.Chip
android:id="@+id/player_media_extension"
style="@style/Widget.Material3.Chip.Suggestion"
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/player_media_quality_sector_center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:clickable="false"
android:text="Unknown"
app:chipStrokeWidth="0dp"
android:layout_marginVertical="8dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/player_media_bitrate"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintHorizontal_chainStyle="packed"/>
app:layout_constraintEnd_toEndOf="parent">
<TextView
android:id="@+id/player_media_bitrate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
app:layout_constraintTop_toTopOf="@id/player_media_extension"
app:layout_constraintBottom_toBottomOf="@id/player_media_extension"
app:layout_constraintStart_toEndOf="@id/player_media_extension"
app:layout_constraintEnd_toEndOf="parent"/>
<TextView
android:id="@+id/player_media_bitrate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
app:layout_constraintTop_toTopOf="@id/player_media_extension"
app:layout_constraintBottom_toBottomOf="@id/player_media_extension"
app:layout_constraintStart_toEndOf="@id/player_media_extension"
app:layout_constraintEnd_toEndOf="parent"/>
<com.google.android.material.chip.Chip
android:id="@+id/player_media_extension"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:checked="true"
android:focusable="true"
android:text="Unknown"
app:chipStrokeWidth="0dp"
app:chipBackgroundColor="@color/material_dynamic_secondary40"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/player_media_bitrate"
app:layout_constraintHorizontal_chainStyle="packed"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<ImageButton
android:id="@+id/player_info_track"

View File

@@ -11,6 +11,12 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_library_to_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.core.widget.NestedScrollView
android:id="@+id/fragment_library_nested_scroll_view"
android:layout_width="match_parent"
@@ -77,21 +83,41 @@
android:paddingEnd="8dp"
android:paddingBottom="8dp">
<TextView
<!-- Refreshable area -->
<LinearLayout
android:id="@+id/album_catalogue_sample_text_view_refreshable"
style="@style/TitleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<TextView
style="@style/TitleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:text="@string/library_title_album" />
<ImageView
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_gravity="bottom"
android:layout_marginBottom="2dp"
android:alpha="0.4"
android:src="@drawable/ic_refresh"/>
</LinearLayout>
<View
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:text="@string/library_title_album" />
android:layout_height="0dp"
android:layout_weight="1" />
<TextView
android:id="@+id/album_catalogue_text_view_clickable"
style="@style/TitleMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:text="@string/library_title_album_see_all_button" />
@@ -130,22 +156,41 @@
android:paddingEnd="8dp"
android:paddingBottom="8dp">
<TextView
<!-- Refreshable area -->
<LinearLayout
android:id="@+id/artist_catalogue_sample_text_view_refreshable"
style="@style/TitleLarge"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:text="@string/library_title_artist" />
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<TextView
style="@style/TitleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:text="@string/library_title_artist" />
<ImageView
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_gravity="bottom"
android:layout_marginBottom="2dp"
android:alpha="0.4"
android:src="@drawable/ic_refresh"/>
</LinearLayout>
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<TextView
android:id="@+id/artist_catalogue_text_view_clickable"
style="@style/TitleMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:text="@string/library_title_artist_see_all_button" />
@@ -184,25 +229,45 @@
android:paddingEnd="8dp"
android:paddingBottom="8dp">
<TextView
<!-- Refreshable area -->
<LinearLayout
android:id="@+id/genre_catalogue_sample_text_view_refreshable"
style="@style/TitleLarge"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:text="@string/library_title_genre" />
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<TextView
style="@style/TitleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:text="@string/library_title_genre" />
<ImageView
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_gravity="bottom"
android:layout_marginBottom="2dp"
android:alpha="0.4"
android:src="@drawable/ic_refresh"/>
</LinearLayout>
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<TextView
android:id="@+id/genre_catalogue_text_view_clickable"
style="@style/TitleMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:text="@string/library_title_genre_see_all_button" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
@@ -236,21 +301,41 @@
android:paddingEnd="8dp"
android:paddingBottom="8dp">
<TextView
<!-- Refreshable area -->
<LinearLayout
android:id="@+id/playlist_catalogue_sample_text_view_refreshable"
style="@style/TitleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<TextView
style="@style/TitleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:text="@string/library_title_playlist" />
<ImageView
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_gravity="bottom"
android:layout_marginBottom="2dp"
android:alpha="0.4"
android:src="@drawable/ic_refresh"/>
</LinearLayout>
<View
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:text="@string/library_title_playlist" />
android:layout_height="0dp"
android:layout_weight="1" />
<TextView
android:id="@+id/playlist_catalogue_text_view_clickable"
style="@style/TitleMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:text="@string/library_title_playlist_see_all_button" />
@@ -270,4 +355,5 @@
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -33,30 +33,42 @@
app:layout_constraintBottom_toBottomOf="parent"
app:tint="?attr/colorOnPrimaryContainer" />
<com.google.android.material.chip.Chip
android:id="@+id/player_media_extension"
style="@style/Widget.Material3.Chip.Suggestion"
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/player_media_quality_sector_center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:clickable="false"
android:text="Unknown"
app:chipStrokeWidth="0dp"
android:layout_marginVertical="8dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/player_media_bitrate"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintHorizontal_chainStyle="packed"/>
app:layout_constraintEnd_toEndOf="parent">
<TextView
android:id="@+id/player_media_bitrate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
app:layout_constraintTop_toTopOf="@id/player_media_extension"
app:layout_constraintBottom_toBottomOf="@id/player_media_extension"
app:layout_constraintStart_toEndOf="@id/player_media_extension"
app:layout_constraintEnd_toEndOf="parent"/>
<TextView
android:id="@+id/player_media_bitrate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
app:layout_constraintTop_toTopOf="@id/player_media_extension"
app:layout_constraintBottom_toBottomOf="@id/player_media_extension"
app:layout_constraintStart_toEndOf="@id/player_media_extension"
app:layout_constraintEnd_toEndOf="parent"/>
<com.google.android.material.chip.Chip
android:id="@+id/player_media_extension"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:checked="true"
android:focusable="true"
android:text="Unknown"
app:chipStrokeWidth="0dp"
app:chipBackgroundColor="@color/material_dynamic_secondary40"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/player_media_bitrate"
app:layout_constraintHorizontal_chainStyle="packed"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<ImageButton
android:id="@+id/player_info_track"

View File

@@ -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