mirror of
https://github.com/wgh136/pixes.git
synced 2025-09-27 12:57:24 +00:00
shortcuts
This commit is contained in:
@@ -148,7 +148,15 @@
|
|||||||
"Local": "本地",
|
"Local": "本地",
|
||||||
"Both": "同时",
|
"Both": "同时",
|
||||||
"This artwork is blocked": "此作品已被屏蔽",
|
"This artwork is blocked": "此作品已被屏蔽",
|
||||||
"Delete Invalid Items": "删除无效项目"
|
"Delete Invalid Items": "删除无效项目",
|
||||||
|
"Private Favorite": "私人收藏",
|
||||||
|
"Shortcuts": "快捷键",
|
||||||
|
"Page down": "向下翻页",
|
||||||
|
"Page up": "向上翻页",
|
||||||
|
"Next work": "下一作品",
|
||||||
|
"Previous work": "上一作品",
|
||||||
|
"Add to favorites": "添加收藏",
|
||||||
|
"Follow the artist": "关注画师"
|
||||||
},
|
},
|
||||||
"zh_TW": {
|
"zh_TW": {
|
||||||
"Search": "搜索",
|
"Search": "搜索",
|
||||||
@@ -299,6 +307,14 @@
|
|||||||
"Local": "本地",
|
"Local": "本地",
|
||||||
"Both": "同時",
|
"Both": "同時",
|
||||||
"This artwork is blocked": "此作品已被屏蔽",
|
"This artwork is blocked": "此作品已被屏蔽",
|
||||||
"Delete Invalid Items": "刪除無效項目"
|
"Delete Invalid Items": "刪除無效項目",
|
||||||
|
"Private Favorite": "私人收藏",
|
||||||
|
"Shortcuts": "快捷鍵",
|
||||||
|
"Page down": "向下翻頁",
|
||||||
|
"Page up": "向上翻頁",
|
||||||
|
"Next work": "下一作品",
|
||||||
|
"Previous work": "上一作品",
|
||||||
|
"Add to favorites": "添加收藏",
|
||||||
|
"Follow the artist": "關注畫師"
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -24,6 +24,15 @@ class _Appdata {
|
|||||||
"readingLineHeight": 1.5,
|
"readingLineHeight": 1.5,
|
||||||
"readingParagraphSpacing": 8.0,
|
"readingParagraphSpacing": 8.0,
|
||||||
"blockTags": [],
|
"blockTags": [],
|
||||||
|
"shortcuts": <int>[
|
||||||
|
LogicalKeyboardKey.arrowDown.keyId,
|
||||||
|
LogicalKeyboardKey.arrowUp.keyId,
|
||||||
|
LogicalKeyboardKey.arrowRight.keyId,
|
||||||
|
LogicalKeyboardKey.arrowLeft.keyId,
|
||||||
|
LogicalKeyboardKey.enter.keyId,
|
||||||
|
LogicalKeyboardKey.keyD.keyId,
|
||||||
|
LogicalKeyboardKey.keyF.keyId,
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
bool lock = false;
|
bool lock = false;
|
||||||
|
61
lib/components/keyboard.dart
Normal file
61
lib/components/keyboard.dart
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:pixes/foundation/app.dart';
|
||||||
|
|
||||||
|
typedef KeyEventHandler = void Function(LogicalKeyboardKey key);
|
||||||
|
|
||||||
|
class KeyEventListener extends StatefulWidget {
|
||||||
|
const KeyEventListener({required this.child, super.key});
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
static KeyEventListenerState? of(BuildContext context) {
|
||||||
|
return context.findAncestorStateOfType<KeyEventListenerState>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<KeyEventListener> createState() => KeyEventListenerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class KeyEventListenerState extends State<KeyEventListener> {
|
||||||
|
final focusNode = FocusNode();
|
||||||
|
|
||||||
|
final List<KeyEventHandler> _handlers = [];
|
||||||
|
|
||||||
|
void addHandler(KeyEventHandler handler) {
|
||||||
|
_handlers.add(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeHandler(KeyEventHandler handler) {
|
||||||
|
_handlers.remove(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeAll() {
|
||||||
|
_handlers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Focus(
|
||||||
|
focusNode: focusNode,
|
||||||
|
autofocus: true,
|
||||||
|
onKeyEvent: (node, event) {
|
||||||
|
if (event is! KeyUpEvent) return KeyEventResult.ignored;
|
||||||
|
if (event.logicalKey == LogicalKeyboardKey.escape) {
|
||||||
|
if (App.rootNavigatorKey.currentState?.canPop() ?? false) {
|
||||||
|
App.rootNavigatorKey.currentState?.pop();
|
||||||
|
}
|
||||||
|
if (App.mainNavigatorKey?.currentState?.canPop() ?? false) {
|
||||||
|
App.mainNavigatorKey?.currentState?.pop();
|
||||||
|
}
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
}
|
||||||
|
for (var handler in _handlers) {
|
||||||
|
handler(event.logicalKey);
|
||||||
|
}
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
},
|
||||||
|
child: widget.child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -6,6 +6,7 @@ import "package:flutter/material.dart" as md;
|
|||||||
import "package:flutter/services.dart";
|
import "package:flutter/services.dart";
|
||||||
import "package:flutter_acrylic/flutter_acrylic.dart" as flutter_acrylic;
|
import "package:flutter_acrylic/flutter_acrylic.dart" as flutter_acrylic;
|
||||||
import "package:pixes/appdata.dart";
|
import "package:pixes/appdata.dart";
|
||||||
|
import "package:pixes/components/keyboard.dart";
|
||||||
import "package:pixes/components/md.dart";
|
import "package:pixes/components/md.dart";
|
||||||
import "package:pixes/components/message.dart";
|
import "package:pixes/components/message.dart";
|
||||||
import "package:pixes/foundation/app.dart";
|
import "package:pixes/foundation/app.dart";
|
||||||
@@ -88,7 +89,7 @@ class MyApp extends StatelessWidget {
|
|||||||
title: 'pixes',
|
title: 'pixes',
|
||||||
theme: FluentThemeData(
|
theme: FluentThemeData(
|
||||||
brightness: brightness,
|
brightness: brightness,
|
||||||
fontFamily: App.isWindows ? 'font' : null,
|
fontFamily: App.isWindows ? '微软雅黑' : null,
|
||||||
accentColor: AccentColor.swatch({
|
accentColor: AccentColor.swatch({
|
||||||
'darkest': darken(colorScheme.primary, 30),
|
'darkest': darken(colorScheme.primary, 30),
|
||||||
'darker': darken(colorScheme.primary, 20),
|
'darker': darken(colorScheme.primary, 20),
|
||||||
@@ -97,7 +98,11 @@ class MyApp extends StatelessWidget {
|
|||||||
'light': lighten(colorScheme.primary, 10),
|
'light': lighten(colorScheme.primary, 10),
|
||||||
'lighter': lighten(colorScheme.primary, 20),
|
'lighter': lighten(colorScheme.primary, 20),
|
||||||
'lightest': lighten(colorScheme.primary, 30)
|
'lightest': lighten(colorScheme.primary, 30)
|
||||||
})),
|
}),
|
||||||
|
focusTheme: const FocusThemeData(
|
||||||
|
primaryBorder: BorderSide.none,
|
||||||
|
secondaryBorder: BorderSide.none,
|
||||||
|
)),
|
||||||
home: const MainPage(),
|
home: const MainPage(),
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
ErrorWidget.builder = (details) {
|
ErrorWidget.builder = (details) {
|
||||||
@@ -151,7 +156,7 @@ class MyApp extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return widget;
|
return KeyEventListener(child: widget);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@@ -7,6 +7,7 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||||
import 'package:pixes/appdata.dart';
|
import 'package:pixes/appdata.dart';
|
||||||
import 'package:pixes/components/animated_image.dart';
|
import 'package:pixes/components/animated_image.dart';
|
||||||
|
import 'package:pixes/components/keyboard.dart';
|
||||||
import 'package:pixes/components/loading.dart';
|
import 'package:pixes/components/loading.dart';
|
||||||
import 'package:pixes/components/message.dart';
|
import 'package:pixes/components/message.dart';
|
||||||
import 'package:pixes/components/page_route.dart';
|
import 'package:pixes/components/page_route.dart';
|
||||||
@@ -154,8 +155,15 @@ class IllustPage extends StatefulWidget {
|
|||||||
class _IllustPageState extends State<IllustPage> {
|
class _IllustPageState extends State<IllustPage> {
|
||||||
String get id => "${widget.illust.author.id}#${widget.illust.id}";
|
String get id => "${widget.illust.author.id}#${widget.illust.id}";
|
||||||
|
|
||||||
|
final _bottomBarController = _BottomBarController();
|
||||||
|
|
||||||
|
KeyEventListenerState? keyboardListener;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
keyboardListener = KeyEventListener.of(context);
|
||||||
|
keyboardListener?.removeAll();
|
||||||
|
keyboardListener?.addHandler(handleKey);
|
||||||
IllustPage.followCallbacks[id] = (v) {
|
IllustPage.followCallbacks[id] = (v) {
|
||||||
setState(() {
|
setState(() {
|
||||||
widget.illust.author.isFollowed = v;
|
widget.illust.author.isFollowed = v;
|
||||||
@@ -166,6 +174,7 @@ class _IllustPageState extends State<IllustPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
keyboardListener?.removeHandler(handleKey);
|
||||||
IllustPage.followCallbacks.remove(id);
|
IllustPage.followCallbacks.remove(id);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@@ -173,7 +182,7 @@ class _IllustPageState extends State<IllustPage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var isBlocked = checkIllusts([widget.illust]).isEmpty;
|
var isBlocked = checkIllusts([widget.illust]).isEmpty;
|
||||||
return buildKeyboardListener(ColoredBox(
|
return ColoredBox(
|
||||||
color: FluentTheme.of(context).micaBackgroundColor,
|
color: FluentTheme.of(context).micaBackgroundColor,
|
||||||
child: SizedBox.expand(
|
child: SizedBox.expand(
|
||||||
child: ColoredBox(
|
child: ColoredBox(
|
||||||
@@ -195,6 +204,7 @@ class _IllustPageState extends State<IllustPage> {
|
|||||||
constrains.maxHeight,
|
constrains.maxHeight,
|
||||||
constrains.maxWidth,
|
constrains.maxWidth,
|
||||||
updateCallback: () => setState(() {}),
|
updateCallback: () => setState(() {}),
|
||||||
|
controller: _bottomBarController,
|
||||||
),
|
),
|
||||||
if (isBlocked)
|
if (isBlocked)
|
||||||
const Positioned.fill(
|
const Positioned.fill(
|
||||||
@@ -209,36 +219,53 @@ class _IllustPageState extends State<IllustPage> {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final scrollController = ScrollController();
|
final scrollController = ScrollController();
|
||||||
|
|
||||||
Widget buildKeyboardListener(Widget child) {
|
void handleKey(LogicalKeyboardKey key) {
|
||||||
return KeyboardListener(
|
const kShortcutScrollOffset = 200;
|
||||||
focusNode: FocusNode(),
|
|
||||||
autofocus: true,
|
var shortcuts = appdata.settings["shortcuts"] as List;
|
||||||
onKeyEvent: (event) {
|
|
||||||
if (event is! KeyUpEvent) return;
|
switch (shortcuts.indexOf(key.keyId)) {
|
||||||
const kShortcutScrollOffset = 200;
|
case 0:
|
||||||
if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
|
if (scrollController.position.pixels >=
|
||||||
|
scrollController.position.maxScrollExtent) {
|
||||||
|
_bottomBarController.openOrClose();
|
||||||
|
} else {
|
||||||
scrollController.animateTo(
|
scrollController.animateTo(
|
||||||
scrollController.offset + kShortcutScrollOffset,
|
scrollController.offset + kShortcutScrollOffset,
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
curve: Curves.easeOut);
|
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();
|
|
||||||
}
|
}
|
||||||
},
|
break;
|
||||||
child: child,
|
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) {
|
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 {
|
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;
|
final void Function()? updateCallback;
|
||||||
|
|
||||||
@@ -355,6 +405,8 @@ class _BottomBar extends StatefulWidget {
|
|||||||
|
|
||||||
final double width;
|
final double width;
|
||||||
|
|
||||||
|
final _BottomBarController? controller;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_BottomBar> createState() => _BottomBarState();
|
State<_BottomBar> createState() => _BottomBarState();
|
||||||
}
|
}
|
||||||
@@ -391,9 +443,32 @@ class _BottomBarState extends State<_BottomBar> with TickerProviderStateMixin {
|
|||||||
..onCancel = _handlePointerCancel;
|
..onCancel = _handlePointerCancel;
|
||||||
animationController = AnimationController(
|
animationController = AnimationController(
|
||||||
vsync: this, duration: const Duration(milliseconds: 180), value: 1);
|
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();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
animationController.dispose();
|
||||||
|
_recognizer.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
void _handlePointerDown(DragStartDetails details) {}
|
void _handlePointerDown(DragStartDetails details) {}
|
||||||
void _handlePointerMove(DragUpdateDetails details) {
|
void _handlePointerMove(DragUpdateDetails details) {
|
||||||
var offset = details.primaryDelta ?? 0;
|
var offset = details.primaryDelta ?? 0;
|
||||||
@@ -541,31 +616,31 @@ class _BottomBarState extends State<_BottomBar> with TickerProviderStateMixin {
|
|||||||
|
|
||||||
bool isFollowing = false;
|
bool isFollowing = false;
|
||||||
|
|
||||||
Widget buildAuthor() {
|
void follow() async {
|
||||||
void follow() async {
|
if (isFollowing) return;
|
||||||
if (isFollowing) return;
|
setState(() {
|
||||||
setState(() {
|
isFollowing = true;
|
||||||
isFollowing = true;
|
});
|
||||||
});
|
var method = widget.illust.author.isFollowed ? "delete" : "add";
|
||||||
var method = widget.illust.author.isFollowed ? "delete" : "add";
|
var res =
|
||||||
var res =
|
await Network().follow(widget.illust.author.id.toString(), method);
|
||||||
await Network().follow(widget.illust.author.id.toString(), method);
|
if (res.error) {
|
||||||
if (res.error) {
|
if (mounted) {
|
||||||
if (mounted) {
|
context.showToast(message: "Network Error");
|
||||||
context.showToast(message: "Network Error");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
widget.illust.author.isFollowed = !widget.illust.author.isFollowed;
|
|
||||||
}
|
}
|
||||||
setState(() {
|
} else {
|
||||||
isFollowing = false;
|
widget.illust.author.isFollowed = !widget.illust.author.isFollowed;
|
||||||
});
|
|
||||||
UserInfoPage.followCallbacks[widget.illust.author.id.toString()]
|
|
||||||
?.call(widget.illust.author.isFollowed);
|
|
||||||
UserPreviewWidget.followCallbacks[widget.illust.author.id.toString()]
|
|
||||||
?.call(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;
|
final bool showUserName = MediaQuery.of(context).size.width > 640;
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
@@ -981,7 +1056,7 @@ class _BottomBarState extends State<_BottomBar> with TickerProviderStateMixin {
|
|||||||
).fixWidth(96),
|
).fixWidth(96),
|
||||||
Button(
|
Button(
|
||||||
onPressed: () {
|
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));
|
Clipboard.setData(ClipboardData(text: text));
|
||||||
showToast(context, message: "Copied".tl);
|
showToast(context, message: "Copied".tl);
|
||||||
},
|
},
|
||||||
|
@@ -3,6 +3,7 @@ import "dart:async";
|
|||||||
import "package:fluent_ui/fluent_ui.dart";
|
import "package:fluent_ui/fluent_ui.dart";
|
||||||
import "package:flutter/foundation.dart";
|
import "package:flutter/foundation.dart";
|
||||||
import "package:pixes/appdata.dart";
|
import "package:pixes/appdata.dart";
|
||||||
|
import "package:pixes/components/keyboard.dart";
|
||||||
import "package:pixes/components/md.dart";
|
import "package:pixes/components/md.dart";
|
||||||
import "package:pixes/foundation/app.dart";
|
import "package:pixes/foundation/app.dart";
|
||||||
import "package:pixes/foundation/image_provider.dart";
|
import "package:pixes/foundation/image_provider.dart";
|
||||||
@@ -214,10 +215,10 @@ class _MainPageState extends State<MainPage> with WindowListener {
|
|||||||
context: context,
|
context: context,
|
||||||
removeTop: true,
|
removeTop: true,
|
||||||
child: Navigator(
|
child: Navigator(
|
||||||
key: navigatorKey,
|
key: navigatorKey,
|
||||||
onGenerateRoute: (settings) => AppPageRoute(
|
onGenerateRoute: (settings) => AppPageRoute(
|
||||||
builder: (context) => const RecommendationPage()),
|
builder: (context) => const RecommendationPage()),
|
||||||
),
|
),
|
||||||
))),
|
))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:fluent_ui/fluent_ui.dart';
|
import 'package:fluent_ui/fluent_ui.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:pixes/appdata.dart';
|
import 'package:pixes/appdata.dart';
|
||||||
|
import 'package:pixes/components/keyboard.dart';
|
||||||
import 'package:pixes/components/md.dart';
|
import 'package:pixes/components/md.dart';
|
||||||
import 'package:pixes/components/message.dart';
|
import 'package:pixes/components/message.dart';
|
||||||
import 'package:pixes/components/page_route.dart';
|
import 'package:pixes/components/page_route.dart';
|
||||||
@@ -242,6 +244,14 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
context.to(() => const _BlockTagsPage());
|
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());
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
56
pubspec.lock
56
pubspec.lock
@@ -73,6 +73,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.3"
|
version: "3.0.3"
|
||||||
|
device_info_plus:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: device_info_plus
|
||||||
|
sha256: eead12d1a1ed83d8283ab4c2f3fca23ac4082f29f25f29dff0f758f57d06ec91
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "10.1.0"
|
||||||
|
device_info_plus_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: device_info_plus_platform_interface
|
||||||
|
sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.0"
|
||||||
dio:
|
dio:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -81,6 +97,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.4.3+1"
|
version: "5.4.3+1"
|
||||||
|
dynamic_color:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: dynamic_color
|
||||||
|
sha256: eae98052fa6e2826bdac3dd2e921c6ce2903be15c6b7f8b6d8a5d49b5086298d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.7.0"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -190,6 +214,14 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_acrylic:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_acrylic
|
||||||
|
sha256: a9a1fdf91ff1fb47858fd82507f57e255a132a5d355056694fdb9fd303633b18
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.3"
|
||||||
flutter_file_dialog:
|
flutter_file_dialog:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -293,6 +325,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "3.0.0"
|
||||||
|
macos_window_utils:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: macos_window_utils
|
||||||
|
sha256: "230be594d26f6dee92c5a1544f4242d25138a5bfb9f185b27f14de3949ef0be8"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.0"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -515,22 +555,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.2.0"
|
||||||
system_theme:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: system_theme
|
|
||||||
sha256: "1f208db140a3d1e1eac2034b54920d95699c1534df576ced44b3312c5de3975f"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.3.1"
|
|
||||||
system_theme_web:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: system_theme_web
|
|
||||||
sha256: "7566f5a928f6d28d7a60c97bea8a851d1c6bc9b86a4df2366230a97458489219"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.0.2"
|
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
Reference in New Issue
Block a user