mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-08 23:18:16 -07:00
fix: Improve keyboard input handling (#102)
Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
This commit is contained in:
parent
2038847552
commit
76ac1aaa5b
7 changed files with 584 additions and 571 deletions
|
|
@ -16,6 +16,7 @@ import 'package:fladder/screens/book_viewer/book_viewer_settings.dart';
|
||||||
import 'package:fladder/screens/shared/default_titlebar.dart';
|
import 'package:fladder/screens/shared/default_titlebar.dart';
|
||||||
import 'package:fladder/screens/shared/fladder_snackbar.dart';
|
import 'package:fladder/screens/shared/fladder_snackbar.dart';
|
||||||
import 'package:fladder/util/adaptive_layout.dart';
|
import 'package:fladder/util/adaptive_layout.dart';
|
||||||
|
import 'package:fladder/util/input_handler.dart';
|
||||||
import 'package:fladder/util/throttler.dart';
|
import 'package:fladder/util/throttler.dart';
|
||||||
import 'package:fladder/widgets/shared/fladder_slider.dart';
|
import 'package:fladder/widgets/shared/fladder_slider.dart';
|
||||||
|
|
||||||
|
|
@ -75,12 +76,10 @@ class _BookViewerControlsState extends ConsumerState<BookViewerControls> {
|
||||||
viewController.visibilityChanged.addListener(() {
|
viewController.visibilityChanged.addListener(() {
|
||||||
toggleControls(value: viewController.controlsVisible);
|
toggleControls(value: viewController.controlsVisible);
|
||||||
});
|
});
|
||||||
ServicesBinding.instance.keyboard.addHandler(_onKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
ServicesBinding.instance.keyboard.removeHandler(_onKey);
|
|
||||||
WakelockPlus.disable();
|
WakelockPlus.disable();
|
||||||
ScreenBrightness().resetScreenBrightness();
|
ScreenBrightness().resetScreenBrightness();
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge, overlays: []);
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge, overlays: []);
|
||||||
|
|
@ -134,230 +133,233 @@ class _BookViewerControlsState extends ConsumerState<BookViewerControls> {
|
||||||
|
|
||||||
return MediaQuery.removePadding(
|
return MediaQuery.removePadding(
|
||||||
context: context,
|
context: context,
|
||||||
child: Stack(
|
child: InputHandler(
|
||||||
children: [
|
onKeyEvent: (node, event) => _onKey(event) ? KeyEventResult.handled : KeyEventResult.ignored,
|
||||||
IgnorePointer(
|
child: Stack(
|
||||||
ignoring: !showControls,
|
children: [
|
||||||
child: AnimatedOpacity(
|
IgnorePointer(
|
||||||
duration: const Duration(milliseconds: 500),
|
ignoring: !showControls,
|
||||||
opacity: showControls ? 1 : 0,
|
child: AnimatedOpacity(
|
||||||
child: Stack(
|
duration: const Duration(milliseconds: 500),
|
||||||
children: [
|
opacity: showControls ? 1 : 0,
|
||||||
Container(
|
child: Stack(
|
||||||
decoration: BoxDecoration(
|
children: [
|
||||||
gradient: LinearGradient(
|
Container(
|
||||||
begin: Alignment.topCenter,
|
decoration: BoxDecoration(
|
||||||
end: Alignment.bottomCenter,
|
gradient: LinearGradient(
|
||||||
colors: [
|
begin: Alignment.topCenter,
|
||||||
overlayColor.withOpacity(1),
|
end: Alignment.bottomCenter,
|
||||||
overlayColor.withOpacity(0.65),
|
colors: [
|
||||||
overlayColor.withOpacity(0),
|
overlayColor.withOpacity(1),
|
||||||
],
|
overlayColor.withOpacity(0.65),
|
||||||
|
overlayColor.withOpacity(0),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
child: Padding(
|
||||||
child: Padding(
|
padding: EdgeInsets.only(top: topPadding).copyWith(bottom: 8),
|
||||||
padding: EdgeInsets.only(top: topPadding).copyWith(bottom: 8),
|
child: Column(
|
||||||
child: Column(
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
children: [
|
||||||
children: [
|
if (AdaptiveLayout.of(context).isDesktop)
|
||||||
if (AdaptiveLayout.of(context).isDesktop)
|
const Flexible(
|
||||||
const Flexible(
|
child: DefaultTitleBar(
|
||||||
child: DefaultTitleBar(
|
height: 50,
|
||||||
height: 50,
|
brightness: Brightness.dark,
|
||||||
brightness: Brightness.dark,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
const BackButton(),
|
|
||||||
const SizedBox(
|
|
||||||
width: 16,
|
|
||||||
),
|
|
||||||
Flexible(
|
|
||||||
child: Text(
|
|
||||||
bookViewerDetails.book?.name ?? "None",
|
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
Row(
|
||||||
),
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
const SizedBox(height: 16),
|
children: [
|
||||||
],
|
const BackButton(),
|
||||||
),
|
const SizedBox(
|
||||||
),
|
width: 16,
|
||||||
),
|
),
|
||||||
if (!bookViewerDetails.loading) ...{
|
Flexible(
|
||||||
if (bookViewerDetails.book != null && bookViewerDetails.pages.isNotEmpty) ...{
|
child: Text(
|
||||||
Align(
|
bookViewerDetails.book?.name ?? "None",
|
||||||
alignment: Alignment.bottomCenter,
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
child: Container(
|
),
|
||||||
decoration: BoxDecoration(
|
)
|
||||||
gradient: LinearGradient(
|
|
||||||
begin: Alignment.topCenter,
|
|
||||||
end: Alignment.bottomCenter,
|
|
||||||
colors: [
|
|
||||||
overlayColor.withOpacity(0),
|
|
||||||
overlayColor.withOpacity(0.65),
|
|
||||||
overlayColor.withOpacity(1),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 16),
|
||||||
child: Padding(
|
],
|
||||||
padding: EdgeInsets.only(bottom: bottomPadding).copyWith(top: 16, bottom: 16),
|
),
|
||||||
child: Column(
|
),
|
||||||
mainAxisSize: MainAxisSize.min,
|
),
|
||||||
children: [
|
if (!bookViewerDetails.loading) ...{
|
||||||
const SizedBox(height: 30),
|
if (bookViewerDetails.book != null && bookViewerDetails.pages.isNotEmpty) ...{
|
||||||
Row(
|
Align(
|
||||||
children: [
|
alignment: Alignment.bottomCenter,
|
||||||
const SizedBox(width: 8),
|
child: Container(
|
||||||
Tooltip(
|
decoration: BoxDecoration(
|
||||||
message: bookViewerSettings.readDirection == ReadDirection.leftToRight
|
gradient: LinearGradient(
|
||||||
? previousChapter?.name != null
|
begin: Alignment.topCenter,
|
||||||
? "Load ${previousChapter?.name}"
|
end: Alignment.bottomCenter,
|
||||||
: ""
|
colors: [
|
||||||
: nextChapter?.name != null
|
overlayColor.withOpacity(0),
|
||||||
? "Load ${nextChapter?.name}"
|
overlayColor.withOpacity(0.65),
|
||||||
: "",
|
overlayColor.withOpacity(1),
|
||||||
child: IconButton.filled(
|
],
|
||||||
onPressed: bookViewerSettings.readDirection == ReadDirection.leftToRight
|
),
|
||||||
? previousChapter != null
|
),
|
||||||
? () async => await loadNextBook(previousChapter)
|
child: Padding(
|
||||||
: null
|
padding: EdgeInsets.only(bottom: bottomPadding).copyWith(top: 16, bottom: 16),
|
||||||
: nextChapter != null
|
child: Column(
|
||||||
? () async => await loadNextBook(nextChapter)
|
mainAxisSize: MainAxisSize.min,
|
||||||
: null,
|
children: [
|
||||||
icon: const Icon(IconsaxOutline.backward),
|
const SizedBox(height: 30),
|
||||||
),
|
Row(
|
||||||
),
|
children: [
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Flexible(
|
Tooltip(
|
||||||
child: Container(
|
message: bookViewerSettings.readDirection == ReadDirection.leftToRight
|
||||||
decoration: BoxDecoration(
|
? previousChapter?.name != null
|
||||||
color: Colors.black.withOpacity(0.7),
|
? "Load ${previousChapter?.name}"
|
||||||
borderRadius: BorderRadius.circular(60),
|
: ""
|
||||||
|
: nextChapter?.name != null
|
||||||
|
? "Load ${nextChapter?.name}"
|
||||||
|
: "",
|
||||||
|
child: IconButton.filled(
|
||||||
|
onPressed: bookViewerSettings.readDirection == ReadDirection.leftToRight
|
||||||
|
? previousChapter != null
|
||||||
|
? () async => await loadNextBook(previousChapter)
|
||||||
|
: null
|
||||||
|
: nextChapter != null
|
||||||
|
? () async => await loadNextBook(nextChapter)
|
||||||
|
: null,
|
||||||
|
icon: const Icon(IconsaxOutline.backward),
|
||||||
),
|
),
|
||||||
child: Padding(
|
),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
const SizedBox(width: 8),
|
||||||
child: Row(
|
Flexible(
|
||||||
children: [
|
child: Container(
|
||||||
if (bookViewerSettings.readDirection == ReadDirection.leftToRight)
|
decoration: BoxDecoration(
|
||||||
...controls(currentPage, bookViewerSettings, bookViewerDetails)
|
color: Colors.black.withOpacity(0.7),
|
||||||
else
|
borderRadius: BorderRadius.circular(60),
|
||||||
...controls(currentPage, bookViewerSettings, bookViewerDetails)
|
),
|
||||||
.reversed,
|
child: Padding(
|
||||||
],
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
if (bookViewerSettings.readDirection == ReadDirection.leftToRight)
|
||||||
|
...controls(currentPage, bookViewerSettings, bookViewerDetails)
|
||||||
|
else
|
||||||
|
...controls(currentPage, bookViewerSettings, bookViewerDetails)
|
||||||
|
.reversed,
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: 8),
|
||||||
const SizedBox(width: 8),
|
Tooltip(
|
||||||
Tooltip(
|
message: bookViewerSettings.readDirection == ReadDirection.leftToRight
|
||||||
message: bookViewerSettings.readDirection == ReadDirection.leftToRight
|
? nextChapter?.name != null
|
||||||
? nextChapter?.name != null
|
? "Load ${nextChapter?.name}"
|
||||||
? "Load ${nextChapter?.name}"
|
: ""
|
||||||
: ""
|
: previousChapter?.name != null
|
||||||
: previousChapter?.name != null
|
? "Load ${previousChapter?.name}"
|
||||||
? "Load ${previousChapter?.name}"
|
: "",
|
||||||
: "",
|
child: IconButton.filled(
|
||||||
child: IconButton.filled(
|
onPressed: bookViewerSettings.readDirection == ReadDirection.leftToRight
|
||||||
onPressed: bookViewerSettings.readDirection == ReadDirection.leftToRight
|
? nextChapter != null
|
||||||
? nextChapter != null
|
? () async => await loadNextBook(nextChapter)
|
||||||
? () async => await loadNextBook(nextChapter)
|
: null
|
||||||
: null
|
: previousChapter != null
|
||||||
: previousChapter != null
|
? () async => await loadNextBook(previousChapter)
|
||||||
? () async => await loadNextBook(previousChapter)
|
: null,
|
||||||
: null,
|
icon: const Icon(IconsaxOutline.forward),
|
||||||
icon: const Icon(IconsaxOutline.forward),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: 8),
|
||||||
const SizedBox(width: 8),
|
],
|
||||||
],
|
),
|
||||||
),
|
const SizedBox(height: 16),
|
||||||
const SizedBox(height: 16),
|
Row(
|
||||||
Row(
|
mainAxisSize: MainAxisSize.max,
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
children: [
|
||||||
children: [
|
Transform.flip(
|
||||||
Transform.flip(
|
flipX: bookViewerSettings.readDirection == ReadDirection.rightToLeft,
|
||||||
flipX: bookViewerSettings.readDirection == ReadDirection.rightToLeft,
|
child: IconButton(
|
||||||
child: IconButton(
|
onPressed: () => widget.controller
|
||||||
onPressed: () => widget.controller
|
.animateToPage(1, duration: pageAnimDuration, curve: pageAnimCurve),
|
||||||
.animateToPage(1, duration: pageAnimDuration, curve: pageAnimCurve),
|
icon: const Icon(IconsaxOutline.backward)),
|
||||||
icon: const Icon(IconsaxOutline.backward)),
|
),
|
||||||
),
|
IconButton(
|
||||||
IconButton(
|
onPressed: () {
|
||||||
onPressed: () {
|
showBookViewerSettings(context);
|
||||||
showBookViewerSettings(context);
|
},
|
||||||
},
|
icon: const Icon(IconsaxOutline.setting_2),
|
||||||
icon: const Icon(IconsaxOutline.setting_2),
|
),
|
||||||
),
|
IconButton(
|
||||||
IconButton(
|
onPressed: chapters.length > 1
|
||||||
onPressed: chapters.length > 1
|
? () {
|
||||||
? () {
|
showBookViewerChapters(
|
||||||
showBookViewerChapters(
|
context,
|
||||||
context,
|
widget.provider,
|
||||||
widget.provider,
|
onPressed: (book) async {
|
||||||
onPressed: (book) async {
|
Navigator.of(context).pop();
|
||||||
Navigator.of(context).pop();
|
loadNextBook(book);
|
||||||
loadNextBook(book);
|
},
|
||||||
},
|
);
|
||||||
);
|
}
|
||||||
}
|
: () => fladderSnackbar(context, title: "No other chapters"),
|
||||||
: () => fladderSnackbar(context, title: "No other chapters"),
|
icon: const Icon(IconsaxOutline.bookmark_2),
|
||||||
icon: const Icon(IconsaxOutline.bookmark_2),
|
)
|
||||||
)
|
],
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
} else
|
||||||
} else
|
const Center(
|
||||||
const Center(
|
child: Card(
|
||||||
child: Card(
|
child: Padding(
|
||||||
child: Padding(
|
padding: EdgeInsets.all(8.0),
|
||||||
padding: EdgeInsets.all(8.0),
|
child: Row(
|
||||||
child: Row(
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
children: [
|
||||||
children: [
|
Icon(Icons.menu_book_rounded),
|
||||||
Icon(Icons.menu_book_rounded),
|
SizedBox(width: 8),
|
||||||
SizedBox(width: 8),
|
Text("Unable to load book"),
|
||||||
Text("Unable to load book"),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
)
|
},
|
||||||
},
|
],
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (bookViewerDetails.loading)
|
|
||||||
Center(
|
|
||||||
child: Card(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
if (bookViewerDetails.book != null) ...{
|
|
||||||
Flexible(
|
|
||||||
child: Text("Loading ${bookViewerDetails.book?.name}",
|
|
||||||
style: Theme.of(context).textTheme.titleMedium),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
},
|
|
||||||
const CircularProgressIndicator.adaptive(strokeCap: StrokeCap.round),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
if (bookViewerDetails.loading)
|
||||||
|
Center(
|
||||||
|
child: Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
if (bookViewerDetails.book != null) ...{
|
||||||
|
Flexible(
|
||||||
|
child: Text("Loading ${bookViewerDetails.book?.name}",
|
||||||
|
style: Theme.of(context).textTheme.titleMedium),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
},
|
||||||
|
const CircularProgressIndicator.adaptive(strokeCap: StrokeCap.round),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import 'package:fladder/providers/user_provider.dart';
|
||||||
import 'package:fladder/screens/shared/flat_button.dart';
|
import 'package:fladder/screens/shared/flat_button.dart';
|
||||||
import 'package:fladder/screens/shared/input_fields.dart';
|
import 'package:fladder/screens/shared/input_fields.dart';
|
||||||
import 'package:fladder/util/adaptive_layout.dart';
|
import 'package:fladder/util/adaptive_layout.dart';
|
||||||
|
import 'package:fladder/util/input_handler.dart';
|
||||||
import 'package:fladder/util/list_padding.dart';
|
import 'package:fladder/util/list_padding.dart';
|
||||||
import 'package:fladder/util/localization_helper.dart';
|
import 'package:fladder/util/localization_helper.dart';
|
||||||
import 'package:fladder/util/throttler.dart';
|
import 'package:fladder/util/throttler.dart';
|
||||||
|
|
@ -99,7 +100,7 @@ class _PhotoViewerControllsState extends ConsumerState<PhotoViewerControls> with
|
||||||
timerController.playPause();
|
timerController.playPause();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (value.logicalKey == LogicalKeyboardKey.keyF) {
|
if (value.logicalKey == LogicalKeyboardKey.space) {
|
||||||
widget.toggleOverlay?.call(null);
|
widget.toggleOverlay?.call(null);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -117,12 +118,10 @@ class _PhotoViewerControllsState extends ConsumerState<PhotoViewerControls> with
|
||||||
timerController.reset();
|
timerController.reset();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
ServicesBinding.instance.keyboard.addHandler(_onKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onWindowMinimize() {
|
void onWindowMinimize() {
|
||||||
ServicesBinding.instance.keyboard.removeHandler(_onKey);
|
|
||||||
timerController.cancel();
|
timerController.cancel();
|
||||||
super.onWindowMinimize();
|
super.onWindowMinimize();
|
||||||
}
|
}
|
||||||
|
|
@ -145,192 +144,197 @@ class _PhotoViewerControllsState extends ConsumerState<PhotoViewerControls> with
|
||||||
|
|
||||||
final padding = MediaQuery.of(context).padding;
|
final padding = MediaQuery.of(context).padding;
|
||||||
return PopScope(
|
return PopScope(
|
||||||
onPopInvokedWithResult: (didPop, result) async {
|
onPopInvokedWithResult: (didPop, result) async => await WakelockPlus.disable(),
|
||||||
await WakelockPlus.disable();
|
child: InputHandler(
|
||||||
},
|
onKeyEvent: (node, event) => _onKey(event) ? KeyEventResult.handled : KeyEventResult.ignored,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
widthFactor: 1,
|
widthFactor: 1,
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
begin: Alignment.topCenter,
|
begin: Alignment.topCenter,
|
||||||
end: Alignment.bottomCenter,
|
end: Alignment.bottomCenter,
|
||||||
colors: gradient,
|
colors: gradient,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
child: Padding(
|
||||||
child: Padding(
|
padding: EdgeInsets.only(top: widget.padding.top),
|
||||||
padding: EdgeInsets.only(top: widget.padding.top),
|
child: Column(
|
||||||
child: Column(
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
children: [
|
||||||
children: [
|
if (AdaptiveLayout.of(context).isDesktop) const SizedBox(height: 25),
|
||||||
if (AdaptiveLayout.of(context).isDesktop) const SizedBox(height: 25),
|
Padding(
|
||||||
Padding(
|
padding: const EdgeInsets.symmetric(vertical: 12)
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12)
|
.add(EdgeInsets.only(left: padding.left, right: padding.right)),
|
||||||
.add(EdgeInsets.only(left: padding.left, right: padding.right)),
|
child: Row(
|
||||||
child: Row(
|
mainAxisSize: MainAxisSize.max,
|
||||||
mainAxisSize: MainAxisSize.max,
|
children: [
|
||||||
children: [
|
ElevatedIconButton(
|
||||||
ElevatedIconButton(
|
onPressed: () => Navigator.of(context).pop(widget.pageController.page?.toInt()),
|
||||||
onPressed: () => Navigator.of(context).pop(widget.pageController.page?.toInt()),
|
icon: getBackIcon(context),
|
||||||
icon: getBackIcon(context),
|
),
|
||||||
),
|
const SizedBox(width: 8),
|
||||||
const SizedBox(width: 8),
|
Expanded(
|
||||||
Expanded(
|
child: Tooltip(
|
||||||
child: Tooltip(
|
message: widget.photo.name,
|
||||||
message: widget.photo.name,
|
child: Text(
|
||||||
child: Text(
|
widget.photo.name,
|
||||||
widget.photo.name,
|
maxLines: 2,
|
||||||
maxLines: 2,
|
style: Theme.of(context)
|
||||||
style: Theme.of(context)
|
.textTheme
|
||||||
.textTheme
|
.titleMedium
|
||||||
.titleMedium
|
?.copyWith(fontWeight: FontWeight.bold, shadows: [
|
||||||
?.copyWith(fontWeight: FontWeight.bold, shadows: [
|
BoxShadow(blurRadius: 1, spreadRadius: 1, color: Colors.black.withOpacity(0.7)),
|
||||||
BoxShadow(blurRadius: 1, spreadRadius: 1, color: Colors.black.withOpacity(0.7)),
|
BoxShadow(blurRadius: 4, spreadRadius: 4, color: Colors.black.withOpacity(0.4)),
|
||||||
BoxShadow(blurRadius: 4, spreadRadius: 4, color: Colors.black.withOpacity(0.4)),
|
BoxShadow(blurRadius: 20, spreadRadius: 6, color: Colors.black.withOpacity(0.2)),
|
||||||
BoxShadow(blurRadius: 20, spreadRadius: 6, color: Colors.black.withOpacity(0.2)),
|
]),
|
||||||
]),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: 8),
|
||||||
const SizedBox(width: 8),
|
Stack(
|
||||||
Stack(
|
children: [
|
||||||
children: [
|
Positioned.fill(
|
||||||
Positioned.fill(
|
child: Container(
|
||||||
child: Container(
|
decoration: BoxDecoration(
|
||||||
decoration: BoxDecoration(
|
borderRadius: BorderRadius.circular(8),
|
||||||
borderRadius: BorderRadius.circular(8),
|
color: Theme.of(context).colorScheme.onPrimary),
|
||||||
color: Theme.of(context).colorScheme.onPrimary),
|
child: SquareProgressIndicator(
|
||||||
child: SquareProgressIndicator(
|
value: widget.currentIndex / (widget.itemCount - 1),
|
||||||
value: widget.currentIndex / (widget.itemCount - 1),
|
borderRadius: 7,
|
||||||
borderRadius: 7,
|
clockwise: false,
|
||||||
clockwise: false,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Padding(
|
||||||
Padding(
|
padding: const EdgeInsets.all(9),
|
||||||
padding: const EdgeInsets.all(9),
|
child: Row(
|
||||||
child: Row(
|
children: [
|
||||||
children: [
|
Text(
|
||||||
Text(
|
"${widget.currentIndex + 1} / ${widget.loadingMoreItems ? "-" : "${widget.itemCount}"} ",
|
||||||
"${widget.currentIndex + 1} / ${widget.loadingMoreItems ? "-" : "${widget.itemCount}"} ",
|
style: Theme.of(context)
|
||||||
style:
|
.textTheme
|
||||||
Theme.of(context).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.bold),
|
.bodyMedium
|
||||||
),
|
?.copyWith(fontWeight: FontWeight.bold),
|
||||||
if (widget.loadingMoreItems)
|
|
||||||
const SizedBox.square(
|
|
||||||
dimension: 16,
|
|
||||||
child: CircularProgressIndicator.adaptive(
|
|
||||||
strokeCap: StrokeCap.round,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
].addInBetween(const SizedBox(width: 6)),
|
if (widget.loadingMoreItems)
|
||||||
|
const SizedBox.square(
|
||||||
|
dimension: 16,
|
||||||
|
child: CircularProgressIndicator.adaptive(
|
||||||
|
strokeCap: StrokeCap.round,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
].addInBetween(const SizedBox(width: 6)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
Positioned.fill(
|
||||||
Positioned.fill(
|
child: FlatButton(
|
||||||
child: FlatButton(
|
borderRadiusGeometry: BorderRadius.circular(8),
|
||||||
borderRadiusGeometry: BorderRadius.circular(8),
|
onTap: () async {
|
||||||
onTap: () async {
|
showDialog(
|
||||||
showDialog(
|
context: context,
|
||||||
context: context,
|
builder: (context) => Dialog(
|
||||||
builder: (context) => Dialog(
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
child: SizedBox(
|
||||||
child: SizedBox(
|
width: 125,
|
||||||
width: 125,
|
child: Padding(
|
||||||
child: Padding(
|
padding: const EdgeInsets.all(8.0),
|
||||||
padding: const EdgeInsets.all(8.0),
|
child: Column(
|
||||||
child: Column(
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
children: [
|
||||||
children: [
|
Text(
|
||||||
Text(
|
context.localized.goTo,
|
||||||
context.localized.goTo,
|
style: Theme.of(context)
|
||||||
style: Theme.of(context)
|
.textTheme
|
||||||
.textTheme
|
.bodyLarge
|
||||||
.bodyLarge
|
?.copyWith(fontWeight: FontWeight.bold),
|
||||||
?.copyWith(fontWeight: FontWeight.bold),
|
),
|
||||||
),
|
const SizedBox(height: 5),
|
||||||
const SizedBox(height: 5),
|
IntInputField(
|
||||||
IntInputField(
|
controller: TextEditingController(
|
||||||
controller:
|
text: (widget.currentIndex + 1).toString()),
|
||||||
TextEditingController(text: (widget.currentIndex + 1).toString()),
|
onSubmitted: (value) {
|
||||||
onSubmitted: (value) {
|
final position =
|
||||||
final position = ((value ?? 0) - 1).clamp(0, widget.itemCount - 1);
|
((value ?? 0) - 1).clamp(0, widget.itemCount - 1);
|
||||||
widget.pageController.jumpToPage(position);
|
widget.pageController.jumpToPage(position);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
},
|
||||||
},
|
),
|
||||||
),
|
)
|
||||||
)
|
],
|
||||||
],
|
),
|
||||||
),
|
const SizedBox(width: 12),
|
||||||
const SizedBox(width: 12),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Align(
|
||||||
Align(
|
alignment: Alignment.bottomCenter,
|
||||||
alignment: Alignment.bottomCenter,
|
child: Container(
|
||||||
child: Container(
|
decoration: BoxDecoration(
|
||||||
decoration: BoxDecoration(
|
gradient: LinearGradient(
|
||||||
gradient: LinearGradient(
|
begin: Alignment.topCenter,
|
||||||
begin: Alignment.topCenter,
|
end: Alignment.bottomCenter,
|
||||||
end: Alignment.bottomCenter,
|
colors: gradient.reversed.toList(),
|
||||||
colors: gradient.reversed.toList(),
|
),
|
||||||
),
|
),
|
||||||
),
|
width: double.infinity,
|
||||||
width: double.infinity,
|
child: Padding(
|
||||||
child: Padding(
|
padding: EdgeInsets.only(bottom: widget.padding.bottom),
|
||||||
padding: EdgeInsets.only(bottom: widget.padding.bottom),
|
child: Column(
|
||||||
child: Column(
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
children: [
|
||||||
children: [
|
Padding(
|
||||||
Padding(
|
padding:
|
||||||
padding: const EdgeInsets.all(8.0).add(EdgeInsets.only(left: padding.left, right: padding.right)),
|
const EdgeInsets.all(8.0).add(EdgeInsets.only(left: padding.left, right: padding.right)),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
ElevatedIconButton(
|
ElevatedIconButton(
|
||||||
onPressed: widget.openOptions,
|
onPressed: widget.openOptions,
|
||||||
icon: IconsaxOutline.more_2,
|
icon: IconsaxOutline.more_2,
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
ElevatedIconButton(
|
ElevatedIconButton(
|
||||||
onPressed: markAsFavourite,
|
onPressed: markAsFavourite,
|
||||||
color: widget.photo.userData.isFavourite ? Colors.red : null,
|
color: widget.photo.userData.isFavourite ? Colors.red : null,
|
||||||
icon: widget.photo.userData.isFavourite ? IconsaxBold.heart : IconsaxOutline.heart,
|
icon: widget.photo.userData.isFavourite ? IconsaxBold.heart : IconsaxOutline.heart,
|
||||||
),
|
),
|
||||||
ProgressFloatingButton(
|
ProgressFloatingButton(
|
||||||
controller: timerController,
|
controller: timerController,
|
||||||
onLongPress: (duration) {
|
onLongPress: (duration) {
|
||||||
if (duration != null) {
|
if (duration != null) {
|
||||||
ref
|
ref
|
||||||
.read(photoViewSettingsProvider.notifier)
|
.read(photoViewSettingsProvider.notifier)
|
||||||
.update((state) => state.copyWith(timer: duration));
|
.update((state) => state.copyWith(timer: duration));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
].addPadding(const EdgeInsets.symmetric(horizontal: 8)),
|
].addPadding(const EdgeInsets.symmetric(horizontal: 8)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import 'package:fladder/screens/shared/animated_fade_size.dart';
|
import 'package:fladder/screens/shared/animated_fade_size.dart';
|
||||||
|
import 'package:fladder/util/input_handler.dart';
|
||||||
import 'package:fladder/util/list_padding.dart';
|
import 'package:fladder/util/list_padding.dart';
|
||||||
|
|
||||||
class PassCodeInput extends ConsumerStatefulWidget {
|
class PassCodeInput extends ConsumerStatefulWidget {
|
||||||
|
|
@ -19,18 +20,6 @@ class _PassCodeInputState extends ConsumerState<PassCodeInput> {
|
||||||
final passCodeLength = 4;
|
final passCodeLength = 4;
|
||||||
var currentPasscode = "";
|
var currentPasscode = "";
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
ServicesBinding.instance.keyboard.addHandler(_onKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
ServicesBinding.instance.keyboard.removeHandler(_onKey);
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _onKey(KeyEvent value) {
|
bool _onKey(KeyEvent value) {
|
||||||
if (value is KeyDownEvent) {
|
if (value is KeyDownEvent) {
|
||||||
final keyInt = int.tryParse(value.logicalKey.keyLabel);
|
final keyInt = int.tryParse(value.logicalKey.keyLabel);
|
||||||
|
|
@ -48,60 +37,63 @@ class _PassCodeInputState extends ConsumerState<PassCodeInput> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AlertDialog(
|
return InputHandler(
|
||||||
scrollable: true,
|
onKeyEvent: (node, event) => _onKey(event) ? KeyEventResult.handled : KeyEventResult.ignored,
|
||||||
content: Column(
|
child: AlertDialog(
|
||||||
mainAxisSize: MainAxisSize.min,
|
scrollable: true,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
content: Column(
|
||||||
children: [
|
mainAxisSize: MainAxisSize.min,
|
||||||
Row(
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
mainAxisSize: MainAxisSize.max,
|
children: [
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
Row(
|
||||||
children: List.generate(
|
mainAxisSize: MainAxisSize.max,
|
||||||
passCodeLength,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
(index) => Expanded(
|
children: List.generate(
|
||||||
child: Padding(
|
passCodeLength,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
(index) => Expanded(
|
||||||
child: SizedBox(
|
child: Padding(
|
||||||
height: iconSize * 1.2,
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
width: iconSize * 1.2,
|
child: SizedBox(
|
||||||
child: Card(
|
height: iconSize * 1.2,
|
||||||
child: Transform.translate(
|
width: iconSize * 1.2,
|
||||||
offset: const Offset(0, 5),
|
child: Card(
|
||||||
child: AnimatedFadeSize(
|
child: Transform.translate(
|
||||||
child: Text(
|
offset: const Offset(0, 5),
|
||||||
currentPasscode.length > index ? "*" : "",
|
child: AnimatedFadeSize(
|
||||||
style: Theme.of(context).textTheme.displayLarge?.copyWith(fontSize: 60),
|
child: Text(
|
||||||
|
currentPasscode.length > index ? "*" : "",
|
||||||
|
style: Theme.of(context).textTheme.displayLarge?.copyWith(fontSize: 60),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
).toList(),
|
||||||
).toList(),
|
),
|
||||||
),
|
Row(
|
||||||
Row(
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
children: List.of([1, 2, 3]).map((e) => passCodeNumber(e)).toList(),
|
||||||
children: List.of([1, 2, 3]).map((e) => passCodeNumber(e)).toList(),
|
),
|
||||||
),
|
Row(
|
||||||
Row(
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
children: List.of([4, 5, 6]).map((e) => passCodeNumber(e)).toList(),
|
||||||
children: List.of([4, 5, 6]).map((e) => passCodeNumber(e)).toList(),
|
),
|
||||||
),
|
Row(
|
||||||
Row(
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
children: List.of([7, 8, 9]).map((e) => passCodeNumber(e)).toList(),
|
||||||
children: List.of([7, 8, 9]).map((e) => passCodeNumber(e)).toList(),
|
),
|
||||||
),
|
Row(
|
||||||
Row(
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
children: [
|
||||||
children: [
|
backSpaceButton,
|
||||||
backSpaceButton,
|
passCodeNumber(0),
|
||||||
passCodeNumber(0),
|
clearAllButton,
|
||||||
clearAllButton,
|
],
|
||||||
],
|
)
|
||||||
)
|
].addPadding(const EdgeInsets.symmetric(vertical: 8)),
|
||||||
].addPadding(const EdgeInsets.symmetric(vertical: 8)),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import 'package:async/async.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import 'package:fladder/providers/video_player_provider.dart';
|
import 'package:fladder/providers/video_player_provider.dart';
|
||||||
|
import 'package:fladder/util/input_handler.dart';
|
||||||
import 'package:fladder/util/localization_helper.dart';
|
import 'package:fladder/util/localization_helper.dart';
|
||||||
|
|
||||||
class VideoPlayerSeekIndicator extends ConsumerStatefulWidget {
|
class VideoPlayerSeekIndicator extends ConsumerStatefulWidget {
|
||||||
|
|
@ -20,18 +21,6 @@ class _VideoPlayerSeekIndicatorState extends ConsumerState<VideoPlayerSeekIndica
|
||||||
bool visible = false;
|
bool visible = false;
|
||||||
int seekPosition = 0;
|
int seekPosition = 0;
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
ServicesBinding.instance.keyboard.addHandler(_onKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
ServicesBinding.instance.keyboard.removeHandler(_onKey);
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
void onSeekEnd() {
|
void onSeekEnd() {
|
||||||
setState(() {
|
setState(() {
|
||||||
visible = false;
|
visible = false;
|
||||||
|
|
@ -46,7 +35,7 @@ class _VideoPlayerSeekIndicatorState extends ConsumerState<VideoPlayerSeekIndica
|
||||||
|
|
||||||
void onSeekStart(int value) {
|
void onSeekStart(int value) {
|
||||||
if (timer == null) {
|
if (timer == null) {
|
||||||
timer = RestartableTimer(const Duration(seconds: 2), () => onSeekEnd());
|
timer = RestartableTimer(const Duration(milliseconds: 500), () => onSeekEnd());
|
||||||
setState(() {
|
setState(() {
|
||||||
seekPosition = 0;
|
seekPosition = 0;
|
||||||
});
|
});
|
||||||
|
|
@ -85,28 +74,32 @@ class _VideoPlayerSeekIndicatorState extends ConsumerState<VideoPlayerSeekIndica
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return IgnorePointer(
|
return InputHandler(
|
||||||
child: AnimatedOpacity(
|
autoFocus: true,
|
||||||
duration: const Duration(milliseconds: 500),
|
onKeyEvent: (node, event) => _onKey(event) ? KeyEventResult.handled : KeyEventResult.ignored,
|
||||||
opacity: (visible && seekPosition != 0) ? 1 : 0,
|
child: IgnorePointer(
|
||||||
child: Center(
|
child: AnimatedOpacity(
|
||||||
child: Container(
|
duration: const Duration(milliseconds: 500),
|
||||||
decoration: BoxDecoration(
|
opacity: (visible && seekPosition != 0) ? 1 : 0,
|
||||||
color: Colors.black.withOpacity(0.85),
|
child: Center(
|
||||||
borderRadius: BorderRadius.circular(16),
|
child: Container(
|
||||||
),
|
decoration: BoxDecoration(
|
||||||
child: Padding(
|
color: Colors.black.withOpacity(0.85),
|
||||||
padding: const EdgeInsets.all(16.0),
|
borderRadius: BorderRadius.circular(16),
|
||||||
child: Row(
|
),
|
||||||
mainAxisSize: MainAxisSize.min,
|
child: Padding(
|
||||||
children: [
|
padding: const EdgeInsets.all(16.0),
|
||||||
Text(
|
child: Row(
|
||||||
seekPosition > 0
|
mainAxisSize: MainAxisSize.min,
|
||||||
? "+$seekPosition ${context.localized.seconds(seekPosition)}"
|
children: [
|
||||||
: "$seekPosition ${context.localized.seconds(seekPosition)}",
|
Text(
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
seekPosition > 0
|
||||||
)
|
? "+$seekPosition ${context.localized.seconds(seekPosition)}"
|
||||||
],
|
: "$seekPosition ${context.localized.seconds(seekPosition)}",
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,10 @@ import 'package:fladder/models/settings/subtitle_settings_model.dart';
|
||||||
|
|
||||||
class VideoSubtitles extends ConsumerStatefulWidget {
|
class VideoSubtitles extends ConsumerStatefulWidget {
|
||||||
final VideoController controller;
|
final VideoController controller;
|
||||||
final bool overlayed;
|
final bool overLayed;
|
||||||
const VideoSubtitles({
|
const VideoSubtitles({
|
||||||
required this.controller,
|
required this.controller,
|
||||||
this.overlayed = false,
|
this.overLayed = false,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -56,7 +56,7 @@ class _VideoSubtitlesState extends ConsumerState<VideoSubtitles> {
|
||||||
return SubtitleText(
|
return SubtitleText(
|
||||||
subModel: settings,
|
subModel: settings,
|
||||||
padding: padding,
|
padding: padding,
|
||||||
offset: (widget.overlayed ? 0.5 : settings.verticalOffset),
|
offset: (widget.overLayed ? 0.5 : settings.verticalOffset),
|
||||||
text: text,
|
text: text,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import 'package:fladder/screens/video_player/components/video_subtitles.dart';
|
||||||
import 'package:fladder/screens/video_player/components/video_volume_slider.dart';
|
import 'package:fladder/screens/video_player/components/video_volume_slider.dart';
|
||||||
import 'package:fladder/util/adaptive_layout.dart';
|
import 'package:fladder/util/adaptive_layout.dart';
|
||||||
import 'package:fladder/util/duration_extensions.dart';
|
import 'package:fladder/util/duration_extensions.dart';
|
||||||
|
import 'package:fladder/util/input_handler.dart';
|
||||||
import 'package:fladder/util/list_padding.dart';
|
import 'package:fladder/util/list_padding.dart';
|
||||||
import 'package:fladder/util/localization_helper.dart';
|
import 'package:fladder/util/localization_helper.dart';
|
||||||
import 'package:fladder/util/string_extensions.dart';
|
import 'package:fladder/util/string_extensions.dart';
|
||||||
|
|
@ -49,7 +50,6 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
||||||
);
|
);
|
||||||
|
|
||||||
final fadeDuration = const Duration(milliseconds: 350);
|
final fadeDuration = const Duration(milliseconds: 350);
|
||||||
final focusNode = FocusNode();
|
|
||||||
bool showOverlay = true;
|
bool showOverlay = true;
|
||||||
bool wasPlaying = false;
|
bool wasPlaying = false;
|
||||||
|
|
||||||
|
|
@ -79,7 +79,6 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
||||||
} else if (showCreditSkipButton) {
|
} else if (showCreditSkipButton) {
|
||||||
skipCredits(introSkipModel);
|
skipCredits(introSkipModel);
|
||||||
}
|
}
|
||||||
focusNode.requestFocus();
|
|
||||||
}
|
}
|
||||||
if (value.logicalKey == LogicalKeyboardKey.escape) {
|
if (value.logicalKey == LogicalKeyboardKey.escape) {
|
||||||
disableFullscreen();
|
disableFullscreen();
|
||||||
|
|
@ -103,105 +102,93 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
ServicesBinding.instance.keyboard.addHandler(_onKey);
|
|
||||||
timer.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
ServicesBinding.instance.keyboard.removeHandler(_onKey);
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final introSkipModel = ref.watch(playBackModel.select((value) => value?.introSkipModel));
|
final introSkipModel = ref.watch(playBackModel.select((value) => value?.introSkipModel));
|
||||||
final player = ref.watch(videoPlayerProvider.select((value) => value.controller));
|
final player = ref.watch(videoPlayerProvider.select((value) => value.controller));
|
||||||
if (AdaptiveLayout.of(context).isDesktop) {
|
return InputHandler(
|
||||||
focusNode.requestFocus();
|
autoFocus: false,
|
||||||
}
|
onKeyEvent: (node, event) => _onKey(event) ? KeyEventResult.handled : KeyEventResult.ignored,
|
||||||
return Listener(
|
child: Listener(
|
||||||
onPointerSignal: (event) => resetTimer(),
|
onPointerSignal: (event) => resetTimer(),
|
||||||
child: PopScope(
|
child: PopScope(
|
||||||
canPop: false,
|
canPop: false,
|
||||||
onPopInvokedWithResult: (didPop, result) {
|
onPopInvokedWithResult: (didPop, result) {
|
||||||
if (!didPop) {
|
if (!didPop) {
|
||||||
closePlayer();
|
closePlayer();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () => toggleOverlay(),
|
onTap: () => toggleOverlay(),
|
||||||
child: MouseRegion(
|
child: MouseRegion(
|
||||||
cursor: showOverlay ? SystemMouseCursors.basic : SystemMouseCursors.none,
|
cursor: showOverlay ? SystemMouseCursors.basic : SystemMouseCursors.none,
|
||||||
onEnter: (event) => toggleOverlay(value: true),
|
onEnter: (event) => toggleOverlay(value: true),
|
||||||
onExit: (event) => toggleOverlay(value: false),
|
onExit: (event) => toggleOverlay(value: false),
|
||||||
onHover: AdaptiveLayout.of(context).isDesktop || kIsWeb ? (event) => toggleOverlay(value: true) : null,
|
onHover: AdaptiveLayout.of(context).isDesktop || kIsWeb ? (event) => toggleOverlay(value: true) : null,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
if (player != null)
|
if (player != null)
|
||||||
VideoSubtitles(
|
VideoSubtitles(
|
||||||
key: const Key('subtitles'),
|
key: const Key('subtitles'),
|
||||||
controller: player,
|
controller: player,
|
||||||
overlayed: showOverlay,
|
overLayed: showOverlay,
|
||||||
),
|
),
|
||||||
if (AdaptiveLayout.of(context).isDesktop)
|
if (AdaptiveLayout.of(context).isDesktop)
|
||||||
Consumer(builder: (context, ref, child) {
|
Consumer(builder: (context, ref, child) {
|
||||||
final playing = ref.watch(mediaPlaybackProvider.select((value) => value.playing));
|
final playing = ref.watch(mediaPlaybackProvider.select((value) => value.playing));
|
||||||
final buffering = ref.watch(mediaPlaybackProvider.select((value) => value.buffering));
|
final buffering = ref.watch(mediaPlaybackProvider.select((value) => value.buffering));
|
||||||
return playButton(playing, buffering);
|
return playButton(playing, buffering);
|
||||||
}),
|
}),
|
||||||
IgnorePointer(
|
IgnorePointer(
|
||||||
ignoring: !showOverlay,
|
ignoring: !showOverlay,
|
||||||
child: AnimatedOpacity(
|
child: AnimatedOpacity(
|
||||||
duration: fadeDuration,
|
duration: fadeDuration,
|
||||||
opacity: showOverlay ? 1 : 0,
|
opacity: showOverlay ? 1 : 0,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
topButtons(context),
|
topButtons(context),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
bottomButtons(context),
|
bottomButtons(context),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const VideoPlayerSeekIndicator(),
|
||||||
const VideoPlayerSeekIndicator(),
|
Consumer(
|
||||||
Consumer(
|
builder: (context, ref, child) {
|
||||||
builder: (context, ref, child) {
|
final position = ref.watch(mediaPlaybackProvider.select((value) => value.position));
|
||||||
final position = ref.watch(mediaPlaybackProvider.select((value) => value.position));
|
bool showIntroSkipButton = introSkipModel?.introInRange(position) ?? false;
|
||||||
bool showIntroSkipButton = introSkipModel?.introInRange(position) ?? false;
|
bool showCreditSkipButton = introSkipModel?.creditsInRange(position) ?? false;
|
||||||
bool showCreditSkipButton = introSkipModel?.creditsInRange(position) ?? false;
|
return Stack(
|
||||||
return Stack(
|
children: [
|
||||||
children: [
|
if (showIntroSkipButton)
|
||||||
if (showIntroSkipButton)
|
Align(
|
||||||
Align(
|
alignment: Alignment.centerRight,
|
||||||
alignment: Alignment.centerRight,
|
child: Padding(
|
||||||
child: Padding(
|
padding: const EdgeInsets.all(32),
|
||||||
padding: const EdgeInsets.all(32),
|
child: IntroSkipButton(
|
||||||
child: IntroSkipButton(
|
isOverlayVisible: showOverlay,
|
||||||
isOverlayVisible: showOverlay,
|
skipIntro: () => skipIntro(introSkipModel),
|
||||||
skipIntro: () => skipIntro(introSkipModel),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
if (showCreditSkipButton)
|
||||||
if (showCreditSkipButton)
|
Align(
|
||||||
Align(
|
alignment: Alignment.centerRight,
|
||||||
alignment: Alignment.centerRight,
|
child: Padding(
|
||||||
child: Padding(
|
padding: const EdgeInsets.all(32),
|
||||||
padding: const EdgeInsets.all(32),
|
child: CreditsSkipButton(
|
||||||
child: CreditsSkipButton(
|
isOverlayVisible: showOverlay,
|
||||||
isOverlayVisible: showOverlay,
|
skipCredits: () => skipCredits(introSkipModel),
|
||||||
skipCredits: () => skipCredits(introSkipModel),
|
),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
)
|
],
|
||||||
],
|
);
|
||||||
);
|
},
|
||||||
},
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
35
lib/util/input_handler.dart
Normal file
35
lib/util/input_handler.dart
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class InputHandler extends StatefulWidget {
|
||||||
|
final bool autoFocus;
|
||||||
|
final KeyEventResult Function(FocusNode node, KeyEvent event)? onKeyEvent;
|
||||||
|
final Widget child;
|
||||||
|
const InputHandler({
|
||||||
|
required this.child,
|
||||||
|
this.autoFocus = true,
|
||||||
|
this.onKeyEvent,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<InputHandler> createState() => _InputHandlerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InputHandlerState extends State<InputHandler> {
|
||||||
|
final focusNode = FocusNode();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Focus(
|
||||||
|
autofocus: widget.autoFocus,
|
||||||
|
focusNode: focusNode,
|
||||||
|
onFocusChange: (value) {
|
||||||
|
if (!focusNode.hasFocus) {
|
||||||
|
focusNode.requestFocus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onKeyEvent: widget.onKeyEvent,
|
||||||
|
child: widget.child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue