mobile: Integrate with server

This commit is contained in:
Elias Projahn 2020-04-26 15:35:45 +02:00
parent 60a474ea56
commit c93ebf17a0
20 changed files with 751 additions and 740 deletions

View file

@ -42,7 +42,7 @@ class _EnsembleEditorState extends State<EnsembleEditor> {
name: nameController.text,
);
await backend.db.updateEnsemble(ensemble);
await backend.client.putEnsemble(ensemble);
Navigator.pop(context, ensemble);
},
)

View file

@ -42,7 +42,7 @@ class _InstrumentEditorState extends State<InstrumentEditor> {
name: nameController.text,
);
await backend.db.updateInstrument(instrument);
await backend.client.putInstrument(instrument);
Navigator.pop(context, instrument);
},
)

View file

@ -0,0 +1,119 @@
import 'package:flutter/material.dart';
import 'package:musicus_database/musicus_database.dart';
import '../selectors/ensemble.dart';
import '../selectors/instruments.dart';
import '../selectors/person.dart';
class PerformanceEditor extends StatefulWidget {
final PerformanceInfo performanceInfo;
PerformanceEditor({
this.performanceInfo,
});
@override
_PerformanceEditorState createState() => _PerformanceEditorState();
}
class _PerformanceEditorState extends State<PerformanceEditor> {
Person person;
Ensemble ensemble;
Instrument role;
@override
void initState() {
super.initState();
if (widget.performanceInfo != null) {
person = widget.performanceInfo.person;
ensemble = widget.performanceInfo.ensemble;
role = widget.performanceInfo.role;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Edit performer'),
actions: <Widget>[
FlatButton(
child: Text('DONE'),
onPressed: () => Navigator.pop(
context,
PerformanceInfo(
person: person,
ensemble: ensemble,
role: role,
),
),
),
],
),
body: ListView(
children: <Widget>[
ListTile(
title: Text('Person'),
subtitle: Text(person != null
? '${person.firstName} ${person.lastName}'
: 'Select person'),
onTap: () async {
final Person newPerson = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PersonsSelector(),
fullscreenDialog: true,
),
);
if (newPerson != null) {
setState(() {
person = newPerson;
});
}
},
),
ListTile(
title: Text('Ensemble'),
subtitle: Text(ensemble?.name ?? 'Select ensemble'),
onTap: () async {
final Ensemble newEnsemble = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EnsembleSelector(),
fullscreenDialog: true,
),
);
if (newEnsemble != null) {
setState(() {
ensemble = newEnsemble;
});
}
},
),
ListTile(
title: Text('Role'),
subtitle: Text(role?.name ?? 'Select instrument/role'),
onTap: () async {
final Instrument newRole = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => InstrumentsSelector(),
fullscreenDialog: true,
),
);
if (newRole != null) {
setState(() {
role = newRole;
});
}
},
),
],
),
);
}
}

View file

@ -45,7 +45,7 @@ class _PersonEditorState extends State<PersonEditor> {
lastName: lastNameController.text,
);
await backend.db.updatePerson(person);
await backend.client.putPerson(person);
Navigator.pop(context, person);
},
),

View file

