From 73544728626f09447f62ac3bd16f9cd7460998fd Mon Sep 17 00:00:00 2001 From: Fodor Benedek Date: Sat, 23 Nov 2024 20:42:29 +0100 Subject: [PATCH] rework cue slides revive logic --- lib/data/cue/cue.dart | 35 ++++------------ lib/data/song/song.dart | 1 + lib/main.dart | 6 +-- .../cue/{from_id.dart => from_uuid.dart} | 8 ++-- lib/services/cue/slide/watch_revived.dart | 16 ++++++++ lib/services/cue/write_cue.dart | 41 +++++++++++++++++-- lib/ui/cue/page.dart | 24 +++++++---- 7 files changed, 85 insertions(+), 46 deletions(-) rename lib/services/cue/{from_id.dart => from_uuid.dart} (55%) create mode 100644 lib/services/cue/slide/watch_revived.dart diff --git a/lib/data/cue/cue.dart b/lib/data/cue/cue.dart index 3ca38a3..0679a96 100644 --- a/lib/data/cue/cue.dart +++ b/lib/data/cue/cue.dart @@ -1,18 +1,14 @@ import 'dart:convert'; import 'package:drift/drift.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lyric/data/database.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:uuid/v4.dart'; import 'slide.dart'; -part 'cue.g.dart'; - const currentCueVersion = 1; @UseRowClass(Cue) +@TableIndex(name: 'cues_uuid', columns: {#uuid}, unique: true) class Cues extends Table { IntColumn get id => integer().autoIncrement()(); TextColumn get uuid => text()(); @@ -32,11 +28,14 @@ class Cue extends Insertable { List content; - List? slides; + //List? slides; + + Future> getRevivedSlides() async { + return await Future.wait(content.map((e) => Slide.reviveFromJson(e))); + } - Future reviveSlides() async { - slides = await Future.wait(content.map((e) => Slide.reviveFromJson(e))); - return; + static List getContentMapFromSlides(List slides) { + return slides.map((s) => s.toJson()).toList(); } Cue( @@ -59,21 +58,8 @@ class Cue extends Insertable { content: Value(content), ).toColumns(nullToAbsent); } - - static CuesCompanion newCueToCompanion(NewCue newCue) { - return CuesCompanion( - id: Value.absent(), - uuid: Value(UuidV4().generate()), - title: Value(newCue.title), - description: newCue.description != null ? Value(newCue.description!) : Value.absent(), - cueVersion: Value(currentCueVersion), - content: Value([]), - ); - } } -typedef NewCue = ({String title, String? description}); - class CueContentConverter extends TypeConverter, String> { const CueContentConverter(); @@ -87,8 +73,3 @@ class CueContentConverter extends TypeConverter, String> { return jsonEncode(value); } } - -@riverpod -Future reviveSlidesForCue(Ref ref, Cue cue) async { - return await cue.reviveSlides(); -} diff --git a/lib/data/song/song.dart b/lib/data/song/song.dart index df31bf8..f4a6bc2 100644 --- a/lib/data/song/song.dart +++ b/lib/data/song/song.dart @@ -85,6 +85,7 @@ class Song extends Insertable { const List mandatoryFields = ['uuid', 'title', 'lyrics']; +@TableIndex(name: 'songs_uuid', columns: {#uuid}, unique: true) @UseRowClass(Song) class Songs extends Table { IntColumn get id => integer().autoIncrement()(); diff --git a/lib/main.dart b/lib/main.dart index 1570744..8e01329 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -101,13 +101,13 @@ final _router = GoRouter( }, ), GoRoute( - path: '/cue/:id', + path: '/cue/:uuid', pageBuilder: (context, state) { - final cueId = int.parse(state.pathParameters['id']!); + final cueUuid = state.pathParameters['uuid']!; int? slideIndex = int.tryParse(state.uri.queryParameters['index'] ?? 'ignoreMe'); return MaterialPage( child: CuePage( - cueId, + cueUuid, initialSlideIndex: slideIndex, ), ); diff --git a/lib/services/cue/from_id.dart b/lib/services/cue/from_uuid.dart similarity index 55% rename from lib/services/cue/from_id.dart rename to lib/services/cue/from_uuid.dart index a44b0dc..c8b4ba9 100644 --- a/lib/services/cue/from_id.dart +++ b/lib/services/cue/from_uuid.dart @@ -5,12 +5,12 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../data/cue/cue.dart'; -part 'from_id.g.dart'; +part 'from_uuid.g.dart'; @riverpod -Stream watchAndReviveCueWithId(Ref ref, int id) async* { - await for (Cue cue in (db.cues.select()..where((c) => c.id.equals(id))).watchSingle()) { - await cue.reviveSlides(); +Stream watchCueWithUuid(Ref ref, String uuid) async* { + await for (Cue cue in (db.cues.select()..where((c) => c.uuid.equals(uuid))).watchSingle()) { + await cue.getRevivedSlides(); yield cue; } } diff --git a/lib/services/cue/slide/watch_revived.dart b/lib/services/cue/slide/watch_revived.dart new file mode 100644 index 0000000..b8ded44 --- /dev/null +++ b/lib/services/cue/slide/watch_revived.dart @@ -0,0 +1,16 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:lyric/services/cue/from_uuid.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../../data/cue/slide.dart'; + +part 'watch_revived.g.dart'; + +@riverpod +Stream> watchRevivedSlidesForCueWithUuid(Ref ref, String uuid) async* { + final cue = ref.watch(watchCueWithUuidProvider(uuid)); + + if (cue.hasValue) { + yield await cue.requireValue.getRevivedSlides(); + } +} diff --git a/lib/services/cue/write_cue.dart b/lib/services/cue/write_cue.dart index 03332c5..b4f5c1a 100644 --- a/lib/services/cue/write_cue.dart +++ b/lib/services/cue/write_cue.dart @@ -1,13 +1,48 @@ +import 'package:drift/drift.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:uuid/v4.dart'; import '../../data/cue/cue.dart'; +import '../../data/cue/slide.dart'; import '../../data/database.dart'; part 'write_cue.g.dart'; @riverpod -Future insertNewCue(Ref ref, NewCue newCue) async { - // we ignore generated id - await db.into(db.cues).insert(Cue.newCueToCompanion(newCue)); +Future insertNewCue(Ref ref, {required String title, String? description}) async { + await db.into(db.cues).insert( + CuesCompanion( + id: Value.absent(), + uuid: Value(UuidV4().generate()), + title: Value(title), + description: description != null ? Value(description) : Value.absent(), + cueVersion: Value(currentCueVersion), + content: Value([]), + ), + ); } + +@riverpod +Future updateCueMetadataFor(Ref ref, int id, {String? title, String? description}) async { + await (db.update(db.cues)..where((c) => c.id.equals(id))).write( + CuesCompanion( + title: Value.absentIfNull(title), + description: Value.absentIfNull(description), + ), + ); +} + +@riverpod +Future updateSlidesOfCue(Ref ref, Cue cue) async { + //cue.updateContentJson(); + + throw UnimplementedError(); +} + +/* +@riverpod +Future updateCueWith(Ref ref, int id, CuesCompanion data) async { + await (db.update(db.cues)..where((e) => e.id.equals(id))).write(data); +} +*/ diff --git a/lib/ui/cue/page.dart b/lib/ui/cue/page.dart index a7c33e5..d4146a2 100644 --- a/lib/ui/cue/page.dart +++ b/lib/ui/cue/page.dart @@ -2,14 +2,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lyric/data/cue/slide.dart'; import 'package:lyric/main.dart'; -import 'package:lyric/services/cue/from_id.dart'; +import 'package:lyric/services/cue/from_uuid.dart'; +import 'package:lyric/services/cue/slide/watch_revived.dart'; import 'package:lyric/ui/common/error.dart'; import 'package:lyric/ui/cue/slide_views/song.dart'; class CuePage extends ConsumerStatefulWidget { - const CuePage(this.cueId, {this.initialSlideIndex, super.key}); + const CuePage(this.uuid, {this.initialSlideIndex, super.key}); - final int cueId; + final String uuid; final int? initialSlideIndex; @override @@ -27,7 +28,8 @@ class _CuePageState extends ConsumerState { @override Widget build(BuildContext context) { - final cue = ref.watch(watchAndReviveCueWithIdProvider(widget.cueId)); + final cue = ref.watch(watchCueWithUuidProvider(widget.uuid)); + final slides = ref.watch(watchRevivedSlidesForCueWithUuidProvider(widget.uuid)); return LayoutBuilder(builder: (context, contraints) { return Flex( @@ -37,11 +39,15 @@ class _CuePageState extends ConsumerState { child: Scaffold( appBar: AppBar( title: Text(cue.value?.title ?? ''), + bottom: (cue.isLoading || slides.isLoading) + ? PreferredSize(preferredSize: const Size.fromHeight(4), child: LinearProgressIndicator()) + : null, ), - body: switch (cue) { + // todo show error message when cue provider returns error + body: switch (slides) { AsyncError(:final error, :final stackTrace) => LErrorCard( type: LErrorType.error, - title: 'Nem sikerült betölteni a listát!', + title: 'Nem sikerült betölteni a lista diáit!', icon: Icons.error, message: error.toString(), stack: stackTrace.toString(), @@ -52,7 +58,7 @@ class _CuePageState extends ConsumerState { AsyncValue(:final value!) => ListTileTheme( selectedTileColor: Theme.of(context).indicatorColor, child: ListView( - children: value.slides!.asMap().entries.map( + children: value.asMap().entries.map( (indexedEntry) { callback() => setState(() { selectedSlideIndex = indexedEntry.key; @@ -96,7 +102,7 @@ class _CuePageState extends ConsumerState { child: Scaffold( backgroundColor: Theme.of(context).indicatorColor, appBar: AppBar( - title: Text(cue.requireValue.slides![selectedSlideIndex!].comment ?? ''), + title: Text(slides.requireValue[selectedSlideIndex!].comment ?? ''), elevation: 1, automaticallyImplyLeading: false, actions: [ @@ -109,7 +115,7 @@ class _CuePageState extends ConsumerState { ], actionsPadding: EdgeInsets.only(right: 5), ), - body: switch (cue.requireValue.slides![selectedSlideIndex!]) { + body: switch (slides.requireValue[selectedSlideIndex!]) { (SongSlide songSlide) => SongSlideView(songSlide), (UnknownTypeSlide unknownSlide) => LErrorCard( type: LErrorType.warning,