mirror of
https://github.com/johrpan/musicus_mobile.git
synced 2025-10-26 10:47: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,
|
name: nameController.text,
|
||||||
);
|
);
|
||||||
|
|
||||||
await backend.db.updateEnsemble(ensemble);
|
await backend.client.putEnsemble(ensemble);
|
||||||
Navigator.pop(context, ensemble);
|
Navigator.pop(context, ensemble);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ class _InstrumentEditorState extends State<InstrumentEditor> {
|
||||||
name: nameController.text,
|
name: nameController.text,
|
||||||
);
|
);
|
||||||
|
|
||||||
await backend.db.updateInstrument(instrument);
|
await backend.client.putInstrument(instrument);
|
||||||
Navigator.pop(context, 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,
|
lastName: lastNameController.text,
|
||||||
);
|
);
|
||||||
|
|
||||||
await backend.db.updatePerson(person);
|
await backend.client.putPerson(person);
|
||||||
Navigator.pop(context, person);
|
Navigator.pop(context, person);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,14 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_database/musicus_database.dart';
|
||||||
|
|
||||||
import '../backend.dart';
|
import '../backend.dart';
|
||||||
import '../selectors/performer.dart';
|
import '../editors/performance.dart';
|
||||||
|
import '../selectors/recording.dart';
|
||||||
import '../selectors/work.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 {
|
class RecordingEditor extends StatefulWidget {
|
||||||
final Recording recording;
|
final Recording recording;
|
||||||
|
|
||||||
|
|
@ -20,8 +24,8 @@ class RecordingEditor extends StatefulWidget {
|
||||||
class _RecordingEditorState extends State<RecordingEditor> {
|
class _RecordingEditorState extends State<RecordingEditor> {
|
||||||
final commentController = TextEditingController();
|
final commentController = TextEditingController();
|
||||||
|
|
||||||
Work work;
|
WorkInfo workInfo;
|
||||||
List<PerformanceModel> performanceModels = [];
|
List<PerformanceInfo> performanceInfos = [];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -37,20 +41,56 @@ class _RecordingEditorState extends State<RecordingEditor> {
|
||||||
final backend = Backend.of(context);
|
final backend = Backend.of(context);
|
||||||
|
|
||||||
Future<void> selectWork() async {
|
Future<void> selectWork() async {
|
||||||
final Work newWork = await Navigator.push(
|
final WorkInfo newWorkInfo = await Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => WorkSelector(),
|
builder: (context) => WorkSelector(),
|
||||||
fullscreenDialog: true,
|
fullscreenDialog: true,
|
||||||
));
|
));
|
||||||
|
|
||||||
if (newWork != null) {
|
if (newWorkInfo != null) {
|
||||||
setState(() {
|
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(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('Recording'),
|
title: Text('Recording'),
|
||||||
|
|
@ -60,11 +100,11 @@ class _RecordingEditorState extends State<RecordingEditor> {
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final recording = Recording(
|
final recording = Recording(
|
||||||
id: widget.recording?.id ?? generateId(),
|
id: widget.recording?.id ?? generateId(),
|
||||||
work: work.id,
|
work: workInfo.work.id,
|
||||||
comment: commentController.text,
|
comment: commentController.text,
|
||||||
);
|
);
|
||||||
|
|
||||||
final performances = performanceModels
|
final performances = performanceInfos
|
||||||
.map((m) => Performance(
|
.map((m) => Performance(
|
||||||
recording: recording.id,
|
recording: recording.id,
|
||||||
person: m.person?.id,
|
person: m.person?.id,
|
||||||
|
|
@ -73,22 +113,28 @@ class _RecordingEditorState extends State<RecordingEditor> {
|
||||||
))
|
))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
await backend.db.updateRecording(RecordingData(
|
final recordingInfo =
|
||||||
|
await backend.client.putRecording(RecordingData(
|
||||||
recording: recording,
|
recording: recording,
|
||||||
performances: performances,
|
performances: performances,
|
||||||
));
|
));
|
||||||
|
|
||||||
Navigator.pop(context, recording);
|
Navigator.pop(context, RecordingSelectorResult(
|
||||||
|
workInfo: workInfo,
|
||||||
|
recordingInfo: recordingInfo,
|
||||||
|
));
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
work != null
|
workInfo != null
|
||||||
? ListTile(
|
? ListTile(
|
||||||
title: WorkText(work.id),
|
title: Text(workInfo.work.title),
|
||||||
subtitle: ComposersText(work.id),
|
subtitle: Text(workInfo.composers
|
||||||
|
.map((p) => '${p.firstName} ${p.lastName}')
|
||||||
|
.join(', ')),
|
||||||
onTap: selectWork,
|
onTap: selectWork,
|
||||||
)
|
)
|
||||||
: ListTile(
|
: ListTile(
|
||||||
|
|
@ -115,37 +161,22 @@ class _RecordingEditorState extends State<RecordingEditor> {
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
icon: const Icon(Icons.add),
|
icon: const Icon(Icons.add),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final PerformanceModel model = await Navigator.push(
|
final PerformanceInfo model = await Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => PerformerSelector(),
|
builder: (context) => PerformanceEditor(),
|
||||||
fullscreenDialog: true,
|
fullscreenDialog: true,
|
||||||
));
|
));
|
||||||
|
|
||||||
if (model != null) {
|
if (model != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
performanceModels.add(model);
|
performanceInfos.add(model);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
for (final performance in performanceModels)
|
...performanceTiles,
|
||||||
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);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,8 @@ class TracksEditor extends StatefulWidget {
|
||||||
|
|
||||||
class _TracksEditorState extends State<TracksEditor> {
|
class _TracksEditorState extends State<TracksEditor> {
|
||||||
BackendState backend;
|
BackendState backend;
|
||||||
int recordingId;
|
WorkInfo workInfo;
|
||||||
|
RecordingInfo recordingInfo;
|
||||||
String parentId;
|
String parentId;
|
||||||
List<TrackModel> trackModels = [];
|
List<TrackModel> trackModels = [];
|
||||||
|
|
||||||
|
|
@ -44,12 +45,72 @@ class _TracksEditorState extends State<TracksEditor> {
|
||||||
|
|
||||||
tracks.add(Track(
|
tracks.add(Track(
|
||||||
fileName: trackModel.fileName,
|
fileName: trackModel.fileName,
|
||||||
recordingId: recordingId,
|
recordingId: recordingInfo.recording.id,
|
||||||
index: i,
|
index: i,
|
||||||
partIds: [trackModel.workPartIndex],
|
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);
|
backend.ml.addTracks(parentId, tracks);
|
||||||
|
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
|
@ -61,9 +122,10 @@ class _TracksEditorState extends State<TracksEditor> {
|
||||||
header: Column(
|
header: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
ListTile(
|
ListTile(
|
||||||
title: recordingId != null
|
title: recordingInfo != null
|
||||||
? RecordingTile(
|
? RecordingTile(
|
||||||
recordingId: recordingId,
|
workInfo: workInfo,
|
||||||
|
recordingInfo: recordingInfo,
|
||||||
)
|
)
|
||||||
: Text('Select recording'),
|
: Text('Select recording'),
|
||||||
onTap: selectRecording,
|
onTap: selectRecording,
|
||||||
|
|
@ -92,7 +154,7 @@ class _TracksEditorState extends State<TracksEditor> {
|
||||||
trackModels = newTrackModels;
|
trackModels = newTrackModels;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (recordingId != null) {
|
if (recordingInfo != null) {
|
||||||
updateAutoParts();
|
updateAutoParts();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -121,16 +183,17 @@ class _TracksEditorState extends State<TracksEditor> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> selectRecording() async {
|
Future<void> selectRecording() async {
|
||||||
final Recording recording = await Navigator.push(
|
final RecordingSelectorResult result = await Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => RecordingsSelector(),
|
builder: (context) => RecordingSelector(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (recording != null) {
|
if (result != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
recordingId = recording.id;
|
workInfo = result.workInfo;
|
||||||
|
recordingInfo = result.recordingInfo;
|
||||||
});
|
});
|
||||||
|
|
||||||
updateAutoParts();
|
updateAutoParts();
|
||||||
|
|
@ -139,18 +202,14 @@ class _TracksEditorState extends State<TracksEditor> {
|
||||||
|
|
||||||
/// Automatically associate the tracks with work parts.
|
/// Automatically associate the tracks with work parts.
|
||||||
Future<void> updateAutoParts() async {
|
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(() {
|
setState(() {
|
||||||
for (var i = 0; i < trackModels.length; i++) {
|
for (var i = 0; i < trackModels.length; i++) {
|
||||||
if (i >= workParts.length) {
|
if (i >= workInfo.parts.length) {
|
||||||
trackModels[i].workPartIndex = null;
|
trackModels[i].workPartIndex = null;
|
||||||
trackModels[i].workPartTitle = null;
|
trackModels[i].workPartTitle = null;
|
||||||
} else {
|
} else {
|
||||||
trackModels[i].workPartIndex = workParts[i].partIndex;
|
trackModels[i].workPartIndex = workInfo.parts[i].work.partIndex;
|
||||||
trackModels[i].workPartTitle = workParts[i].title;
|
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 {
|
class WorkEditor extends StatefulWidget {
|
||||||
final Work work;
|
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,
|
data: data,
|
||||||
partData: partData,
|
partData: partData,
|
||||||
));
|
));
|
||||||
|
|
||||||
Navigator.pop(context, data.work);
|
Navigator.pop(context, workInfo);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,6 @@ class HomeScreen extends StatelessWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
// For debugging purposes
|
|
||||||
body: StreamBuilder<List<Person>>(
|
body: StreamBuilder<List<Person>>(
|
||||||
stream: backend.db.allPersons().watch(),
|
stream: backend.db.allPersons().watch(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ class PersonScreen extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final backend = Backend.of(context);
|
final backend = Backend.of(context);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('${person.firstName} ${person.lastName}'),
|
title: Text('${person.firstName} ${person.lastName}'),
|
||||||
|
|
|
||||||
|
|
@ -8,90 +8,6 @@ import '../music_library.dart';
|
||||||
import '../widgets/play_pause_button.dart';
|
import '../widgets/play_pause_button.dart';
|
||||||
import '../widgets/recording_tile.dart';
|
import '../widgets/recording_tile.dart';
|
||||||
|
|
||||||
/// Data class to bundle information from the database on one track.
|
|
||||||
class ProgramItem {
|
|
||||||
/// ID of the recording.
|
|
||||||
///
|
|
||||||
/// We don't need the real recording, as the [RecordingTile] widget handles
|
|
||||||
/// that for us. If the recording is the same one, as the one from the
|
|
||||||
/// previous track, this will be null.
|
|
||||||
final int recordingId;
|
|
||||||
|
|
||||||
/// List of work parts contained in this track.
|
|
||||||
///
|
|
||||||
/// This will include the parts linked in the track as well as all parents of
|
|
||||||
/// them, if there are gaps between them (i.e. some parts are missing).
|
|
||||||
final List<Work> workParts;
|
|
||||||
|
|
||||||
ProgramItem({
|
|
||||||
this.recordingId,
|
|
||||||
this.workParts,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Widget displaying a [ProgramItem].
|
|
||||||
class ProgramTile extends StatelessWidget {
|
|
||||||
final ProgramItem item;
|
|
||||||
final bool isPlaying;
|
|
||||||
|
|
||||||
ProgramTile({
|
|
||||||
this.item,
|
|
||||||
this.isPlaying,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Row(
|
|
||||||
children: <Widget>[
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(4.0),
|
|
||||||
child: isPlaying
|
|
||||||
? const Icon(Icons.play_arrow)
|
|
||||||
: SizedBox(
|
|
||||||
width: 24.0,
|
|
||||||
height: 24.0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: <Widget>[
|
|
||||||
if (item.recordingId != null) ...[
|
|
||||||
RecordingTile(
|
|
||||||
recordingId: item.recordingId,
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
height: 8.0,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
|
||||||
for (final part in item.workParts)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
left: 8.0,
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
part.title,
|
|
||||||
style: TextStyle(
|
|
||||||
fontStyle: FontStyle.italic,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ProgramScreen extends StatefulWidget {
|
class ProgramScreen extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
_ProgramScreenState createState() => _ProgramScreenState();
|
_ProgramScreenState createState() => _ProgramScreenState();
|
||||||
|
|
@ -103,7 +19,7 @@ class _ProgramScreenState extends State<ProgramScreen> {
|
||||||
StreamSubscription<bool> playerActiveSubscription;
|
StreamSubscription<bool> playerActiveSubscription;
|
||||||
|
|
||||||
StreamSubscription<List<InternalTrack>> playlistSubscription;
|
StreamSubscription<List<InternalTrack>> playlistSubscription;
|
||||||
List<ProgramItem> items = [];
|
List<Widget> widgets = [];
|
||||||
|
|
||||||
StreamSubscription<double> positionSubscription;
|
StreamSubscription<double> positionSubscription;
|
||||||
double position = 0.0;
|
double position = 0.0;
|
||||||
|
|
@ -149,7 +65,7 @@ class _ProgramScreenState extends State<ProgramScreen> {
|
||||||
|
|
||||||
/// Go through the tracks of [playlist] and preprocess them for displaying.
|
/// Go through the tracks of [playlist] and preprocess them for displaying.
|
||||||
Future<void> updateProgram(List<InternalTrack> playlist) async {
|
Future<void> updateProgram(List<InternalTrack> playlist) async {
|
||||||
List<ProgramItem> newItems = [];
|
List<Widget> newWidgets = [];
|
||||||
|
|
||||||
// The following variables exist to adapt the resulting ProgramItem to its
|
// The following variables exist to adapt the resulting ProgramItem to its
|
||||||
// predecessor.
|
// predecessor.
|
||||||
|
|
@ -162,42 +78,59 @@ class _ProgramScreenState extends State<ProgramScreen> {
|
||||||
// from the database again.
|
// from the database again.
|
||||||
int lastWorkId;
|
int lastWorkId;
|
||||||
|
|
||||||
// This will always contain the parts of the current work.
|
// This will contain information on the last new work.
|
||||||
List<Work> workParts = [];
|
WorkInfo workInfo;
|
||||||
|
|
||||||
for (var i = 0; i < playlist.length; i++) {
|
for (var i = 0; i < playlist.length; i++) {
|
||||||
// The data that will be stored in the resulting ProgramItem.
|
// The widgets displayed for this track.
|
||||||
int newRecordingId;
|
List<Widget> children = [];
|
||||||
List<Work> newWorkParts = [];
|
|
||||||
|
|
||||||
final track = playlist[i];
|
final track = playlist[i];
|
||||||
final recordingId = track.track.recordingId;
|
final recordingId = track.track.recordingId;
|
||||||
final partIds = track.track.partIds;
|
final partIds = track.track.partIds;
|
||||||
|
|
||||||
// newRecordingId will be null, if the recording ID is the same. This
|
// If the recording is the same, the work will also be the same, so
|
||||||
// also means, that the work is the same, so workParts doesn't have to
|
// workInfo doesn't have to be updated either.
|
||||||
// be updated either.
|
|
||||||
if (recordingId != lastRecordingId) {
|
if (recordingId != lastRecordingId) {
|
||||||
lastRecordingId = recordingId;
|
lastRecordingId = recordingId;
|
||||||
newRecordingId = recordingId;
|
|
||||||
|
|
||||||
final recording =
|
final recordingInfo = await backend.db.getRecording(recordingId);
|
||||||
await backend.db.recordingById(recordingId).getSingle();
|
|
||||||
|
|
||||||
if (recording.work != lastWorkId) {
|
if (recordingInfo.recording.work != lastWorkId) {
|
||||||
workParts = await backend.db.workParts(recording.work).get();
|
lastWorkId = recordingInfo.recording.work;
|
||||||
|
workInfo = await backend.db.getWork(lastWorkId);
|
||||||
}
|
}
|
||||||
|
|
||||||
lastWorkId = recording.work;
|
children.addAll([
|
||||||
|
RecordingTile(
|
||||||
|
workInfo: workInfo,
|
||||||
|
recordingInfo: recordingInfo,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 8.0,
|
||||||
|
),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final partId in partIds) {
|
for (final partId in partIds) {
|
||||||
newWorkParts.add(workParts[partId]);
|
final partInfo = workInfo.parts[partId];
|
||||||
|
|
||||||
|
children.add(Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 8.0,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
partInfo.work.title,
|
||||||
|
style: TextStyle(
|
||||||
|
fontStyle: FontStyle.italic,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
newItems.add(ProgramItem(
|
newWidgets.add(Column(
|
||||||
recordingId: newRecordingId,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
workParts: newWorkParts,
|
children: children,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -205,7 +138,7 @@ class _ProgramScreenState extends State<ProgramScreen> {
|
||||||
// function might take some time.
|
// function might take some time.
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
items = newItems;
|
widgets = newWidgets;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -225,12 +158,27 @@ class _ProgramScreenState extends State<ProgramScreen> {
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasData) {
|
if (snapshot.hasData) {
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
itemCount: items.length,
|
itemCount: widgets.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return InkWell(
|
return InkWell(
|
||||||
child: ProgramTile(
|
child: Row(
|
||||||
item: items[index],
|
children: <Widget>[
|
||||||
isPlaying: index == snapshot?.data,
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(4.0),
|
||||||
|
child: index == snapshot.data
|
||||||
|
? const Icon(Icons.play_arrow)
|
||||||
|
: SizedBox(
|
||||||
|
width: 24.0,
|
||||||
|
height: 24.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: widgets[index],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
backend.player.skipTo(index);
|
backend.player.skipTo(index);
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,20 @@ class WorkScreen extends StatelessWidget {
|
||||||
itemCount: snapshot.data.length,
|
itemCount: snapshot.data.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final recording = snapshot.data[index];
|
final recording = snapshot.data[index];
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: PerformancesText(recording.id),
|
title: FutureBuilder<RecordingInfo>(
|
||||||
|
future: backend.db.getRecordingInfo(recording),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasData) {
|
||||||
|
return PerformancesText(
|
||||||
|
performanceInfos: snapshot.data.performances,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Text('...');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final tracks = backend.ml.tracks[recording.id];
|
final tracks = backend.ml.tracks[recording.id];
|
||||||
tracks.sort(
|
tracks.sort(
|
||||||
|
|
|
||||||
41
mobile/lib/selectors/ensemble.dart
Normal file
41
mobile/lib/selectors/ensemble.dart
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:musicus_database/musicus_database.dart';
|
||||||
|
|
||||||
|
import '../editors/ensemble.dart';
|
||||||
|
import '../widgets/lists.dart';
|
||||||
|
|
||||||
|
/// A screen to select an ensemble.
|
||||||
|
///
|
||||||
|
/// If the user has selected one, it will be returned as an [Ensemble] object
|
||||||
|
/// using the navigator.
|
||||||
|
class EnsembleSelector extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text('Select ensemble'),
|
||||||
|
),
|
||||||
|
body: EnsemblesList(
|
||||||
|
onSelected: (ensemble) {
|
||||||
|
Navigator.pop(context, ensemble);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
child: const Icon(Icons.add),
|
||||||
|
onPressed: () async {
|
||||||
|
final Ensemble ensemble = await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => EnsembleEditor(),
|
||||||
|
fullscreenDialog: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (ensemble != null) {
|
||||||
|
Navigator.pop(context, ensemble);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -35,7 +35,9 @@ class _InstrumentsSelectorState extends State<InstrumentsSelector> {
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(widget.multiple ? 'Select instruments/roles' : 'Select instrument/role'),
|
title: Text(widget.multiple
|
||||||
|
? 'Select instruments/roles'
|
||||||
|
: 'Select instrument/role'),
|
||||||
actions: widget.multiple
|
actions: widget.multiple
|
||||||
? <Widget>[
|
? <Widget>[
|
||||||
FlatButton(
|
FlatButton(
|
||||||
|
|
@ -45,8 +47,8 @@ class _InstrumentsSelectorState extends State<InstrumentsSelector> {
|
||||||
]
|
]
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
body: StreamBuilder(
|
body: FutureBuilder<List<Instrument>>(
|
||||||
stream: backend.db.allInstruments().watch(),
|
future: backend.client.getInstruments(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasData) {
|
if (snapshot.hasData) {
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
|
|
|
||||||
|
|
@ -1,212 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
|
||||||
|
|
||||||
import '../backend.dart';
|
|
||||||
import '../editors/ensemble.dart';
|
|
||||||
import '../editors/person.dart';
|
|
||||||
|
|
||||||
import 'instruments.dart';
|
|
||||||
|
|
||||||
class PerformanceModel {
|
|
||||||
final Person person;
|
|
||||||
final Ensemble ensemble;
|
|
||||||
final Instrument role;
|
|
||||||
|
|
||||||
PerformanceModel({
|
|
||||||
this.person,
|
|
||||||
this.ensemble,
|
|
||||||
this.role,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class PerformerSelector extends StatefulWidget {
|
|
||||||
@override
|
|
||||||
_PerformerSelectorState createState() => _PerformerSelectorState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _Selection {
|
|
||||||
final bool isPerson;
|
|
||||||
final Person person;
|
|
||||||
final Ensemble ensemble;
|
|
||||||
|
|
||||||
_Selection.person(this.person)
|
|
||||||
: isPerson = true,
|
|
||||||
ensemble = null;
|
|
||||||
|
|
||||||
_Selection.ensemble(this.ensemble)
|
|
||||||
: isPerson = false,
|
|
||||||
person = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PerformerSelectorState extends State<PerformerSelector> {
|
|
||||||
Instrument role;
|
|
||||||
_Selection selection;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final backend = Backend.of(context);
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: Text('Select performer'),
|
|
||||||
actions: <Widget>[
|
|
||||||
FlatButton(
|
|
||||||
child: Text('DONE'),
|
|
||||||
onPressed: () => Navigator.pop(
|
|
||||||
context,
|
|
||||||
PerformanceModel(
|
|
||||||
person: selection?.person,
|
|
||||||
ensemble: selection?.ensemble,
|
|
||||||
role: role,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
Material(
|
|
||||||
elevation: 2.0,
|
|
||||||
child: ListTile(
|
|
||||||
title: Text('Instrument/Role'),
|
|
||||||
subtitle:
|
|
||||||
Text(role != null ? role.name : 'Select instrument/role'),
|
|
||||||
trailing: IconButton(
|
|
||||||
icon: const Icon(Icons.delete),
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
role = null;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
onTap: () async {
|
|
||||||
final Instrument newRole = await Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => InstrumentsSelector(),
|
|
||||||
fullscreenDialog: true,
|
|
||||||
));
|
|
||||||
|
|
||||||
if (newRole != null) {
|
|
||||||
setState(() {
|
|
||||||
role = newRole;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: ListView(
|
|
||||||
children: <Widget>[
|
|
||||||
StreamBuilder<List<Person>>(
|
|
||||||
stream: backend.db.allPersons().watch(),
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.hasData && snapshot.data.isNotEmpty) {
|
|
||||||
return ExpansionTile(
|
|
||||||
initiallyExpanded: true,
|
|
||||||
title: Text('Persons'),
|
|
||||||
children: snapshot.data
|
|
||||||
.map((person) => RadioListTile<Person>(
|
|
||||||
title: Text(
|
|
||||||
'${person.lastName}, ${person.firstName}'),
|
|
||||||
value: person,
|
|
||||||
groupValue: selection?.person,
|
|
||||||
onChanged: (person) {
|
|
||||||
setState(() {
|
|
||||||
selection = _Selection.person(person);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Container();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
StreamBuilder<List<Ensemble>>(
|
|
||||||
stream: backend.db.allEnsembles().watch(),
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.hasData && snapshot.data.isNotEmpty) {
|
|
||||||
return ExpansionTile(
|
|
||||||
initiallyExpanded: true,
|
|
||||||
title: Text('Ensembles'),
|
|
||||||
children: snapshot.data
|
|
||||||
.map((ensemble) => RadioListTile<Ensemble>(
|
|
||||||
title: Text(ensemble.name),
|
|
||||||
value: ensemble,
|
|
||||||
groupValue: selection?.ensemble,
|
|
||||||
onChanged: (ensemble) {
|
|
||||||
setState(() {
|
|
||||||
selection = _Selection.ensemble(ensemble);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Container();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
floatingActionButton: FloatingActionButton(
|
|
||||||
child: const Icon(Icons.add),
|
|
||||||
onPressed: () async {
|
|
||||||
showModalBottomSheet(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: <Widget>[
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Icons.add),
|
|
||||||
title: Text('Add person'),
|
|
||||||
onTap: () async {
|
|
||||||
final Person person = await Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => PersonEditor(),
|
|
||||||
fullscreenDialog: true,
|
|
||||||
));
|
|
||||||
|
|
||||||
if (person != null) {
|
|
||||||
setState(() {
|
|
||||||
selection = _Selection.person(person);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Icons.add),
|
|
||||||
title: Text('Add ensemble'),
|
|
||||||
onTap: () async {
|
|
||||||
final Ensemble ensemble = await Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => EnsembleEditor(),
|
|
||||||
fullscreenDialog: true,
|
|
||||||
));
|
|
||||||
|
|
||||||
if (ensemble != null) {
|
|
||||||
setState(() {
|
|
||||||
selection = _Selection.ensemble(ensemble);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,47 +1,35 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_database/musicus_database.dart';
|
||||||
|
|
||||||
import '../backend.dart';
|
|
||||||
import '../editors/person.dart';
|
import '../editors/person.dart';
|
||||||
|
import '../widgets/lists.dart';
|
||||||
|
|
||||||
|
/// A screen to select a person.
|
||||||
|
///
|
||||||
|
/// If the user has selected a person, it will be returned as a [Person] object
|
||||||
|
/// using the navigator.
|
||||||
class PersonsSelector extends StatelessWidget {
|
class PersonsSelector extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final backend = Backend.of(context);
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('Select person'),
|
title: Text('Select person'),
|
||||||
),
|
),
|
||||||
body: StreamBuilder(
|
body: PersonsList(
|
||||||
stream: backend.db.allPersons().watch(),
|
onSelected: (person) {
|
||||||
builder: (context, snapshot) {
|
Navigator.pop(context, person);
|
||||||
if (snapshot.hasData) {
|
|
||||||
return ListView.builder(
|
|
||||||
itemCount: snapshot.data.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final person = snapshot.data[index];
|
|
||||||
|
|
||||||
return ListTile(
|
|
||||||
title: Text('${person.lastName}, ${person.firstName}'),
|
|
||||||
onTap: () => Navigator.pop(context, person),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Container();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
child: const Icon(Icons.add),
|
child: const Icon(Icons.add),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final Person person = await Navigator.push(
|
final Person person = await Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => PersonEditor(),
|
builder: (context) => PersonEditor(),
|
||||||
fullscreenDialog: true,
|
fullscreenDialog: true,
|
||||||
));
|
),
|
||||||
|
);
|
||||||
|
|
||||||
if (person != null) {
|
if (person != null) {
|
||||||
Navigator.pop(context, person);
|
Navigator.pop(context, person);
|
||||||
|
|
|
||||||
|
|
@ -1,211 +1,90 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_database/musicus_database.dart';
|
||||||
|
|
||||||
import '../backend.dart';
|
|
||||||
import '../editors/recording.dart';
|
import '../editors/recording.dart';
|
||||||
import '../widgets/texts.dart';
|
import '../widgets/lists.dart';
|
||||||
import '../widgets/works_by_composer.dart';
|
|
||||||
|
|
||||||
class PersonList extends StatelessWidget {
|
class RecordingSelectorResult {
|
||||||
final void Function(int personId) onSelect;
|
final WorkInfo workInfo;
|
||||||
|
final RecordingInfo recordingInfo;
|
||||||
|
|
||||||
PersonList({
|
RecordingSelectorResult({
|
||||||
this.onSelect,
|
this.workInfo,
|
||||||
|
this.recordingInfo,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final backend = Backend.of(context);
|
|
||||||
|
|
||||||
return Column(
|
|
||||||
children: <Widget>[
|
|
||||||
Material(
|
|
||||||
elevation: 2.0,
|
|
||||||
child: ListTile(
|
|
||||||
title: Text('Composers'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: StreamBuilder<List<Person>>(
|
|
||||||
stream: backend.db.allPersons().watch(),
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.hasData) {
|
|
||||||
return ListView.builder(
|
|
||||||
itemCount: snapshot.data.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final person = snapshot.data[index];
|
|
||||||
return ListTile(
|
|
||||||
title: Text('${person.lastName}, ${person.firstName}'),
|
|
||||||
onTap: () => onSelect(person.id),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Container();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class WorkList extends StatelessWidget {
|
/// A screen to select a recording.
|
||||||
final int composerId;
|
///
|
||||||
final void Function(int workId) onSelect;
|
/// If the user has selected a recording, a [RecordingSelectorResult] containing
|
||||||
|
/// the selected recording and the recorded work will be returned using the
|
||||||
|
/// navigator.
|
||||||
|
class RecordingSelector extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_RecordingSelectorState createState() => _RecordingSelectorState();
|
||||||
|
}
|
||||||
|
|
||||||
WorkList({
|
class _RecordingSelectorState extends State<RecordingSelector> {
|
||||||
this.composerId,
|
Person person;
|
||||||
this.onSelect,
|
WorkInfo workInfo;
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
Widget body;
|
||||||
children: <Widget>[
|
|
||||||
Material(
|
if (person == null) {
|
||||||
elevation: 2.0,
|
body = PersonsList(
|
||||||
child: ListTile(
|
onSelected: (newPerson) {
|
||||||
leading: IconButton(
|
setState(() {
|
||||||
icon: const Icon(Icons.arrow_back),
|
person = newPerson;
|
||||||
onPressed: () => Navigator.pop(context),
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if (workInfo == null) {
|
||||||
|
body = WorksList(
|
||||||
|
personId: person.id,
|
||||||
|
onSelected: (newWorkInfo) {
|
||||||
|
setState(() {
|
||||||
|
workInfo = newWorkInfo;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
body = RecordingsList(
|
||||||
|
workId: workInfo.work.id,
|
||||||
|
onSelected: (recordingInfo) {
|
||||||
|
Navigator.pop(
|
||||||
|
context,
|
||||||
|
RecordingSelectorResult(
|
||||||
|
workInfo: workInfo,
|
||||||
|
recordingInfo: recordingInfo,
|
||||||
),
|
),
|
||||||
title: PersonText(composerId),
|
);
|
||||||
),
|
},
|
||||||
),
|
);
|
||||||
Expanded(
|
|
||||||
child: WorksByComposer(
|
|
||||||
personId: composerId,
|
|
||||||
onTap: (selectedWork) => onSelect(selectedWork.id),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RecordingList extends StatelessWidget {
|
|
||||||
final int workId;
|
|
||||||
final void Function(Recording recording) onSelect;
|
|
||||||
|
|
||||||
RecordingList({
|
|
||||||
this.workId,
|
|
||||||
this.onSelect,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final backend = Backend.of(context);
|
|
||||||
return Column(
|
|
||||||
children: <Widget>[
|
|
||||||
Material(
|
|
||||||
elevation: 2.0,
|
|
||||||
child: ListTile(
|
|
||||||
leading: IconButton(
|
|
||||||
icon: const Icon(Icons.arrow_back),
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
),
|
|
||||||
title: WorkText(workId),
|
|
||||||
subtitle: ComposersText(workId),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: StreamBuilder<List<Recording>>(
|
|
||||||
stream: backend.db.recordingsByWork(workId).watch(),
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.hasData) {
|
|
||||||
return ListView.builder(
|
|
||||||
itemCount: snapshot.data.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final recording = snapshot.data[index];
|
|
||||||
return ListTile(
|
|
||||||
title: PerformancesText(recording.id),
|
|
||||||
onTap: () => onSelect(recording),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Container();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RecordingsSelector extends StatefulWidget {
|
|
||||||
@override
|
|
||||||
_RecordingsSelectorState createState() => _RecordingsSelectorState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _RecordingsSelectorState extends State<RecordingsSelector> {
|
|
||||||
final nestedNavigator = GlobalKey<NavigatorState>();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
// This exists to circumvent the nested navigator when selecting a
|
|
||||||
// recording.
|
|
||||||
void popUpperNavigator(Recording recording) {
|
|
||||||
Navigator.pop(context, recording);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return WillPopScope(
|
return Scaffold(
|
||||||
child: Scaffold(
|
appBar: AppBar(
|
||||||
appBar: AppBar(
|
title: Text('Select recording'),
|
||||||
leading: IconButton(
|
),
|
||||||
icon: const Icon(Icons.close),
|
body: body,
|
||||||
onPressed: () => Navigator.pop(context),
|
floatingActionButton: FloatingActionButton(
|
||||||
),
|
child: const Icon(Icons.add),
|
||||||
title: Text('Select recording'),
|
onPressed: () async {
|
||||||
),
|
final RecordingSelectorResult result = await Navigator.push(
|
||||||
body: Navigator(
|
context,
|
||||||
key: nestedNavigator,
|
MaterialPageRoute(
|
||||||
onGenerateRoute: (settings) => settings.name == '/'
|
builder: (context) => RecordingEditor(),
|
||||||
? MaterialPageRoute(
|
fullscreenDialog: true,
|
||||||
builder: (context) => PersonList(
|
),
|
||||||
onSelect: (personId) => nestedNavigator.currentState.push(
|
);
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => WorkList(
|
if (result != null) {
|
||||||
composerId: personId,
|
Navigator.pop(context, result);
|
||||||
onSelect: (workId) =>
|
}
|
||||||
nestedNavigator.currentState.push(
|
},
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => RecordingList(
|
|
||||||
workId: workId,
|
|
||||||
onSelect: (recording) =>
|
|
||||||
popUpperNavigator(recording),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
initialRoute: '/',
|
|
||||||
),
|
|
||||||
floatingActionButton: FloatingActionButton(
|
|
||||||
child: const Icon(Icons.add),
|
|
||||||
onPressed: () async {
|
|
||||||
final recording = await Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => RecordingEditor(),
|
|
||||||
fullscreenDialog: true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (recording != null) {
|
|
||||||
Navigator.pop(context, recording);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
onWillPop: () async => !(await nestedNavigator.currentState.maybePop()),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,68 +1,62 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_database/musicus_database.dart';
|
||||||
|
|
||||||
import '../backend.dart';
|
|
||||||
import '../editors/work.dart';
|
import '../editors/work.dart';
|
||||||
|
import '../widgets/lists.dart';
|
||||||
|
|
||||||
|
/// A screen to select a work.
|
||||||
|
///
|
||||||
|
/// If the user has selected a work, a [WorkInfo] will be returned
|
||||||
|
/// using the navigator.
|
||||||
|
class WorkSelector extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_WorkSelectorState createState() => _WorkSelectorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WorkSelectorState extends State<WorkSelector> {
|
||||||
|
Person person;
|
||||||
|
|
||||||
// TODO: Lazy load works and/or optimize queries.
|
|
||||||
class WorkSelector extends StatelessWidget {
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final backend = Backend.of(context);
|
Widget body;
|
||||||
|
|
||||||
|
if (person == null) {
|
||||||
|
body = PersonsList(
|
||||||
|
onSelected: (newPerson) {
|
||||||
|
setState(() {
|
||||||
|
person = newPerson;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
body = WorksList(
|
||||||
|
personId: person.id,
|
||||||
|
onSelected: (workInfo) {
|
||||||
|
setState(() {
|
||||||
|
Navigator.pop(context, workInfo);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('Select work'),
|
title: Text('Select work'),
|
||||||
),
|
),
|
||||||
body: StreamBuilder<List<Person>>(
|
body: body,
|
||||||
stream: backend.db.allPersons().watch(),
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.hasData) {
|
|
||||||
return ListView.builder(
|
|
||||||
itemCount: snapshot.data.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final person = snapshot.data[index];
|
|
||||||
final title = Text('${person.lastName}, ${person.firstName}');
|
|
||||||
return StreamBuilder<List<Work>>(
|
|
||||||
stream: backend.db.worksByComposer(person.id).watch(),
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.hasData && snapshot.data.isNotEmpty) {
|
|
||||||
return ExpansionTile(
|
|
||||||
title: title,
|
|
||||||
children: <Widget>[
|
|
||||||
for (final work in snapshot.data)
|
|
||||||
ListTile(
|
|
||||||
title: Text(work.title),
|
|
||||||
onTap: () => Navigator.pop(context, work),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return ListTile(
|
|
||||||
title: title,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Container();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
child: const Icon(Icons.add),
|
child: const Icon(Icons.add),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final Work work = await Navigator.push(
|
final WorkInfo workInfo = await Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => WorkEditor(),
|
builder: (context) => WorkEditor(),
|
||||||
fullscreenDialog: true,
|
fullscreenDialog: true,
|
||||||
));
|
),
|
||||||
|
);
|
||||||
|
|
||||||
if (work != null) {
|
if (workInfo != null) {
|
||||||
Navigator.pop(context, work);
|
Navigator.pop(context, workInfo);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
||||||
183
mobile/lib/widgets/lists.dart
Normal file
183
mobile/lib/widgets/lists.dart
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:musicus_database/musicus_database.dart';
|
||||||
|
|
||||||
|
import '../backend.dart';
|
||||||
|
import '../widgets/texts.dart';
|
||||||
|
|
||||||
|
/// A list of persons.
|
||||||
|
class PersonsList extends StatelessWidget {
|
||||||
|
/// Called, when the user has selected a person.
|
||||||
|
final void Function(Person person) onSelected;
|
||||||
|
|
||||||
|
PersonsList({
|
||||||
|
this.onSelected,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final backend = Backend.of(context);
|
||||||
|
|
||||||
|
return FutureBuilder<List<Person>>(
|
||||||
|
future: backend.client.getPersons(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasData) {
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: snapshot.data.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final person = snapshot.data[index];
|
||||||
|
|
||||||
|
return ListTile(
|
||||||
|
title: Text('${person.lastName}, ${person.firstName}'),
|
||||||
|
onTap: () {
|
||||||
|
if (onSelected != null) {
|
||||||
|
onSelected(person);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A list of ensembles.
|
||||||
|
class EnsemblesList extends StatelessWidget {
|
||||||
|
/// Called, when the user has selected an ensemble.
|
||||||
|
final void Function(Ensemble ensemble) onSelected;
|
||||||
|
|
||||||
|
EnsemblesList({
|
||||||
|
this.onSelected,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final backend = Backend.of(context);
|
||||||
|
|
||||||
|
return FutureBuilder<List<Ensemble>>(
|
||||||
|
future: backend.client.getEnsembles(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasData) {
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: snapshot.data.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final ensemble = snapshot.data[index];
|
||||||
|
|
||||||
|
return ListTile(
|
||||||
|
title: Text(ensemble.name),
|
||||||
|
onTap: () {
|
||||||
|
if (onSelected != null) {
|
||||||
|
onSelected(ensemble);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A list of works by one composer.
|
||||||
|
class WorksList extends StatelessWidget {
|
||||||
|
/// The ID of the composer.
|
||||||
|
final int personId;
|
||||||
|
|
||||||
|
/// Called, when the user has selected a work.
|
||||||
|
final void Function(WorkInfo workInfo) onSelected;
|
||||||
|
|
||||||
|
WorksList({
|
||||||
|
this.personId,
|
||||||
|
this.onSelected,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final backend = Backend.of(context);
|
||||||
|
|
||||||
|
return FutureBuilder<List<WorkInfo>>(
|
||||||
|
future: backend.client.getWorks(personId),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasData) {
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: snapshot.data.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final workInfo = snapshot.data[index];
|
||||||
|
|
||||||
|
return ListTile(
|
||||||
|
title: Text(workInfo.work.title),
|
||||||
|
onTap: () {
|
||||||
|
if (onSelected != null) {
|
||||||
|
onSelected(workInfo);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A list of recordings of a work.
|
||||||
|
class RecordingsList extends StatelessWidget {
|
||||||
|
/// The ID of the work.
|
||||||
|
final int workId;
|
||||||
|
|
||||||
|
/// Called, when the user has selected a recording.
|
||||||
|
final void Function(RecordingInfo recordingInfo) onSelected;
|
||||||
|
|
||||||
|
RecordingsList({
|
||||||
|
this.workId,
|
||||||
|
this.onSelected,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final backend = Backend.of(context);
|
||||||
|
|
||||||
|
return FutureBuilder<List<RecordingInfo>>(
|
||||||
|
future: backend.client.getRecordings(workId),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasData) {
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: snapshot.data.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final recordingInfo = snapshot.data[index];
|
||||||
|
|
||||||
|
return ListTile(
|
||||||
|
title: PerformancesText(
|
||||||
|
performanceInfos: recordingInfo.performances,
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
if (onSelected != null) {
|
||||||
|
onSelected(recordingInfo);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,50 +1,48 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_database/musicus_database.dart';
|
||||||
|
|
||||||
import '../backend.dart';
|
|
||||||
|
|
||||||
import 'texts.dart';
|
import 'texts.dart';
|
||||||
|
|
||||||
class RecordingTile extends StatelessWidget {
|
class RecordingTile extends StatelessWidget {
|
||||||
final int recordingId;
|
final WorkInfo workInfo;
|
||||||
|
final RecordingInfo recordingInfo;
|
||||||
|
|
||||||
RecordingTile({
|
RecordingTile({
|
||||||
this.recordingId,
|
this.workInfo,
|
||||||
|
this.recordingInfo,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final backend = Backend.of(context);
|
|
||||||
final textTheme = Theme.of(context).textTheme;
|
final textTheme = Theme.of(context).textTheme;
|
||||||
|
|
||||||
return StreamBuilder<Recording>(
|
return Padding(
|
||||||
stream: backend.db.recordingById(recordingId).watchSingle(),
|
padding: const EdgeInsets.symmetric(
|
||||||
builder: (context, snapshot) => Padding(
|
vertical: 8.0,
|
||||||
padding: const EdgeInsets.symmetric(
|
),
|
||||||
vertical: 8.0,
|
child: Column(
|
||||||
),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
child: Column(
|
children: <Widget>[
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
DefaultTextStyle(
|
||||||
children: <Widget>[
|
style: textTheme.subtitle1,
|
||||||
if (snapshot.hasData) ...[
|
child: Text(workInfo.composers
|
||||||
DefaultTextStyle(
|
.map((p) => '${p.firstName} ${p.lastName}')
|
||||||
style: textTheme.subtitle1,
|
.join(', ')),
|
||||||
child: ComposersText(snapshot.data.work),
|
),
|
||||||
),
|
DefaultTextStyle(
|
||||||
DefaultTextStyle(
|
style: textTheme.headline6,
|
||||||
style: textTheme.headline6,
|
child: Text(workInfo.work.title),
|
||||||
child: WorkText(snapshot.data.work),
|
),
|
||||||
),
|
const SizedBox(
|
||||||
],
|
height: 4.0,
|
||||||
const SizedBox(
|
),
|
||||||
height: 4.0,
|
DefaultTextStyle(
|
||||||
|
style: textTheme.bodyText1,
|
||||||
|
child: PerformancesText(
|
||||||
|
performanceInfos: recordingInfo.performances,
|
||||||
),
|
),
|
||||||
DefaultTextStyle(
|
),
|
||||||
style: textTheme.bodyText1,
|
],
|
||||||
child: PerformancesText(recordingId),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,72 +49,38 @@ class PersonText extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PerformancesText extends StatefulWidget {
|
/// A widget showing information on a list of performances.
|
||||||
final int recordingId;
|
class PerformancesText extends StatelessWidget {
|
||||||
|
/// The information to show.
|
||||||
|
final List<PerformanceInfo> performanceInfos;
|
||||||
|
|
||||||
PerformancesText(this.recordingId);
|
PerformancesText({
|
||||||
|
this.performanceInfos,
|
||||||
@override
|
});
|
||||||
_PerformancesTextState createState() => _PerformancesTextState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PerformancesTextState extends State<PerformancesText> {
|
|
||||||
BackendState backend;
|
|
||||||
StreamSubscription<List<Performance>> performancesSubscription;
|
|
||||||
String text = '...';
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeDependencies() {
|
|
||||||
super.didChangeDependencies();
|
|
||||||
|
|
||||||
performancesSubscription?.cancel();
|
|
||||||
backend = Backend.of(context);
|
|
||||||
|
|
||||||
performancesSubscription = backend.db
|
|
||||||
.performancesByRecording(widget.recordingId)
|
|
||||||
.watch()
|
|
||||||
.listen((performances) async {
|
|
||||||
final List<String> texts = [];
|
|
||||||
|
|
||||||
for (final performance in performances) {
|
|
||||||
final buffer = StringBuffer();
|
|
||||||
|
|
||||||
if (performance.person != null) {
|
|
||||||
final person =
|
|
||||||
await backend.db.personById(performance.person).getSingle();
|
|
||||||
buffer.write('${person.firstName} ${person.lastName}');
|
|
||||||
} else if (performance.ensemble != null) {
|
|
||||||
final ensemble =
|
|
||||||
await backend.db.ensembleById(performance.ensemble).getSingle();
|
|
||||||
buffer.write(ensemble.name);
|
|
||||||
} else {
|
|
||||||
buffer.write('Unknown');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (performance.role != null) {
|
|
||||||
final role =
|
|
||||||
await backend.db.instrumentById(performance.role).getSingle();
|
|
||||||
buffer.write(' (${role.name})');
|
|
||||||
}
|
|
||||||
|
|
||||||
texts.add(buffer.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
text = texts.join(', ');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Text(text);
|
final List<String> performanceTexts = [];
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
for (final p in performanceInfos) {
|
||||||
void dispose() {
|
final buffer = StringBuffer();
|
||||||
super.dispose();
|
|
||||||
performancesSubscription?.cancel();
|
if (p.person != null) {
|
||||||
|
buffer.write('${p.person.firstName} ${p.person.lastName}');
|
||||||
|
} else if (p.ensemble != null) {
|
||||||
|
buffer.write(p.ensemble.name);
|
||||||
|
} else {
|
||||||
|
buffer.write('Unknown');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p.role != null) {
|
||||||
|
buffer.write(' (${p.role.name})');
|
||||||
|
}
|
||||||
|
|
||||||
|
performanceTexts.add(buffer.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Text(performanceTexts.join(', '));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue