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

@@ -342,4 +342,34 @@ class _IconButtonState extends State<_IconButton> {
),
);
}
}
}
class MenuButton extends StatefulWidget {
const MenuButton({super.key, required this.entries});
final List<MenuEntry> entries;
@override
State<MenuButton> createState() => _MenuButtonState();
}
class _MenuButtonState extends State<MenuButton> {
@override
Widget build(BuildContext context) {
return Tooltip(
message: 'more'.tl,
child: Button.icon(
icon: const Icon(Icons.more_horiz),
onPressed: () {
var renderBox = context.findRenderObject() as RenderBox;
var offset = renderBox.localToGlobal(Offset.zero);
showMenuX(
context,
offset,
widget.entries,
);
},
),
);
}
}

View File

@@ -53,6 +53,13 @@ class ComicTile extends StatelessWidget {
App.rootContext.showMessage(message: 'Title copied'.tl);
},
),
MenuEntry(
icon: Icons.stars_outlined,
text: 'Add to favorites'.tl,
onClick: () {
addFavorite(comic);
},
),
...?menuOptions,
],
);

View File

@@ -23,6 +23,7 @@ import 'package:venera/foundation/local.dart';
import 'package:venera/foundation/res.dart';
import 'package:venera/foundation/state_controller.dart';
import 'package:venera/pages/comic_page.dart';
import 'package:venera/pages/favorites/favorites_page.dart';
import 'package:venera/utils/ext.dart';
import 'package:venera/utils/translations.dart';

View File

@@ -1,7 +1,7 @@
part of "components.dart";
void showMenuX(BuildContext context, Offset location, List<MenuEntry> entries) {
Navigator.of(context).push(_MenuRoute(entries, location));
Navigator.of(context, rootNavigator: true).push(_MenuRoute(entries, location));
}
class _MenuRoute<T> extends PopupRoute<T> {

View File

@@ -1,12 +1,17 @@
part of "components.dart";
void showToast({required String message, required BuildContext context, Widget? icon, Widget? trailing,}) {
void showToast({
required String message,
required BuildContext context,
Widget? icon,
Widget? trailing,
}) {
var newEntry = OverlayEntry(
builder: (context) => _ToastOverlay(
message: message,
icon: icon,
trailing: trailing,
));
message: message,
icon: icon,
trailing: trailing,
));
var state = context.findAncestorStateOfType<OverlayWidgetState>();
@@ -36,9 +41,11 @@ class _ToastOverlay extends StatelessWidget {
color: Theme.of(context).colorScheme.inverseSurface,
borderRadius: BorderRadius.circular(8),
elevation: 2,
textStyle: ts.withColor(Theme.of(context).colorScheme.onInverseSurface),
textStyle:
ts.withColor(Theme.of(context).colorScheme.onInverseSurface),
child: IconTheme(
data: IconThemeData(color: Theme.of(context).colorScheme.onInverseSurface),
data: IconThemeData(
color: Theme.of(context).colorScheme.onInverseSurface),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 16),
child: Row(
@@ -121,23 +128,28 @@ void showDialogMessage(BuildContext context, String title, String message) {
);
}
void showConfirmDialog(BuildContext context, String title, String content,
void Function() onConfirm) {
void showConfirmDialog({
required BuildContext context,
required String title,
required String content,
required void Function() onConfirm,
}) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(title),
content: Text(content),
actions: [
TextButton(onPressed: context.pop, child: Text("Cancel".tl)),
TextButton(
onPressed: () {
context.pop();
onConfirm();
},
child: Text("Confirm".tl)),
],
));
context: context,
builder: (context) => ContentDialog(
title: title,
content: Text(content).paddingHorizontal(16).paddingVertical(8),
actions: [
FilledButton(
onPressed: () {
context.pop();
onConfirm();
},
child: Text("Confirm".tl),
),
],
),
);
}
class LoadingDialogController {
@@ -234,6 +246,7 @@ class ContentDialog extends StatelessWidget {
Widget build(BuildContext context) {
var content = Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Appbar(
leading: IconButton(
@@ -281,3 +294,83 @@ class ContentDialog extends StatelessWidget {
);
}
}
void showInputDialog({
required BuildContext context,
required String title,
required String hintText,
required FutureOr<Object?> Function(String) onConfirm,
String? initialValue,
String confirmText = "Confirm",
String cancelText = "Cancel",
}) {
var controller = TextEditingController(text: initialValue);
bool isLoading = false;
String? error;
showDialog(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setState) {
return ContentDialog(
title: title,
content: TextField(
controller: controller,
decoration: InputDecoration(
hintText: hintText,
border: const OutlineInputBorder(),
errorText: error,
),
).paddingHorizontal(12),
actions: [
Button.filled(
isLoading: isLoading,
onPressed: () async {
var futureOr = onConfirm(controller.text);
Object? result;
if (futureOr is Future) {
setState(() => isLoading = true);
result = await futureOr;
setState(() => isLoading = false);
} else {
result = futureOr;
}
if(result == null) {
context.pop();
} else {
setState(() => error = result.toString());
}
},
child: Text(confirmText.tl),
),
],
);
},
);
},
);
}
void showInfoDialog({
required BuildContext context,
required String title,
required String content,
String confirmText = "OK",
}) {
showDialog(
context: context,
builder: (context) {
return ContentDialog(
title: title,
content: Text(content).paddingHorizontal(16).paddingVertical(8),
actions: [
Button.filled(
onPressed: context.pop,
child: Text(confirmText.tl),
),
],
);
},
);
}

View File

@@ -135,7 +135,7 @@ class _NaviPaneState extends State<NaviPane>
controller.value = target;
} else {
StateController.findOrNull<NaviPaddingWidgetController>()
?.setWithPadding(true, false, false);
?.setWithPadding(false, false, false);
controller.animateTo(target);
}
animationTarget = target;
@@ -555,7 +555,15 @@ class _SingleBottomNaviWidgetState extends State<_SingleBottomNaviWidget>
class NaviObserver extends NavigatorObserver implements Listenable {
var routes = Queue<Route>();
int get pageCount => routes.length;
int get pageCount {
int count = 0;
for (var route in routes) {
if (route is AppPageRoute) {
count++;
}
}
return count;
}
@override
void didPop(Route route, Route? previousRoute) {
@@ -693,7 +701,12 @@ class NaviPaddingWidget extends StatelessWidget {
: 0),
)
: EdgeInsets.zero,
child: child,
child: MediaQuery.removePadding(
removeTop: controller._withPadding,
removeBottom: controller._withPadding,
context: context,
child: child,
),
);
},
);

View File

@@ -6,14 +6,17 @@ class Select extends StatelessWidget {
required this.current,
required this.values,
this.onTap,
this.minWidth,
});
final String current;
final String? current;
final List<String> values;
final void Function(int index)? onTap;
final double? minWidth;
@override
Widget build(BuildContext context) {
return Container(
@@ -58,7 +61,12 @@ class Select extends StatelessWidget {
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(current, style: ts.s14),
ConstrainedBox(
constraints: BoxConstraints(
minWidth: minWidth != null ? (minWidth! - 32) : 0,
),
child: Text(current ?? ' ', style: ts.s14),
),
const SizedBox(width: 8),
Icon(Icons.arrow_drop_down, color: context.colorScheme.primary),
],