Files
venera/lib/pages/settings/settings_page.dart
2024-11-10 10:38:46 +08:00

347 lines
9.0 KiB
Dart

import 'dart:convert';
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/data.dart';
import 'package:venera/utils/data_sync.dart';
import 'package:venera/utils/io.dart';
import 'package:venera/utils/translations.dart';
import 'package:yaml/yaml.dart';
part 'reader.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;
@override
void onPopInvokedWithResult(bool didPop, result) {
if (currentPage != -1) {
setState(() {
currentPage = -1;
});
}
}
@override
void onPopInvoked(bool didPop) {
if (currentPage != -1) {
setState(() {
currentPage = -1;
});
}
}
}