Merge pull request #520 from NBA2K1/better-UX

Improved UX for Migration, Anime Playback & Navigation
This commit is contained in:
Moustapha Kodjo Amadou 2025-07-21 09:14:41 +01:00 committed by GitHub
commit 936a91403b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 125 additions and 116 deletions

View file

@ -49,7 +49,7 @@ class _DesktopControllerWidgetState
bool visible = true;
bool cursorVisible = true;
Duration controlsTransitionDuration = const Duration(milliseconds: 300);
Color backdropColor = const Color(0x66000000);
// Color backdropColor = const Color(0x66000000);
Timer? _timer;
int swipeDuration = 0; // Duration to seek in video
@ -63,6 +63,7 @@ class _DesktopControllerWidgetState
final List<StreamSubscription> subscriptions = [];
DateTime last = DateTime.now();
Timer? _tapTimer;
@override
void setState(VoidCallback fn) {
@ -98,6 +99,9 @@ class _DesktopControllerWidgetState
for (final subscription in subscriptions) {
subscription.cancel();
}
subscriptions.clear();
_timer?.cancel();
_tapTimer?.cancel();
super.dispose();
}
@ -146,8 +150,8 @@ class _DesktopControllerWidgetState
_timer?.cancel();
}
final bool modifyVolumeOnScroll = true;
final bool toggleFullscreenOnDoublePress = true;
final bool modifyVolumeOnScroll = true; // TODO. The variable is never changed
final bool toggleFullscreenOnDoublePress = true; // TODO. variable not changed
@override
Widget build(BuildContext context) {
return CallbackShortcuts(
@ -250,6 +254,14 @@ class _DesktopControllerWidgetState
}
: null,
child: GestureDetector(
onTap: () {
// use own timer with onTapUp instead of onDoubleTap.
// onDoubleTap uses 300ms which feels laggy when pausing
// https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/gestures/constants.dart#L35
_tapTimer = Timer(const Duration(milliseconds: 100), () {
widget.videoController.player.playOrPause();
});
},
onLongPressStart: (e) {
previousPlaybackSpeed =
widget.videoController.player.state.rate;
@ -274,6 +286,8 @@ class _DesktopControllerWidgetState
final difference = now.difference(last);
last = now;
if (difference < const Duration(milliseconds: 400)) {
_tapTimer?.cancel();
_tapTimer = null;
final fullScreen = widget.desktopFullScreenPlayer;
await _changeFullScreen(ref, fullScreen);
}

View file

@ -126,34 +126,10 @@ class _LibraryGridViewWidgetState extends State<LibraryGridViewWidget> {
}
},
onLongPress: () {
if (!isLongPressed) {
ref
.read(mangasListStateProvider.notifier)
.update(entry);
ref
.read(isLongPressedMangaStateProvider.notifier)
.update(!isLongPressed);
} else {
ref
.read(mangasListStateProvider.notifier)
.update(entry);
}
_handleLongOrSecondaryTap(isLongPressed, ref, entry);
},
onSecondaryTap: () {
if (!isLongPressed) {
ref
.read(mangasListStateProvider.notifier)
.update(entry);
ref
.read(isLongPressedMangaStateProvider.notifier)
.update(!isLongPressed);
} else {
ref
.read(mangasListStateProvider.notifier)
.update(entry);
}
_handleLongOrSecondaryTap(isLongPressed, ref, entry);
},
children: [
Stack(
@ -446,4 +422,17 @@ class _LibraryGridViewWidgetState extends State<LibraryGridViewWidget> {
},
);
}
void _handleLongOrSecondaryTap(
bool isLongPressed,
WidgetRef ref,
Manga entry,
) {
if (!isLongPressed) {
ref.read(mangasListStateProvider.notifier).update(entry);
ref.read(isLongPressedMangaStateProvider.notifier).update(!isLongPressed);
} else {
ref.read(mangasListStateProvider.notifier).update(entry);
}
}
}

View file

@ -7,6 +7,7 @@ class SeachFormTextField extends StatelessWidget {
final VoidCallback onSuffixPressed;
final TextEditingController controller;
final Function(String)? onFieldSubmitted;
final bool autofocus;
const SeachFormTextField({
super.key,
required this.onChanged,
@ -14,6 +15,7 @@ class SeachFormTextField extends StatelessWidget {
required this.controller,
this.onFieldSubmitted,
required this.onSuffixPressed,
this.autofocus = true,
});
@override
@ -21,7 +23,7 @@ class SeachFormTextField extends StatelessWidget {
final l10n = l10nLocalizations(context)!;
return Flexible(
child: TextFormField(
autofocus: true,
autofocus: autofocus,
controller: controller,
keyboardType: TextInputType.text,
onChanged: onChanged,

View file

@ -24,113 +24,116 @@ class ChapterListTileWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final isLongPressed = ref.watch(isLongPressedStateProvider);
final l10n = l10nLocalizations(context)!;
return Container(
color: chapterList.contains(chapter)
? context.primaryColor.withValues(alpha: 0.4)
: null,
child: ListTile(
textColor: chapter.isRead!
? context.isLight
? Colors.black.withValues(alpha: 0.4)
: Colors.white.withValues(alpha: 0.3)
: null,
selectedColor: chapter.isRead!
? Colors.white.withValues(alpha: 0.3)
: Colors.white,
onLongPress: () {
if (!isLongPressed) {
ref.read(chaptersListStateProvider.notifier).update(chapter);
ref
.read(isLongPressedStateProvider.notifier)
.update(!isLongPressed);
} else {
ref.read(chaptersListStateProvider.notifier).update(chapter);
}
},
onTap: () async {
if (isLongPressed) {
ref.read(chaptersListStateProvider.notifier).update(chapter);
} else {
chapter.pushToReaderView(context, ignoreIsRead: true);
}
},
title: Row(
children: [
chapter.isBookmarked!
? Icon(Icons.bookmark, size: 16, color: context.primaryColor)
: Container(),
Flexible(child: _buildTitle(chapter.name!, context)),
],
),
subtitle: Row(
children: [
if ((chapter.manga.value!.isLocalArchive ?? false) == false)
Text(
chapter.dateUpload == null || chapter.dateUpload!.isEmpty
? ""
: dateFormat(
chapter.dateUpload!,
ref: ref,
context: context,
child: GestureDetector(
onLongPress: () => _handleInteraction(ref),
onSecondaryTap: () => _handleInteraction(ref),
child: ListTile(
textColor: chapter.isRead!
? context.isLight
? Colors.black.withValues(alpha: 0.4)
: Colors.white.withValues(alpha: 0.3)
: null,
selectedColor: chapter.isRead!
? Colors.white.withValues(alpha: 0.3)
: Colors.white,
onTap: () async => _handleInteraction(ref, context),
title: Row(
children: [
chapter.isBookmarked!
? Icon(Icons.bookmark, size: 16, color: context.primaryColor)
: Container(),
Flexible(child: _buildTitle(chapter.name!, context)),
],
),
subtitle: Row(
children: [
if ((chapter.manga.value!.isLocalArchive ?? false) == false)
Text(
chapter.dateUpload == null || chapter.dateUpload!.isEmpty
? ""
: dateFormat(
chapter.dateUpload!,
ref: ref,
context: context,
),
style: const TextStyle(fontSize: 11),
),
if (!chapter.isRead!)
if (chapter.lastPageRead!.isNotEmpty &&
chapter.lastPageRead != "1")
Row(
children: [
const Text(''),
Text(
chapter.manga.value!.itemType == ItemType.anime
? l10n.episode_progress(
Duration(
milliseconds: int.parse(
chapter.lastPageRead!,
),
).toString().substringBefore("."),
)
: l10n.page(
chapter.manga.value!.itemType == ItemType.manga
? chapter.lastPageRead!
: "${((double.tryParse(chapter.lastPageRead!) ?? 0) * 100).toStringAsFixed(0)} %",
),
style: TextStyle(
fontSize: 11,
color: context.isLight
? Colors.black.withValues(alpha: 0.4)
: Colors.white.withValues(alpha: 0.3),
),
),
style: const TextStyle(fontSize: 11),
),
if (!chapter.isRead!)
if (chapter.lastPageRead!.isNotEmpty &&
chapter.lastPageRead != "1")
],
),
if (chapter.scanlator?.isNotEmpty ?? false)
Row(
children: [
const Text(''),
Text(
chapter.manga.value!.itemType == ItemType.anime
? l10n.episode_progress(
Duration(
milliseconds: int.parse(chapter.lastPageRead!),
).toString().substringBefore("."),
)
: l10n.page(
chapter.manga.value!.itemType == ItemType.manga
? chapter.lastPageRead!
: "${((double.tryParse(chapter.lastPageRead!) ?? 0) * 100).toStringAsFixed(0)} %",
),
chapter.scanlator!,
style: TextStyle(
fontSize: 11,
color: context.isLight
? Colors.black.withValues(alpha: 0.4)
: Colors.white.withValues(alpha: 0.3),
color: chapter.isRead!
? context.isLight
? Colors.black.withValues(alpha: 0.4)
: Colors.white.withValues(alpha: 0.3)
: null,
),
),
],
),
if (chapter.scanlator?.isNotEmpty ?? false)
Row(
children: [
const Text(''),
Text(
chapter.scanlator!,
style: TextStyle(
fontSize: 11,
color: chapter.isRead!
? context.isLight
? Colors.black.withValues(alpha: 0.4)
: Colors.white.withValues(alpha: 0.3)
: null,
),
),
],
),
],
],
),
trailing:
!sourceExist || (chapter.manga.value!.isLocalArchive ?? false)
? null
: ChapterPageDownload(chapter: chapter),
),
trailing: !sourceExist || (chapter.manga.value!.isLocalArchive ?? false)
? null
: ChapterPageDownload(chapter: chapter),
),
);
}
void _handleInteraction(WidgetRef ref, [BuildContext? context]) {
final isLongPressed = ref.read(isLongPressedStateProvider);
if (isLongPressed) {
ref.read(chaptersListStateProvider.notifier).update(chapter);
} else {
if (context != null) {
chapter.pushToReaderView(context, ignoreIsRead: true);
} else {
ref.read(chaptersListStateProvider.notifier).update(chapter);
ref.read(isLongPressedStateProvider.notifier).update(!isLongPressed);
}
}
}
Widget _buildTitle(String text, BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {

View file

@ -105,6 +105,7 @@ class _MigrationScreenScreenState extends ConsumerState<MigrationScreen> {
});
},
controller: _textEditingController,
autofocus: false,
),
],
),