Skip to content

Commit

Permalink
Busy progress indicator for pages implemented. (Not yet implemented f…
Browse files Browse the repository at this point in the history
…or menu actions.)
  • Loading branch information
Kern, Thomas committed Dec 30, 2023
1 parent 7a61379 commit 2cc85e9
Show file tree
Hide file tree
Showing 5 changed files with 283 additions and 142 deletions.
86 changes: 86 additions & 0 deletions lib/components/busy_wrapper.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import 'package:flutter/material.dart';
import 'package:mopicon/utils/globals.dart';

/// Shows a modal busy indicator.
///
/// If [busy] is `true`, interaction with [child] is disabled and
/// a progress indicator is displayed hovering on top of [child].
/// The progress indicator is slowly fading in.
class BusyWrapper extends StatefulWidget {
final bool busy;
final Widget child;

const BusyWrapper(this.child, this.busy, {super.key});

@override
State<BusyWrapper> createState() => BusyWrapperState();
}

class BusyWrapperState extends State<BusyWrapper>
with TickerProviderStateMixin {
late final AnimationController _controller = AnimationController(
duration: const Duration(seconds: 4),
vsync: this,
);

late final Animation<double> _animation = CurvedAnimation(
parent: _controller,
curve: Curves.easeIn,
);

bool _busy = false;

bool get busy => _busy;

set busy(bool b) {
setState(() {
b ? _controller.forward() : _controller.reset();
_busy = b;
});
}

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

@override
dispose() {
_controller.dispose();
super.dispose();
}

@override
void didUpdateWidget(covariant BusyWrapper oldWidget) {
_controller.reset();
_controller.forward();
super.didUpdateWidget(oldWidget);
}

@override
Widget build(BuildContext context) {
if (!widget.busy) {
return widget.child;
}

return Stack(
children: [
widget.child,
Opacity(
opacity: 0.4,
child: ModalBarrier(
dismissible: false,
color: Globals.preferences.theme.data.dialogBackgroundColor),
),
Center(
child: FadeTransition(
opacity: _animation,
child: const CircularProgressIndicator(),
),
),
],
);
}
}
87 changes: 49 additions & 38 deletions lib/pages/browse/library_browser_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import 'package:mopicon/components/volume_control.dart';
import 'package:mopicon/services/mopidy_service.dart';
import 'package:mopicon/utils/parameters.dart';
import 'package:mopicon/components/action_buttons.dart';
import 'package:mopicon/components/busy_wrapper.dart';
import 'package:mopicon/utils/globals.dart';
import 'package:mopicon/generated/l10n.dart';
import 'package:mopicon/extensions/mopidy_utils.dart';
Expand All @@ -52,6 +53,7 @@ class _LibraryBrowserPageState extends State<LibraryBrowserPage> {
Ref? parent;
List<Ref> items = [];
var images = <String, Widget>{};
bool showBusy = false;

// selection mode (single/multiple) of track list view
SelectionMode selectionMode = SelectionMode.off;
Expand Down Expand Up @@ -88,6 +90,7 @@ class _LibraryBrowserPageState extends State<LibraryBrowserPage> {

Future updateItems() async {
try {
showBusy = true;
if (widget.parent != null) {
parent = Ref.fromMap(Parameter.fromBase64(widget.parent!));
}
Expand Down Expand Up @@ -118,12 +121,13 @@ class _LibraryBrowserPageState extends State<LibraryBrowserPage> {
var image = await item.getImage();
images.putIfAbsent(item.uri, () => image);
}

} catch (e, s) {
logger.e(e, stackTrace: s);
} finally {
if (mounted) {
showBusy = false;
setState(() {});
}
} catch (e, s) {
logger.e(e, stackTrace: s);
}
}

Expand Down Expand Up @@ -180,40 +184,47 @@ class _LibraryBrowserPageState extends State<LibraryBrowserPage> {
}
}).build();

return Scaffold(
appBar: AppBar(
title: Text(widget.title ?? S.of(context).libraryBrowserPageTitle),
centerTitle: true,
leading: widget.parent != null
? ActionButton<SelectedItemPositions>(Icons.arrow_back, () {
if (libraryController.selectionChanged.value.isEmpty) {
Navigator.of(context).pop();
} else {
libraryController.unselect();
}
})
: null,
actions: [
parent == null
? ActionButton<SelectedItemPositions>(Icons.delete,
() => libraryController.deleteSelectedPlaylists(),
valueListenable: libraryController.selectionChanged)
: const SizedBox(),
ActionButton<SelectedItemPositions>(Icons.queue_music, () async {
var selectedItems =
await libraryController.getSelectedItems(parent);
await libraryController.addItemsToTracklist<Ref>(selectedItems);
libraryController.unselect();
}, valueListenable: libraryController.selectionChanged),
ActionButton<SelectedItemPositions>(Icons.playlist_add, () async {
var selectedItems =
await libraryController.getSelectedItems(parent);
await libraryController.addItemsToPlaylist<Ref>(selectedItems);
libraryController.unselect();
}, valueListenable: libraryController.selectionChanged),
VolumeControl(),
LibraryBrowserAppBarMenu(items, libraryController)
]),
body: MaterialPageFrame(child: listView));
return BusyWrapper(
Scaffold(
appBar: AppBar(
title:
Text(widget.title ?? S.of(context).libraryBrowserPageTitle),
centerTitle: true,
leading: widget.parent != null
? ActionButton<SelectedItemPositions>(Icons.arrow_back, () {
if (libraryController.selectionChanged.value.isEmpty) {
Navigator.of(context).pop();
} else {
libraryController.unselect();
}
})
: null,
actions: [
parent == null
? ActionButton<SelectedItemPositions>(Icons.delete,
() => libraryController.deleteSelectedPlaylists(),
valueListenable: libraryController.selectionChanged)
: const SizedBox(),
ActionButton<SelectedItemPositions>(Icons.queue_music,
() async {
var selectedItems =
await libraryController.getSelectedItems(parent);
await libraryController
.addItemsToTracklist<Ref>(selectedItems);
libraryController.unselect();
}, valueListenable: libraryController.selectionChanged),
ActionButton<SelectedItemPositions>(Icons.playlist_add,
() async {
var selectedItems =
await libraryController.getSelectedItems(parent);
await libraryController
.addItemsToPlaylist<Ref>(selectedItems);
libraryController.unselect();
}, valueListenable: libraryController.selectionChanged),
VolumeControl(),
LibraryBrowserAppBarMenu(items, libraryController)
]),
body: MaterialPageFrame(child: listView)),
showBusy);
}
}
93 changes: 54 additions & 39 deletions lib/pages/playlist/playlist_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import 'package:get_it/get_it.dart';
import 'package:flutter/material.dart';
import 'package:mopicon/components/material_page_frame.dart';
import 'package:mopicon/components/action_buttons.dart';
import 'package:mopicon/components/busy_wrapper.dart';
import 'package:mopicon/components/volume_control.dart';
import 'package:mopicon/utils/globals.dart';
import 'package:mopicon/utils/parameters.dart';
Expand Down Expand Up @@ -54,31 +55,37 @@ class _PlaylistPageState extends State<PlaylistPage> {
late Ref playlist;
List<Track> tracks = [];
var images = <String, Widget>{};
bool showBusy = false;

// selection mode (single/multiple) of track list view
SelectionMode selectionMode = SelectionMode.off;

final controller = GetIt.instance<PlaylistViewController>();

Future loadPlaylistItems() async {
List<Track> trx = [];
try {
setState(() {
showBusy = true;
});
if (widget.parent != null) {
playlist = Ref.fromMap(Parameter.fromBase64(widget.parent!));
controller.currentPlaylist = playlist;
}

var trx = await controller.getPlaylistItems(playlist);
trx = await controller.getPlaylistItems(playlist);
if (trx.isNotEmpty) {
await loadImages(trx);

if (mounted) {
setState(() {
tracks = trx;
});
}
}
} catch (e, s) {
logger.e(e, stackTrace: s);
} finally {
if (mounted) {
setState(() {
tracks = trx;
showBusy = false;
});
}
}
}

Expand Down Expand Up @@ -158,37 +165,45 @@ class _PlaylistPageState extends State<PlaylistPage> {
}
}).buildListView();

return Scaffold(
appBar: AppBar(
title: Text(widget.title ?? S.of(context).playlistPageTitle),
centerTitle: true,
//automaticallyImplyLeading: true,
leading: ActionButton<SelectedItemPositions>(Icons.arrow_back, () {
if (controller.selectionChanged.value.isEmpty) {
Navigator.of(context).pop();
} else {
controller.unselect();
}
}),
actions: [
ActionButton<SelectedItemPositions>(
Icons.delete,
valueListenable: controller.selectionChanged,
() => controller.deleteSelectedPlaylistItems(playlist)),
ActionButton<SelectedItemPositions>(Icons.queue_music, () async {
var selectedItems = await controller.getSelectedItems(playlist);
await controller.addItemsToTracklist<Ref>(selectedItems.asRef);
controller.unselect();
}, valueListenable: controller.selectionChanged),
ActionButton<SelectedItemPositions>(Icons.playlist_add, () async {
var selectedItems = await controller.getSelectedItems(playlist);
await controller.addItemsToPlaylist<Ref>(selectedItems.asRef);
controller.unselect();
}, valueListenable: controller.selectionChanged),
VolumeControl(),
PlaylistAppBarMenu(controller, playlist)
]),
body: MaterialPageFrame(child: listView),
);
return BusyWrapper(
Scaffold(
appBar: AppBar(
title: Text(widget.title ?? S.of(context).playlistPageTitle),
centerTitle: true,
//automaticallyImplyLeading: true,
leading:
ActionButton<SelectedItemPositions>(Icons.arrow_back, () {
if (controller.selectionChanged.value.isEmpty) {
Navigator.of(context).pop();
} else {
controller.unselect();
}
}),
actions: [
ActionButton<SelectedItemPositions>(
Icons.delete,
valueListenable: controller.selectionChanged,
() => controller.deleteSelectedPlaylistItems(playlist)),
ActionButton<SelectedItemPositions>(Icons.queue_music,
() async {
var selectedItems =
await controller.getSelectedItems(playlist);
await controller
.addItemsToTracklist<Ref>(selectedItems.asRef);
controller.unselect();
}, valueListenable: controller.selectionChanged),
ActionButton<SelectedItemPositions>(Icons.playlist_add,
() async {
var selectedItems =
await controller.getSelectedItems(playlist);
await controller.addItemsToPlaylist<Ref>(selectedItems.asRef);
controller.unselect();
}, valueListenable: controller.selectionChanged),
VolumeControl(),
PlaylistAppBarMenu(controller, playlist)
]),
body: MaterialPageFrame(child: listView),
),
showBusy);
}
}
Loading

0 comments on commit 2cc85e9

Please sign in to comment.