Skip to content

Commit

Permalink
Start busy animation only after a delay. Handle nested busy state. Im…
Browse files Browse the repository at this point in the history
…prove performance of image loading.
  • Loading branch information
Kern, Thomas committed Jan 15, 2024
1 parent 322fadd commit 6d5a46a
Show file tree
Hide file tree
Showing 14 changed files with 514 additions and 289 deletions.
34 changes: 18 additions & 16 deletions lib/components/busy_wrapper.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:mopicon/pages/settings/preferences_controller.dart';
Expand All @@ -24,8 +26,11 @@ class _BusyWrapperState extends State<BusyWrapper> with TickerProviderStateMixin
final preferences = GetIt.instance<PreferencesController>();
final mopidyService = GetIt.instance<MopidyService>();

double opacity = 0;
Timer? timer;

late final AnimationController _primaryController = AnimationController(
duration: const Duration(seconds: 4),
duration: const Duration(seconds: 5),
vsync: this,
);

Expand All @@ -45,35 +50,32 @@ class _BusyWrapperState extends State<BusyWrapper> with TickerProviderStateMixin
);

void _startAnimation() {
_primaryController.forward(from: 0);
_secondaryController.forward(from: 0);
_stopAnimation();
timer = Timer(const Duration(milliseconds: 800), () {
_primaryController.forward(from: 0);
_secondaryController.forward(from: 0);
setState(() {
opacity = 0.3;
});
});
}

void _stopAnimation() {
opacity = 0;
timer?.cancel();
_primaryController.reset();
_secondaryController.reset();
}

bool _busy = false;

bool get busy => _busy;

set busy(bool b) {
setState(() {
b ? _startAnimation() : _stopAnimation();
_busy = b;
});
}

@override
initState() {
super.initState();
_busy = widget.busy;
_startAnimation();
}