@ -2,10 +2,14 @@ import 'package:flutter/material.dart';
import 'package:musicus_database/musicus_database.dart';
import '../backend.dart';
import '../selectors/performer.dart';
import '../editors/performance.dart';
import '../selectors/recording.dart';
import '../selectors/work.dart';
import '../widgets/texts.dart';
/// Screen for editing a recording.
///
/// If the user has finished editing, the result will be returned using the
/// navigator as a [RecordingSelectorResult] object.
class RecordingEditor extends StatefulWidget {
final Recording recording;
@ -20,8 +24,8 @@ class RecordingEditor extends StatefulWidget {
class _RecordingEditorState extends State<RecordingEditor> {
final commentController = TextEditingController();
Work work;
List<PerformanceModel> performanceModels = [];
WorkInfo workInfo;
List<PerformanceInfo> performanceInfos = [];
@override
void initState() {
@ -37,20 +41,56 @@ class _RecordingEditorState extends State<RecordingEditor> {
final backend = Backend.of(context);
Future<void> selectWork() async {
final Work newWork = await Navigator.push(
final WorkInfo newWorkInfo = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => WorkSelector(),
fullscreenDialog: true,
));
if (newWork != null) {
if (newWorkInfo != null) {
setState(() {
work = newWork;
workInfo = newWorkInfo;
});
}
}
final List<Widget> performanceTiles = [];
for (var i = 0; i < performanceInfos.length; i++) {
final p = performanceInfos[i];
performanceTiles.add(ListTile(
title: Text(p.person != null
? '${p.person.firstName} ${p.person.lastName}'
: p.ensemble.name),
subtitle: p.role != null ? Text(p.role.name) : null,
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
setState(() {
performanceInfos.remove(p);
});
},
),
onTap: () async {
final PerformanceInfo performanceInfo = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PerformanceEditor(
performanceInfo: p,
),
fullscreenDialog: true,
));
if (performanceInfo != null) {
setState(() {
performanceInfos[i] = performanceInfo;
});
}
},
));
}
return Scaffold(
appBar: AppBar(
title: Text('Recording'),
@ -60,11 +100,11 @@ class _RecordingEditorState extends State<RecordingEditor> {
onPressed: () async {
final recording = Recording(
id: widget.recording?.id ?? generateId(),
work: work.id,
work: workInfo.work.id,
comment: commentController.text,
);
final performances = performanceModels
final performances = performanceInfos
.map((m) => Performance(
recording: recording.id,
person: m.person?.id,
@ -73,22 +113,28 @@ class _RecordingEditorState extends State<RecordingEditor> {
))
.toList();
await backend.db.updateRecording(RecordingData(
final recordingInfo =
await backend.client.putRecording(RecordingData(
recording: recording,
performances: performances,
));
Navigator.pop(context, recording);
Navigator.pop(context, RecordingSelectorResult(
workInfo: workInfo,
recordingInfo: recordingInfo,
));
},
)
],
),
body: ListView(
children: <Widget>[
work != null
workInfo != null
? ListTile(
title: WorkText(work.id),
subtitle: ComposersText(work.id),
title: Text(workInfo.work.title),
subtitle: Text(workInfo.composers
.map((p) => '${p.firstName} ${p.lastName}')
.join(', ')),
onTap: selectWork,
)
: ListTile(
@ -115,37 +161,22 @@ class _RecordingEditorState extends State<RecordingEditor> {
trailing: IconButton(
icon: const Icon(Icons.add),
onPressed: () async {
final PerformanceModel model = await Navigator.push(
final PerformanceInfo model = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PerformerSelector(),
builder: (context) => PerformanceEditor(),
fullscreenDialog: true,
));
if (model != null) {
setState(() {
performanceModels.add(model);
performanceInfos.add(model);
});
}
},
),
),
for (final performance in performanceModels)
ListTile(
title: Text(performance.person != null
? '${performance.person.firstName} ${performance.person.lastName}'
: performance.ensemble.name),
subtitle:
performance.role != null ? Text(performance.role.name) : null,
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
setState(() {
performanceModels.remove(performance);
});
},
),
),
...performanceTiles,
],
),
);

View file

@ -22,7 +22,8 @@ class TracksEditor extends StatefulWidget {
class _TracksEditorState extends State<TracksEditor> {
BackendState backend;
int recordingId;
WorkInfo workInfo;
RecordingInfo recordingInfo;
String parentId;
List<TrackModel> trackModels = [];
@ -44,12 +45,72 @@ class _TracksEditorState extends State<TracksEditor> {
tracks.add(Track(
fileName: trackModel.fileName,
recordingId: recordingId,
recordingId: recordingInfo.recording.id,
index: i,
partIds: [trackModel.workPartIndex],
));
}
// We need to copy all information associated with this track we
// got by asking the server to our local database. For now, we
// will just override everything that we already had previously.
// TODO: Think about efficiency.
backend.db.transaction(() async {
for (final composer in workInfo.composers) {
await backend.db.updatePerson(composer);
}
for (final instrument in workInfo.instruments) {
await backend.db.updateInstrument(instrument);
}
for (final partInfo in workInfo.parts) {
for (final instrument in partInfo.instruments) {
await backend.db.updateInstrument(instrument);
}
}
await backend.db.updateWork(WorkData(
data: WorkPartData(
work: workInfo.work,
instrumentIds:
workInfo.instruments.map((i) => i.id).toList(),
),
partData: workInfo.parts
.map((p) => WorkPartData(
work: p.work,
instrumentIds:
p.instruments.map((i) => i.id).toList(),
))
.toList(),
));
for (final performance in recordingInfo.performances) {
if (performance.person != null) {
await backend.db.updatePerson(performance.person);
}
if (performance.ensemble != null) {
await backend.db.updateEnsemble(performance.ensemble);
}
if (performance.role != null) {
await backend.db.updateInstrument(performance.role);
}
}
await backend.db.updateRecording(RecordingData(
recording: recordingInfo.recording,
performances: recordingInfo.performances
.map((p) => Performance(
recording: recordingInfo.recording.id,
person: p.person?.id,
ensemble: p.ensemble?.id,
role: p.role?.id,
))
.toList(),
));
});
backend.ml.addTracks(parentId, tracks);
Navigator.pop(context);
@ -61,9 +122,10 @@ class _TracksEditorState extends State<TracksEditor> {
header: Column(
children: <Widget>[
ListTile(
title: recordingId != null
title: recordingInfo != null
? RecordingTile(
recordingId: recordingId,
workInfo: workInfo,
recordingInfo: recordingInfo,
)
: Text('Select recording'),
onTap: selectRecording,
@ -92,7 +154,7 @@ class _TracksEditorState extends State<TracksEditor> {
trackModels = newTrackModels;
});
if (recordingId != null) {
if (recordingInfo != null) {
updateAutoParts();
}
}
@ -121,16 +183,17 @@ class _TracksEditorState extends State<TracksEditor> {
}
Future<void> selectRecording() async {
final Recording recording = await Navigator.push(
final RecordingSelectorResult result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RecordingsSelector(),
builder: (context) => RecordingSelector(),
),
);
if (recording != null) {
if (result != null) {
setState(() {
recordingId = recording.id;
workInfo = result.workInfo;
recordingInfo = result.recordingInfo;
});
updateAutoParts();
@ -139,18 +202,14 @@ class _TracksEditorState extends State<TracksEditor> {
/// Automatically associate the tracks with work parts.
Future<void> updateAutoParts() async {
final recording = await backend.db.recordingById(recordingId).getSingle();
final workId = recording.work;
final workParts = await backend.db.workParts(workId).get();
setState(() {
for (var i = 0; i < trackModels.length; i++) {
if (i >= workParts.length) {
if (i >= workInfo.parts.length) {
trackModels[i].workPartIndex = null;
trackModels[i].workPartTitle = null;
} else {
trackModels[i].workPartIndex = workParts[i].partIndex;
trackModels[i].workPartTitle = workParts[i].title;
trackModels[i].workPartIndex = workInfo.parts[i].work.partIndex;
trackModels[i].workPartTitle = workInfo.parts[i].work.title;
}
}
});

View file

@ -152,6 +152,10 @@ class _PartTileState extends State<PartTile> {
}
}
/// Screen for editing a work.
///
/// If the user is finished editing, the result will be returned as a [WorkInfo]
/// object.
class WorkEditor extends StatefulWidget {
final Work work;
@ -319,12 +323,12 @@ class _WorkEditorState extends State<WorkEditor> {
));
}
await backend.db.updateWork(WorkData(
final workInfo = await backend.client.putWork(WorkData(
data: data,
partData: partData,
));
Navigator.pop(context, data.work);
Navigator.pop(context, workInfo);
},
),
],