Skip to content
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
6bcbf3d
add instant mix menu entry for all item types, create dedicated mix b…
Chaphasilor Nov 26, 2025
4c9b817
hide appbar icons before overflow on album/playlist screen
Chaphasilor Nov 26, 2025
1174eb1
use configured buffer duration as minimum duration if size constraint…
Chaphasilor Dec 1, 2025
61a8316
allow disabling ducking on Android
Chaphasilor Dec 1, 2025
6dae7ff
add button for deleting stored queues to ErrorApp
Chaphasilor Dec 2, 2025
cbd1df9
fix covers not appearing in Android Auto
Chaphasilor Dec 2, 2025
065f62f
fix conditions based on review
Chaphasilor Dec 4, 2025
0aca615
Merge branch 'redesign' into redesign-misc-fixes
Chaphasilor Dec 4, 2025
60bd548
Merge branch 'redesign' into redesign-misc-fixes
Chaphasilor Dec 4, 2025
e9a9448
enable predictive back on Android
Chaphasilor Dec 4, 2025
54ef357
unhide max concurrent downloads setting on iOS
Chaphasilor Dec 4, 2025
a5670b3
fix reshuffle and random mode in purely radio-based queues, remove de…
Chaphasilor Dec 4, 2025
f7d3be4
Prevent current track menu from interfering with saved value of playb…
Komodo5197 Dec 5, 2025
572eb1c
Prevent overscroll when setting queue initial scroll position with pr…
Komodo5197 Dec 5, 2025
ee54511
fix album mix radio mode
Chaphasilor Dec 6, 2025
e53bb8c
Fully block creation of download locations if write errors occur.
Komodo5197 Dec 7, 2025
46933ed
add base item to queue source when starting track on tap
Chaphasilor Dec 9, 2025
b4e2e0c
opt back in to Impeller
Chaphasilor Dec 9, 2025
4d47bdc
add setting for enabling audio offloading on Android
Chaphasilor Dec 9, 2025
123542d
open source when tapping radio source on player screen
Chaphasilor Dec 9, 2025
ad795f8
make album mix third mode
Chaphasilor Dec 9, 2025
d813373
make "Start Radio" faster and fill the cache, fix queue not clearing …
Chaphasilor Dec 9, 2025
cdf4427
fix text color contrast on queue list and now playing bar
Chaphasilor Dec 9, 2025
19226a3
apply primary text color to remaining text and icons on now playing bar
Chaphasilor Dec 9, 2025
0e7c937
improve locking behavior for "start radio" and "stop & clear queue"
Chaphasilor Dec 9, 2025
14a2b64
move padding of back button into interactive area, migrate track_name…
Chaphasilor Dec 9, 2025
b2a9e5f
increase max width for track titles on player screen
Chaphasilor Dec 9, 2025
2b6ccd2
use more space efficient fallback for unlocalized downloads
Chaphasilor Dec 10, 2025
ae56214
shrink floating action button on lyrics/queue screen
Chaphasilor Dec 10, 2025
5bd2656
give track name fixed height on player screen, fix favorite status no…
Chaphasilor Dec 10, 2025
277570c
ensure a contrasting color is used for lyrics cue highlight
Chaphasilor Dec 10, 2025
90513a3
improve "start radio" for reshuffle and random modes, move locks, enf…
Chaphasilor Dec 10, 2025
e50aa32
remember albumMix fallback mode, add reset handling for new settings
Chaphasilor Dec 10, 2025
a6b98ff
enable reordering if shuffle is active
Chaphasilor Dec 10, 2025
2d7ebd7
shuffle indices and album mix fallback mode caching fixes
Chaphasilor Dec 11, 2025
e1377c1
add missing tooltips/semantics
Chaphasilor Dec 11, 2025
c4abf63
rename `_queueAudioSourceIndex` to `_currentQueueIndex` make reorder …
Chaphasilor Dec 11, 2025
366a2b2
handle albumMix fallback more deliberately
Chaphasilor Dec 11, 2025
45093ac
don't update fallback mode for custom seed items
Chaphasilor Dec 11, 2025
8c5cd6b
improve sleep timer pausing
Chaphasilor Dec 12, 2025
a7259a8
add "explicit" indicator if certain content ratings are set
Chaphasilor Dec 12, 2025
f5032a2
fix long-press for add to playlist button, fix case for sleep timer m…
Chaphasilor Dec 13, 2025
98b82d2
Fix android CI.
Komodo5197 Dec 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
android:fullBackupContent="@xml/backup_rules_legacy"
android:dataExtractionRules="@xml/backup_rules"
android:networkSecurityConfig="@xml/network_security_config"
android:enableOnBackInvokedCallback="true"
>
<activity android:name=".MainActivity" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize" android:exported="true">
<!-- Specifies an Android theme to apply to this Activity as soon as
Expand Down Expand Up @@ -90,8 +91,6 @@
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data android:name="flutterEmbedding" android:value="2" />

