mirror of
https://github.com/Dr-Blank/Vaani.git
synced 2026-01-16 07:09:32 +00:00
chore: run dart format
Some checks are pending
Flutter CI & Release / Test (push) Waiting to run
Flutter CI & Release / Build Android APKs (push) Blocked by required conditions
Flutter CI & Release / build_linux (push) Blocked by required conditions
Flutter CI & Release / Create GitHub Release (push) Blocked by required conditions
Some checks are pending
Flutter CI & Release / Test (push) Waiting to run
Flutter CI & Release / Build Android APKs (push) Blocked by required conditions
Flutter CI & Release / build_linux (push) Blocked by required conditions
Flutter CI & Release / Create GitHub Release (push) Blocked by required conditions
This commit is contained in:
parent
a520136e01
commit
e23c0b6c5f
84 changed files with 1565 additions and 1945 deletions
|
|
@ -26,10 +26,7 @@ import 'package:vaani/shared/extensions/model_conversions.dart';
|
|||
import 'package:vaani/shared/utils.dart';
|
||||
|
||||
class LibraryItemActions extends HookConsumerWidget {
|
||||
const LibraryItemActions({
|
||||
super.key,
|
||||
required this.id,
|
||||
});
|
||||
const LibraryItemActions({super.key, required this.id});
|
||||
|
||||
final String id;
|
||||
|
||||
|
|
@ -68,9 +65,7 @@ class LibraryItemActions extends HookConsumerWidget {
|
|||
// read list button
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(
|
||||
Icons.playlist_add_rounded,
|
||||
),
|
||||
icon: const Icon(Icons.playlist_add_rounded),
|
||||
),
|
||||
// share button
|
||||
IconButton(
|
||||
|
|
@ -79,8 +74,9 @@ class LibraryItemActions extends HookConsumerWidget {
|
|||
var currentServerUrl =
|
||||
apiSettings.activeServer!.serverUrl;
|
||||
if (!currentServerUrl.hasScheme) {
|
||||
currentServerUrl =
|
||||
Uri.https(currentServerUrl.toString());
|
||||
currentServerUrl = Uri.https(
|
||||
currentServerUrl.toString(),
|
||||
);
|
||||
}
|
||||
handleLaunchUrl(
|
||||
Uri.parse(
|
||||
|
|
@ -140,7 +136,8 @@ class LibraryItemActions extends HookConsumerWidget {
|
|||
.database
|
||||
.deleteRecordWithId(
|
||||
record
|
||||
.task.taskId,
|
||||
.task
|
||||
.taskId,
|
||||
);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
|
|
@ -161,8 +158,8 @@ class LibraryItemActions extends HookConsumerWidget {
|
|||
// open the file location
|
||||
final didOpen =
|
||||
await FileDownloader().openFile(
|
||||
task: record.task,
|
||||
);
|
||||
task: record.task,
|
||||
);
|
||||
|
||||
if (!didOpen) {
|
||||
appLogger.warning(
|
||||
|
|
@ -182,16 +179,13 @@ class LibraryItemActions extends HookConsumerWidget {
|
|||
loading: () => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
error: (error, stackTrace) => Center(
|
||||
child: Text('Error: $error'),
|
||||
),
|
||||
error: (error, stackTrace) =>
|
||||
Center(child: Text('Error: $error')),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.more_vert_rounded,
|
||||
),
|
||||
icon: const Icon(Icons.more_vert_rounded),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -206,10 +200,7 @@ class LibraryItemActions extends HookConsumerWidget {
|
|||
}
|
||||
|
||||
class LibItemDownloadButton extends HookConsumerWidget {
|
||||
const LibItemDownloadButton({
|
||||
super.key,
|
||||
required this.item,
|
||||
});
|
||||
const LibItemDownloadButton({super.key, required this.item});
|
||||
|
||||
final shelfsdk.LibraryItemExpanded item;
|
||||
|
||||
|
|
@ -222,9 +213,7 @@ class LibItemDownloadButton extends HookConsumerWidget {
|
|||
final isItemDownloading = ref.watch(isItemDownloadingProvider(item.id));
|
||||
|
||||
return isItemDownloading
|
||||
? ItemCurrentlyInDownloadQueue(
|
||||
item: item,
|
||||
)
|
||||
? ItemCurrentlyInDownloadQueue(item: item)
|
||||
: IconButton(
|
||||
onPressed: () {
|
||||
appLogger.fine('Pressed download button');
|
||||
|
|
@ -233,18 +222,13 @@ class LibItemDownloadButton extends HookConsumerWidget {
|
|||
.read(downloadManagerProvider.notifier)
|
||||
.queueAudioBookDownload(item);
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.download_rounded,
|
||||
),
|
||||
icon: const Icon(Icons.download_rounded),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ItemCurrentlyInDownloadQueue extends HookConsumerWidget {
|
||||
const ItemCurrentlyInDownloadQueue({
|
||||
super.key,
|
||||
required this.item,
|
||||
});
|
||||
const ItemCurrentlyInDownloadQueue({super.key, required this.item});
|
||||
|
||||
final shelfsdk.LibraryItemExpanded item;
|
||||
|
||||
|
|
@ -263,17 +247,12 @@ class ItemCurrentlyInDownloadQueue extends HookConsumerWidget {
|
|||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
value: progress,
|
||||
strokeWidth: 2,
|
||||
),
|
||||
CircularProgressIndicator(value: progress, strokeWidth: 2),
|
||||
const Icon(
|
||||
Icons.download,
|
||||
// color: Theme.of(context).progressIndicatorTheme.color,
|
||||
)
|
||||
.animate(
|
||||
onPlay: (controller) => controller.repeat(),
|
||||
Icons.download,
|
||||
// color: Theme.of(context).progressIndicatorTheme.color,
|
||||
)
|
||||
.animate(onPlay: (controller) => controller.repeat())
|
||||
.fade(
|
||||
duration: shimmerDuration,
|
||||
end: 1,
|
||||
|
|
@ -292,10 +271,7 @@ class ItemCurrentlyInDownloadQueue extends HookConsumerWidget {
|
|||
}
|
||||
|
||||
class AlreadyItemDownloadedButton extends HookConsumerWidget {
|
||||
const AlreadyItemDownloadedButton({
|
||||
super.key,
|
||||
required this.item,
|
||||
});
|
||||
const AlreadyItemDownloadedButton({super.key, required this.item});
|
||||
|
||||
final shelfsdk.LibraryItemExpanded item;
|
||||
|
||||
|
|
@ -317,25 +293,18 @@ class AlreadyItemDownloadedButton extends HookConsumerWidget {
|
|||
top: 8.0,
|
||||
bottom: (isBookPlaying ? playerMinHeight : 0) + 8,
|
||||
),
|
||||
child: DownloadSheet(
|
||||
item: item,
|
||||
),
|
||||
child: DownloadSheet(item: item),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.download_done_rounded,
|
||||
),
|
||||
icon: const Icon(Icons.download_done_rounded),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DownloadSheet extends HookConsumerWidget {
|
||||
const DownloadSheet({
|
||||
super.key,
|
||||
required this.item,
|
||||
});
|
||||
const DownloadSheet({super.key, required this.item});
|
||||
|
||||
final shelfsdk.LibraryItemExpanded item;
|
||||
|
||||
|
|
@ -367,9 +336,7 @@ class DownloadSheet extends HookConsumerWidget {
|
|||
// ),
|
||||
ListTile(
|
||||
title: const Text('Delete'),
|
||||
leading: const Icon(
|
||||
Icons.delete_rounded,
|
||||
),
|
||||
leading: const Icon(Icons.delete_rounded),
|
||||
onTap: () async {
|
||||
// show the delete dialog
|
||||
final wasDeleted = await showDialog<bool>(
|
||||
|
|
@ -387,9 +354,7 @@ class DownloadSheet extends HookConsumerWidget {
|
|||
// delete the file
|
||||
ref
|
||||
.read(downloadManagerProvider.notifier)
|
||||
.deleteDownloadedItem(
|
||||
item,
|
||||
);
|
||||
.deleteDownloadedItem(item);
|
||||
GoRouter.of(context).pop(true);
|
||||
},
|
||||
child: const Text('Yes'),
|
||||
|
|
@ -409,11 +374,7 @@ class DownloadSheet extends HookConsumerWidget {
|
|||
appLogger.fine('Deleted ${item.media.metadata.title}');
|
||||
GoRouter.of(context).pop();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'Deleted ${item.media.metadata.title}',
|
||||
),
|
||||
),
|
||||
SnackBar(content: Text('Deleted ${item.media.metadata.title}')),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
|
@ -424,9 +385,7 @@ class DownloadSheet extends HookConsumerWidget {
|
|||
}
|
||||
|
||||
class _LibraryItemPlayButton extends HookConsumerWidget {
|
||||
const _LibraryItemPlayButton({
|
||||
required this.item,
|
||||
});
|
||||
const _LibraryItemPlayButton({required this.item});
|
||||
|
||||
final shelfsdk.LibraryItemExpanded item;
|
||||
|
||||
|
|
@ -477,9 +436,7 @@ class _LibraryItemPlayButton extends HookConsumerWidget {
|
|||
),
|
||||
label: Text(getPlayDisplayText()),
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -502,11 +459,11 @@ class DynamicItemPlayIcon extends StatelessWidget {
|
|||
return Icon(
|
||||
isCurrentBookSetInPlayer
|
||||
? isPlayingThisBook
|
||||
? Icons.pause_rounded
|
||||
: Icons.play_arrow_rounded
|
||||
? Icons.pause_rounded
|
||||
: Icons.play_arrow_rounded
|
||||
: isBookCompleted
|
||||
? Icons.replay_rounded
|
||||
: Icons.play_arrow_rounded,
|
||||
? Icons.replay_rounded
|
||||
: Icons.play_arrow_rounded,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -529,8 +486,9 @@ Future<void> libraryItemPlayButtonOnPressed({
|
|||
appLogger.info('Setting the book ${book.libraryItemId}');
|
||||
appLogger.info('Initial position: ${userMediaProgress?.currentTime}');
|
||||
final downloadManager = ref.watch(simpleDownloadManagerProvider);
|
||||
final libItem =
|
||||
await ref.read(libraryItemProvider(book.libraryItemId).future);
|
||||
final libItem = await ref.read(
|
||||
libraryItemProvider(book.libraryItemId).future,
|
||||
);
|
||||
final downloadedUris = await downloadManager.getDownloadedFilesUri(libItem);
|
||||
setSourceFuture = player.setSourceAudiobook(
|
||||
book,
|
||||
|
|
@ -546,8 +504,9 @@ Future<void> libraryItemPlayButtonOnPressed({
|
|||
}
|
||||
}
|
||||
// set the volume as this is the first time playing and dismissing causes the volume to go to 0
|
||||
var bookPlayerSettings =
|
||||
ref.read(bookSettingsProvider(book.libraryItemId)).playerSettings;
|
||||
var bookPlayerSettings = ref
|
||||
.read(bookSettingsProvider(book.libraryItemId))
|
||||
.playerSettings;
|
||||
var appPlayerSettings = ref.read(appSettingsProvider).playerSettings;
|
||||
|
||||
var configurePlayerForEveryBook =
|
||||
|
|
@ -559,14 +518,14 @@ Future<void> libraryItemPlayButtonOnPressed({
|
|||
player.setVolume(
|
||||
configurePlayerForEveryBook
|
||||
? bookPlayerSettings.preferredDefaultVolume ??
|
||||
appPlayerSettings.preferredDefaultVolume
|
||||
appPlayerSettings.preferredDefaultVolume
|
||||
: appPlayerSettings.preferredDefaultVolume,
|
||||
),
|
||||
// set the speed
|
||||
player.setSpeed(
|
||||
configurePlayerForEveryBook
|
||||
? bookPlayerSettings.preferredDefaultSpeed ??
|
||||
appPlayerSettings.preferredDefaultSpeed
|
||||
appPlayerSettings.preferredDefaultSpeed
|
||||
: appPlayerSettings.preferredDefaultSpeed,
|
||||
),
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -42,14 +42,13 @@ class LibraryItemHeroSection extends HookConsumerWidget {
|
|||
child: Column(
|
||||
children: [
|
||||
Hero(
|
||||
tag: HeroTagPrefixes.bookCover +
|
||||
tag:
|
||||
HeroTagPrefixes.bookCover +
|
||||
itemId +
|
||||
(extraMap?.heroTagSuffix ?? ''),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: _BookCover(
|
||||
itemId: itemId,
|
||||
),
|
||||
child: _BookCover(itemId: itemId),
|
||||
),
|
||||
),
|
||||
// a progress bar
|
||||
|
|
@ -59,9 +58,7 @@ class LibraryItemHeroSection extends HookConsumerWidget {
|
|||
right: 8.0,
|
||||
left: 8.0,
|
||||
),
|
||||
child: _LibraryItemProgressIndicator(
|
||||
id: itemId,
|
||||
),
|
||||
child: _LibraryItemProgressIndicator(id: itemId),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -77,10 +74,7 @@ class LibraryItemHeroSection extends HookConsumerWidget {
|
|||
}
|
||||
|
||||
class _BookDetails extends HookConsumerWidget {
|
||||
const _BookDetails({
|
||||
required this.id,
|
||||
this.extraMap,
|
||||
});
|
||||
const _BookDetails({required this.id, this.extraMap});
|
||||
|
||||
final String id;
|
||||
final LibraryItemExtras? extraMap;
|
||||
|
|
@ -99,10 +93,7 @@ class _BookDetails extends HookConsumerWidget {
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
_BookTitle(
|
||||
extraMap: extraMap,
|
||||
itemBookMetadata: itemBookMetadata,
|
||||
),
|
||||
_BookTitle(extraMap: extraMap, itemBookMetadata: itemBookMetadata),
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Column(
|
||||
|
|
@ -134,9 +125,7 @@ class _BookDetails extends HookConsumerWidget {
|
|||
}
|
||||
|
||||
class _LibraryItemProgressIndicator extends HookConsumerWidget {
|
||||
const _LibraryItemProgressIndicator({
|
||||
required this.id,
|
||||
});
|
||||
const _LibraryItemProgressIndicator({required this.id});
|
||||
|
||||
final String id;
|
||||
|
||||
|
|
@ -157,13 +146,15 @@ class _LibraryItemProgressIndicator extends HookConsumerWidget {
|
|||
Duration remainingTime;
|
||||
if (player.book?.libraryItemId == libraryItem.id) {
|
||||
// final positionStream = useStream(player.slowPositionStream);
|
||||
progress = (player.positionInBook).inSeconds /
|
||||
progress =
|
||||
(player.positionInBook).inSeconds /
|
||||
libraryItem.media.asBookExpanded.duration.inSeconds;
|
||||
remainingTime =
|
||||
libraryItem.media.asBookExpanded.duration - player.positionInBook;
|
||||
} else {
|
||||
progress = mediaProgress?.progress ?? 0;
|
||||
remainingTime = (libraryItem.media.asBookExpanded.duration -
|
||||
remainingTime =
|
||||
(libraryItem.media.asBookExpanded.duration -
|
||||
mediaProgress!.currentTime);
|
||||
}
|
||||
|
||||
|
|
@ -190,20 +181,17 @@ class _LibraryItemProgressIndicator extends HookConsumerWidget {
|
|||
semanticsLabel: 'Book progress',
|
||||
semanticsValue: '${progressInPercent.toStringAsFixed(2)}%',
|
||||
),
|
||||
const SizedBox.square(
|
||||
dimension: 4.0,
|
||||
),
|
||||
const SizedBox.square(dimension: 4.0),
|
||||
// time remaining
|
||||
Text(
|
||||
// only show 2 decimal places
|
||||
'${remainingTime.smartBinaryFormat} left',
|
||||
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withValues(alpha: 0.75),
|
||||
),
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurface.withValues(alpha: 0.75),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -212,10 +200,7 @@ class _LibraryItemProgressIndicator extends HookConsumerWidget {
|
|||
}
|
||||
|
||||
class _HeroSectionSubLabelWithIcon extends HookConsumerWidget {
|
||||
const _HeroSectionSubLabelWithIcon({
|
||||
required this.icon,
|
||||
required this.text,
|
||||
});
|
||||
const _HeroSectionSubLabelWithIcon({required this.icon, required this.text});
|
||||
|
||||
final IconData icon;
|
||||
final Widget text;
|
||||
|
|
@ -225,8 +210,10 @@ class _HeroSectionSubLabelWithIcon extends HookConsumerWidget {
|
|||
final themeData = Theme.of(context);
|
||||
final useFontAwesome =
|
||||
icon.runtimeType == FontAwesomeIcons.book.runtimeType;
|
||||
final useMaterialThemeOnItemPage =
|
||||
ref.watch(appSettingsProvider).themeSettings.useMaterialThemeOnItemPage;
|
||||
final useMaterialThemeOnItemPage = ref
|
||||
.watch(appSettingsProvider)
|
||||
.themeSettings
|
||||
.useMaterialThemeOnItemPage;
|
||||
final color = useMaterialThemeOnItemPage
|
||||
? themeData.colorScheme.primary
|
||||
: themeData.colorScheme.onSurface.withValues(alpha: 0.75);
|
||||
|
|
@ -237,20 +224,10 @@ class _HeroSectionSubLabelWithIcon extends HookConsumerWidget {
|
|||
Container(
|
||||
margin: const EdgeInsets.only(right: 8, top: 2),
|
||||
child: useFontAwesome
|
||||
? FaIcon(
|
||||
icon,
|
||||
size: 16,
|
||||
color: color,
|
||||
)
|
||||
: Icon(
|
||||
icon,
|
||||
size: 16,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: text,
|
||||
? FaIcon(icon, size: 16, color: color)
|
||||
: Icon(icon, size: 16, color: color),
|
||||
),
|
||||
Expanded(child: text),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
@ -338,9 +315,7 @@ class _BookNarrators extends StatelessWidget {
|
|||
}
|
||||
|
||||
class _BookCover extends HookConsumerWidget {
|
||||
const _BookCover({
|
||||
required this.itemId,
|
||||
});
|
||||
const _BookCover({required this.itemId});
|
||||
|
||||
final String itemId;
|
||||
|
||||
|
|
@ -358,7 +333,8 @@ class _BookCover extends HookConsumerWidget {
|
|||
themeOfLibraryItemProvider(
|
||||
itemId,
|
||||
brightness: Theme.of(context).brightness,
|
||||
highContrast: themeSettings.highContrast ||
|
||||
highContrast:
|
||||
themeSettings.highContrast ||
|
||||
MediaQuery.of(context).highContrast,
|
||||
),
|
||||
)
|
||||
|
|
@ -391,15 +367,10 @@ class _BookCover extends HookConsumerWidget {
|
|||
return const Icon(Icons.error);
|
||||
}
|
||||
|
||||
return Image.memory(
|
||||
image,
|
||||
fit: BoxFit.cover,
|
||||
);
|
||||
return Image.memory(image, fit: BoxFit.cover);
|
||||
},
|
||||
loading: () {
|
||||
return const Center(
|
||||
child: BookCoverSkeleton(),
|
||||
);
|
||||
return const Center(child: BookCoverSkeleton());
|
||||
},
|
||||
error: (error, stack) {
|
||||
return const Center(child: Icon(Icons.error));
|
||||
|
|
@ -411,10 +382,7 @@ class _BookCover extends HookConsumerWidget {
|
|||
}
|
||||
|
||||
class _BookTitle extends StatelessWidget {
|
||||
const _BookTitle({
|
||||
required this.extraMap,
|
||||
required this.itemBookMetadata,
|
||||
});
|
||||
const _BookTitle({required this.extraMap, required this.itemBookMetadata});
|
||||
|
||||
final LibraryItemExtras? extraMap;
|
||||
final shelfsdk.BookMetadataExpanded? itemBookMetadata;
|
||||
|
|
@ -426,7 +394,8 @@ class _BookTitle extends StatelessWidget {
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Hero(
|
||||
tag: HeroTagPrefixes.bookTitle +
|
||||
tag:
|
||||
HeroTagPrefixes.bookTitle +
|
||||
// itemId +
|
||||
(extraMap?.heroTagSuffix ?? ''),
|
||||
child: Text(
|
||||
|
|
|
|||
|
|
@ -4,10 +4,7 @@ import 'package:vaani/api/library_item_provider.dart';
|
|||
import 'package:vaani/shared/extensions/model_conversions.dart';
|
||||
|
||||
class LibraryItemMetadata extends HookConsumerWidget {
|
||||
const LibraryItemMetadata({
|
||||
super.key,
|
||||
required this.id,
|
||||
});
|
||||
const LibraryItemMetadata({super.key, required this.id});
|
||||
|
||||
final String id;
|
||||
|
||||
|
|
@ -72,7 +69,8 @@ class LibraryItemMetadata extends HookConsumerWidget {
|
|||
),
|
||||
_MetadataItem(
|
||||
title: 'Published',
|
||||
value: itemBookMetadata?.publishedDate ??
|
||||
value:
|
||||
itemBookMetadata?.publishedDate ??
|
||||
itemBookMetadata?.publishedYear ??
|
||||
'Unknown',
|
||||
),
|
||||
|
|
@ -87,22 +85,18 @@ class LibraryItemMetadata extends HookConsumerWidget {
|
|||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
// alternate between metadata and vertical divider
|
||||
children: List.generate(
|
||||
children.length * 2 - 1,
|
||||
(index) {
|
||||
if (index.isEven) {
|
||||
return children[index ~/ 2];
|
||||
}
|
||||
return VerticalDivider(
|
||||
indent: 6,
|
||||
endIndent: 6,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withValues(alpha: 0.6),
|
||||
);
|
||||
},
|
||||
),
|
||||
children: List.generate(children.length * 2 - 1, (index) {
|
||||
if (index.isEven) {
|
||||
return children[index ~/ 2];
|
||||
}
|
||||
return VerticalDivider(
|
||||
indent: 6,
|
||||
endIndent: 6,
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -111,10 +105,7 @@ class LibraryItemMetadata extends HookConsumerWidget {
|
|||
|
||||
/// key-value pair to display as column
|
||||
class _MetadataItem extends StatelessWidget {
|
||||
const _MetadataItem({
|
||||
required this.title,
|
||||
required this.value,
|
||||
});
|
||||
const _MetadataItem({required this.title, required this.value});
|
||||
|
||||
final String title;
|
||||
final String value;
|
||||
|
|
|
|||
|
|
@ -16,49 +16,43 @@ import 'library_item_hero_section.dart';
|
|||
import 'library_item_metadata.dart';
|
||||
|
||||
class LibraryItemPage extends HookConsumerWidget {
|
||||
const LibraryItemPage({
|
||||
super.key,
|
||||
required this.itemId,
|
||||
this.extra,
|
||||
});
|
||||
const LibraryItemPage({super.key, required this.itemId, this.extra});
|
||||
|
||||
final String itemId;
|
||||
final Object? extra;
|
||||
static const double _showFabThreshold = 300.0;
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final additionalItemData =
|
||||
extra is LibraryItemExtras ? extra as LibraryItemExtras : null;
|
||||
final additionalItemData = extra is LibraryItemExtras
|
||||
? extra as LibraryItemExtras
|
||||
: null;
|
||||
final scrollController = useScrollController();
|
||||
final showFab = useState(false);
|
||||
|
||||
// Effect to listen to scroll changes and update FAB visibility
|
||||
useEffect(
|
||||
() {
|
||||
void listener() {
|
||||
if (!scrollController.hasClients) {
|
||||
return; // Ensure controller is attached
|
||||
}
|
||||
final shouldShow = scrollController.offset > _showFabThreshold;
|
||||
// Update state only if it changes and widget is still mounted
|
||||
if (showFab.value != shouldShow && context.mounted) {
|
||||
showFab.value = shouldShow;
|
||||
}
|
||||
useEffect(() {
|
||||
void listener() {
|
||||
if (!scrollController.hasClients) {
|
||||
return; // Ensure controller is attached
|
||||
}
|
||||
final shouldShow = scrollController.offset > _showFabThreshold;
|
||||
// Update state only if it changes and widget is still mounted
|
||||
if (showFab.value != shouldShow && context.mounted) {
|
||||
showFab.value = shouldShow;
|
||||
}
|
||||
}
|
||||
|
||||
scrollController.addListener(listener);
|
||||
// Initial check in case the view starts scrolled (less likely but safe)
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (scrollController.hasClients && context.mounted) {
|
||||
listener();
|
||||
}
|
||||
});
|
||||
scrollController.addListener(listener);
|
||||
// Initial check in case the view starts scrolled (less likely but safe)
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (scrollController.hasClients && context.mounted) {
|
||||
listener();
|
||||
}
|
||||
});
|
||||
|
||||
// Cleanup: remove the listener when the widget is disposed
|
||||
return () => scrollController.removeListener(listener);
|
||||
},
|
||||
[scrollController],
|
||||
); // Re-run effect if scrollController changes
|
||||
// Cleanup: remove the listener when the widget is disposed
|
||||
return () => scrollController.removeListener(listener);
|
||||
}, [scrollController]); // Re-run effect if scrollController changes
|
||||
|
||||
// --- FAB Scroll-to-Top Logic ---
|
||||
void scrollToTop() {
|
||||
|
|
@ -82,10 +76,7 @@ class LibraryItemPage extends HookConsumerWidget {
|
|||
transitionBuilder: (Widget child, Animation<double> animation) {
|
||||
return ScaleTransition(
|
||||
scale: animation,
|
||||
child: FadeTransition(
|
||||
opacity: animation,
|
||||
child: child,
|
||||
),
|
||||
child: FadeTransition(opacity: animation, child: child),
|
||||
);
|
||||
},
|
||||
child: showFab.value
|
||||
|
|
@ -96,9 +87,7 @@ class LibraryItemPage extends HookConsumerWidget {
|
|||
tooltip: 'Scroll to top',
|
||||
child: const Icon(Icons.arrow_upward),
|
||||
)
|
||||
: const SizedBox.shrink(
|
||||
key: ValueKey('fab-empty'),
|
||||
),
|
||||
: const SizedBox.shrink(key: ValueKey('fab-empty')),
|
||||
),
|
||||
body: CustomScrollView(
|
||||
controller: scrollController,
|
||||
|
|
@ -115,17 +104,11 @@ class LibraryItemPage extends HookConsumerWidget {
|
|||
),
|
||||
),
|
||||
// a horizontal display with dividers of metadata
|
||||
SliverToBoxAdapter(
|
||||
child: LibraryItemMetadata(id: itemId),
|
||||
),
|
||||
SliverToBoxAdapter(child: LibraryItemMetadata(id: itemId)),
|
||||
// a row of actions like play, download, share, etc
|
||||
SliverToBoxAdapter(
|
||||
child: LibraryItemActions(id: itemId),
|
||||
),
|
||||
SliverToBoxAdapter(child: LibraryItemActions(id: itemId)),
|
||||
// a expandable section for book description
|
||||
SliverToBoxAdapter(
|
||||
child: LibraryItemDescription(id: itemId),
|
||||
),
|
||||
SliverToBoxAdapter(child: LibraryItemDescription(id: itemId)),
|
||||
// a padding at the bottom to make sure the last item is not hidden by mini player
|
||||
const SliverToBoxAdapter(child: MiniPlayerBottomPadding()),
|
||||
],
|
||||
|
|
@ -137,10 +120,7 @@ class LibraryItemPage extends HookConsumerWidget {
|
|||
}
|
||||
|
||||
class LibraryItemDescription extends HookConsumerWidget {
|
||||
const LibraryItemDescription({
|
||||
super.key,
|
||||
required this.id,
|
||||
});
|
||||
const LibraryItemDescription({super.key, required this.id});
|
||||
|
||||
final String id;
|
||||
@override
|
||||
|
|
@ -160,16 +140,21 @@ class LibraryItemDescription extends HookConsumerWidget {
|
|||
double calculateWidth(
|
||||
BuildContext context,
|
||||
BoxConstraints constraints, {
|
||||
|
||||
/// width ratio of the cover image to the available width
|
||||
double widthRatio = 0.4,
|
||||
|
||||
/// height ratio of the cover image to the available height
|
||||
double maxHeightToUse = 0.25,
|
||||
}) {
|
||||
final availHeight =
|
||||
min(constraints.maxHeight, MediaQuery.of(context).size.height);
|
||||
final availWidth =
|
||||
min(constraints.maxWidth, MediaQuery.of(context).size.width);
|
||||
final availHeight = min(
|
||||
constraints.maxHeight,
|
||||
MediaQuery.of(context).size.height,
|
||||
);
|
||||
final availWidth = min(
|
||||
constraints.maxWidth,
|
||||
MediaQuery.of(context).size.width,
|
||||
);
|
||||
|
||||
// make the width widthRatio of the available width
|
||||
var width = availWidth * widthRatio;
|
||||
|
|
|
|||
|
|
@ -21,28 +21,26 @@ class LibraryItemSliverAppBar extends HookConsumerWidget {
|
|||
|
||||
final showTitle = useState(false);
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
void listener() {
|
||||
final shouldShow = scrollController.hasClients &&
|
||||
scrollController.offset > _showTitleThreshold;
|
||||
if (showTitle.value != shouldShow) {
|
||||
showTitle.value = shouldShow;
|
||||
}
|
||||
useEffect(() {
|
||||
void listener() {
|
||||
final shouldShow =
|
||||
scrollController.hasClients &&
|
||||
scrollController.offset > _showTitleThreshold;
|
||||
if (showTitle.value != shouldShow) {
|
||||
showTitle.value = shouldShow;
|
||||
}
|
||||
}
|
||||
|
||||
scrollController.addListener(listener);
|
||||
// Trigger listener once initially in case the view starts scrolled
|
||||
// (though unlikely for this specific use case, it's good practice)
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (scrollController.hasClients) {
|
||||
listener();
|
||||
}
|
||||
});
|
||||
return () => scrollController.removeListener(listener);
|
||||
},
|
||||
[scrollController],
|
||||
);
|
||||
scrollController.addListener(listener);
|
||||
// Trigger listener once initially in case the view starts scrolled
|
||||
// (though unlikely for this specific use case, it's good practice)
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (scrollController.hasClients) {
|
||||
listener();
|
||||
}
|
||||
});
|
||||
return () => scrollController.removeListener(listener);
|
||||
}, [scrollController]);
|
||||
|
||||
return SliverAppBar(
|
||||
elevation: 0,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue