Skip to content

Commit

Permalink
Merge pull request #665 from Komodo5197/redesign-blur-performance
Browse files Browse the repository at this point in the history
[Redesign] Blurred background performance improvment (maybe)
  • Loading branch information
Chaphasilor committed Apr 3, 2024
2 parents cc5b84c + 895dc96 commit 1b81e2b
Show file tree
Hide file tree
Showing 18 changed files with 161 additions and 180 deletions.
17 changes: 16 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,21 @@ unlinked_spec.ds
**/ios/Runner/GeneratedPluginRegistrant.*

# macOS
**/Flutter/ephemeral/
**/Pods/
**/macos/Flutter/GeneratedPluginRegistrant.swift
**/macos/Flutter/ephemeral
**/xcuserdata/

# Windows
**/windows/flutter/generated_plugin_registrant.cc
**/windows/flutter/generated_plugin_registrant.h
**/windows/flutter/generated_plugins.cmake

# Linux
**/linux/flutter/generated_plugin_registrant.cc
**/linux/flutter/generated_plugin_registrant.h
**/linux/flutter/generated_plugins.cmake

# Coverage
coverage/
Expand All @@ -95,4 +109,5 @@ app.*.symbols
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
!/dev/ci/**/Gemfile.lock
!/dev/ci/**/Gemfile.lock
!.vscode/settings.json
8 changes: 3 additions & 5 deletions lib/components/AlbumScreen/song_menu.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import 'package:finamp/components/PlayerScreen/sleep_timer_dialog.dart';
import 'package:finamp/models/finamp_models.dart';
import 'package:finamp/screens/artist_screen.dart';
import 'package:finamp/screens/blurred_player_screen_background.dart';
import 'package:finamp/services/feedback_helper.dart';
import 'package:finamp/services/music_player_background_task.dart';
import 'package:finamp/services/queue_service.dart';
import 'package:finamp/services/feedback_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
Expand Down Expand Up @@ -207,8 +207,7 @@ class _SongMenuState extends State<SongMenu> {
builder: (context, scrollController) {
return Stack(
children: [
if (FinampSettingsHelper
.finampSettings.useCoverAsBackground)
if (FinampSettingsHelper.finampSettings.useCoverAsBackground)
BlurredPlayerScreenBackground(
customImageProvider: _imageProvider,
opacityFactor:
Expand Down Expand Up @@ -525,8 +524,7 @@ class _SongMenuState extends State<SongMenu> {
if (!context.mounted) return;

// re-sync playlist to delete removed item if not required anymore
final downloadsService =
GetIt.instance<DownloadsService>();
final downloadsService = GetIt.instance<DownloadsService>();
unawaited(downloadsService.resync(
DownloadStub.fromItem(
type: DownloadItemType.collection,
Expand Down
23 changes: 6 additions & 17 deletions lib/components/PlayerScreen/player_buttons_more.dart
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import 'package:audio_service/audio_service.dart';
import 'package:finamp/components/AlbumScreen/song_list_tile.dart';
import 'package:finamp/components/AlbumScreen/song_menu.dart';
import 'package:finamp/models/jellyfin_models.dart';
import 'package:finamp/screens/add_to_playlist_screen.dart';
import 'package:finamp/services/music_player_background_task.dart';
import 'package:finamp/services/player_screen_theme_provider.dart';
import 'package:finamp/services/feedback_helper.dart';
import 'package:finamp/services/music_player_background_task.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_tabler_icons/flutter_tabler_icons.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_vibrate/flutter_vibrate.dart';
import 'package:get_it/get_it.dart';

Expand All @@ -23,23 +19,16 @@ class PlayerButtonsMore extends ConsumerWidget {

@override
Widget build(BuildContext context, WidgetRef ref) {
ColorScheme? colorScheme =
ref.watch(playerScreenThemeProvider(Theme.of(context).brightness));
return IconTheme(
data: IconThemeData(
color: colorScheme == null
? (Theme.of(context).brightness == Brightness.light
? Colors.black
: Colors.white)
: colorScheme.primary,
size: 24,
color: IconTheme.of(context).color,
size: 24,
),
child: GestureDetector(
onLongPress: () {
FeedbackHelper.feedback(FeedbackType.medium);
Navigator.of(context).pushNamed(
AddToPlaylistScreen.routeName,
arguments: item!.id);
Navigator.of(context)
.pushNamed(AddToPlaylistScreen.routeName, arguments: item!.id);
},
child: IconButton(
icon: const Icon(
Expand All @@ -51,7 +40,7 @@ class PlayerButtonsMore extends ConsumerWidget {
await showModalSongMenu(
context: context,
item: item!,
playerScreenTheme: colorScheme,
playerScreenTheme: Theme.of(context).colorScheme,
showPlaybackControls: true, // show controls on player screen
isInPlaylist: false,
);
Expand Down
36 changes: 18 additions & 18 deletions lib/components/PlayerScreen/queue_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import 'package:finamp/components/favourite_button.dart';
import 'package:finamp/components/global_snackbar.dart';
import 'package:finamp/models/finamp_models.dart';
import 'package:finamp/screens/blurred_player_screen_background.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/player_screen_theme_provider.dart';
import 'package:finamp/services/feedback_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
Expand Down Expand Up @@ -108,13 +108,13 @@ class _QueueListState extends State<QueueList> {

_source = _queueService.getQueue().source;

_contents = <Widget>[
];
_contents = <Widget>[];

widget.scrollController.addListener(() {
final screenSize = MediaQuery.of(context).size;
double offset = widget.scrollController.offset - _currentTrackScroll;
bool showJump = offset > screenSize.height*0.5 || offset < - screenSize.height;
bool showJump =
offset > screenSize.height * 0.5 || offset < -screenSize.height;
widget.jumpToCurrentKey.currentState?.showJumpToTop = showJump;
});
}
Expand Down Expand Up @@ -181,12 +181,13 @@ class _QueueListState extends State<QueueList> {
builder: (context, snapshot) {
if (snapshot.data != null && snapshot.data!.nextUp.isNotEmpty) {
return SliverStickyHeader(
header: NextUpSectionHeader(
controls: true,
nextUpHeaderKey: widget.nextUpHeaderKey,
),
sliver: NextUpTracksList(previousTracksHeaderKey: widget.previousTracksHeaderKey),
);
header: NextUpSectionHeader(
controls: true,
nextUpHeaderKey: widget.nextUpHeaderKey,
),
sliver: NextUpTracksList(
previousTracksHeaderKey: widget.previousTracksHeaderKey),
);
} else {
return const SliverToBoxAdapter();
}
Expand Down Expand Up @@ -217,7 +218,8 @@ class _QueueListState extends State<QueueList> {
queueHeaderKey: widget.queueHeaderKey,
scrollController: widget.scrollController,
),
sliver: QueueTracksList(previousTracksHeaderKey: widget.previousTracksHeaderKey),
sliver: QueueTracksList(
previousTracksHeaderKey: widget.previousTracksHeaderKey),
),
const SliverPadding(
padding: EdgeInsets.only(bottom: 80.0, top: 40.0),
Expand Down Expand Up @@ -506,7 +508,7 @@ class _NextUpTracksListState extends State<NextUpTracksList> {
builder: (context, snapshot) {
if (snapshot.hasData) {
_nextUp ??= snapshot.data!.nextUp;

return SliverPadding(
padding: const EdgeInsets.only(top: 0.0, left: 4.0, right: 4.0),
sliver: SliverReorderableList(
Expand Down Expand Up @@ -534,8 +536,8 @@ class _NextUpTracksListState extends State<NextUpTracksList> {
key = key as GlobalObjectKey;
final ValueKey<String> valueKey =
key.value as ValueKey<String>;
final index =
_nextUp!.indexWhere((item) => item.id == valueKey.value);
final index = _nextUp!
.indexWhere((item) => item.id == valueKey.value);
if (index == -1) return null;
return index;
},
Expand Down Expand Up @@ -1134,7 +1136,6 @@ class QueueSectionHeader extends StatelessWidget {
},
);
}

}

class NextUpSectionHeader extends StatelessWidget {
Expand Down Expand Up @@ -1171,8 +1172,8 @@ class NextUpSectionHeader extends StatelessWidget {
iconPosition: IconPosition.end,
iconSize: 32.0,
iconColor: Theme.of(context).brightness == Brightness.light
? Colors.black
: Colors.white,
? Colors.black
: Colors.white,
onPressed: () {
queueService.clearNextUp();
FeedbackHelper.feedback(FeedbackType.success);
Expand All @@ -1182,7 +1183,6 @@ class NextUpSectionHeader extends StatelessWidget {
),
);
}

}

class PreviousTracksSectionHeader extends SliverPersistentHeaderDelegate {
Expand Down
147 changes: 107 additions & 40 deletions lib/screens/blurred_player_screen_background.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:ui';
import 'dart:ui' as ui;

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:octo_image/octo_image.dart';

Expand All @@ -24,45 +25,111 @@ class BlurredPlayerScreenBackground extends ConsumerWidget {
final imageProvider =
customImageProvider ?? ref.watch(currentAlbumImageProvider).value;

return ColorFiltered(
// Force total opacity to always be 100%
colorFilter: const ColorFilter.matrix(<double>[
1, 0, 0, 0, 0, // R
0, 1, 0, 0, 0, // G
0, 0, 1, 0, 0, // B
0, 0, 0, 2, 0, // A
]),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 1000),
child: ClipRect(
// Don't transition between images with identical files/urls
key: ValueKey(imageProvider.toString()),
child: imageProvider == null
? const SizedBox.shrink()
: OctoImage(
image: imageProvider,
fit: BoxFit.cover,
placeholderBuilder: (_) => const SizedBox.shrink(),
errorBuilder: (_, __, ___) => const SizedBox.shrink(),
imageBuilder: (context, child) => ColorFiltered(
colorFilter: ColorFilter.mode(
Theme.of(context).brightness == Brightness.dark
? Colors.black.withOpacity(
clampDouble(0.675 * opacityFactor, 0.0, 1.0))
: Colors.white.withOpacity(
clampDouble(0.75 * opacityFactor, 0.0, 1.0)),
BlendMode.srcOver),
child: ImageFiltered(
imageFilter: ImageFilter.blur(
sigmaX: 85,
sigmaY: 85,
tileMode: TileMode.mirror,
),
child: SizedBox.expand(child: child),
return AnimatedSwitcher(
duration: const Duration(milliseconds: 1000),
switchOutCurve: const Threshold(0.0),
child: imageProvider == null
? const SizedBox.shrink()
: OctoImage(
// Don't transition between images with identical files/urls
key: ValueKey(imageProvider.toString()),
image: imageProvider,
fit: BoxFit.cover,
color: Theme.of(context).brightness == Brightness.dark
? Colors.black.withOpacity(
ui.clampDouble(0.675 * opacityFactor, 0.0, 1.0))
: Colors.white.withOpacity(
ui.clampDouble(0.75 * opacityFactor, 0.0, 1.0)),
colorBlendMode: BlendMode.srcOver,
filterQuality: FilterQuality.none,
errorBuilder: (_, __, ___) => const SizedBox.shrink(),
imageBuilder: (context, child) => CachePaint(
imageKey: imageProvider.toString(),
child: ImageFiltered(
imageFilter: ui.ImageFilter.blur(
sigmaX: 85,
sigmaY: 85,
tileMode: TileMode.mirror,
),
),
),
)),
);
child: SizedBox.expand(child: child),
))));
}
}

class CachePaint extends SingleChildRenderObjectWidget {
const CachePaint({super.key, super.child, required this.imageKey});

final String imageKey;

@override
RenderObject createRenderObject(BuildContext context) {
return RenderCachePaint(MediaQuery.sizeOf(context), imageKey);
}
}

class RenderCachePaint extends RenderProxyBox {
RenderCachePaint(this._screenSize, var imageKey)
: _imageKey = imageKey + _screenSize.toString();

final Size _screenSize;

final String _imageKey;

bool _hasCache = false;

static final Map<String, (List<RenderCachePaint>, ui.Image?)> _cache = {};

@override
bool get isRepaintBoundary => true;

@override
void paint(PaintingContext context, ui.Offset offset) {
if (_cache[_imageKey] != null) {
if (!_hasCache) {
// Add use to list of widgets using image
_hasCache = true;
_cache[_imageKey]!.$1.add(this);
}
if (_cache[_imageKey]!.$2 != null) {
// Use cached child
context.canvas.drawImage(_cache[_imageKey]!.$2!, offset, Paint());
} else {
// Image is currently building, so paint child and move on.
super.paint(context, offset);
}
} else {
// Create cache entry
_hasCache = true;
_cache[_imageKey] = ([this], null);
// Paint our child
super.paint(context, offset);
// Save image of child to cache
final OffsetLayer offsetLayer = layer! as OffsetLayer;
Future.sync(() async {
_cache[_imageKey] = (
_cache[_imageKey]!.$1,
await offsetLayer.toImage(offset & _screenSize)
);
// Schedule repaint next frame because the image is lighter than the full
// child during compositing, which is more frequent than paints.
for (var element in _cache[_imageKey]!.$1) {
element.markNeedsPaint();
}
});
}
}

@override
void dispose() {
if (_hasCache) {
_cache[_imageKey]!.$1.remove(this);
if (_cache[_imageKey]!.$1.isEmpty) {
// If we are last user of image, dispose
_cache[_imageKey]!.$2?.dispose();
_cache.remove(_imageKey);
}
}
_hasCache = false;
super.dispose();
}
}
2 changes: 1 addition & 1 deletion lib/screens/player_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class PlayerScreen extends ConsumerWidget {
ref.watch(playerScreenThemeProvider(Theme.of(context).brightness));

return AnimatedTheme(
duration: const Duration(milliseconds: 500),
duration: const Duration(milliseconds: 1000),
data: ThemeData(
colorScheme: imageTheme.copyWith(
brightness: Theme.of(context).brightness,
Expand Down
1 change: 0 additions & 1 deletion linux/flutter/ephemeral/.plugin_symlinks/device_info_plus

This file was deleted.

1 change: 0 additions & 1 deletion linux/flutter/ephemeral/.plugin_symlinks/isar_flutter_libs

This file was deleted.

1 change: 0 additions & 1 deletion linux/flutter/ephemeral/.plugin_symlinks/package_info_plus

This file was deleted.

0 comments on commit 1b81e2b

Please sign in to comment.