<meta-data android:name="io.flutter.embedding.android.EnableImpeller" android:value="false" />
</application>

<!-- <uses-feature
Expand Down
12 changes: 7 additions & 5 deletions lib/components/AlbumScreen/album_screen_content.dart
Original file line number Diff line number Diff line change
Expand Up @@ -119,17 +119,19 @@ class _AlbumScreenContentState extends ConsumerState<AlbumScreenContent> {
slivers: [
SliverLayoutBuilder(
builder: (context, constraints) {
final maxActions = constraints.crossAxisExtent ~/ 48.0;
final actions = [
if (widget.parent.type == "Playlist" &&
if (maxActions >= 9 &&
widget.parent.type == "Playlist" &&
!ref.watch(finampSettingsProvider.isOffline) &&
ref.watch(canEditPlaylistProvider(widget.parent)))
PlaylistEditButton(playlist: widget.parent),
if (widget.parent.type == "Playlist")
if (widget.parent.type == "Playlist") ...[
SortOrderButton(tabType: TabContentType.tracks, forPlaylistTracks: true),
if (widget.parent.type == "Playlist")
SortByMenuButton(tabType: TabContentType.tracks, forPlaylistTracks: true),
FavoriteButton(item: widget.parent),
if (!isLoading)
],
FavoriteButton(item: widget.parent, visualDensity: VisualDensity.standard),
if (maxActions >= 8 && !isLoading)
DownloadButton(
item: downloadStub,
children: displayChildren,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import 'package:finamp/menus/components/playbackActions/playback_action_row.dart';
import 'package:finamp/menus/components/playbackActions/playback_actions.dart';
import 'package:finamp/services/finamp_settings_helper.dart';
import 'package:finamp/services/queue_service.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:get_it/get_it.dart';

import '../../models/jellyfin_models.dart';
import '../../services/audio_service_helper.dart';
import '../album_image.dart';
import 'item_info.dart';
import 'package:finamp/components/AlbumScreen/item_info.dart';
import 'package:finamp/components/album_image.dart';
import 'package:finamp/menus/components/playbackActions/playback_action_row.dart';
import 'package:finamp/models/jellyfin_models.dart';

enum AlbumMenuItems {
addFavorite,
Expand Down
9 changes: 3 additions & 6 deletions lib/components/AlbumScreen/track_list_tile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import 'package:finamp/services/current_album_image_provider.dart';
import 'package:finamp/services/datetime_helper.dart';
import 'package:finamp/services/feedback_helper.dart';
import 'package:finamp/services/finamp_user_helper.dart';
import 'package:finamp/services/item_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_tabler_icons/flutter_tabler_icons.dart';
Expand Down Expand Up @@ -215,12 +216,8 @@ class TrackListTile extends ConsumerWidget {
await audioServiceHelper.startInstantMixForItem(item);
} else {
await queueService.startPlayback(
items: [item],
source: QueueItemSource(
name: QueueItemSourceName(type: QueueItemSourceNameType.preTranslated, pretranslatedName: item.name),
type: QueueItemSourceType.track,
id: item.id,
),
items: await loadChildTracks(item: item, genreFilter: genreFilter),
source: QueueItemSource.fromBaseItem(item),
);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import 'package:finamp/l10n/app_localizations.dart';
import 'package:finamp/services/finamp_settings_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../../services/finamp_settings_helper.dart';

class TrackShuffleItemCountEditor extends ConsumerStatefulWidget {
const TrackShuffleItemCountEditor({super.key});

Expand Down
39 changes: 39 additions & 0 deletions lib/components/Buttons/finamp_extended_floating_action_button.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import 'package:finamp/extensions/color_extensions.dart';
import 'package:flutter/material.dart';

class FinampExtendedFloatingActionButton extends StatelessWidget {
const FinampExtendedFloatingActionButton({
super.key,
required this.label,
required this.icon,
required this.onTap,
this.backgroundColor,
});

final IconData icon;
final String label;
final Color? backgroundColor;
final void Function() onTap;

@override
Widget build(BuildContext context) {
Color actualBackgroundColor = backgroundColor ?? ColorScheme.of(context).primary;
Color textColor = AtContrast.getContrastiveTintedTextColor(onBackground: actualBackgroundColor);

return SizedBox(
height: 48.0,
child: FittedBox(
child: FloatingActionButton.extended(
onPressed: onTap,
backgroundColor: actualBackgroundColor,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16.0))),
icon: Icon(icon, size: 20.0, color: textColor),
label: Text(
label,
style: TextTheme.of(context).bodyMedium?.copyWith(color: textColor, fontWeight: FontWeight.w500),
),
),
),
);
}
}
5 changes: 2 additions & 3 deletions lib/components/PlayerScreen/player_screen_appbar_title.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import 'package:balanced_text/balanced_text.dart';
import 'package:finamp/components/PlayerScreen/queue_source_helper.dart';
import 'package:finamp/l10n/app_localizations.dart';
import 'package:finamp/models/finamp_models.dart';
import 'package:finamp/services/queue_service.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';

import '../../models/finamp_models.dart';
import 'queue_source_helper.dart';

class PlayerScreenAppBarTitle extends StatefulWidget {
const PlayerScreenAppBarTitle({super.key, required this.maxLines});

Expand Down
43 changes: 26 additions & 17 deletions lib/components/PlayerScreen/queue_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import 'dart:async';
import 'dart:math';

import 'package:audio_service/audio_service.dart';
import 'package:collection/collection.dart';
import 'package:finamp/components/AddToPlaylistScreen/add_to_playlist_button.dart';
import 'package:finamp/components/AlbumScreen/track_list_tile.dart';
import 'package:finamp/components/Buttons/finamp_extended_floating_action_button.dart';
import 'package:finamp/components/Buttons/simple_button.dart';
import 'package:finamp/components/PlayerScreen/queue_source_helper.dart';
import 'package:finamp/components/album_image.dart';
Expand All @@ -13,6 +15,7 @@ import 'package:finamp/components/one_line_marquee_helper.dart';
import 'package:finamp/components/padded_custom_scrollview.dart';
import 'package:finamp/components/print_duration.dart';
import 'package:finamp/components/themed_bottom_sheet.dart';
import 'package:finamp/extensions/color_extensions.dart';
import 'package:finamp/l10n/app_localizations.dart';
import 'package:finamp/main.dart';
import 'package:finamp/menus/choice_menu.dart';
Expand Down Expand Up @@ -140,7 +143,12 @@ class _QueueListState extends ConsumerState<QueueList> {
widget.scrollController.hasClients &&
FinampSettingsHelper.finampSettings.previousTracksExpaned) {
final changeHeight = _queueService.getQueue().previousTracks.length * QueueListTile.height;
widget.scrollController.position.correctBy(changeHeight - 50);
var target = widget.scrollController.position.pixels + changeHeight - 50;
target = target.clamp(
widget.scrollController.position.minScrollExtent,
widget.scrollController.position.maxScrollExtent,
);
widget.scrollController.position.correctPixels(target);
}
});
}
Expand Down Expand Up @@ -372,22 +380,13 @@ class JumpToCurrentButtonState extends State<JumpToCurrentButton> {
@override
Widget build(BuildContext context) {
return _jumpToCurrentTrackDirection != 0
? FloatingActionButton.extended(
onPressed: () {
? FinampExtendedFloatingActionButton(
icon: _jumpToCurrentTrackDirection < 0 ? TablerIcons.arrow_bar_to_up : TablerIcons.arrow_bar_to_down,
label: AppLocalizations.of(context)!.scrollToCurrentTrack,
onTap: () {
FeedbackHelper.feedback(FeedbackType.heavy);
scrollToKey(key: widget.previousTracksHeaderKey, duration: const Duration(milliseconds: 500));
},
backgroundColor: IconTheme.of(context).color!.withOpacity(0.70),
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16.0))),
icon: Icon(
_jumpToCurrentTrackDirection < 0 ? TablerIcons.arrow_bar_to_up : TablerIcons.arrow_bar_to_down,
size: 28.0,
color: Colors.white.withOpacity(0.9),
),
label: Text(
AppLocalizations.of(context)!.scrollToCurrentTrack,
style: TextStyle(color: Colors.white.withOpacity(0.9), fontSize: 14.0, fontWeight: FontWeight.w500),
),
)
: const SizedBox.shrink();
}
Expand Down Expand Up @@ -705,7 +704,17 @@ class _CurrentTrackState extends ConsumerState<CurrentTrack> {
const horizontalPadding = 8.0;
const albumImageSize = 70.0;

final primaryTextColor = Colors.white;
final elapsedPartBackgroundColor = ColorScheme.of(context).primary;
final remainingPartBackgroundColor = Color.alphaBlend(
elapsedPartBackgroundColor.withOpacity(0.7),
// this is an approximation, the actual background has the blurred cover image
ref.watch(brightnessProvider) == Brightness.dark ? Colors.black : Colors.white,
);
final averageBackgroundColor = Color.alphaBlend(
elapsedPartBackgroundColor.withOpacity(0.5),
remainingPartBackgroundColor,
);
Color primaryTextColor = AtContrast.getContrastiveTintedTextColor(onBackground: averageBackgroundColor);

return SliverAppBar(
pinned: true,
Expand All @@ -721,7 +730,7 @@ class _CurrentTrackState extends ConsumerState<CurrentTrack> {
child: Container(
clipBehavior: Clip.antiAlias,
decoration: ShapeDecoration(
color: ColorScheme.of(context).primary.withOpacity(0.7),
color: remainingPartBackgroundColor,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12.0))),
),
child: Row(
Expand Down Expand Up @@ -775,7 +784,7 @@ class _CurrentTrackState extends ConsumerState<CurrentTrack> {
: max(0, playbackPosition!.inMilliseconds / itemLength.inMilliseconds),
child: DecoratedBox(
decoration: ShapeDecoration(
color: ColorScheme.of(context).primary,
color: elapsedPartBackgroundColor,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topRight: Radius.circular(12),
Expand Down
44 changes: 38 additions & 6 deletions lib/components/PlayerScreen/queue_source_helper.dart
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import 'dart:async';

import 'package:finamp/components/confirmation_prompt_dialog.dart';
import 'package:finamp/components/global_snackbar.dart';
import 'package:finamp/l10n/app_localizations.dart';
import 'package:finamp/menus/track_menu.dart';
import 'package:finamp/models/finamp_models.dart';
import 'package:finamp/models/jellyfin_models.dart';
import 'package:finamp/screens/album_screen.dart';
import 'package:finamp/screens/artist_screen.dart';
import 'package:finamp/screens/downloads_screen.dart';
import 'package:finamp/screens/genre_screen.dart';
import 'package:finamp/screens/music_screen.dart';
import 'package:finamp/services/downloads_service.dart';
import 'package:finamp/services/feedback_helper.dart';
import 'package:finamp/services/finamp_settings_helper.dart';
import 'package:finamp/services/jellyfin_api_helper.dart';
import 'package:finamp/services/queue_service.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';

import '../../models/jellyfin_models.dart';
import '../../services/downloads_service.dart';
import '../../services/jellyfin_api_helper.dart';
import '../confirmation_prompt_dialog.dart';

void navigateToSource(BuildContext context, QueueItemSource source) {
switch (source.type) {
case QueueItemSourceType.album:
Expand Down Expand Up @@ -56,6 +56,38 @@ void navigateToSource(BuildContext context, QueueItemSource source) {
if (source.item != null) {
showModalTrackMenu(context: context, item: source.item!);
}
case QueueItemSourceType.radio:
final radioSource = GetIt.instance<QueueService>().getQueue().source;
if (radioSource.item == null) {
break;
}
switch (BaseItemDtoType.fromItem(radioSource.item!)) {
case BaseItemDtoType.track:
showModalTrackMenu(context: context, item: radioSource.item!);
break;
case BaseItemDtoType.album:
case BaseItemDtoType.playlist:
Navigator.of(context).pushNamed(AlbumScreen.routeName, arguments: radioSource.item);
break;
case BaseItemDtoType.artist:
Navigator.of(context).pushNamed(ArtistScreen.routeName, arguments: radioSource.item);
break;
case BaseItemDtoType.genre:
Navigator.of(context).pushNamed(GenreScreen.routeName, arguments: radioSource.item);
break;
case BaseItemDtoType.noItem:
case BaseItemDtoType.library:
case BaseItemDtoType.folder:
case BaseItemDtoType.musicVideo:
case BaseItemDtoType.audioBook:
case BaseItemDtoType.tvEpisode:
case BaseItemDtoType.video:
case BaseItemDtoType.movie:
case BaseItemDtoType.trailer:
case BaseItemDtoType.unknown:
break;
}
break;
case QueueItemSourceType.downloads:
Navigator.of(context).pushNamed(DownloadsScreen.routeName);
break;
Expand All @@ -65,7 +97,7 @@ void navigateToSource(BuildContext context, QueueItemSource source) {
case QueueItemSourceType.unknown:
break;
case QueueItemSourceType.filteredList:
default:
case QueueItemSourceType.queue:
FeedbackHelper.feedback(FeedbackType.warning);
GlobalSnackbar.message((scaffold) => AppLocalizations.of(context)!.notImplementedYet);
}
Expand Down
Loading