mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 07:47:24 +00:00
settings page
This commit is contained in:
@@ -1,8 +1,361 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_reorderable_grid_view/widgets/reorderable_builder.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:venera/components/components.dart';
|
||||
import 'package:venera/foundation/app.dart';
|
||||
import 'package:venera/foundation/appdata.dart';
|
||||
import 'package:venera/foundation/cache_manager.dart';
|
||||
import 'package:venera/foundation/comic_source/comic_source.dart';
|
||||
import 'package:venera/foundation/consts.dart';
|
||||
import 'package:venera/foundation/local.dart';
|
||||
import 'package:venera/foundation/log.dart';
|
||||
import 'package:venera/network/app_dio.dart';
|
||||
import 'package:venera/utils/io.dart';
|
||||
import 'package:venera/utils/translations.dart';
|
||||
import 'package:yaml/yaml.dart';
|
||||
|
||||
part 'reader.dart';
|
||||
part 'setting_components.dart';
|
||||
part 'explore_settings.dart';
|
||||
part 'setting_components.dart';
|
||||
part 'appearance.dart';
|
||||
part 'local_favorites.dart';
|
||||
part 'app.dart';
|
||||
part 'about.dart';
|
||||
part 'network.dart';
|
||||
|
||||
class SettingsPage extends StatefulWidget {
|
||||
const SettingsPage({this.initialPage = -1, super.key});
|
||||
|
||||
final int initialPage;
|
||||
|
||||
@override
|
||||
State<SettingsPage> createState() => _SettingsPageState();
|
||||
}
|
||||
|
||||
class _SettingsPageState extends State<SettingsPage> implements PopEntry {
|
||||
int currentPage = -1;
|
||||
|
||||
ColorScheme get colors => Theme.of(context).colorScheme;
|
||||
|
||||
bool get enableTwoViews => context.width > changePoint;
|
||||
|
||||
final categories = <String>[
|
||||
"Explore",
|
||||
"Reading",
|
||||
"Appearance",
|
||||
"Local Favorites",
|
||||
"APP",
|
||||
"Network",
|
||||
"About",
|
||||
];
|
||||
|
||||
final icons = <IconData>[
|
||||
Icons.explore,
|
||||
Icons.book,
|
||||
Icons.color_lens,
|
||||
Icons.collections_bookmark_rounded,
|
||||
Icons.apps,
|
||||
Icons.public,
|
||||
Icons.info
|
||||
];
|
||||
|
||||
double offset = 0;
|
||||
|
||||
late final HorizontalDragGestureRecognizer gestureRecognizer;
|
||||
|
||||
ModalRoute? _route;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
final ModalRoute<dynamic>? nextRoute = ModalRoute.of(context);
|
||||
if (nextRoute != _route) {
|
||||
_route?.unregisterPopEntry(this);
|
||||
_route = nextRoute;
|
||||
_route?.registerPopEntry(this);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
currentPage = widget.initialPage;
|
||||
gestureRecognizer = HorizontalDragGestureRecognizer(debugOwner: this)
|
||||
..onUpdate = ((details) => setState(() => offset += details.delta.dx))
|
||||
..onEnd = (details) async {
|
||||
if (details.velocity.pixelsPerSecond.dx.abs() > 1 &&
|
||||
details.velocity.pixelsPerSecond.dx >= 0) {
|
||||
setState(() {
|
||||
Future.delayed(const Duration(milliseconds: 300), () => offset = 0);
|
||||
currentPage = -1;
|
||||
});
|
||||
} else if (offset > MediaQuery.of(context).size.width / 2) {
|
||||
setState(() {
|
||||
Future.delayed(const Duration(milliseconds: 300), () => offset = 0);
|
||||
currentPage = -1;
|
||||
});
|
||||
} else {
|
||||
int i = 10;
|
||||
while (offset != 0) {
|
||||
setState(() {
|
||||
offset -= i;
|
||||
i *= 10;
|
||||
if (offset < 0) {
|
||||
offset = 0;
|
||||
}
|
||||
});
|
||||
await Future.delayed(const Duration(milliseconds: 10));
|
||||
}
|
||||
}
|
||||
}
|
||||
..onCancel = () async {
|
||||
int i = 10;
|
||||
while (offset != 0) {
|
||||
setState(() {
|
||||
offset -= i;
|
||||
i *= 10;
|
||||
if (offset < 0) {
|
||||
offset = 0;
|
||||
}
|
||||
});
|
||||
await Future.delayed(const Duration(milliseconds: 10));
|
||||
}
|
||||
};
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
dispose() {
|
||||
super.dispose();
|
||||
gestureRecognizer.dispose();
|
||||
_route?.unregisterPopEntry(this);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (currentPage != -1) {
|
||||
canPop.value = false;
|
||||
} else {
|
||||
canPop.value = true;
|
||||
}
|
||||
return Material(
|
||||
child: buildBody(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildBody() {
|
||||
if (enableTwoViews) {
|
||||
return Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 280,
|
||||
height: double.infinity,
|
||||
child: buildLeft(),
|
||||
),
|
||||
Container(
|
||||
height: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
left: BorderSide(
|
||||
color: context.colorScheme.outlineVariant,
|
||||
width: 0.6,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(child: buildRight())
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned.fill(child: buildLeft()),
|
||||
Positioned(
|
||||
left: offset,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.of(context).size.height,
|
||||
child: Listener(
|
||||
onPointerDown: handlePointerDown,
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
reverseDuration: const Duration(milliseconds: 300),
|
||||
switchInCurve: Curves.fastOutSlowIn,
|
||||
switchOutCurve: Curves.fastOutSlowIn,
|
||||
transitionBuilder: (child, animation) {
|
||||
var tween = Tween<Offset>(
|
||||
begin: const Offset(1, 0), end: const Offset(0, 0));
|
||||
|
||||
return SlideTransition(
|
||||
position: tween.animate(animation),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: currentPage == -1
|
||||
? const SizedBox(
|
||||
key: Key("1"),
|
||||
)
|
||||
: buildRight(),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void handlePointerDown(PointerDownEvent event) {
|
||||
if (event.position.dx < 20) {
|
||||
gestureRecognizer.addPointer(event);
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildLeft() {
|
||||
return Material(
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).padding.top,
|
||||
),
|
||||
SizedBox(
|
||||
height: 56,
|
||||
child: Row(children: [
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Tooltip(
|
||||
message: "Back",
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: context.pop,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 24,
|
||||
),
|
||||
Text(
|
||||
"Settings".tl,
|
||||
style: ts.s20,
|
||||
)
|
||||
]),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Expanded(
|
||||
child: buildCategories(),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildCategories() {
|
||||
Widget buildItem(String name, int id) {
|
||||
final bool selected = id == currentPage;
|
||||
|
||||
Widget content = AnimatedContainer(
|
||||
key: ValueKey(id),
|
||||
duration: const Duration(milliseconds: 200),
|
||||
width: double.infinity,
|
||||
height: 46,
|
||||
padding: const EdgeInsets.fromLTRB(12, 0, 12, 0),
|
||||
decoration: BoxDecoration(
|
||||
color: selected ? colors.primaryContainer.withOpacity(0.36) : null,
|
||||
border: Border(
|
||||
left: BorderSide(
|
||||
color: selected ? colors.primary : Colors.transparent,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Row(children: [
|
||||
Icon(icons[id]),
|
||||
const SizedBox(width: 16),
|
||||
Text(
|
||||
name,
|
||||
style: ts.s16,
|
||||
),
|
||||
const Spacer(),
|
||||
if (selected) const Icon(Icons.arrow_right)
|
||||
]),
|
||||
);
|
||||
|
||||
return Padding(
|
||||
padding: enableTwoViews
|
||||
? const EdgeInsets.fromLTRB(8, 0, 8, 0)
|
||||
: EdgeInsets.zero,
|
||||
child: InkWell(
|
||||
onTap: () => setState(() => currentPage = id),
|
||||
child: content,
|
||||
).paddingVertical(4),
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
itemCount: categories.length,
|
||||
itemBuilder: (context, index) => buildItem(categories[index].tl, index),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildRight() {
|
||||
final Widget body = switch (currentPage) {
|
||||
-1 => const SizedBox(),
|
||||
0 => const ExploreSettings(),
|
||||
1 => const ReaderSettings(),
|
||||
2 => const AppearanceSettings(),
|
||||
3 => const LocalFavoritesSettings(),
|
||||
4 => const AppSettings(),
|
||||
5 => const NetworkSettings(),
|
||||
6 => const AboutSettings(),
|
||||
_ => throw UnimplementedError()
|
||||
};
|
||||
|
||||
return Material(
|
||||
child: body,
|
||||
);
|
||||
}
|
||||
|
||||
var canPop = ValueNotifier(true);
|
||||
|
||||
@override
|
||||
ValueListenable<bool> get canPopNotifier => canPop;
|
||||
|
||||
/*
|
||||
flutter >=3.24.0 api
|
||||
|
||||
@override
|
||||
void onPopInvokedWithResult(bool didPop, result) {
|
||||
if (currentPage != -1) {
|
||||
setState(() {
|
||||
currentPage = -1;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onPopInvoked(bool didPop) {
|
||||
if (currentPage != -1) {
|
||||
setState(() {
|
||||
currentPage = -1;
|
||||
});
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// flutter <3.24.0 api
|
||||
@override
|
||||
PopInvokedCallback? get onPopInvoked => (bool didPop) {
|
||||
if (currentPage != -1) {
|
||||
setState(() {
|
||||
currentPage = -1;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
Reference in New Issue
Block a user