fix: Keyboard controls and translations (#79)

Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
This commit is contained in:
PartyDonut 2024-10-26 14:00:15 +02:00 committed by GitHub
parent 7394077726
commit 1babf05834
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 796 additions and 745 deletions

View file

@ -1,8 +1,18 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:extended_image/extended_image.dart';
import 'package:ficonsax/ficonsax.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:share_plus/share_plus.dart';
import 'package:square_progress_indicator/square_progress_indicator.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import 'package:window_manager/window_manager.dart';
import 'package:fladder/models/items/photos_model.dart';
import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/providers/settings/photo_view_settings_provider.dart';
import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/screens/shared/flat_button.dart';
import 'package:fladder/screens/shared/input_fields.dart';
import 'package:fladder/util/adaptive_layout.dart';
@ -11,14 +21,6 @@ import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/util/throttler.dart';
import 'package:fladder/widgets/shared/elevated_icon.dart';
import 'package:fladder/widgets/shared/progress_floating_button.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:share_plus/share_plus.dart';
import 'package:square_progress_indicator/square_progress_indicator.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import 'package:window_manager/window_manager.dart';
class PhotoViewerControls extends ConsumerStatefulWidget {
final EdgeInsets padding;
@ -48,7 +50,6 @@ class PhotoViewerControls extends ConsumerStatefulWidget {
}
class _PhotoViewerControllsState extends ConsumerState<PhotoViewerControls> with WindowListener {
final FocusNode focusNode = FocusNode();
final Throttler throttler = Throttler(duration: const Duration(milliseconds: 130));
late int currentPage = widget.pageController.page?.round() ?? 0;
double dragUpDelta = 0.0;
@ -69,13 +70,46 @@ class _PhotoViewerControllsState extends ConsumerState<PhotoViewerControls> with
}
}
bool _onKey(KeyEvent value) {
if (value is KeyRepeatEvent) {
if (value.logicalKey == LogicalKeyboardKey.arrowLeft) {
throttler.run(() =>
widget.pageController.previousPage(duration: const Duration(milliseconds: 125), curve: Curves.easeInOut));
return true;
}
if (value.logicalKey == LogicalKeyboardKey.arrowRight) {
throttler.run(
() => widget.pageController.nextPage(duration: const Duration(milliseconds: 125), curve: Curves.easeInOut));
return true;
}
}
if (value is KeyDownEvent) {
if (value.logicalKey == LogicalKeyboardKey.arrowLeft) {
throttler.run(() =>
widget.pageController.previousPage(duration: const Duration(milliseconds: 125), curve: Curves.easeInOut));
return true;
}
if (value.logicalKey == LogicalKeyboardKey.arrowRight) {
throttler.run(
() => widget.pageController.nextPage(duration: const Duration(milliseconds: 125), curve: Curves.easeInOut));
return true;
}
if (value.logicalKey == LogicalKeyboardKey.keyK) {
timerController.playPause();
return true;
}
if (value.logicalKey == LogicalKeyboardKey.keyF) {
widget.toggleOverlay?.call(null);
return true;
}
}
return false;
}
@override
void initState() {
super.initState();
Future.microtask(() => () {
if (AdaptiveLayout.of(context).isDesktop) focusNode.requestFocus();
});
windowManager.addListener(this);
widget.pageController.addListener(
() {
@ -83,10 +117,12 @@ class _PhotoViewerControllsState extends ConsumerState<PhotoViewerControls> with
timerController.reset();
},
);
ServicesBinding.instance.keyboard.addHandler(_onKey);
}
@override
void onWindowMinimize() {
ServicesBinding.instance.keyboard.removeHandler(_onKey);
timerController.cancel();
super.onWindowMinimize();
}
@ -100,8 +136,6 @@ class _PhotoViewerControllsState extends ConsumerState<PhotoViewerControls> with
@override
Widget build(BuildContext context) {
if (AdaptiveLayout.of(context).isDesktop) focusNode.requestFocus();
final gradient = [
Colors.black.withOpacity(0.6),
Colors.black.withOpacity(0.3),
@ -109,217 +143,187 @@ class _PhotoViewerControllsState extends ConsumerState<PhotoViewerControls> with
Colors.black.withOpacity(0.0),
];
if (AdaptiveLayout.of(context).isDesktop) {
focusNode.requestFocus();
}
final padding = MediaQuery.of(context).padding;
return PopScope(
onPopInvokedWithResult: (didPop, result) async {
await WakelockPlus.disable();
},
child: KeyboardListener(
focusNode: focusNode,
autofocus: true,
onKeyEvent: (value) {
if (value is KeyDownEvent) {
if (value.logicalKey == LogicalKeyboardKey.arrowLeft) {
throttler.run(() => widget.pageController
.previousPage(duration: const Duration(milliseconds: 125), curve: Curves.easeInOut));
}
if (value.logicalKey == LogicalKeyboardKey.arrowRight) {
throttler.run(() =>
widget.pageController.nextPage(duration: const Duration(milliseconds: 125), curve: Curves.easeInOut));
}
if (value.logicalKey == LogicalKeyboardKey.keyK) {
timerController.playPause();
}
if (value.logicalKey == LogicalKeyboardKey.space) {
widget.toggleOverlay?.call(null);
}
}
},
child: Stack(
children: [
Align(
alignment: Alignment.topCenter,
widthFactor: 1,
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: gradient,
),
child: Stack(
children: [
Align(
alignment: Alignment.topCenter,
widthFactor: 1,
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: gradient,
),
child: Padding(
padding: EdgeInsets.only(top: widget.padding.top),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (AdaptiveLayout.of(context).isDesktop) const SizedBox(height: 25),
Padding(
padding: const EdgeInsets.symmetric(vertical: 12)
.add(EdgeInsets.only(left: padding.left, right: padding.right)),
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
ElevatedIconButton(
onPressed: () => Navigator.of(context).pop(widget.pageController.page?.toInt()),
icon: getBackIcon(context),
),
const SizedBox(width: 8),
Expanded(
child: Tooltip(
message: widget.photo.name,
child: Text(
widget.photo.name,
maxLines: 2,
style: Theme.of(context)
.textTheme
.titleMedium
?.copyWith(fontWeight: FontWeight.bold, shadows: [
BoxShadow(blurRadius: 1, spreadRadius: 1, color: Colors.black.withOpacity(0.7)),
BoxShadow(blurRadius: 4, spreadRadius: 4, color: Colors.black.withOpacity(0.4)),
BoxShadow(blurRadius: 20, spreadRadius: 6, color: Colors.black.withOpacity(0.2)),
]),
),
),
child: Padding(
padding: EdgeInsets.only(top: widget.padding.top),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (AdaptiveLayout.of(context).isDesktop) const SizedBox(height: 25),
Padding(
padding: const EdgeInsets.symmetric(vertical: 12)
.add(EdgeInsets.only(left: padding.left, right: padding.right)),
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
ElevatedIconButton(
onPressed: () => Navigator.of(context).pop(widget.pageController.page?.toInt()),
icon: getBackIcon(context),
),
const SizedBox(width: 8),
Expanded(
child: Tooltip(
message: widget.photo.name,
child: Text(
widget.photo.name,
maxLines: 2,
style: Theme.of(context)
.textTheme
.titleMedium
?.copyWith(fontWeight: FontWeight.bold, shadows: [
BoxShadow(blurRadius: 1, spreadRadius: 1, color: Colors.black.withOpacity(0.7)),
BoxShadow(blurRadius: 4, spreadRadius: 4, color: Colors.black.withOpacity(0.4)),
BoxShadow(blurRadius: 20, spreadRadius: 6, color: Colors.black.withOpacity(0.2)),
]),
),
),
const SizedBox(width: 8),
Stack(
children: [
Positioned.fill(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Theme.of(context).colorScheme.onPrimary),
child: SquareProgressIndicator(
value: widget.currentIndex / (widget.itemCount - 1),
borderRadius: 7,
clockwise: false,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(width: 8),
Stack(
children: [
Positioned.fill(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Theme.of(context).colorScheme.onPrimary),
child: SquareProgressIndicator(
value: widget.currentIndex / (widget.itemCount - 1),
borderRadius: 7,
clockwise: false,
color: Theme.of(context).colorScheme.primary,
),
),
),
Padding(
padding: const EdgeInsets.all(9),
child: Row(
children: [
Text(
"${widget.currentIndex + 1} / ${widget.loadingMoreItems ? "-" : "${widget.itemCount}"} ",
style:
Theme.of(context).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.bold),
),
),
),
Padding(
padding: const EdgeInsets.all(9),
child: Row(
children: [
Text(
"${widget.currentIndex + 1} / ${widget.loadingMoreItems ? "-" : "${widget.itemCount}"} ",
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(fontWeight: FontWeight.bold),
),
if (widget.loadingMoreItems)
const SizedBox.square(
dimension: 16,
child: CircularProgressIndicator.adaptive(
strokeCap: StrokeCap.round,
),
if (widget.loadingMoreItems)
const SizedBox.square(
dimension: 16,
child: CircularProgressIndicator.adaptive(
strokeCap: StrokeCap.round,
),
].addInBetween(const SizedBox(width: 6)),
),
),
].addInBetween(const SizedBox(width: 6)),
),
Positioned.fill(
child: FlatButton(
borderRadiusGeometry: BorderRadius.circular(8),
onTap: () async {
showDialog(
context: context,
builder: (context) => Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
child: SizedBox(
width: 125,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
context.localized.goTo,
style: Theme.of(context)
.textTheme
.bodyLarge
?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 5),
IntInputField(
controller: TextEditingController(
text: (widget.currentIndex + 1).toString()),
onSubmitted: (value) {
final position =
((value ?? 0) - 1).clamp(0, widget.itemCount - 1);
widget.pageController.jumpToPage(position);
Navigator.of(context).pop();
},
),
],
),
),
Positioned.fill(
child: FlatButton(
borderRadiusGeometry: BorderRadius.circular(8),
onTap: () async {
showDialog(
context: context,
builder: (context) => Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
child: SizedBox(
width: 125,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
context.localized.goTo,
style: Theme.of(context)
.textTheme
.bodyLarge
?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 5),
IntInputField(
controller:
TextEditingController(text: (widget.currentIndex + 1).toString()),
onSubmitted: (value) {
final position = ((value ?? 0) - 1).clamp(0, widget.itemCount - 1);
widget.pageController.jumpToPage(position);
Navigator.of(context).pop();
},
),
],
),
),
),
);
},
),
)
],
),
const SizedBox(width: 12),
],
),
),
);
},
),
)
],
),
const SizedBox(width: 12),
],
),
],
),
),
],
),
),
),
Align(
alignment: Alignment.bottomCenter,
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: gradient.reversed.toList(),
),
),
Align(
alignment: Alignment.bottomCenter,
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: gradient.reversed.toList(),
),
width: double.infinity,
child: Padding(
padding: EdgeInsets.only(bottom: widget.padding.bottom),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding:
const EdgeInsets.all(8.0).add(EdgeInsets.only(left: padding.left, right: padding.right)),
child: Row(
children: [
ElevatedIconButton(
onPressed: widget.openOptions,
icon: IconsaxOutline.more_2,
),
const Spacer(),
ElevatedIconButton(
onPressed: markAsFavourite,
color: widget.photo.userData.isFavourite ? Colors.red : null,
icon: widget.photo.userData.isFavourite ? IconsaxBold.heart : IconsaxOutline.heart,
),
ProgressFloatingButton(
controller: timerController,
),
].addPadding(const EdgeInsets.symmetric(horizontal: 8)),
),
)
],
),
),
width: double.infinity,
child: Padding(
padding: EdgeInsets.only(bottom: widget.padding.bottom),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.all(8.0).add(EdgeInsets.only(left: padding.left, right: padding.right)),
child: Row(
children: [
ElevatedIconButton(
onPressed: widget.openOptions,
icon: IconsaxOutline.more_2,
),
const Spacer(),
ElevatedIconButton(
onPressed: markAsFavourite,
color: widget.photo.userData.isFavourite ? Colors.red : null,
icon: widget.photo.userData.isFavourite ? IconsaxBold.heart : IconsaxOutline.heart,
),
ProgressFloatingButton(
controller: timerController,
),
].addPadding(const EdgeInsets.symmetric(horizontal: 8)),
),
)
],
),
),
),
],
),
),
],
),
);
}