favorites page

This commit is contained in:
nyne
2024-10-12 20:38:24 +08:00
parent a26e5e20de
commit 5a3537657a
22 changed files with 1388 additions and 120 deletions

View File

@@ -1,61 +1,115 @@
import 'package:flutter/material.dart';
import 'package:venera/components/components.dart';
import 'package:venera/foundation/app.dart';
import 'package:venera/foundation/favorites.dart';
import 'package:venera/utils/translations.dart';
part of 'favorites_page.dart';
/// Open a dialog to create a new favorite folder.
Future<void> newFolder() async {
return showDialog(context: App.rootContext, builder: (context) {
var controller = TextEditingController();
var folders = LocalFavoritesManager().folderNames;
String? error;
return showDialog(
context: App.rootContext,
builder: (context) {
var controller = TextEditingController();
var folders = LocalFavoritesManager().folderNames;
String? error;
return StatefulBuilder(builder: (context, setState) {
return ContentDialog(
title: "New Folder".tl,
content: Column(
children: [
TextField(
controller: controller,
decoration: InputDecoration(
hintText: "Folder Name".tl,
errorText: error,
return StatefulBuilder(builder: (context, setState) {
return ContentDialog(
title: "New Folder".tl,
content: Column(
children: [
TextField(
controller: controller,
decoration: InputDecoration(
hintText: "Folder Name".tl,
errorText: error,
),
onChanged: (s) {
if (error != null) {
setState(() {
error = null;
});
}
},
)
],
).paddingHorizontal(16),
actions: [
FilledButton(
onPressed: () {
var e = validateFolderName(controller.text);
if (e != null) {
setState(() {
error = e;
});
} else {
LocalFavoritesManager().createFolder(controller.text);
context.pop();
}
},
child: Text("Create".tl),
),
onChanged: (s) {
if(error != null) {
setState(() {
error = null;
});
],
);
});
});
}
String? validateFolderName(String newFolderName) {
var folders = LocalFavoritesManager().folderNames;
if (newFolderName.isEmpty) {
return "Folder name cannot be empty".tl;
} else if (newFolderName.length > 50) {
return "Folder name is too long".tl;
} else if (folders.contains(newFolderName)) {
return "Folder already exists".tl;
}
return null;
}
void addFavorite(Comic comic) {
var folders = LocalFavoritesManager().folderNames;
showDialog(
context: App.rootContext,
builder: (context) {
String? selectedFolder;
return StatefulBuilder(builder: (context, setState) {
return ContentDialog(
title: "Select a folder".tl,
content: ListTile(
title: Text("Folder".tl),
trailing: Select(
current: selectedFolder,
values: folders,
minWidth: 112,
onTap: (v) {
setState(() {
selectedFolder = folders[v];
});
},
),
),
actions: [
FilledButton(
onPressed: () {
if (selectedFolder != null) {
LocalFavoritesManager().addComic(
selectedFolder!,
FavoriteItem(
id: comic.id,
name: comic.title,
coverPath: comic.cover,
author: comic.subtitle ?? '',
type: ComicType(comic.sourceKey.hashCode),
tags: comic.tags ?? [],
),
);
context.pop();
}
},
)
child: Text("Confirm".tl),
),
],
).paddingHorizontal(16),
actions: [
FilledButton(
onPressed: () {
if(controller.text.isEmpty) {
setState(() {
error = "Folder name cannot be empty".tl;
});
} else if(controller.text.length > 50) {
setState(() {
error = "Folder name is too long".tl;
});
} else if(folders.contains(controller.text)) {
setState(() {
error = "Folder already exists".tl;
});
} else {
LocalFavoritesManager().createFolder(controller.text);
context.pop();
}
},
child: Text("Create".tl),
),
],
);
});
});
}
);
});
},
);
}

View File

@@ -0,0 +1,158 @@
import 'package:flutter/material.dart';
import 'package:flutter_reorderable_grid_view/widgets/reorderable_builder.dart';
import 'package:venera/components/components.dart';
import 'package:venera/foundation/app.dart';
import 'package:venera/foundation/appdata.dart';
import 'package:venera/foundation/comic_source/comic_source.dart';
import 'package:venera/foundation/comic_type.dart';
import 'package:venera/foundation/favorites.dart';
import 'package:venera/foundation/res.dart';
import 'package:venera/utils/translations.dart';
part 'favorite_actions.dart';
part 'side_bar.dart';
part 'local_favorites_page.dart';
part 'network_favorites_page.dart';
const _kLeftBarWidth = 256.0;
const _kTwoPanelChangeWidth = 720.0;
class FavoritesPage extends StatefulWidget {
const FavoritesPage({super.key});
@override
State<FavoritesPage> createState() => _FavoritesPageState();
}
class _FavoritesPageState extends State<FavoritesPage> {
String? folder;
bool isNetwork = false;
FolderList? folderList;
void setFolder(bool isNetwork, String? folder) {
setState(() {
this.isNetwork = isNetwork;
this.folder = folder;
});
folderList?.update();
appdata.implicitData['favoriteFolder'] = {
'name': folder,
'isNetwork': isNetwork,
};
appdata.writeImplicitData();
}
@override
void initState() {
var data = appdata.implicitData['favoriteFolder'];
if(data != null){
folder = data['name'];
isNetwork = data['isNetwork'] ?? false;
}
super.initState();
}
@override
Widget build(BuildContext context) {
return IconTheme(
data: IconThemeData(color: Theme.of(context).colorScheme.secondary),
child: Stack(
children: [
AnimatedPositioned(
left: context.width <= _kTwoPanelChangeWidth ? -_kLeftBarWidth : 0,
top: 0,
bottom: 0,
duration: const Duration(milliseconds: 200),
child: (const _LeftBar()).fixWidth(_kLeftBarWidth),
),
Positioned(
top: 0,
left: context.width <= _kTwoPanelChangeWidth ? 0 : _kLeftBarWidth,
right: 0,
bottom: 0,
child: buildBody(),
),
],
),
);
}
void showFolderSelector() {
Navigator.of(App.rootContext).push(PageRouteBuilder(
barrierDismissible: true,
fullscreenDialog: true,
opaque: false,
barrierColor: Colors.black.withOpacity(0.36),
pageBuilder: (context, animation, secondary) {
return Align(
alignment: Alignment.centerLeft,
child: Material(
color: context.colorScheme.surfaceContainerLow,
child: SizedBox(
width: 256,
child: _LeftBar(
withAppbar: true,
favPage: this,
onSelected: () {
context.pop();
},
),
),
),
);
},
transitionsBuilder: (context, animation, secondary, child) {
var offset =
Tween<Offset>(begin: const Offset(-1, 0), end: const Offset(0, 0));
return SlideTransition(
position: offset.animate(CurvedAnimation(
parent: animation,
curve: Curves.fastOutSlowIn,
)),
child: child,
);
},
));
}
Widget buildBody() {
if (folder == null) {
return CustomScrollView(
slivers: [
SliverAppbar(
leading: Tooltip(
message: "Folders".tl,
child: context.width <= _kTwoPanelChangeWidth
? IconButton(
icon: const Icon(Icons.menu),
color: context.colorScheme.primary,
onPressed: showFolderSelector,
)
: null,
),
title: Text("Unselected".tl),
),
],
);
}
if (!isNetwork) {
return _LocalFavoritesPage(folder: folder!, key: Key(folder!));
} else {
var favoriteData = getFavoriteDataOrNull(folder!);
if (favoriteData == null) {
return const Center(child: Text("Unknown source"));
} else {
return NetworkFavoritePage(favoriteData, key: Key(folder!));
}
}
}
}
abstract interface class FolderList {
void update();
void updateFolders();
}

View File

@@ -0,0 +1,257 @@
part of 'favorites_page.dart';
class _LocalFavoritesPage extends StatefulWidget {
const _LocalFavoritesPage({required this.folder, super.key});
final String folder;
@override
State<_LocalFavoritesPage> createState() => _LocalFavoritesPageState();
}
class _LocalFavoritesPageState extends State<_LocalFavoritesPage> {
late _FavoritesPageState favPage;
late List<FavoriteItem> comics;
void updateComics() {
setState(() {
comics = LocalFavoritesManager().getAllComics(widget.folder);
});
}
@override
void initState() {
favPage = context.findAncestorStateOfType<_FavoritesPageState>()!;
comics = LocalFavoritesManager().getAllComics(widget.folder);
super.initState();
}
@override
Widget build(BuildContext context) {
return SmoothCustomScrollView(
slivers: [
SliverAppbar(
leading: Tooltip(
message: "Folders".tl,
child: context.width <= _kTwoPanelChangeWidth
? IconButton(
icon: const Icon(Icons.menu),
color: context.colorScheme.primary,
onPressed: favPage.showFolderSelector,
)
: const SizedBox(),
),
title: Text(favPage.folder ?? "Unselected".tl),
actions: [
MenuButton(
entries: [
MenuEntry(
icon: Icons.delete_outline,
text: "Delete Folder".tl,
onClick: () {
showConfirmDialog(
context: App.rootContext,
title: "Delete".tl,
content:
"Are you sure you want to delete this folder?".tl,
onConfirm: () {
favPage.setFolder(false, null);
LocalFavoritesManager().deleteFolder(widget.folder);
favPage.folderList?.updateFolders();
context.pop();
},
);
}),
MenuEntry(
icon: Icons.edit_outlined,
text: "Rename".tl,
onClick: () {
showInputDialog(
context: App.rootContext,
title: "Rename".tl,
hintText: "New Name".tl,
onConfirm: (value) {
var err = validateFolderName(value.toString());
if (err != null) {
return err;
}
LocalFavoritesManager().rename(
widget.folder,
value.toString(),
);
favPage.folderList?.updateFolders();
favPage.setFolder(false, value.toString());
return null;
},
);
}),
MenuEntry(
icon: Icons.reorder,
text: "Reorder".tl,
onClick: () {
context.to(
() {
return _ReorderComicsPage(
widget.folder,
(comics) {
this.comics = comics;
},
);
},
).then(
(value) {
setState(() {});
},
);
}),
],
),
],
),
SliverGridComics(
comics: comics.map((e) {
var comicSource = e.type.comicSource;
return Comic(
e.name,
e.coverPath,
e.id,
e.author,
e.tags,
"${e.time} | ${comicSource?.name ?? "Unknown"}",
comicSource?.key ?? "Unknown",
null,
);
}).toList(),
menuBuilder: (c) {
return [
MenuEntry(
icon: Icons.delete_outline,
text: "Delete".tl,
onClick: () {
showConfirmDialog(
context: context,
title: "Delete".tl,
content: "Are you sure you want to delete this comic?".tl,
onConfirm: () {
LocalFavoritesManager().deleteComicWithId(
widget.folder,
c.id,
ComicType(c.sourceKey.hashCode),
);
updateComics();
},
);
},
),
];
},
),
],
);
}
}
class _ReorderComicsPage extends StatefulWidget {
const _ReorderComicsPage(this.name, this.onReorder);
final String name;
final void Function(List<FavoriteItem>) onReorder;
@override
State<_ReorderComicsPage> createState() => _ReorderComicsPageState();
}
class _ReorderComicsPageState extends State<_ReorderComicsPage> {
final _key = GlobalKey();
var reorderWidgetKey = UniqueKey();
final _scrollController = ScrollController();
late var comics = LocalFavoritesManager().getAllComics(widget.name);
bool changed = false;
Color lightenColor(Color color, double lightenValue) {
int red = (color.red + ((255 - color.red) * lightenValue)).round();
int green = (color.green + ((255 - color.green) * lightenValue)).round();
int blue = (color.blue + ((255 - color.blue) * lightenValue)).round();
return Color.fromARGB(color.alpha, red, green, blue);
}
@override
void dispose() {
if (changed) {
LocalFavoritesManager().reorder(comics, widget.name);
}
super.dispose();
}
@override
Widget build(BuildContext context) {
var tiles = comics.map(
(e) {
var comicSource = e.type.comicSource;
return ComicTile(
key: Key(e.hashCode.toString()),
comic: Comic(
e.name,
e.coverPath,
e.id,
e.author,
e.tags,
"${e.time} | ${comicSource?.name ?? "Unknown"}",
comicSource?.key ?? "Unknown",
null,
),
);
},
).toList();
return Scaffold(
appBar: Appbar(
title: Text("Reorder".tl),
actions: [
IconButton(
icon: const Icon(Icons.info_outline),
onPressed: () {
showInfoDialog(
context: context,
title: "Reorder".tl,
content: "Long press and drag to reorder.".tl,
);
},
),
],
),
body: ReorderableBuilder(
key: reorderWidgetKey,
scrollController: _scrollController,
longPressDelay: App.isDesktop
? const Duration(milliseconds: 100)
: const Duration(milliseconds: 500),
onReorder: (reorderFunc) {
changed = true;
setState(() {
comics = reorderFunc(comics) as List<FavoriteItem>;
});
widget.onReorder(comics);
},
dragChildBoxDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
color: lightenColor(
Theme.of(context).splashColor.withOpacity(1),
0.2,
),
),
builder: (children) {
return GridView(
key: _key,
controller: _scrollController,
gridDelegate: SliverGridDelegateWithComics(),
children: children,
);
},
children: tiles,
),
);
}
}

View File

@@ -0,0 +1,433 @@
part of 'favorites_page.dart';
class NetworkFavoritePage extends StatelessWidget {
const NetworkFavoritePage(this.data, {super.key});
final FavoriteData data;
@override
Widget build(BuildContext context) {
return data.multiFolder
? _MultiFolderFavoritesPage(data)
: _NormalFavoritePage(data);
}
}
class _NormalFavoritePage extends StatelessWidget {
const _NormalFavoritePage(this.data);
final FavoriteData data;
@override
Widget build(BuildContext context) {
return ComicList(
leadingSliver: SliverAppbar(
leading: Tooltip(
message: "Folders".tl,
child: context.width <= _kTwoPanelChangeWidth
? IconButton(
icon: const Icon(Icons.menu),
color: context.colorScheme.primary,
onPressed: context
.findAncestorStateOfType<_FavoritesPageState>()!
.showFolderSelector,
)
: null,
),
title: Text(data.title),
),
errorLeading: Appbar(
leading: Tooltip(
message: "Folders".tl,
child: context.width <= _kTwoPanelChangeWidth
? IconButton(
icon: const Icon(Icons.menu),
color: context.colorScheme.primary,
onPressed: context
.findAncestorStateOfType<_FavoritesPageState>()!
.showFolderSelector,
)
: null,
),
title: Text(data.title),
),
loadPage: (i) => data.loadComic(i),
);
}
}
class _MultiFolderFavoritesPage extends StatefulWidget {
const _MultiFolderFavoritesPage(this.data);
final FavoriteData data;
@override
State<_MultiFolderFavoritesPage> createState() =>
_MultiFolderFavoritesPageState();
}
class _MultiFolderFavoritesPageState extends State<_MultiFolderFavoritesPage> {
bool _loading = true;
String? _errorMessage;
Map<String, String>? folders;
void loadPage() async {
var res = await widget.data.loadFolders!();
_loading = false;
if (res.error) {
setState(() {
_errorMessage = res.errorMessage;
});
} else {
setState(() {
folders = res.data;
});
}
}
void openFolder(String key, String title) {
context.to(() => _FavoriteFolder(widget.data, key, title));
}
@override
Widget build(BuildContext context) {
var sliverAppBar = SliverAppbar(
leading: Tooltip(
message: "Folders".tl,
child: context.width <= _kTwoPanelChangeWidth
? IconButton(
icon: const Icon(Icons.menu),
color: context.colorScheme.primary,
onPressed: context
.findAncestorStateOfType<_FavoritesPageState>()!
.showFolderSelector,
)
: null,
),
title: Text(widget.data.title),
);
var appBar = Appbar(
leading: Tooltip(
message: "Folders".tl,
child: context.width <= _kTwoPanelChangeWidth
? IconButton(
icon: const Icon(Icons.menu),
color: context.colorScheme.primary,
onPressed: context
.findAncestorStateOfType<_FavoritesPageState>()!
.showFolderSelector,
)
: null,
),
title: Text(widget.data.title),
);
if (_loading) {
loadPage();
return Column(
children: [
appBar,
const Expanded(
child: Center(
child: CircularProgressIndicator(),
),
),
],
);
} else if (_errorMessage != null) {
return Column(
children: [
appBar,
Expanded(
child: NetworkError(
message: _errorMessage!,
withAppbar: false,
retry: () {
setState(() {
_loading = true;
_errorMessage = null;
});
},
),
)
],
);
} else {
var length = folders!.length;
if (widget.data.allFavoritesId != null) length++;
final keys = folders!.keys.toList();
return SmoothCustomScrollView(
slivers: [
sliverAppBar,
SliverGridViewWithFixedItemHeight(
delegate:
SliverChildBuilderDelegate(childCount: length, (context, i) {
if (widget.data.allFavoritesId != null) {
if (i == 0) {
return _FolderTile(
name: "All".tl,
onTap: () =>
openFolder(widget.data.allFavoritesId!, "All".tl));
} else {
i--;
return _FolderTile(
name: folders![keys[i]]!,
onTap: () => openFolder(keys[i], folders![keys[i]]!),
deleteFolder: widget.data.deleteFolder == null
? null
: () => widget.data.deleteFolder!(keys[i]),
updateState: () => setState(() {
_loading = true;
}),
);
}
} else {
return _FolderTile(
name: folders![keys[i]]!,
onTap: () => openFolder(keys[i], folders![keys[i]]!),
deleteFolder: widget.data.deleteFolder == null
? null
: () => widget.data.deleteFolder!(keys[i]),
updateState: () => setState(() {
_loading = true;
}),
);
}
}),
maxCrossAxisExtent: 450,
itemHeight: 64,
),
if (widget.data.addFolder != null)
SliverToBoxAdapter(
child: SizedBox(
height: 60,
width: double.infinity,
child: Center(
child: TextButton(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text("Create a folder".tl),
const Icon(
Icons.add,
size: 18,
),
],
),
onPressed: () {
showDialog(
context: context,
builder: (context) {
return _CreateFolderDialog(
widget.data,
() => setState(() {
_loading = true;
}));
});
},
),
),
),
)
],
);
}
}
}
class _FolderTile extends StatelessWidget {
const _FolderTile(
{required this.name,
required this.onTap,
this.deleteFolder,
this.updateState});
final String name;
final Future<Res<bool>> Function()? deleteFolder;
final void Function()? updateState;
final void Function() onTap;
@override
Widget build(BuildContext context) {
return Material(
child: InkWell(
onTap: onTap,
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: Padding(
padding: const EdgeInsets.fromLTRB(8, 8, 16, 8),
child: Row(
children: [
const SizedBox(
width: 16,
),
Icon(
Icons.folder,
size: 35,
color: Theme.of(context).colorScheme.secondary,
),
const SizedBox(
width: 16,
),
Expanded(
child: Align(
alignment: Alignment.centerLeft,
child: Text(
name,
style: const TextStyle(
fontSize: 16, fontWeight: FontWeight.w500),
),
),
),
if (deleteFolder != null)
IconButton(
icon: const Icon(Icons.delete_forever_outlined),
onPressed: () => onDeleteFolder(context),
)
else
const Icon(Icons.arrow_right),
if (deleteFolder == null)
const SizedBox(
width: 8,
)
],
),
),
),
);
}
void onDeleteFolder(BuildContext context) {
showDialog(
context: context,
builder: (context) {
bool loading = false;
return StatefulBuilder(builder: (context, setState) {
return ContentDialog(
title: "Delete".tl,
content: Text("Are you sure you want to delete this folder?".tl),
actions: [
Button.filled(
isLoading: loading,
color: context.colorScheme.error,
onPressed: () async {
setState(() {
loading = true;
});
var res = await deleteFolder!();
if (res.success) {
context.showMessage(message: "Deleted".tl);
context.pop();
updateState?.call();
} else {
setState(() {
loading = false;
});
context.showMessage(message: res.errorMessage!);
}
},
child: Text("Confirm".tl),
),
],
);
});
},
);
}
}
class _CreateFolderDialog extends StatefulWidget {
const _CreateFolderDialog(this.data, this.updateState);
final FavoriteData data;
final void Function() updateState;
@override
State<_CreateFolderDialog> createState() => _CreateFolderDialogState();
}
class _CreateFolderDialogState extends State<_CreateFolderDialog> {
var controller = TextEditingController();
bool loading = false;
@override
Widget build(BuildContext context) {
return SimpleDialog(
title: Text("Create a folder".tl),
children: [
Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
child: TextField(
controller: controller,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: "name".tl,
),
),
),
const SizedBox(
width: 200,
height: 10,
),
if (loading)
const SizedBox(
child: Center(
child: CircularProgressIndicator(),
),
)
else
SizedBox(
height: 35,
child: Center(
child: TextButton(
onPressed: () {
setState(() {
loading = true;
});
widget.data.addFolder!(controller.text).then((b) {
if (b.error) {
context.showMessage(message: b.errorMessage!);
setState(() {
loading = false;
});
} else {
context.pop();
context.showMessage(
message: "Created successfully".tl);
widget.updateState();
}
});
},
child: Text("Submit".tl)),
))
],
);
}
}
class _FavoriteFolder extends StatelessWidget {
const _FavoriteFolder(this.data, this.folderID, this.title);
final FavoriteData data;
final String folderID;
final String title;
@override
Widget build(BuildContext context) {
return ComicList(
leadingSliver: SliverAppbar(
title: Text(title),
),
loadPage: (i) => data.loadComic(i, folderID),
);
}
}

