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

This commit is contained in:
Dr.Blank 2026-01-10 16:51:05 +05:30
parent a520136e01
commit e23c0b6c5f
No known key found for this signature in database
GPG key ID: BA5F87FF0560C57B
84 changed files with 1565 additions and 1945 deletions

View file

@ -4,10 +4,7 @@ import 'package:vaani/constants/sizes.dart';
import 'package:vaani/features/player/providers/audiobook_player.dart';
class AudiobookPlayerSeekButton extends HookConsumerWidget {
const AudiobookPlayerSeekButton({
super.key,
required this.isForward,
});
const AudiobookPlayerSeekButton({super.key, required this.isForward});
/// if true, the button seeks forward, else it seeks backwards
final bool isForward;

View file

@ -5,10 +5,7 @@ import 'package:vaani/constants/sizes.dart';
import 'package:vaani/features/player/providers/audiobook_player.dart';
class AudiobookPlayerSeekChapterButton extends HookConsumerWidget {
const AudiobookPlayerSeekChapterButton({
super.key,
required this.isForward,
});
const AudiobookPlayerSeekChapterButton({super.key, required this.isForward});
/// if true, the button seeks forward, else it seeks backwards
final bool isForward;
@ -27,9 +24,7 @@ class AudiobookPlayerSeekChapterButton extends HookConsumerWidget {
void seekForward() {
final index = player.book!.chapters.indexOf(player.currentChapter!);
if (index < player.book!.chapters.length - 1) {
player.seek(
player.book!.chapters[index + 1].start + offset,
);
player.seek(player.book!.chapters[index + 1].start + offset);
} else {
player.seek(player.currentChapter!.end);
}
@ -37,8 +32,9 @@ class AudiobookPlayerSeekChapterButton extends HookConsumerWidget {
/// seek backward to the previous chapter or the start of the current chapter
void seekBackward() {
final currentPlayingChapterIndex =
player.book!.chapters.indexOf(player.currentChapter!);
final currentPlayingChapterIndex = player.book!.chapters.indexOf(
player.currentChapter!,
);
final chapterPosition =
player.positionInBook - player.currentChapter!.start;
BookChapter chapterToSeekTo;
@ -49,9 +45,7 @@ class AudiobookPlayerSeekChapterButton extends HookConsumerWidget {
} else {
chapterToSeekTo = player.currentChapter!;
}
player.seek(
chapterToSeekTo.start + offset,
);
player.seek(chapterToSeekTo.start + offset);
}
return IconButton(

View file

@ -15,9 +15,7 @@ import 'package:vaani/shared/extensions/duration_format.dart'
import 'package:vaani/shared/hooks.dart' show useTimer;
class ChapterSelectionButton extends HookConsumerWidget {
const ChapterSelectionButton({
super.key,
});
const ChapterSelectionButton({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
@ -49,9 +47,7 @@ class ChapterSelectionButton extends HookConsumerWidget {
}
class ChapterSelectionModal extends HookConsumerWidget {
const ChapterSelectionModal({
super.key,
});
const ChapterSelectionModal({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
@ -87,41 +83,40 @@ class ChapterSelectionModal extends HookConsumerWidget {
child: currentBook?.chapters == null
? const Text('No chapters found')
: Column(
children: currentBook!.chapters.map(
(chapter) {
final isCurrent = currentChapterIndex == chapter.id;
final isPlayed = currentChapterIndex != null &&
chapter.id < currentChapterIndex;
return ListTile(
autofocus: isCurrent,
iconColor: isPlayed && !isCurrent
? theme.disabledColor
children: currentBook!.chapters.map((chapter) {
final isCurrent = currentChapterIndex == chapter.id;
final isPlayed =
currentChapterIndex != null &&
chapter.id < currentChapterIndex;
return ListTile(
autofocus: isCurrent,
iconColor: isPlayed && !isCurrent
? theme.disabledColor
: null,
title: Text(
chapter.title,
style: isPlayed && !isCurrent
? TextStyle(color: theme.disabledColor)
: null,
title: Text(
chapter.title,
style: isPlayed && !isCurrent
? TextStyle(color: theme.disabledColor)
: null,
),
subtitle: Text(
'(${chapter.duration.smartBinaryFormat})',
style: isPlayed && !isCurrent
? TextStyle(color: theme.disabledColor)
: null,
),
trailing: isCurrent
? const PlayingIndicatorIcon()
: const Icon(Icons.play_arrow),
selected: isCurrent,
key: isCurrent ? chapterKey : null,
onTap: () {
Navigator.of(context).pop();
notifier.seek(chapter.start + 90.ms);
notifier.play();
},
);
},
).toList(),
),
subtitle: Text(
'(${chapter.duration.smartBinaryFormat})',
style: isPlayed && !isCurrent
? TextStyle(color: theme.disabledColor)
: null,
),
trailing: isCurrent
? const PlayingIndicatorIcon()
: const Icon(Icons.play_arrow),
selected: isCurrent,
key: isCurrent ? chapterKey : null,
onTap: () {
Navigator.of(context).pop();
notifier.seek(chapter.start + 90.ms);
notifier.play();
},
);
}).toList(),
),
),
),

View file

@ -10,9 +10,7 @@ import 'package:vaani/settings/app_settings_provider.dart';
final _logger = Logger('PlayerSpeedAdjustButton');
class PlayerSpeedAdjustButton extends HookConsumerWidget {
const PlayerSpeedAdjustButton({
super.key,
});
const PlayerSpeedAdjustButton({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
@ -35,21 +33,19 @@ class PlayerSpeedAdjustButton extends HookConsumerWidget {
notifier.setSpeed(speed);
if (appSettings.playerSettings.configurePlayerForEveryBook) {
ref
.read(
bookSettingsProvider(bookId).notifier,
)
.read(bookSettingsProvider(bookId).notifier)
.update(
bookSettings.copyWith
.playerSettings(preferredDefaultSpeed: speed),
bookSettings.copyWith.playerSettings(
preferredDefaultSpeed: speed,
),
);
} else {
ref
.read(
appSettingsProvider.notifier,
)
.read(appSettingsProvider.notifier)
.update(
appSettings.copyWith
.playerSettings(preferredDefaultSpeed: speed),
appSettings.copyWith.playerSettings(
preferredDefaultSpeed: speed,
),
);
}
},

View file

@ -59,8 +59,11 @@ class _PlayingIndicatorIconState extends State<PlayingIndicatorIcon> {
@override
void initState() {
super.initState();
_animationParams =
List.generate(widget.barCount, _createRandomParams, growable: false);
_animationParams = List.generate(
widget.barCount,
_createRandomParams,
growable: false,
);
}
// Helper to generate random parameters for one bar's animation cycle
@ -72,10 +75,12 @@ class _PlayingIndicatorIconState extends State<PlayingIndicatorIcon> {
// Note: These factors represent the scale relative to the *half-height*
// if centerSymmetric is true, controlled by the alignment in scaleY.
final targetHeightFactor1 = widget.minHeightFactor +
final targetHeightFactor1 =
widget.minHeightFactor +
_random.nextDouble() *
(widget.maxHeightFactor - widget.minHeightFactor);
final targetHeightFactor2 = widget.minHeightFactor +
final targetHeightFactor2 =
widget.minHeightFactor +
_random.nextDouble() *
(widget.maxHeightFactor - widget.minHeightFactor);
@ -95,7 +100,8 @@ class _PlayingIndicatorIconState extends State<PlayingIndicatorIcon> {
@override
Widget build(BuildContext context) {
final color = widget.color ??
final color =
widget.color ??
IconTheme.of(context).color ??
Theme.of(context).colorScheme.primary;
@ -110,8 +116,9 @@ class _PlayingIndicatorIconState extends State<PlayingIndicatorIcon> {
final double maxHeight = widget.size;
// Determine the alignment for scaling based on the symmetric flag
final Alignment scaleAlignment =
widget.centerSymmetric ? Alignment.center : Alignment.bottomCenter;
final Alignment scaleAlignment = widget.centerSymmetric
? Alignment.center
: Alignment.bottomCenter;
// Determine the cross axis alignment for the Row
final CrossAxisAlignment rowAlignment = widget.centerSymmetric
@ -129,47 +136,40 @@ class _PlayingIndicatorIconState extends State<PlayingIndicatorIcon> {
crossAxisAlignment: rowAlignment,
// Use spaceEvenly for better distribution, especially with center alignment
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(
widget.barCount,
(index) {
final params = _animationParams[index];
// The actual bar widget that will be animated
return Container(
width: barWidth,
// Set initial height to the max potential height
// The scaleY animation will control the visible height
height: maxHeight,
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(barWidth / 2),
),
)
.animate(
delay: params.initialDelay,
onPlay: (controller) => controller.repeat(
reverse: true,
),
)
// 1. Scale to targetHeightFactor1
.scaleY(
begin:
widget.minHeightFactor, // Scale factor starts near min
end: params.targetHeightFactor1,
duration: params.duration1,
curve: Curves.easeInOutCirc,
alignment: scaleAlignment, // Apply chosen alignment
)
// 2. Then scale to targetHeightFactor2
.then()
.scaleY(
end: params.targetHeightFactor2,
duration: params.duration2,
curve: Curves.easeInOutCirc,
alignment: scaleAlignment, // Apply chosen alignment
);
},
growable: false,
),
children: List.generate(widget.barCount, (index) {
final params = _animationParams[index];
// The actual bar widget that will be animated
return Container(
width: barWidth,
// Set initial height to the max potential height
// The scaleY animation will control the visible height
height: maxHeight,
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(barWidth / 2),
),
)
.animate(
delay: params.initialDelay,
onPlay: (controller) => controller.repeat(reverse: true),
)
// 1. Scale to targetHeightFactor1
.scaleY(
begin: widget.minHeightFactor, // Scale factor starts near min
end: params.targetHeightFactor1,
duration: params.duration1,
curve: Curves.easeInOutCirc,
alignment: scaleAlignment, // Apply chosen alignment
)
// 2. Then scale to targetHeightFactor2
.then()
.scaleY(
end: params.targetHeightFactor2,
duration: params.duration2,
curve: Curves.easeInOutCirc,
alignment: scaleAlignment, // Apply chosen alignment
);
}, growable: false),
),
),
);

View file

@ -10,10 +10,7 @@ import 'package:vaani/settings/app_settings_provider.dart';
const double itemExtent = 25;
class SpeedSelector extends HookConsumerWidget {
const SpeedSelector({
super.key,
required this.onSpeedSelected,
});
const SpeedSelector({super.key, required this.onSpeedSelected});
final void Function(double speed) onSpeedSelected;
@ -26,34 +23,22 @@ class SpeedSelector extends HookConsumerWidget {
final speedState = useState(currentSpeed);
// hook the onSpeedSelected function to the state
useEffect(
() {
onSpeedSelected(speedState.value);
return null;
},
[speedState.value],
);
useEffect(() {
onSpeedSelected(speedState.value);
return null;
}, [speedState.value]);
// the speed options
final minSpeed = min(
speeds.reduce(min),
playerSettings.minSpeed,
);
final maxSpeed = max(
speeds.reduce(max),
playerSettings.maxSpeed,
);
final minSpeed = min(speeds.reduce(min), playerSettings.minSpeed);
final maxSpeed = max(speeds.reduce(max), playerSettings.maxSpeed);
final speedIncrement = playerSettings.speedIncrement;
final availableSpeeds = ((maxSpeed - minSpeed) / speedIncrement).ceil() + 1;
final availableSpeedsList = List.generate(
availableSpeeds,
(index) {
// need to round to 2 decimal place to avoid floating point errors
return double.parse(
(minSpeed + index * speedIncrement).toStringAsFixed(2),
);
},
);
final availableSpeedsList = List.generate(availableSpeeds, (index) {
// need to round to 2 decimal place to avoid floating point errors
return double.parse(
(minSpeed + index * speedIncrement).toStringAsFixed(2),
);
});
final scrollController = useFixedExtentScrollController(
initialItem: availableSpeedsList.indexOf(currentSpeed),
@ -107,18 +92,19 @@ class SpeedSelector extends HookConsumerWidget {
(speed) => TextButton(
style: speed == speedState.value
? TextButton.styleFrom(
backgroundColor:
Theme.of(context).colorScheme.primaryContainer,
foregroundColor: Theme.of(context)
.colorScheme
.onPrimaryContainer,
backgroundColor: Theme.of(
context,
).colorScheme.primaryContainer,
foregroundColor: Theme.of(
context,
).colorScheme.onPrimaryContainer,
)
// border if not selected
: TextButton.styleFrom(
side: BorderSide(
color: Theme.of(context)
.colorScheme
.primaryContainer,
color: Theme.of(
context,
).colorScheme.primaryContainer,
),
),
onPressed: () async {
@ -195,14 +181,13 @@ class SpeedWheel extends StatelessWidget {
controller: scrollController,
scrollDirection: Axis.horizontal,
itemExtent: itemExtent,
diameterRatio: 1.5, squeeze: 1.2,
diameterRatio: 1.5,
squeeze: 1.2,
// useMagnifier: true,
// magnification: 1.5,
physics: const FixedExtentScrollPhysics(),
children: availableSpeedsList
.map(
(speed) => SpeedLine(speed: speed),
)
.map((speed) => SpeedLine(speed: speed))
.toList(),
onSelectedItemChanged: (index) {
speedState.value = availableSpeedsList[index];
@ -232,10 +217,7 @@ class SpeedWheel extends StatelessWidget {
}
class SpeedLine extends StatelessWidget {
const SpeedLine({
super.key,
required this.speed,
});
const SpeedLine({super.key, required this.speed});
final double speed;
@ -250,8 +232,8 @@ class SpeedLine extends StatelessWidget {
width: speed % 0.5 == 0
? 3
: speed % 0.25 == 0
? 2
: 0.5,
? 2
: 0.5,
color: Theme.of(context).colorScheme.onSurface,
),
),