mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 15:57:25 +00:00
favorites page
This commit is contained in:
@@ -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,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -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,
|
||||
],
|
||||
);
|
||||
|
@@ -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';
|
||||
|
||||
|
@@ -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> {
|
||||
|
@@ -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),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@@ -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,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@@ -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),
|
||||
],
|
||||
|
Reference in New Issue
Block a user