Skip to content

Commit

Permalink
Implement local sort
Browse files Browse the repository at this point in the history
  • Loading branch information
r52 committed Jun 12, 2024
1 parent a59ea25 commit 9212fa7
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 39 deletions.
3 changes: 2 additions & 1 deletion lib/local/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,14 @@ class LocalLibraryHome extends StatelessWidget {
builder: (BuildContext context, WidgetRef ref, Widget? child) {
final settings = ref.watch(localConfigProvider);
final libraryProvider = ref.watch(localLibraryProvider);
final sort = ref.watch(librarySortTypeProvider);
final currentItem =
useState<LocalLibraryItem?>(libraryProvider.value);

useEffect(() {
final newVal = libraryProvider.value;
if (currentItem.value != null && newVal != null) {
final result = findLibraryItem(currentItem.value!, newVal);
final result = findLibraryItem(currentItem.value!, newVal, sort);

if (result != null) {
currentItem.value = result;
Expand Down
111 changes: 91 additions & 20 deletions lib/local/model.dart
Original file line number Diff line number Diff line change
@@ -1,28 +1,82 @@
// ignore_for_file: constant_identifier_names

import 'dart:io';

import 'package:collection/collection.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:gagaku/local/config.dart';
import 'package:hooks_riverpod/legacy.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'model.g.dart';

enum LibraryItemType { directory, archive }

int compareLibraryItems(LocalLibraryItem a, LocalLibraryItem b) {
if (!a.isReadable && b.isReadable) {
return -1;
}
typedef LibraryItemCompare = int Function(LocalLibraryItem, LocalLibraryItem);

if (a.isReadable && !b.isReadable) {
return 1;
}
enum LibrarySort {
name_desc('Name descending'),
name_asc('Name ascending'),
modified_desc('Modified descending'),
modified_asc('Modified ascending');

return compareNatural(a.name ?? a.path, b.name ?? b.path);
const LibrarySort(this.label);
final String label;
}

LocalLibraryItem? libraryItemBinarySearch(
List<LocalLibraryItem> list, int low, int high, LocalLibraryItem item) {
final librarySortTypeProvider = StateProvider((ref) => LibrarySort.name_desc);

Map<LibrarySort, LibraryItemCompare> _libraryCompare = {
LibrarySort.name_desc: (LocalLibraryItem a, LocalLibraryItem b) {
if (!a.isReadable && b.isReadable) {
return -1;
}

if (a.isReadable && !b.isReadable) {
return 1;
}

return compareNatural(a.name?.toLowerCase() ?? a.path.toLowerCase(),
b.name?.toLowerCase() ?? b.path.toLowerCase());
},
LibrarySort.name_asc: (LocalLibraryItem a, LocalLibraryItem b) {
if (!a.isReadable && b.isReadable) {
return -1;
}

if (a.isReadable && !b.isReadable) {
return 1;
}

return compareNatural(b.name?.toLowerCase() ?? b.path.toLowerCase(),
a.name?.toLowerCase() ?? a.path.toLowerCase());
},
LibrarySort.modified_desc: (LocalLibraryItem a, LocalLibraryItem b) {
if (!a.isReadable && b.isReadable) {
return -1;
}

if (a.isReadable && !b.isReadable) {
return 1;
}

return a.modified.compareTo(b.modified);
},
LibrarySort.modified_asc: (LocalLibraryItem a, LocalLibraryItem b) {
if (!a.isReadable && b.isReadable) {
return -1;
}

if (a.isReadable && !b.isReadable) {
return 1;
}

return b.modified.compareTo(a.modified);
},
};

LocalLibraryItem? libraryItemBinarySearch(List<LocalLibraryItem> list, int low,
int high, LocalLibraryItem item, LibrarySort sort) {
if (low > high) {
return null;
}
Expand All @@ -34,14 +88,15 @@ LocalLibraryItem? libraryItemBinarySearch(
return mid;
}

if (compareLibraryItems(item, mid) > 0) {
return libraryItemBinarySearch(list, middle + 1, high, item);
if (_libraryCompare[sort]!(item, mid) > 0) {
return libraryItemBinarySearch(list, middle + 1, high, item, sort);
}

return libraryItemBinarySearch(list, low, middle - 1, item);
return libraryItemBinarySearch(list, low, middle - 1, item, sort);
}

LocalLibraryItem? findLibraryItem(LocalLibraryItem old, LocalLibraryItem curr) {
LocalLibraryItem? findLibraryItem(
LocalLibraryItem old, LocalLibraryItem curr, LibrarySort sort) {
if (curr.type == old.type && curr.path == old.path) {
return curr;
}
Expand All @@ -52,14 +107,15 @@ LocalLibraryItem? findLibraryItem(LocalLibraryItem old, LocalLibraryItem curr) {
.toList();

if (children.isNotEmpty) {
final result = libraryItemBinarySearch(children, 0, children.length, old);
final result =
libraryItemBinarySearch(children, 0, children.length - 1, old, sort);

if (result != null) {
return result;
}

for (final e in children) {
final cres = findLibraryItem(old, e);
final cres = findLibraryItem(old, e, sort);
if (cres != null) {
return cres;
}
Expand All @@ -73,6 +129,7 @@ class LocalLibraryItem {
LocalLibraryItem({
required this.path,
required this.type,
required this.modified,
this.name,
this.thumbnail,
this.isReadable = false,
Expand All @@ -81,6 +138,7 @@ class LocalLibraryItem {

final String path;
final LibraryItemType type;
final DateTime modified;
final String? name;
String? thumbnail;
bool isReadable;
Expand All @@ -94,16 +152,20 @@ class LocalLibrary extends _$LocalLibrary {
Future<LocalLibraryItem?> _processDirectory(
Directory dir, LocalLibraryItem? parent) async {
final formats = await ref.watch(supportedFormatsProvider.future);
final sort = ref.watch(librarySortTypeProvider);

final entities = await dir.list().toList();
final files = entities.whereType<File>();
final name = (dir.uri.pathSegments.length - 2 >= 0)
? dir.uri.pathSegments.elementAt(dir.uri.pathSegments.length - 2)
: dir.path;

final stats = dir.statSync();

final res = LocalLibraryItem(
path: dir.path,
name: name,
modified: stats.modified,
type: LibraryItemType.directory,
parent: parent,
);
Expand Down Expand Up @@ -137,7 +199,7 @@ class LocalLibrary extends _$LocalLibrary {
}
}

res.children.sort(compareLibraryItems);
res.children.sort(_libraryCompare[sort]);

if (res.children.isNotEmpty) {
return res;
Expand All @@ -156,6 +218,7 @@ class LocalLibrary extends _$LocalLibrary {
return LocalLibraryItem(
path: file.path,
type: LibraryItemType.archive,
modified: await file.lastModified(),
name: file.uri.pathSegments.last,
isReadable: true,
parent: parent);
Expand All @@ -166,9 +229,13 @@ class LocalLibrary extends _$LocalLibrary {

Future<LocalLibraryItem> _scanLibrary() async {
final cfg = ref.watch(localConfigProvider);
final sort = ref.watch(librarySortTypeProvider);
if (cfg.libraryDirectory.isNotEmpty) {
final top = LocalLibraryItem(
path: cfg.libraryDirectory, type: LibraryItemType.directory);
path: cfg.libraryDirectory,
type: LibraryItemType.directory,
modified: DateTime.now(),
);

final dir = Directory(cfg.libraryDirectory);
final entities = await dir.list().toList();
Expand All @@ -189,12 +256,16 @@ class LocalLibrary extends _$LocalLibrary {
// otherwise skip
}

top.children.sort(compareLibraryItems);
top.children.sort(_libraryCompare[sort]);

return top;
}

return LocalLibraryItem(path: '', type: LibraryItemType.directory);
return LocalLibraryItem(
path: '',
type: LibraryItemType.directory,
modified: DateTime.now(),
);
}

@override
Expand Down
2 changes: 1 addition & 1 deletion lib/local/model.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

65 changes: 48 additions & 17 deletions lib/local/widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gagaku/local/model.dart';
import 'package:gagaku/ui.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

typedef LibraryItemTapCallback = void Function(LocalLibraryItem);

Expand All @@ -30,25 +31,55 @@ class LibraryListWidget extends StatelessWidget {
physics: physics,
slivers: [
...leading,
SliverToBoxAdapter(
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 8.0, vertical: 10.0),
child: Row(
children: [
if (item.parent != null && onTap != null)
BackButton(
onPressed: () {
onTap!(item.parent!);
},
SliverAppBar(
pinned: true,
leading: (item.parent != null && onTap != null)
? BackButton(
onPressed: () {
onTap!(item.parent!);
},
)
: const SizedBox.shrink(),
title: title,
actions: [
Consumer(
builder: (context, ref, child) {
final theme = Theme.of(context);
final initial = ref.watch(librarySortTypeProvider);

return DropdownMenu<LibrarySort>(
initialSelection: initial,
width: 180.0,
enableFilter: false,
enableSearch: false,
requestFocusOnTap: false,
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: theme.colorScheme.surface.withAlpha(200),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
width: 2.0,
color: theme.colorScheme.inversePrimary,
),
),
),
const SizedBox(
width: 10,
),
title,
],
onSelected: (LibrarySort? sort) async {
if (sort != null) {
ref.read(librarySortTypeProvider.notifier).state = sort;
}
},
dropdownMenuEntries:
List<DropdownMenuEntry<LibrarySort>>.generate(
LibrarySort.values.length,
(int index) => DropdownMenuEntry<LibrarySort>(
value: LibrarySort.values.elementAt(index),
label: LibrarySort.values.elementAt(index).label,
),
),
);
},
),
),
],
),
SliverGrid.builder(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
Expand Down

0 comments on commit 9212fa7

Please sign in to comment.