✨ Lyrics offset
This commit is contained in:
@@ -12,6 +12,9 @@ class Tracks extends Table {
|
||||
TextColumn get path => text().unique()();
|
||||
TextColumn get artUri => text().nullable()(); // Path to local cover art
|
||||
TextColumn get lyrics => text().nullable()(); // JSON formatted lyrics
|
||||
IntColumn get lyricsOffset => integer().withDefault(
|
||||
const Constant(0),
|
||||
)(); // Offset in milliseconds for lyrics timing
|
||||
DateTimeColumn get addedAt => dateTime().withDefault(currentDateAndTime)();
|
||||
}
|
||||
|
||||
@@ -34,7 +37,7 @@ class AppDatabase extends _$AppDatabase {
|
||||
AppDatabase() : super(_openConnection());
|
||||
|
||||
@override
|
||||
int get schemaVersion => 4; // Bump version for lyrics column
|
||||
int get schemaVersion => 5; // Bump version for lyricsOffset column
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration {
|
||||
@@ -53,6 +56,9 @@ class AppDatabase extends _$AppDatabase {
|
||||
if (from < 4) {
|
||||
await m.addColumn(tracks, tracks.lyrics);
|
||||
}
|
||||
if (from < 5) {
|
||||
await m.addColumn(tracks, tracks.lyricsOffset);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -87,6 +87,18 @@ class $TracksTable extends Tracks with TableInfo<$TracksTable, Track> {
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: false,
|
||||
);
|
||||
static const VerificationMeta _lyricsOffsetMeta = const VerificationMeta(
|
||||
'lyricsOffset',
|
||||
);
|
||||
@override
|
||||
late final GeneratedColumn<int> lyricsOffset = GeneratedColumn<int>(
|
||||
'lyrics_offset',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
defaultValue: const Constant(0),
|
||||
);
|
||||
static const VerificationMeta _addedAtMeta = const VerificationMeta(
|
||||
'addedAt',
|
||||
);
|
||||
@@ -109,6 +121,7 @@ class $TracksTable extends Tracks with TableInfo<$TracksTable, Track> {
|
||||
path,
|
||||
artUri,
|
||||
lyrics,
|
||||
lyricsOffset,
|
||||
addedAt,
|
||||
];
|
||||
@override
|
||||
@@ -172,6 +185,15 @@ class $TracksTable extends Tracks with TableInfo<$TracksTable, Track> {
|
||||
lyrics.isAcceptableOrUnknown(data['lyrics']!, _lyricsMeta),
|
||||
);
|
||||
}
|
||||
if (data.containsKey('lyrics_offset')) {
|
||||
context.handle(
|
||||
_lyricsOffsetMeta,
|
||||
lyricsOffset.isAcceptableOrUnknown(
|
||||
data['lyrics_offset']!,
|
||||
_lyricsOffsetMeta,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (data.containsKey('added_at')) {
|
||||
context.handle(
|
||||
_addedAtMeta,
|
||||
@@ -219,6 +241,10 @@ class $TracksTable extends Tracks with TableInfo<$TracksTable, Track> {
|
||||
DriftSqlType.string,
|
||||
data['${effectivePrefix}lyrics'],
|
||||
),
|
||||
lyricsOffset: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.int,
|
||||
data['${effectivePrefix}lyrics_offset'],
|
||||
)!,
|
||||
addedAt: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.dateTime,
|
||||
data['${effectivePrefix}added_at'],
|
||||
@@ -241,6 +267,7 @@ class Track extends DataClass implements Insertable<Track> {
|
||||
final String path;
|
||||
final String? artUri;
|
||||
final String? lyrics;
|
||||
final int lyricsOffset;
|
||||
final DateTime addedAt;
|
||||
const Track({
|
||||
required this.id,
|
||||
@@ -251,6 +278,7 @@ class Track extends DataClass implements Insertable<Track> {
|
||||
required this.path,
|
||||
this.artUri,
|
||||
this.lyrics,
|
||||
required this.lyricsOffset,
|
||||
required this.addedAt,
|
||||
});
|
||||
@override
|
||||
@@ -274,6 +302,7 @@ class Track extends DataClass implements Insertable<Track> {
|
||||
if (!nullToAbsent || lyrics != null) {
|
||||
map['lyrics'] = Variable<String>(lyrics);
|
||||
}
|
||||
map['lyrics_offset'] = Variable<int>(lyricsOffset);
|
||||
map['added_at'] = Variable<DateTime>(addedAt);
|
||||
return map;
|
||||
}
|
||||
@@ -298,6 +327,7 @@ class Track extends DataClass implements Insertable<Track> {
|
||||
lyrics: lyrics == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(lyrics),
|
||||
lyricsOffset: Value(lyricsOffset),
|
||||
addedAt: Value(addedAt),
|
||||
);
|
||||
}
|
||||
@@ -316,6 +346,7 @@ class Track extends DataClass implements Insertable<Track> {
|
||||
path: serializer.fromJson<String>(json['path']),
|
||||
artUri: serializer.fromJson<String?>(json['artUri']),
|
||||
lyrics: serializer.fromJson<String?>(json['lyrics']),
|
||||
lyricsOffset: serializer.fromJson<int>(json['lyricsOffset']),
|
||||
addedAt: serializer.fromJson<DateTime>(json['addedAt']),
|
||||
);
|
||||
}
|
||||
@@ -331,6 +362,7 @@ class Track extends DataClass implements Insertable<Track> {
|
||||
'path': serializer.toJson<String>(path),
|
||||
'artUri': serializer.toJson<String?>(artUri),
|
||||
'lyrics': serializer.toJson<String?>(lyrics),
|
||||
'lyricsOffset': serializer.toJson<int>(lyricsOffset),
|
||||
'addedAt': serializer.toJson<DateTime>(addedAt),
|
||||
};
|
||||
}
|
||||
@@ -344,6 +376,7 @@ class Track extends DataClass implements Insertable<Track> {
|
||||
String? path,
|
||||
Value<String?> artUri = const Value.absent(),
|
||||
Value<String?> lyrics = const Value.absent(),
|
||||
int? lyricsOffset,
|
||||
DateTime? addedAt,
|
||||
}) => Track(
|
||||
id: id ?? this.id,
|
||||
@@ -354,6 +387,7 @@ class Track extends DataClass implements Insertable<Track> {
|
||||
path: path ?? this.path,
|
||||
artUri: artUri.present ? artUri.value : this.artUri,
|
||||
lyrics: lyrics.present ? lyrics.value : this.lyrics,
|
||||
lyricsOffset: lyricsOffset ?? this.lyricsOffset,
|
||||
addedAt: addedAt ?? this.addedAt,
|
||||
);
|
||||
Track copyWithCompanion(TracksCompanion data) {
|
||||
@@ -366,6 +400,9 @@ class Track extends DataClass implements Insertable<Track> {
|
||||
path: data.path.present ? data.path.value : this.path,
|
||||
artUri: data.artUri.present ? data.artUri.value : this.artUri,
|
||||
lyrics: data.lyrics.present ? data.lyrics.value : this.lyrics,
|
||||
lyricsOffset: data.lyricsOffset.present
|
||||
? data.lyricsOffset.value
|
||||
: this.lyricsOffset,
|
||||
addedAt: data.addedAt.present ? data.addedAt.value : this.addedAt,
|
||||
);
|
||||
}
|
||||
@@ -381,6 +418,7 @@ class Track extends DataClass implements Insertable<Track> {
|
||||
..write('path: $path, ')
|
||||
..write('artUri: $artUri, ')
|
||||
..write('lyrics: $lyrics, ')
|
||||
..write('lyricsOffset: $lyricsOffset, ')
|
||||
..write('addedAt: $addedAt')
|
||||
..write(')'))
|
||||
.toString();
|
||||
@@ -396,6 +434,7 @@ class Track extends DataClass implements Insertable<Track> {
|
||||
path,
|
||||
artUri,
|
||||
lyrics,
|
||||
lyricsOffset,
|
||||
addedAt,
|
||||
);
|
||||
@override
|
||||
@@ -410,6 +449,7 @@ class Track extends DataClass implements Insertable<Track> {
|
||||
other.path == this.path &&
|
||||
other.artUri == this.artUri &&
|
||||
other.lyrics == this.lyrics &&
|
||||
other.lyricsOffset == this.lyricsOffset &&
|
||||
other.addedAt == this.addedAt);
|
||||
}
|
||||
|
||||
@@ -422,6 +462,7 @@ class TracksCompanion extends UpdateCompanion<Track> {
|
||||
final Value<String> path;
|
||||
final Value<String?> artUri;
|
||||
final Value<String?> lyrics;
|
||||
final Value<int> lyricsOffset;
|
||||
final Value<DateTime> addedAt;
|
||||
const TracksCompanion({
|
||||
this.id = const Value.absent(),
|
||||
@@ -432,6 +473,7 @@ class TracksCompanion extends UpdateCompanion<Track> {
|
||||
this.path = const Value.absent(),
|
||||
this.artUri = const Value.absent(),
|
||||
this.lyrics = const Value.absent(),
|
||||
this.lyricsOffset = const Value.absent(),
|
||||
this.addedAt = const Value.absent(),
|
||||
});
|
||||
TracksCompanion.insert({
|
||||
@@ -443,6 +485,7 @@ class TracksCompanion extends UpdateCompanion<Track> {
|
||||
required String path,
|
||||
this.artUri = const Value.absent(),
|
||||
this.lyrics = const Value.absent(),
|
||||
this.lyricsOffset = const Value.absent(),
|
||||
this.addedAt = const Value.absent(),
|
||||
}) : title = Value(title),
|
||||
path = Value(path);
|
||||
@@ -455,6 +498,7 @@ class TracksCompanion extends UpdateCompanion<Track> {
|
||||
Expression<String>? path,
|
||||
Expression<String>? artUri,
|
||||
Expression<String>? lyrics,
|
||||
Expression<int>? lyricsOffset,
|
||||
Expression<DateTime>? addedAt,
|
||||
}) {
|
||||
return RawValuesInsertable({
|
||||
@@ -466,6 +510,7 @@ class TracksCompanion extends UpdateCompanion<Track> {
|
||||
if (path != null) 'path': path,
|
||||
if (artUri != null) 'art_uri': artUri,
|
||||
if (lyrics != null) 'lyrics': lyrics,
|
||||
if (lyricsOffset != null) 'lyrics_offset': lyricsOffset,
|
||||
if (addedAt != null) 'added_at': addedAt,
|
||||
});
|
||||
}
|
||||
@@ -479,6 +524,7 @@ class TracksCompanion extends UpdateCompanion<Track> {
|
||||
Value<String>? path,
|
||||
Value<String?>? artUri,
|
||||
Value<String?>? lyrics,
|
||||
Value<int>? lyricsOffset,
|
||||
Value<DateTime>? addedAt,
|
||||
}) {
|
||||
return TracksCompanion(
|
||||
@@ -490,6 +536,7 @@ class TracksCompanion extends UpdateCompanion<Track> {
|
||||
path: path ?? this.path,
|
||||
artUri: artUri ?? this.artUri,
|
||||
lyrics: lyrics ?? this.lyrics,
|
||||
lyricsOffset: lyricsOffset ?? this.lyricsOffset,
|
||||
addedAt: addedAt ?? this.addedAt,
|
||||
);
|
||||
}
|
||||
@@ -521,6 +568,9 @@ class TracksCompanion extends UpdateCompanion<Track> {
|
||||
if (lyrics.present) {
|
||||
map['lyrics'] = Variable<String>(lyrics.value);
|
||||
}
|
||||
if (lyricsOffset.present) {
|
||||
map['lyrics_offset'] = Variable<int>(lyricsOffset.value);
|
||||
}
|
||||
if (addedAt.present) {
|
||||
map['added_at'] = Variable<DateTime>(addedAt.value);
|
||||
}
|
||||
@@ -538,6 +588,7 @@ class TracksCompanion extends UpdateCompanion<Track> {
|
||||
..write('path: $path, ')
|
||||
..write('artUri: $artUri, ')
|
||||
..write('lyrics: $lyrics, ')
|
||||
..write('lyricsOffset: $lyricsOffset, ')
|
||||
..write('addedAt: $addedAt')
|
||||
..write(')'))
|
||||
.toString();
|
||||
@@ -1135,6 +1186,7 @@ typedef $$TracksTableCreateCompanionBuilder =
|
||||
required String path,
|
||||
Value<String?> artUri,
|
||||
Value<String?> lyrics,
|
||||
Value<int> lyricsOffset,
|
||||
Value<DateTime> addedAt,
|
||||
});
|
||||
typedef $$TracksTableUpdateCompanionBuilder =
|
||||
@@ -1147,6 +1199,7 @@ typedef $$TracksTableUpdateCompanionBuilder =
|
||||
Value<String> path,
|
||||
Value<String?> artUri,
|
||||
Value<String?> lyrics,
|
||||
Value<int> lyricsOffset,
|
||||
Value<DateTime> addedAt,
|
||||
});
|
||||
|
||||
@@ -1224,6 +1277,11 @@ class $$TracksTableFilterComposer
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
ColumnFilters<int> get lyricsOffset => $composableBuilder(
|
||||
column: $table.lyricsOffset,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
ColumnFilters<DateTime> get addedAt => $composableBuilder(
|
||||
column: $table.addedAt,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
@@ -1304,6 +1362,11 @@ class $$TracksTableOrderingComposer
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<int> get lyricsOffset => $composableBuilder(
|
||||
column: $table.lyricsOffset,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<DateTime> get addedAt => $composableBuilder(
|
||||
column: $table.addedAt,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
@@ -1343,6 +1406,11 @@ class $$TracksTableAnnotationComposer
|
||||
GeneratedColumn<String> get lyrics =>
|
||||
$composableBuilder(column: $table.lyrics, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<int> get lyricsOffset => $composableBuilder(
|
||||
column: $table.lyricsOffset,
|
||||
builder: (column) => column,
|
||||
);
|
||||
|
||||
GeneratedColumn<DateTime> get addedAt =>
|
||||
$composableBuilder(column: $table.addedAt, builder: (column) => column);
|
||||
|
||||
@@ -1408,6 +1476,7 @@ class $$TracksTableTableManager
|
||||
Value<String> path = const Value.absent(),
|
||||
Value<String?> artUri = const Value.absent(),
|
||||
Value<String?> lyrics = const Value.absent(),
|
||||
Value<int> lyricsOffset = const Value.absent(),
|
||||
Value<DateTime> addedAt = const Value.absent(),
|
||||
}) => TracksCompanion(
|
||||
id: id,
|
||||
@@ -1418,6 +1487,7 @@ class $$TracksTableTableManager
|
||||
path: path,
|
||||
artUri: artUri,
|
||||
lyrics: lyrics,
|
||||
lyricsOffset: lyricsOffset,
|
||||
addedAt: addedAt,
|
||||
),
|
||||
createCompanionCallback:
|
||||
@@ -1430,6 +1500,7 @@ class $$TracksTableTableManager
|
||||
required String path,
|
||||
Value<String?> artUri = const Value.absent(),
|
||||
Value<String?> lyrics = const Value.absent(),
|
||||
Value<int> lyricsOffset = const Value.absent(),
|
||||
Value<DateTime> addedAt = const Value.absent(),
|
||||
}) => TracksCompanion.insert(
|
||||
id: id,
|
||||
@@ -1440,6 +1511,7 @@ class $$TracksTableTableManager
|
||||
path: path,
|
||||
artUri: artUri,
|
||||
lyrics: lyrics,
|
||||
lyricsOffset: lyricsOffset,
|
||||
addedAt: addedAt,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
|
||||
@@ -33,7 +33,7 @@ final class TrackRepositoryProvider
|
||||
TrackRepository create() => TrackRepository();
|
||||
}
|
||||
|
||||
String _$trackRepositoryHash() => r'ad77006c472739d9d5067d394d6c5a3437535a11';
|
||||
String _$trackRepositoryHash() => r'244e5fc82fcaa34cb1276a41e4158a0eefcc7258';
|
||||
|
||||
abstract class _$TrackRepository extends $AsyncNotifier<void> {
|
||||
FutureOr<void> build();
|
||||
|
||||
@@ -41,7 +41,7 @@ final class LyricsFetcherProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$lyricsFetcherHash() => r'52296b2ccb55755ec5ad7ab751fe974dc3c64024';
|
||||
String _$lyricsFetcherHash() => r'071b83cb569812a6f90d42d7b7cf6954ac9631d7';
|
||||
|
||||
abstract class _$LyricsFetcher extends $Notifier<LyricsFetcherState> {
|
||||
LyricsFetcherState build();
|
||||
|
||||
@@ -372,7 +372,11 @@ class _PlayerLyrics extends HookConsumerWidget {
|
||||
if (lyricsData.type == 'timed') {
|
||||
return Stack(
|
||||
children: [
|
||||
_TimedLyricsView(lyrics: lyricsData, player: player),
|
||||
_TimedLyricsView(
|
||||
lyrics: lyricsData,
|
||||
player: player,
|
||||
trackPath: trackPath!,
|
||||
),
|
||||
_LyricsRefreshButton(trackPath: trackPath!),
|
||||
],
|
||||
);
|
||||
@@ -603,12 +607,14 @@ class _LyricsRefreshButton extends HookConsumerWidget {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Refresh Lyrics'),
|
||||
title: const Text('Lyrics Options'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text('Choose an action:'),
|
||||
const SizedBox(height: 16),
|
||||
Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
@@ -661,7 +667,9 @@ class _LyricsRefreshButton extends HookConsumerWidget {
|
||||
);
|
||||
debugPrint('Cleared lyrics from database');
|
||||
// Invalidate the track provider to refresh the UI
|
||||
ref.invalidate(trackByPathProvider(trackPath));
|
||||
ref.invalidate(
|
||||
trackByPathProvider(trackPath),
|
||||
);
|
||||
debugPrint(
|
||||
'Invalidated track provider for $trackPath',
|
||||
);
|
||||
@@ -673,6 +681,30 @@ class _LyricsRefreshButton extends HookConsumerWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton.icon(
|
||||
icon: const Icon(Icons.tune),
|
||||
label: const Text('Adjust Timing'),
|
||||
onPressed: trackAsync.maybeWhen(
|
||||
data: (track) => track != null
|
||||
? () {
|
||||
Navigator.of(context).pop();
|
||||
_showLyricsOffsetDialog(
|
||||
context,
|
||||
ref,
|
||||
track,
|
||||
trackPath,
|
||||
);
|
||||
}
|
||||
: null,
|
||||
orElse: () => null,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
@@ -684,27 +716,77 @@ class _LyricsRefreshButton extends HookConsumerWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showLyricsOffsetDialog(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
db.Track track,
|
||||
String trackPath,
|
||||
) {
|
||||
final offsetController = TextEditingController(
|
||||
text: track.lyricsOffset.toString(),
|
||||
);
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Adjust Lyrics Timing'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text(
|
||||
'Enter offset in milliseconds.\nPositive values delay lyrics, negative values advance them.',
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: offsetController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Offset (ms)',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
final offset = int.tryParse(offsetController.text) ?? 0;
|
||||
Navigator.of(context).pop();
|
||||
|
||||
final database = ref.read(databaseProvider);
|
||||
await (database.update(database.tracks)
|
||||
..where((t) => t.id.equals(track.id)))
|
||||
.write(db.TracksCompanion(lyricsOffset: drift.Value(offset)));
|
||||
|
||||
// Invalidate the track provider to refresh the UI
|
||||
ref.invalidate(trackByPathProvider(trackPath));
|
||||
},
|
||||
child: const Text('Save'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Provider to fetch a single track by path
|
||||
final trackByPathProvider = FutureProvider.family<db.Track?, String>((
|
||||
ref,
|
||||
trackPath,
|
||||
) async {
|
||||
final database = ref.watch(databaseProvider);
|
||||
return (database.select(
|
||||
database.tracks,
|
||||
)..where((t) => t.path.equals(trackPath))).getSingleOrNull();
|
||||
});
|
||||
|
||||
class _TimedLyricsView extends HookWidget {
|
||||
class _TimedLyricsView extends HookConsumerWidget {
|
||||
final LyricsData lyrics;
|
||||
final Player player;
|
||||
final String trackPath;
|
||||
|
||||
const _TimedLyricsView({required this.lyrics, required this.player});
|
||||
const _TimedLyricsView({
|
||||
required this.lyrics,
|
||||
required this.player,
|
||||
required this.trackPath,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final isDesktop = MediaQuery.sizeOf(context).width > 640;
|
||||
|
||||
final listController = useMemoized(() => ListController(), []);
|
||||
@@ -715,12 +797,21 @@ class _TimedLyricsView extends HookWidget {
|
||||
);
|
||||
final previousIndex = useState(-1);
|
||||
|
||||
// Get track data to access lyrics offset
|
||||
final trackAsync = ref.watch(trackByPathProvider(trackPath));
|
||||
|
||||
return trackAsync.when(
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (e, _) => Center(child: Text('Error: $e')),
|
||||
data: (track) {
|
||||
final lyricsOffset = track?.lyricsOffset ?? 0;
|
||||
|
||||
return StreamBuilder<Duration>(
|
||||
stream: player.stream.position,
|
||||
initialData: player.state.position,
|
||||
builder: (context, snapshot) {
|
||||
final position = snapshot.data ?? Duration.zero;
|
||||
final positionMs = position.inMilliseconds;
|
||||
final positionMs = position.inMilliseconds + lyricsOffset;
|
||||
|
||||
// Find current line index
|
||||
int currentIndex = 0;
|
||||
@@ -796,9 +887,10 @@ class _TimedLyricsView extends HookWidget {
|
||||
: FontWeight.normal,
|
||||
color: isActive
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurface.withOpacity(0.7),
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withOpacity(0.7),
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
child: Text(
|
||||
@@ -843,7 +935,9 @@ class _TimedLyricsView extends HookWidget {
|
||||
duration: const Duration(milliseconds: 200),
|
||||
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||
fontSize: isActive ? 20 : 16,
|
||||
fontWeight: isActive ? FontWeight.bold : FontWeight.normal,
|
||||
fontWeight: isActive
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
color: isActive
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(
|
||||
@@ -859,6 +953,8 @@ class _TimedLyricsView extends HookWidget {
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -954,11 +1050,11 @@ class _PlayerControls extends HookWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
_formatDuration(
|
||||
formatDuration(
|
||||
Duration(milliseconds: currentValue.toInt()),
|
||||
),
|
||||
),
|
||||
Text(_formatDuration(totalDuration)),
|
||||
Text(formatDuration(totalDuration)),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -1117,9 +1213,20 @@ class _PlayerControls extends HookWidget {
|
||||
);
|
||||
}
|
||||
|
||||
String _formatDuration(Duration d) {
|
||||
String formatDuration(Duration d) {
|
||||
final minutes = d.inMinutes;
|
||||
final seconds = d.inSeconds % 60;
|
||||
return '$minutes:${seconds.toString().padLeft(2, '0')}';
|
||||
}
|
||||
}
|
||||
|
||||
// Provider to fetch a single track by path
|
||||
final trackByPathProvider = FutureProvider.family<db.Track?, String>((
|
||||
ref,
|
||||
trackPath,
|
||||
) async {
|
||||
final database = ref.watch(databaseProvider);
|
||||
return (database.select(
|
||||
database.tracks,
|
||||
)..where((t) => t.path.equals(trackPath))).getSingleOrNull();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user