This commit is contained in:
stormdragon2976 2026-02-28 02:08:09 +00:00 committed by GitHub
commit f64529e482
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 159 additions and 68 deletions

View file

@ -257,6 +257,7 @@ class BookSearchResultMini extends HookConsumerWidget {
); );
}, },
trailing: IconButton( trailing: IconButton(
tooltip: 'More options',
icon: const Icon(Icons.more_vert), icon: const Icon(Icons.more_vert),
onPressed: () { onPressed: () {
// TODO: show a menu with options for the book // TODO: show a menu with options for the book
@ -306,11 +307,12 @@ class SearchResultMiniSection extends HookConsumerWidget {
Row( Row(
children: [ children: [
Text( Text(
category.toString().split('.').last, category.displayLabel,
style: Theme.of(context).textTheme.headlineSmall, style: Theme.of(context).textTheme.headlineSmall,
), ),
const Spacer(), const Spacer(),
IconButton( IconButton(
tooltip: 'View more ${category.displayLabel}',
icon: const Icon(Icons.arrow_forward_ios), icon: const Icon(Icons.arrow_forward_ios),
onPressed: onTap ?? openSearch, onPressed: onTap ?? openSearch,
), ),

View file

@ -7,6 +7,23 @@ import 'package:vaani/shared/extensions/model_conversions.dart';
enum SearchResultCategory { books, authors, series, tags, narrators } enum SearchResultCategory { books, authors, series, tags, narrators }
extension SearchResultCategoryDisplay on SearchResultCategory {
String get displayLabel {
switch (this) {
case SearchResultCategory.books:
return 'Books';
case SearchResultCategory.authors:
return 'Authors';
case SearchResultCategory.series:
return 'Series';
case SearchResultCategory.tags:
return 'Tags';
case SearchResultCategory.narrators:
return 'Narrators';
}
}
}
class SearchResultPage extends HookConsumerWidget { class SearchResultPage extends HookConsumerWidget {
const SearchResultPage({ const SearchResultPage({
super.key, super.key,

View file

@ -24,6 +24,7 @@ import 'package:vaani/settings/api_settings_provider.dart';
import 'package:vaani/settings/app_settings_provider.dart'; import 'package:vaani/settings/app_settings_provider.dart';
import 'package:vaani/shared/extensions/model_conversions.dart'; import 'package:vaani/shared/extensions/model_conversions.dart';
import 'package:vaani/shared/utils.dart'; import 'package:vaani/shared/utils.dart';
import 'package:vaani/shared/widgets/not_implemented.dart';
class LibraryItemActions extends HookConsumerWidget { class LibraryItemActions extends HookConsumerWidget {
const LibraryItemActions({super.key, required this.id}); const LibraryItemActions({super.key, required this.id});
@ -64,11 +65,15 @@ class LibraryItemActions extends HookConsumerWidget {
children: [ children: [
// read list button // read list button
IconButton( IconButton(
onPressed: () {}, tooltip: 'Add to playlist',
onPressed: () {
showNotImplementedToast(context);
},
icon: const Icon(Icons.playlist_add_rounded), icon: const Icon(Icons.playlist_add_rounded),
), ),
// share button // share button
IconButton( IconButton(
tooltip: 'Share item',
onPressed: () { onPressed: () {
appLogger.fine('Sharing'); appLogger.fine('Sharing');
var currentServerUrl = var currentServerUrl =
@ -97,6 +102,7 @@ class LibraryItemActions extends HookConsumerWidget {
// more button // more button
IconButton( IconButton(
tooltip: 'More options',
onPressed: () { onPressed: () {
// show the bottom sheet with download history // show the bottom sheet with download history
showModalBottomSheet( showModalBottomSheet(
@ -215,6 +221,7 @@ class LibItemDownloadButton extends HookConsumerWidget {
return isItemDownloading return isItemDownloading
? ItemCurrentlyInDownloadQueue(item: item) ? ItemCurrentlyInDownloadQueue(item: item)
: IconButton( : IconButton(
tooltip: 'Download item',
onPressed: () { onPressed: () {
appLogger.fine('Pressed download button'); appLogger.fine('Pressed download button');
@ -280,6 +287,7 @@ class AlreadyItemDownloadedButton extends HookConsumerWidget {
final isBookPlaying = ref.watch(audiobookPlayerProvider).book != null; final isBookPlaying = ref.watch(audiobookPlayerProvider).book != null;
return IconButton( return IconButton(
tooltip: 'Downloaded item options',
onPressed: () { onPressed: () {
appLogger.fine('Pressed already downloaded button'); appLogger.fine('Pressed already downloaded button');
// manager.openDownloadedFile(item); // manager.openDownloadedFile(item);

View file

@ -137,16 +137,22 @@ class UserLoginWithPassword extends HookConsumerWidget {
).colorScheme.primary.withValues(alpha: 0.8), ).colorScheme.primary.withValues(alpha: 0.8),
BlendMode.srcIn, BlendMode.srcIn,
), ),
child: InkWell( child: Semantics(
borderRadius: BorderRadius.circular(50), button: true,
onTap: () { label: isPasswordVisible.value
isPasswordVisible.value = !isPasswordVisible.value; ? 'Hide password'
}, : 'Show password',
child: Container( child: InkWell(
margin: const EdgeInsets.only(left: 8, right: 8), borderRadius: BorderRadius.circular(50),
child: Lottie.asset( onTap: () {
'assets/animations/Animation - 1714930099660.json', isPasswordVisible.value = !isPasswordVisible.value;
controller: isPasswordVisibleAnimationController, },
child: Container(
margin: const EdgeInsets.only(left: 8, right: 8),
child: Lottie.asset(
'assets/animations/Animation - 1714930099660.json',
controller: isPasswordVisibleAnimationController,
),
), ),
), ),
), ),

View file

@ -163,6 +163,7 @@ class AudiobookPlayerPlayPauseButton extends HookConsumerWidget {
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
), ),
ProcessingState.completed => IconButton( ProcessingState.completed => IconButton(
tooltip: 'Replay',
onPressed: () async { onPressed: () async {
await player.seek(const Duration(seconds: 0)); await player.seek(const Duration(seconds: 0));
await player.play(); await player.play();
@ -170,6 +171,7 @@ class AudiobookPlayerPlayPauseButton extends HookConsumerWidget {
icon: const Icon(Icons.replay), icon: const Icon(Icons.replay),
), ),
ProcessingState.ready => IconButton( ProcessingState.ready => IconButton(
tooltip: player.playing ? 'Pause' : 'Play',
onPressed: () async { onPressed: () async {
await player.togglePlayPause(); await player.togglePlayPause();
}, },

View file

@ -64,6 +64,7 @@ class PlayerWhenExpanded extends HookConsumerWidget {
IconButton( IconButton(
iconSize: 30, iconSize: 30,
icon: const Icon(Icons.keyboard_arrow_down), icon: const Icon(Icons.keyboard_arrow_down),
tooltip: 'Minimize player',
onPressed: () { onPressed: () {
// minimize the player // minimize the player
audioBookMiniplayerController.animateToHeight( audioBookMiniplayerController.animateToHeight(
@ -75,6 +76,7 @@ class PlayerWhenExpanded extends HookConsumerWidget {
// the cast button // the cast button
IconButton( IconButton(
icon: const Icon(Icons.cast), icon: const Icon(Icons.cast),
tooltip: 'Cast',
onPressed: () { onPressed: () {
showNotImplementedToast(context); showNotImplementedToast(context);
}, },
@ -108,14 +110,11 @@ class PlayerWhenExpanded extends HookConsumerWidget {
), ),
child: SizedBox( child: SizedBox(
height: imageSize, height: imageSize,
child: InkWell( child: ClipRRect(
onTap: () {}, borderRadius: BorderRadius.circular(
child: ClipRRect( AppElementSizes.borderRadiusRegular * earlyPercentage,
borderRadius: BorderRadius.circular(
AppElementSizes.borderRadiusRegular * earlyPercentage,
),
child: img,
), ),
child: img,
), ),
), ),
), ),

View file

@ -51,20 +51,24 @@ class PlayerWhenMinimized extends HookConsumerWidget {
horizontal: horizontal:
((availWidth - maxImgSize) / 2) * percentageMiniplayer, ((availWidth - maxImgSize) / 2) * percentageMiniplayer,
), ),
child: InkWell( child: Semantics(
onTap: () { button: true,
// navigate to item page label: 'Open current book details',
context.pushNamed( child: InkWell(
Routes.libraryItem.name, onTap: () {
pathParameters: { // navigate to item page
Routes.libraryItem.pathParamName!: context.pushNamed(
player.book!.libraryItemId, Routes.libraryItem.name,
}, pathParameters: {
); Routes.libraryItem.pathParamName!:
}, player.book!.libraryItemId,
child: ConstrainedBox( },
constraints: BoxConstraints(maxWidth: maxImgSize), );
child: imgWidget, },
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: maxImgSize),
child: imgWidget,
),
), ),
), ),
), ),
@ -112,11 +116,18 @@ class PlayerWhenMinimized extends HookConsumerWidget {
child: Padding( child: Padding(
padding: const EdgeInsets.only(left: 8), padding: const EdgeInsets.only(left: 8),
child: IconButton( child: IconButton(
tooltip: 'Rewind 30 seconds',
icon: const Icon( icon: const Icon(
Icons.replay_30, Icons.replay_30,
size: AppElementSizes.iconSizeSmall, size: AppElementSizes.iconSizeSmall,
), ),
onPressed: () {}, onPressed: () {
player.seek(
player.positionInBook > const Duration(seconds: 30)
? player.positionInBook - const Duration(seconds: 30)
: Duration.zero,
);
},
), ),
), ),
), ),

View file

@ -13,6 +13,9 @@ class AudiobookPlayerSeekButton extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final player = ref.watch(audiobookPlayerProvider); final player = ref.watch(audiobookPlayerProvider);
return IconButton( return IconButton(
tooltip: isForward
? 'Seek forward 30 seconds'
: 'Seek backward 30 seconds',
icon: Icon( icon: Icon(
isForward ? Icons.forward_30 : Icons.replay_30, isForward ? Icons.forward_30 : Icons.replay_30,
size: AppElementSizes.iconSizeSmall, size: AppElementSizes.iconSizeSmall,

View file

@ -49,6 +49,7 @@ class AudiobookPlayerSeekChapterButton extends HookConsumerWidget {
} }
return IconButton( return IconButton(
tooltip: isForward ? 'Next chapter' : 'Previous chapter',
icon: Icon( icon: Icon(
isForward ? Icons.skip_next : Icons.skip_previous, isForward ? Icons.skip_next : Icons.skip_previous,
size: AppElementSizes.iconSizeSmall, size: AppElementSizes.iconSizeSmall,

View file

@ -162,6 +162,7 @@ class SpeedWheel extends StatelessWidget {
// a minus button to decrease the speed // a minus button to decrease the speed
if (showIncrementButtons) if (showIncrementButtons)
IconButton.filledTonal( IconButton.filledTonal(
tooltip: 'Decrease speed',
icon: const Icon(Icons.remove), icon: const Icon(Icons.remove),
onPressed: () { onPressed: () {
// animate to index - 1 // animate to index - 1
@ -198,6 +199,7 @@ class SpeedWheel extends StatelessWidget {
if (showIncrementButtons) if (showIncrementButtons)
// a plus button to increase the speed // a plus button to increase the speed
IconButton.filledTonal( IconButton.filledTonal(
tooltip: 'Increase speed',
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
onPressed: () { onPressed: () {
// animate to index + 1 // animate to index + 1

View file

@ -15,6 +15,8 @@ import 'package:vaani/shared/extensions/duration_format.dart';
class SleepTimerButton extends HookConsumerWidget { class SleepTimerButton extends HookConsumerWidget {
const SleepTimerButton({super.key}); const SleepTimerButton({super.key});
static const _sleepTimerLabel = 'Sleep timer';
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final sleepTimer = ref.watch(sleepTimerProvider); final sleepTimer = ref.watch(sleepTimerProvider);
@ -23,40 +25,44 @@ class SleepTimerButton extends HookConsumerWidget {
// if sleep timer is not active, show the button with the sleep timer icon // if sleep timer is not active, show the button with the sleep timer icon
// if the sleep timer is active, show the remaining time in a pill shaped container // if the sleep timer is active, show the remaining time in a pill shaped container
return Tooltip( return Tooltip(
message: 'Sleep Timer', message: _sleepTimerLabel,
child: InkWell( child: Semantics(
onTap: () async { button: true,
appLogger.fine('Sleep Timer button pressed'); label: _sleepTimerLabel,
pendingPlayerModals++; child: InkWell(
// show the sleep timer dialog onTap: () async {
await showModalBottomSheet<Duration?>( appLogger.fine('Sleep Timer button pressed');
context: context, pendingPlayerModals++;
barrierLabel: 'Sleep Timer', // show the sleep timer dialog
builder: (context) { await showModalBottomSheet<Duration?>(
return SleepTimerBottomSheet( context: context,
onDurationSelected: (duration) { barrierLabel: 'Sleep Timer',
durationState.value = duration; builder: (context) {
// ref return SleepTimerBottomSheet(
// .read(sleepTimerProvider.notifier) onDurationSelected: (duration) {
// .setTimer(duration, notifyListeners: false); durationState.value = duration;
}, // ref
); // .read(sleepTimerProvider.notifier)
}, // .setTimer(duration, notifyListeners: false);
); },
pendingPlayerModals--; );
ref.read(sleepTimerProvider.notifier).setTimer(durationState.value); },
appLogger.fine( );
'Sleep Timer dialog closed with ${durationState.value}', pendingPlayerModals--;
); ref.read(sleepTimerProvider.notifier).setTimer(durationState.value);
}, appLogger.fine(
child: AnimatedSwitcher( 'Sleep Timer dialog closed with ${durationState.value}',
duration: const Duration(milliseconds: 300), );
child: sleepTimer == null },
? Icon( child: AnimatedSwitcher(
Symbols.bedtime, duration: const Duration(milliseconds: 300),
color: Theme.of(context).colorScheme.onSurface, child: sleepTimer == null
) ? Icon(
: RemainingSleepTimeDisplay(timer: sleepTimer), Symbols.bedtime,
color: Theme.of(context).colorScheme.onSurface,
)
: RemainingSleepTimeDisplay(timer: sleepTimer),
),
), ),
), ),
); );
@ -256,6 +262,7 @@ class SleepTimerWheel extends StatelessWidget {
// a minus button to decrease the speed // a minus button to decrease the speed
if (showIncrementButtons) if (showIncrementButtons)
IconButton.filledTonal( IconButton.filledTonal(
tooltip: 'Decrease sleep timer',
icon: const Icon(Icons.remove), icon: const Icon(Icons.remove),
onPressed: () { onPressed: () {
// animate to index - 1 // animate to index - 1
@ -294,6 +301,7 @@ class SleepTimerWheel extends StatelessWidget {
if (showIncrementButtons) if (showIncrementButtons)
// a plus button to increase the speed // a plus button to increase the speed
IconButton.filledTonal( IconButton.filledTonal(
tooltip: 'Increase sleep timer',
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
onPressed: () { onPressed: () {
// animate to index + 1 // animate to index + 1

View file

@ -283,6 +283,7 @@ class AvailableUserTile extends HookConsumerWidget {
context.goNamed(Routes.home.name); context.goNamed(Routes.home.name);
}, },
trailing: IconButton( trailing: IconButton(
tooltip: 'Remove user',
icon: const Icon(Icons.delete), icon: const Icon(Icons.delete),
onPressed: () { onPressed: () {
showDialog( showDialog(

View file

@ -235,6 +235,7 @@ class RestoreDialogue extends HookConsumerWidget {
hintText: 'Paste the backup here', hintText: 'Paste the backup here',
// clear button // clear button
suffixIcon: IconButton( suffixIcon: IconButton(
tooltip: 'Clear backup',
icon: Icon(Icons.clear), icon: Icon(Icons.clear),
onPressed: () { onPressed: () {
settingsInputController.clear(); settingsInputController.clear();

View file

@ -350,6 +350,7 @@ class NotificationTitlePicker extends HookConsumerWidget {
decoration: InputDecoration( decoration: InputDecoration(
helper: const Text('Select a field below to insert it'), helper: const Text('Select a field below to insert it'),
suffix: IconButton( suffix: IconButton(
tooltip: 'Clear title',
icon: const Icon(Icons.clear), icon: const Icon(Icons.clear),
onPressed: () { onPressed: () {
controller.clear(); controller.clear();

View file

@ -283,6 +283,7 @@ class ShakeForceSelector extends HookConsumerWidget {
decoration: InputDecoration( decoration: InputDecoration(
// clear button // clear button
suffix: IconButton( suffix: IconButton(
tooltip: 'Clear threshold',
icon: const Icon(Icons.clear), icon: const Icon(Icons.clear),
onPressed: () { onPressed: () {
controller.clear(); controller.clear();

View file

@ -206,6 +206,27 @@ class _BookOnShelfPlayButton extends HookConsumerWidget {
/// the id of the library item of the book /// the id of the library item of the book
final String libraryItemId; final String libraryItemId;
String getPlayTooltip({
required bool isCurrentBookSetInPlayer,
required bool isPlayingThisBook,
required bool isBookCompleted,
required bool hasProgress,
}) {
if (isCurrentBookSetInPlayer) {
return isPlayingThisBook ? 'Pause' : 'Resume';
}
if (isBookCompleted) {
return 'Listen again';
}
if (hasProgress) {
return 'Continue listening';
}
return 'Start listening';
}
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final me = ref.watch(meProvider); final me = ref.watch(meProvider);
@ -218,6 +239,12 @@ class _BookOnShelfPlayButton extends HookConsumerWidget {
(element) => element.libraryItemId == libraryItemId, (element) => element.libraryItemId == libraryItemId,
); );
final isBookCompleted = userProgress?.isFinished ?? false; final isBookCompleted = userProgress?.isFinished ?? false;
final playTooltip = getPlayTooltip(
isCurrentBookSetInPlayer: isCurrentBookSetInPlayer,
isPlayingThisBook: isPlayingThisBook,
isBookCompleted: isBookCompleted,
hasProgress: userProgress?.progress != null,
);
const size = 40.0; const size = 40.0;
@ -268,6 +295,7 @@ class _BookOnShelfPlayButton extends HookConsumerWidget {
// the play button // the play button
IconButton( IconButton(
tooltip: playTooltip,
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
style: ButtonStyle( style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero), padding: WidgetStateProperty.all(EdgeInsets.zero),