View File

@@ -0,0 +1,208 @@
part of 'favorites_page.dart';
class _LeftBar extends StatefulWidget {
const _LeftBar({this.favPage, this.onSelected, this.withAppbar = false});
final _FavoritesPageState? favPage;
final VoidCallback? onSelected;
final bool withAppbar;
@override
State<_LeftBar> createState() => _LeftBarState();
}
class _LeftBarState extends State<_LeftBar> implements FolderList {
late _FavoritesPageState favPage;
var folders = <String>[];
var networkFolders = <String>[];
@override
void initState() {
favPage = widget.favPage ??
context.findAncestorStateOfType<_FavoritesPageState>()!;
favPage.folderList = this;
folders = LocalFavoritesManager().folderNames;
networkFolders = ComicSource.all()
.where((e) => e.favoriteData != null)
.map((e) => e.favoriteData!.key)
.toList();
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
border: Border(
right: BorderSide(
color: context.colorScheme.outlineVariant,
width: 0.6,
),
),
),
child: Column(
children: [
if (widget.withAppbar)
SizedBox(
height: 56,
child: Row(
children: [
const SizedBox(width: 8),
const CloseButton(),
const SizedBox(width: 8),
Text("Folders".tl, style: ts.s18,),
],
),
).paddingTop(context.padding.top),
Expanded(
child: ListView.builder(
padding: widget.withAppbar ? EdgeInsets.zero : EdgeInsets.only(top: context.padding.top),
itemCount: folders.length + networkFolders.length + 2,
itemBuilder: (context, index) {
if (index == 0) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
const SizedBox(width: 16),
const Icon(Icons.local_activity),
const SizedBox(width: 8),
Text("Local".tl),
const Spacer(),
IconButton(
icon: const Icon(Icons.add),
color: context.colorScheme.primary,
onPressed: () {
newFolder().then((value) {
setState(() {
folders = LocalFavoritesManager().folderNames;
});
});
},
),
const SizedBox(width: 16),
],
),
);
}
index--;
if (index < folders.length) {
return buildLocalFolder(folders[index]);
}
index -= folders.length;
if (index == 0) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
const SizedBox(width: 16),
const Icon(Icons.cloud),
const SizedBox(width: 8),
Text("Network".tl),
],
),
);
}
index--;
return buildNetworkFolder(networkFolders[index]);
},
),
)
],
),
);
}
Widget buildLocalFolder(String name) {
bool isSelected = name == favPage.folder && !favPage.isNetwork;
return InkWell(
onTap: () {
if (isSelected) {
return;
}
favPage.setFolder(false, name);
widget.onSelected?.call();
},
child: Container(
height: 42,
alignment: Alignment.centerLeft,
decoration: BoxDecoration(
color: isSelected
? context.colorScheme.primaryContainer.withOpacity(0.36)
: null,
border: Border(
left: BorderSide(
color:
isSelected ? context.colorScheme.primary : Colors.transparent,
width: 2,
),
),
),
padding: const EdgeInsets.only(left: 16),
child: Text(name),
),
);
}
Widget buildNetworkFolder(String key) {
var data = getFavoriteDataOrNull(key);
if (data == null) {
return const SizedBox();
}
bool isSelected = key == favPage.folder && favPage.isNetwork;
return InkWell(
onTap: () {
if (isSelected) {
return;
}
favPage.setFolder(true, key);
widget.onSelected?.call();
},
child: Container(
height: 42,
alignment: Alignment.centerLeft,
decoration: BoxDecoration(
color: isSelected
? context.colorScheme.primaryContainer.withOpacity(0.36)
: null,
border: Border(
left: BorderSide(
color:
isSelected ? context.colorScheme.primary : Colors.transparent,
width: 2,
),
),
),
padding: const EdgeInsets.only(left: 16),
child: Text(data.title),
),
);
}
@override
void update() {
setState(() {});
}
@override
void updateFolders() {
setState(() {
folders = LocalFavoritesManager().folderNames;
networkFolders = ComicSource.all()
.where((e) => e.favoriteData != null)
.map((e) => e.favoriteData!.key)
.toList();
});
}
}