shortcuts

This commit is contained in:
wgh19
2024-05-31 17:38:27 +08:00
parent 9ad6207bd5
commit cb356dbf71
8 changed files with 353 additions and 74 deletions

View File

@@ -7,6 +7,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:pixes/appdata.dart';
import 'package:pixes/components/animated_image.dart';
import 'package:pixes/components/keyboard.dart';
import 'package:pixes/components/loading.dart';
import 'package:pixes/components/message.dart';
import 'package:pixes/components/page_route.dart';
@@ -154,8 +155,15 @@ class IllustPage extends StatefulWidget {
class _IllustPageState extends State<IllustPage> {
String get id => "${widget.illust.author.id}#${widget.illust.id}";
final _bottomBarController = _BottomBarController();
KeyEventListenerState? keyboardListener;
@override
void initState() {
keyboardListener = KeyEventListener.of(context);
keyboardListener?.removeAll();
keyboardListener?.addHandler(handleKey);
IllustPage.followCallbacks[id] = (v) {
setState(() {
widget.illust.author.isFollowed = v;
@@ -166,6 +174,7 @@ class _IllustPageState extends State<IllustPage> {
@override
void dispose() {
keyboardListener?.removeHandler(handleKey);
IllustPage.followCallbacks.remove(id);
super.dispose();
}
@@ -173,7 +182,7 @@ class _IllustPageState extends State<IllustPage> {
@override
Widget build(BuildContext context) {
var isBlocked = checkIllusts([widget.illust]).isEmpty;
return buildKeyboardListener(ColoredBox(
return ColoredBox(
color: FluentTheme.of(context).micaBackgroundColor,
child: SizedBox.expand(
child: ColoredBox(
@@ -195,6 +204,7 @@ class _IllustPageState extends State<IllustPage> {
constrains.maxHeight,
constrains.maxWidth,
updateCallback: () => setState(() {}),
controller: _bottomBarController,
),
if (isBlocked)
const Positioned.fill(
@@ -209,36 +219,53 @@ class _IllustPageState extends State<IllustPage> {
}),
),
),
));
);
}
final scrollController = ScrollController();
Widget buildKeyboardListener(Widget child) {
return KeyboardListener(
focusNode: FocusNode(),
autofocus: true,
onKeyEvent: (event) {
if (event is! KeyUpEvent) return;
const kShortcutScrollOffset = 200;
if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
void handleKey(LogicalKeyboardKey key) {
const kShortcutScrollOffset = 200;
var shortcuts = appdata.settings["shortcuts"] as List;
switch (shortcuts.indexOf(key.keyId)) {
case 0:
if (scrollController.position.pixels >=
scrollController.position.maxScrollExtent) {
_bottomBarController.openOrClose();
} else {
scrollController.animateTo(
scrollController.offset + kShortcutScrollOffset,
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut);
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
scrollController.animateTo(
scrollController.offset - kShortcutScrollOffset,
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut);
} else if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
widget.nextPage?.call();
} else if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
widget.previousPage?.call();
scrollController.offset + kShortcutScrollOffset,
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
);
}
},
child: child,
);
break;
case 1:
if (_bottomBarController.isOpen()) {
_bottomBarController.openOrClose();
break;
}
scrollController.animateTo(
scrollController.offset - kShortcutScrollOffset,
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
);
break;
case 2:
widget.nextPage?.call();
break;
case 3:
widget.previousPage?.call();
break;
case 4:
_bottomBarController.favorite();
case 5:
_bottomBarController.download();
case 6:
_bottomBarController.follow();
}
}
Widget buildBody(double width, double height) {
@@ -344,8 +371,31 @@ class _IllustPageState extends State<IllustPage> {
}
}
class _BottomBarController {
VoidCallback? _openOrClose;
VoidCallback get openOrClose => _openOrClose!;
bool Function()? _isOpen;
bool isOpen() => _isOpen!();
VoidCallback? _favorite;
VoidCallback get favorite => _favorite!;
VoidCallback? _download;
VoidCallback get download => _download!;
VoidCallback? _follow;
VoidCallback get follow => _follow!;
}
class _BottomBar extends StatefulWidget {
const _BottomBar(this.illust, this.height, this.width, {this.updateCallback});
const _BottomBar(this.illust, this.height, this.width,
{this.updateCallback, this.controller});
final void Function()? updateCallback;
@@ -355,6 +405,8 @@ class _BottomBar extends StatefulWidget {
final double width;
final _BottomBarController? controller;
@override
State<_BottomBar> createState() => _BottomBarState();
}
@@ -391,9 +443,32 @@ class _BottomBarState extends State<_BottomBar> with TickerProviderStateMixin {
..onCancel = _handlePointerCancel;
animationController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 180), value: 1);
if (widget.controller != null) {
widget.controller!._openOrClose = () {
if (animationController.value == 0) {
animationController.animateTo(1);
} else if (animationController.value == 1) {
animationController.animateTo(0);
}
};
widget.controller!._isOpen = () => animationController.value == 0;
widget.controller!._favorite = favorite;
widget.controller!._download = () {
DownloadManager().addDownloadingTask(widget.illust);
setState(() {});
};
widget.controller!._follow = follow;
}
super.initState();
}
@override
void dispose() {
animationController.dispose();
_recognizer.dispose();
super.dispose();
}
void _handlePointerDown(DragStartDetails details) {}
void _handlePointerMove(DragUpdateDetails details) {
var offset = details.primaryDelta ?? 0;
@@ -541,31 +616,31 @@ class _BottomBarState extends State<_BottomBar> with TickerProviderStateMixin {
bool isFollowing = false;
Widget buildAuthor() {
void follow() async {
if (isFollowing) return;
setState(() {
isFollowing = true;
});
var method = widget.illust.author.isFollowed ? "delete" : "add";
var res =
await Network().follow(widget.illust.author.id.toString(), method);
if (res.error) {
if (mounted) {
context.showToast(message: "Network Error");
}
} else {
widget.illust.author.isFollowed = !widget.illust.author.isFollowed;
void follow() async {
if (isFollowing) return;
setState(() {
isFollowing = true;
});
var method = widget.illust.author.isFollowed ? "delete" : "add";
var res =
await Network().follow(widget.illust.author.id.toString(), method);
if (res.error) {
if (mounted) {
context.showToast(message: "Network Error");
}
setState(() {
isFollowing = false;
});
UserInfoPage.followCallbacks[widget.illust.author.id.toString()]
?.call(widget.illust.author.isFollowed);
UserPreviewWidget.followCallbacks[widget.illust.author.id.toString()]
?.call(widget.illust.author.isFollowed);
} else {
widget.illust.author.isFollowed = !widget.illust.author.isFollowed;
}
setState(() {
isFollowing = false;
});
UserInfoPage.followCallbacks[widget.illust.author.id.toString()]
?.call(widget.illust.author.isFollowed);
UserPreviewWidget.followCallbacks[widget.illust.author.id.toString()]
?.call(widget.illust.author.isFollowed);
}
Widget buildAuthor() {
final bool showUserName = MediaQuery.of(context).size.width > 640;
return Card(
@@ -981,7 +1056,7 @@ class _BottomBarState extends State<_BottomBar> with TickerProviderStateMixin {
).fixWidth(96),
Button(
onPressed: () {
var text = "https://www.pixiv.net/artworks/${widget.illust.id}";
var text = "https://pixiv.net/artworks/${widget.illust.id}";
Clipboard.setData(ClipboardData(text: text));
showToast(context, message: "Copied".tl);
},

View File

@@ -3,6 +3,7 @@ import "dart:async";
import "package:fluent_ui/fluent_ui.dart";
import "package:flutter/foundation.dart";
import "package:pixes/appdata.dart";
import "package:pixes/components/keyboard.dart";
import "package:pixes/components/md.dart";
import "package:pixes/foundation/app.dart";
import "package:pixes/foundation/image_provider.dart";
@@ -214,10 +215,10 @@ class _MainPageState extends State<MainPage> with WindowListener {
context: context,
removeTop: true,
child: Navigator(
key: navigatorKey,
onGenerateRoute: (settings) => AppPageRoute(
builder: (context) => const RecommendationPage()),
),
key: navigatorKey,
onGenerateRoute: (settings) => AppPageRoute(
builder: (context) => const RecommendationPage()),
),
))),
);
}

View File

@@ -1,7 +1,9 @@
import 'dart:io';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/services.dart';
import 'package:pixes/appdata.dart';
import 'package:pixes/components/keyboard.dart';
import 'package:pixes/components/md.dart';
import 'package:pixes/components/message.dart';
import 'package:pixes/components/page_route.dart';
@@ -242,6 +244,14 @@ class _SettingsPageState extends State<SettingsPage> {
context.to(() => const _BlockTagsPage());
},
)),
buildItem(
title: "Shortcuts".tl,
action: Button(
child: Text("Edit".tl).fixWidth(64),
onPressed: () {
context.to(() => const ShortcutsSettings());
},
)),
],
),
);
@@ -538,3 +548,81 @@ class __BlockTagsPageState extends State<_BlockTagsPage> {
);
}
}
class ShortcutsSettings extends StatefulWidget {
const ShortcutsSettings({super.key});
@override
State<ShortcutsSettings> createState() => _ShortcutsSettingsState();
}
class _ShortcutsSettingsState extends State<ShortcutsSettings> {
int listening = -1;
KeyEventListenerState? listener;
@override
void initState() {
listener = KeyEventListener.of(context);
super.initState();
}
@override
void dispose() {
listener?.removeAll();
super.dispose();
}
final settings = <String>[
"Page down",
"Page up",
"Next work",
"Previous work",
"Add to favorites",
"Download",
"Follow the artist",
];
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(children: [
TitleBar(title: "Shortcuts".tl),
...settings.map((e) => buildItem(e, settings.indexOf(e)))
]),
);
}
Widget buildItem(String text, int index) {
var keyText = listening == index
? "Waiting..."
: LogicalKeyboardKey(appdata.settings['shortcuts'][index]).keyLabel;
return Card(
padding: EdgeInsets.zero,
margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 12),
child: ListTile(
title: Text(text.tl),
trailing: Button(
child: Text(keyText),
onPressed: () {
if (listening != -1) {
listener?.removeAll();
}
setState(() {
listening = index;
});
listener?.addHandler((key) {
if (key == LogicalKeyboardKey.escape) return;
setState(() {
appdata.settings['shortcuts'][index] = key.keyId;
listening = -1;
appdata.writeData();
});
Future.microtask(() => listener?.removeAll());
});
},
),
),
);
}
}