mirror of
https://github.com/johrpan/musicus_mobile.git
synced 2025-10-26 18:57:25 +01:00
mobile: Integrate with server
This commit is contained in:
parent
60a474ea56
commit
c93ebf17a0
20 changed files with 751 additions and 740 deletions
|
|
@ -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);
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
)
|
||||
|
|
|
|||
119
mobile/lib/editors/performance.dart
Normal file
119
mobile/lib/editors/performance.dart
Normal 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;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
),
|
||||
],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue