Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
35 changes: 19 additions & 16 deletions zune_ui/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:zune_ui/pages/overlays_page/index.dart';
import 'package:zune_ui/pages/player_page/index.dart';
import 'package:zune_ui/providers/global_state/index.dart';
import 'package:zune_ui/providers/scroll_state/scroll_state.dart';
import 'package:zune_ui/providers/animation_provider/index.dart';
import 'package:zune_ui/pages/home_page/page.dart';
import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:go_router/go_router.dart';
Expand Down Expand Up @@ -90,23 +91,25 @@ class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return SizedBox(
width: initialSize.width,
height: initialSize.height,
child: Stack(
alignment: Alignment.topCenter,
children: [
WidgetsApp.router(
debugShowCheckedModeBanner: false,
routerConfig: _router,
color: const Color.fromARGB(255, 0, 0, 0),
textStyle: const TextStyle(
// Classic Zune Font :)
fontFamily: 'Zegoe UI',
return MusicPlayerAnimationProvider(
child: SizedBox(
width: initialSize.width,
height: initialSize.height,
child: Stack(
alignment: Alignment.topCenter,
children: [
WidgetsApp.router(
debugShowCheckedModeBanner: false,
routerConfig: _router,
color: const Color.fromARGB(255, 0, 0, 0),
textStyle: const TextStyle(
// Classic Zune Font :)
fontFamily: 'Zegoe UI',
),
),
),
WindowBar(router: _router),
],
WindowBar(router: _router),
],
),
),
);
}
Expand Down
83 changes: 50 additions & 33 deletions zune_ui/lib/pages/music_page/album_grid/album_grid_tile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ const SCALE_VALUE = 3;
const INVERSE_SCALE_VALUE = 1 / SCALE_VALUE;

class AlbumsGridTile extends StatelessWidget {
final GlobalKey _globalKey = GlobalKey();
final GlobalKey _transformedTextKey = GlobalKey();
final GlobalKey _albumTileKey = GlobalKey();

final AlbumGridTileGroup albumGroup;

Expand Down Expand Up @@ -35,38 +36,54 @@ class AlbumsGridTile extends StatelessWidget {
final albumCover = album.album_cover;
final albumName = album.album_name;

return Stack(
children: [
SquareTile(
size: TileUtility.regularTileWidth,
alignment: Alignment.bottomRight,
textStyle: Styles.albumTileFont,
background: albumCover,
text: albumCover != null ? null : albumName.toUpperCase(),
),
Transform(
transform: Matrix4.identity()
// Honestly this is best I could come up with.
..scale(INVERSE_SCALE_VALUE)
// Computing with SCALE_VALUE * .95 to closely match spacing as in Zune UI
..translate(TileUtility.regularTileWidth * SCALE_VALUE * .95,
TileUtility.regularTileWidth, 0.0),
child: OverflowBox(
alignment: Alignment.center,
maxHeight: TileUtility.regularTileWidth * SCALE_VALUE,
child: Flow(
delegate: ParallaxFlowDelegate(
scrollable: Scrollable.of(context),
itemContext: context,
itemKey: _globalKey,
onAlbumTapHandler() {
final renderBox =
_albumTileKey.currentContext?.findRenderObject() as RenderBox?;

// if (renderBox != null) {
// final widgetPosition = renderBox.localToGlobal(Offset.zero);
// context.go(ApplicationRoute.albums.route, extra: widgetPosition);
// }

//TBI: Implement album tap handler
}

return GestureDetector(
key: _albumTileKey,
onTap: onAlbumTapHandler,
child: Stack(
children: [
SquareTile(
size: TileUtility.regularTileWidth,
alignment: Alignment.bottomRight,
textStyle: Styles.albumTileFont,
background: albumCover,
text: albumCover != null ? null : albumName.toUpperCase(),
),
Transform(
transform: Matrix4.identity()
// Honestly this is best I could come up with.
..scale(INVERSE_SCALE_VALUE)
// Computing with SCALE_VALUE * .95 to closely match spacing as in Zune UI
..translate(TileUtility.regularTileWidth * SCALE_VALUE * .95,
TileUtility.regularTileWidth, 0.0),
child: OverflowBox(
alignment: Alignment.center,
maxHeight: TileUtility.regularTileWidth * SCALE_VALUE,
child: Flow(
delegate: ParallaxBackgroundFlowDelegate(
scrollable: Scrollable.of(context),
itemContext: context,
itemKey: _transformedTextKey,
),
children: [
Text(key: _transformedTextKey, albumName),
],
),
children: [
Text(key: _globalKey, albumName),
],
),
),
),
],
],
),
);
}

Expand All @@ -83,12 +100,12 @@ class AlbumsGridTile extends StatelessWidget {
/// NOTE: This code is taken & slightly modified from:
/// -> https://docs.flutter.dev/cookbook/effects/parallax-scrolling
///
class ParallaxFlowDelegate extends FlowDelegate {
class ParallaxBackgroundFlowDelegate extends FlowDelegate {
final ScrollableState scrollable;
final BuildContext itemContext;
final GlobalKey itemKey;

ParallaxFlowDelegate({
ParallaxBackgroundFlowDelegate({
required this.scrollable,
required this.itemContext,
required this.itemKey,
Expand Down Expand Up @@ -160,7 +177,7 @@ class ParallaxFlowDelegate extends FlowDelegate {
}

@override
bool shouldRepaint(ParallaxFlowDelegate oldDelegate) {
bool shouldRepaint(ParallaxBackgroundFlowDelegate oldDelegate) {
return scrollable != oldDelegate.scrollable ||
itemContext != oldDelegate.itemContext ||
itemKey != oldDelegate.itemKey;
Expand Down
99 changes: 99 additions & 0 deletions zune_ui/lib/pages/music_page/album_list/album_list.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
part of album_list_widget;

typedef AlbumGroupMap = LinkedHashMap<String, List<AlbumSummary>>;

class AlbumList extends StatefulWidget {
const AlbumList({
super.key,
});

@override
State<AlbumList> createState() => _AlbumListState();
}

class _AlbumListState extends State<AlbumList> {
// void _onReturnTapHandler() {
// console.log("Should go back to album Grid");
// final musicPlayerAnimationContext =
// parent.MusicPlayerAnimationProvider.of(context);

// musicPlayerAnimationContext?.executeWith(() async {
// if (context.mounted) {
// // context.go(ApplicationRoute.home.route);
// console.log("Should go back to album Grid");
// }
// });
// }

/// NOTE: Album List view allows "jumping" to a specific album via
/// group keys rendered in the list.
///
/// This method is responsible for generating search index configuration map
/// which represents a group key e.g. letter "a" mapped to a function that
/// animated scroll controller to a location of the group key in the list.
///
/// Using pre-defined constant values to derive the group collection & key heights
/// because the list view wrapper is dynamically built. This might not be the best
/// solution to derive the search index configuration, perhaps a global solution
/// might be faster.
void _generateSearchIndexConfiguration(
ScrollController? scrollController, AlbumGroupMap albumGroupMap) {
// SearchIndexConfig is by default an empty map
if (scrollController == null) return;
double offset = 0.0;
SearchIndexConfig searchIndexConfiguration = {};

for (final entry in albumGroupMap.entries) {
// Since offset is a mutable closure, need to define a final value here
final currentOffset = offset;
searchIndexConfiguration.putIfAbsent(
entry.key,
() => () async => await scrollController.animateTo(
currentOffset,
duration: const Duration(milliseconds: 250),
curve: Curves.ease,
),
);
// Derive group collection & key heights to compute offsets needed for animation
final groupCollectionHeight = entry.value.fold(
offset, (acc, album) => acc + ALBUM_LIST_TILE_SIZE + ALBUM_LIST_GAP);
const groupKeyHeight = ALBUM_LIST_TILE_SIZE + ALBUM_LIST_GAP;

offset = groupCollectionHeight + groupKeyHeight;
}

OverlaysProvider.of(context)?.setSearchTileConfig(searchIndexConfiguration);
}

List<AlbumListTileGroup> _generateAlbumGroups(
UnmodifiableListView<AlbumSummary> albums, {
ScrollController? scrollController,
}) {
AlbumGroupMap albumGroupMap = parent.generateItemMap(
albums,
(e) => parent.generateItemGroupKey(e.album_name),
);

// Generate search index configuration needed for "jumping" to a specific album via group keys
_generateSearchIndexConfiguration(scrollController, albumGroupMap);

return parent.generateItemListFromMap(
albumGroupMap,
(groupKey, item) => (groupKey: groupKey, album: item),
);
}

@override
Widget build(BuildContext context) {
/// NOT RIGHT, the menu wrapper needs to be around music categories when this happens.....
/// Here you need to lerp from album location in the grid to location in the list
return ListWrapper<UnmodifiableListView<AlbumSummary>, AlbumSummary,
AlbumListTileGroup>(
selector: (state) => state.allAlbums,
listGap: ALBUM_LIST_GAP,
itemBuilder: (context, albumGroup) =>
AlbumListTile(albumGroup: albumGroup),
itemsMiddleware: _generateAlbumGroups,
);
}
}
Loading