@override
dispose() {
timer?.cancel();
_primaryController.dispose();
_secondaryController.dispose();
super.dispose();
Expand Down Expand Up @@ -102,7 +104,7 @@ class _BusyWrapperState extends State<BusyWrapper> with TickerProviderStateMixin
children: [
widget.child,
Opacity(
opacity: 0.5,
opacity: opacity,
child: ModalBarrier(dismissible: false, color: preferences.theme.data.dialogBackgroundColor),
),
FadeTransition(
Expand Down
4 changes: 2 additions & 2 deletions lib/components/volume_control.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ class _VolumeControlState extends State<VolumeControl> {

void updateState() async {
try {
var m = await _mopidyService.isMuted() ?? false;
var v = await _mopidyService.getVolume() ?? 0;
var m = await _mopidyService.isMuted();
var v = await _mopidyService.getVolume();
if (mounted) {
setState(() {
muted = m;
Expand Down
21 changes: 21 additions & 0 deletions lib/extensions/mopidy_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,27 @@ extension MopidyRefExtensions on Ref {
}
}

extension MopidyRefListExtensions on List<Ref> {
/// Returns the images for [List<Ref>]
Future<Map<String, Widget>> getImages() async {
return _coverService.getImages(map((e) => e.uri).toList());
}
}

extension MopidyTrackListExtensions on List<Track> {
/// Returns the images for [List<Track>]
Future<Map<String, Widget>> getImages() async {
return _coverService.getImages(map((e) => e.uri).toList());
}
}

extension MopidyTlTrackListExtensions on List<TlTrack> {
/// Returns the images for [List<TlTrack>]
Future<Map<String, Widget>> getImages() async {
return _coverService.getImages(map((e) => e.track.uri).toList());
}
}

extension MopidyTrackExtensions on Track {
/// Returns the image for [Track]
Future<Widget> getImage() async {
Expand Down
4 changes: 2 additions & 2 deletions lib/pages/browse/album_list_item.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ class AlbumListItem extends StatelessWidget {
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 5.0),
padding: const EdgeInsets.symmetric(vertical: 2.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
thumbnail,
Expanded(
Expand Down
23 changes: 16 additions & 7 deletions lib/pages/browse/library_browser_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
* DEALINGS IN THE SOFTWARE.
*/
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:mopicon/pages/settings/preferences_controller.dart';
import 'package:mopicon/components/error_snackbar.dart';
import 'package:mopicon/services/mopidy_service.dart';
import 'package:mopicon/components/menu_builder.dart';
Expand All @@ -43,6 +45,10 @@ abstract class LibraryBrowserController extends BaseController with TracklistMet
}

class LibraryBrowserControllerImpl extends LibraryBrowserController {
final _preferences = GetIt.instance<PreferencesController>();

var extendedCategoriesNames = ['Performers', 'Release Years', "Last Week's Updates", "Last Month's Updates"];

@override
MenuBuilder<Ref> popupMenu(BuildContext? context, Ref? item, int? index) {
assert(context != null);
Expand Down Expand Up @@ -144,23 +150,26 @@ class LibraryBrowserControllerImpl extends LibraryBrowserController {

@override
Future<List<Ref>> getSelectedItems(Ref? parent) async {
var refs = await mopidyService.browse(parent);
if (parent == null) {
refs.addAll(await mopidyService.getPlaylists());
}
var refs = await browse(parent);
return Future.value(selectionChanged.value.filterSelected(refs));
}

@override
Future<List<Ref>> browse(Ref? parent) async {
late List<Ref> items;
items = await mopidyService.browse(parent);
if (parent == null) {
// toplevel: show both, media and playlists
items = await mopidyService.browse(null);
var playlists = await mopidyService.getPlaylists();
items.addAll(playlists);
} else {
items = await mopidyService.browse(parent);
}

if (parent == null && _preferences.hideFileExtension) {
items.removeWhere((item) => item.type == Ref.typeDirectory && item.name == 'Files');
}

if (!_preferences.showAllMediaCategories) {
items.removeWhere((item) => item.type == Ref.typeDirectory && extendedCategoriesNames.contains(item.name));
}
return items;
}
Expand Down
21 changes: 5 additions & 16 deletions lib/pages/browse/library_browser_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,11 @@ class _LibraryBrowserPageState extends State<LibraryBrowserPage> {

final libraryController = GetIt.instance<LibraryBrowserController>();
final mopidyService = GetIt.instance<MopidyService>();

final preferences = GetIt.instance<PreferencesController>();

StreamSubscription? refreshSubscription;

var extendedCategoriesNames = ['Performers', 'Release Years', "Last Week's Updates", "Last Month's Updates"];

Future updateItems() async {
try {
mopidyService.setBusy(true);
Expand All @@ -73,24 +72,14 @@ class _LibraryBrowserPageState extends State<LibraryBrowserPage> {
}

items = await libraryController.browse(parent);
if (parent == null && preferences.hideFileExtension) {
items.removeWhere((item) => item.type == Ref.typeDirectory && item.name == 'Files');
}

if (!preferences.showAllMediaCategories) {
items.removeWhere((item) => item.type == Ref.typeDirectory && extendedCategoriesNames.contains(item.name));
}

// load images into local map
for (Ref item in items) {
var image = await item.getImage();
images.putIfAbsent(item.uri, () => image);
}
images = await items.getImages();
} catch (e, s) {
Globals.logger.e(e, stackTrace: s);
} finally {
mopidyService.setBusy(false);
setState(() {});
if (mounted) {
setState(() {});
}
}
}

Expand Down
24 changes: 12 additions & 12 deletions lib/pages/home/home_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,16 @@ class HomeView extends StatefulWidget {
}

class _HomeViewState extends State<HomeView> {
final _mopidyService = GetIt.instance<MopidyService>();
final mopidyService = GetIt.instance<MopidyService>();

int trackListCount = 0;
bool _showBusy = true;
bool showBusy = true;

StreamSubscription? connectionSubscription;
StreamSubscription? busySubscription;

void initTrackListCount() async {
int count = await _mopidyService.getTracklistLength();
int count = await mopidyService.getTracklistLength();
if (mounted) {
setState(() {
trackListCount = count;
Expand All @@ -60,7 +60,7 @@ class _HomeViewState extends State<HomeView> {
void updateTrackListCount() {
if (mounted) {
setState(() {
trackListCount = _mopidyService.tracklistChangedNotifier.value.length;
trackListCount = mopidyService.tracklistChangedNotifier.value.length;
});
}
}
Expand All @@ -69,26 +69,26 @@ class _HomeViewState extends State<HomeView> {
void initState() {
super.initState();
GetIt.instance<MopidyService>().connect(GetIt.instance<PreferencesController>().url);
_showBusy = true;
connectionSubscription = _mopidyService.connectionState$.listen((MopidyConnectionState state) {
showBusy = true;
connectionSubscription = mopidyService.connectionState$.listen((MopidyConnectionState state) {
setState(() {
_showBusy = state != MopidyConnectionState.online;
showBusy = state != MopidyConnectionState.online;
});
});
busySubscription = _mopidyService.busyState$.listen((bool busy) {
busySubscription = mopidyService.busyState$.listen((bool busy) {
setState(() {
_showBusy = busy ? true : !(!busy && _mopidyService.connected);
showBusy = busy ? true : !(!busy && mopidyService.connected);
});
});
_mopidyService.tracklistChangedNotifier.addListener(updateTrackListCount);
mopidyService.tracklistChangedNotifier.addListener(updateTrackListCount);
initTrackListCount();
}

@override
void dispose() {
connectionSubscription?.cancel();
busySubscription?.cancel();
_mopidyService.tracklistChangedNotifier.removeListener(updateTrackListCount);
mopidyService.tracklistChangedNotifier.removeListener(updateTrackListCount);
super.dispose();
}

Expand Down Expand Up @@ -127,6 +127,6 @@ class _HomeViewState extends State<HomeView> {
initialLocation: index == widget.navigationShell.currentIndex,
);
})),
_showBusy);
showBusy);
}
}
Loading

0 comments on commit 6d5a46a

Please sign in to comment.