mirror of
https://github.com/johrpan/musicus_mobile.git
synced 2025-10-25 19:27:24 +02:00
Support work sections
This commit is contained in:
parent
813fa2e47a
commit
93a5a06b55
6 changed files with 134 additions and 51 deletions
|
|
@ -6,12 +6,14 @@ import '../selectors/instruments.dart';
|
|||
import '../selectors/person.dart';
|
||||
|
||||
class PartData {
|
||||
final bool isSection;
|
||||
final titleController = TextEditingController();
|
||||
|
||||
Person composer;
|
||||
List<Instrument> instruments;
|
||||
|
||||
PartData({
|
||||
this.isSection = false,
|
||||
String title,
|
||||
this.composer,
|
||||
this.instruments = const [],
|
||||
|
|
@ -111,7 +113,7 @@ class PartTile extends StatefulWidget {
|
|||
PartTile({
|
||||
Key key,
|
||||
@required this.part,
|
||||
@required this.onMore,
|
||||
this.onMore,
|
||||
@required this.onDelete,
|
||||
}) : super(key: key);
|
||||
|
||||
|
|
@ -122,10 +124,12 @@ class PartTile extends StatefulWidget {
|
|||
class _PartTileState extends State<PartTile> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isSection = widget.part.isSection;
|
||||
|
||||
return Row(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0, right: 8.0),
|
||||
padding: EdgeInsets.only(left: isSection ? 8.0 : 24.0, right: 8.0),
|
||||
child: Icon(
|
||||
Icons.drag_handle,
|
||||
),
|
||||
|
|
@ -135,14 +139,15 @@ class _PartTileState extends State<PartTile> {
|
|||
controller: widget.part.titleController,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: 'Part title',
|
||||
hintText: isSection ? 'Section title' : 'Part title',
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.more_horiz),
|
||||
onPressed: widget.onMore,
|
||||
),
|
||||
if (!isSection)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.more_horiz),
|
||||
onPressed: widget.onMore,
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: widget.onDelete,
|
||||
|
|
@ -273,19 +278,31 @@ class _WorkEditorState extends State<WorkEditor> {
|
|||
final workId = widget?.workInfo?.work?.id ?? generateId();
|
||||
|
||||
List<PartInfo> partInfos = [];
|
||||
List<WorkSection> sections = [];
|
||||
int sectionCount = 0;
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
final part = parts[i];
|
||||
partInfos.add(PartInfo(
|
||||
part: WorkPart(
|
||||
if (part.isSection) {
|
||||
sections.add(WorkSection(
|
||||
id: generateId(),
|
||||
work: workId,
|
||||
title: part.titleController.text,
|
||||
composer: part.composer?.id,
|
||||
partOf: workId,
|
||||
partIndex: i,
|
||||
),
|
||||
instruments: part.instruments,
|
||||
composer: part.composer,
|
||||
));
|
||||
beforePartIndex: i - sectionCount,
|
||||
));
|
||||
sectionCount++;
|
||||
} else {
|
||||
partInfos.add(PartInfo(
|
||||
part: WorkPart(
|
||||
id: generateId(),
|
||||
title: part.titleController.text,
|
||||
composer: part.composer?.id,
|
||||
partOf: workId,
|
||||
partIndex: i - sectionCount,
|
||||
),
|
||||
instruments: part.instruments,
|
||||
composer: part.composer,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
final workInfo = WorkInfo(
|
||||
|
|
@ -299,6 +316,7 @@ class _WorkEditorState extends State<WorkEditor> {
|
|||
// from the parts.
|
||||
composers: [composer],
|
||||
parts: partInfos,
|
||||
sections: sections,
|
||||
);
|
||||
|
||||
final success = await backend.client.putWork(workInfo);
|
||||
|
|
@ -337,14 +355,37 @@ class _WorkEditorState extends State<WorkEditor> {
|
|||
});
|
||||
},
|
||||
),
|
||||
if (parts.length > 0)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0, top: 16.0),
|
||||
child: Text(
|
||||
'Parts',
|
||||
style: Theme.of(context).textTheme.subtitle1,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0, top: 16.0),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Parts',
|
||||
style: Theme.of(context).textTheme.subtitle1,
|
||||
),
|
||||
),
|
||||
FlatButton(
|
||||
child: Text('ADD SECTION'),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
parts.add(PartData(
|
||||
isSection: true,
|
||||
));
|
||||
});
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
child: Text('ADD PART'),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
parts.add(PartData());
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
children: partTiles,
|
||||
|
|
@ -357,15 +398,6 @@ class _WorkEditorState extends State<WorkEditor> {
|
|||
});
|
||||
},
|
||||
),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
icon: const Icon(Icons.add),
|
||||
label: Text('Add part'),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
parts.add(PartData());
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,11 +112,17 @@ class Database extends _$Database {
|
|||
));
|
||||
}
|
||||
|
||||
final List<WorkSection> sections = [];
|
||||
for (final section in await sectionsByWork(id).get()) {
|
||||
sections.add(section);
|
||||
}
|
||||
|
||||
return WorkInfo(
|
||||
work: work,
|
||||
instruments: instruments,
|
||||
composers: composers,
|
||||
parts: parts,
|
||||
sections: sections,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -160,8 +166,8 @@ class Database extends _$Database {
|
|||
await transaction(() async {
|
||||
final workId = workInfo.work.id;
|
||||
|
||||
// Delete old work data first. The parts and instrumentations will be
|
||||
// deleted automatically due to their foreign key constraints.
|
||||
// Delete old work data first. The parts, sections and instrumentations
|
||||
// will be deleted automatically due to their foreign key constraints.
|
||||
await deleteWork(workId);
|
||||
|
||||
// This will also include the composers of the work's parts.
|
||||
|
|
@ -194,6 +200,10 @@ class Database extends _$Database {
|
|||
));
|
||||
}
|
||||
}
|
||||
|
||||
for (final section in workInfo.sections) {
|
||||
await into(workSections).insert(section);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,13 @@ CREATE TABLE part_instrumentations (
|
|||
instrument INTEGER NOT NULL REFERENCES instruments(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE work_sections (
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
work INTEGER NOT NULL REFERENCES works(id) ON DELETE CASCADE,
|
||||
title TEXT NOT NULL,
|
||||
before_part_index INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE ensembles (
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
name TEXT NOT NULL
|
||||
|
|
@ -80,6 +87,9 @@ SELECT * FROM works WHERE id = :id LIMIT 1;
|
|||
partsByWork:
|
||||
SELECT * FROM work_parts WHERE part_of = :id ORDER BY part_index;
|
||||
|
||||
sectionsByWork:
|
||||
SELECT * FROM work_sections WHERE work = :id ORDER BY before_part_index;
|
||||
|
||||
worksByComposer:
|
||||
SELECT DISTINCT works.* FROM works
|
||||
JOIN work_parts ON work_parts.part_of = works.id
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ class PartInfo {
|
|||
final List<Instrument> instruments;
|
||||
|
||||
/// The composer of this part.
|
||||
///
|
||||
///
|
||||
/// This is null, if this part doesn't have a specific composer.
|
||||
final Person composer;
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ class WorkInfo {
|
|||
final Work work;
|
||||
|
||||
/// A list of instruments.
|
||||
///
|
||||
///
|
||||
/// This will not the include the instruments, that are specific to the work
|
||||
/// parts.
|
||||
final List<Instrument> instruments;
|
||||
|
|
@ -56,11 +56,15 @@ class WorkInfo {
|
|||
/// All available information on the work parts.
|
||||
final List<PartInfo> parts;
|
||||
|
||||
/// The sections of this work.
|
||||
final List<WorkSection> sections;
|
||||
|
||||
WorkInfo({
|
||||
this.work,
|
||||
this.instruments,
|
||||
this.composers,
|
||||
this.parts,
|
||||
this.sections,
|
||||
});
|
||||
|
||||
factory WorkInfo.fromJson(Map<String, dynamic> json) => WorkInfo(
|
||||
|
|
@ -72,6 +76,9 @@ class WorkInfo {
|
|||
json['composers'].map<Person>((j) => Person.fromJson(j)).toList(),
|
||||
parts:
|
||||
json['parts'].map<PartInfo>((j) => PartInfo.fromJson(j)).toList(),
|
||||
sections: json['sections']
|
||||
.map<WorkSection>((j) => WorkSection.fromJson(j))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
|
|
@ -79,6 +86,7 @@ class WorkInfo {
|
|||
'instruments': instruments.map((i) => i.toJson()).toList(),
|
||||
'composers': composers.map((c) => c.toJson()).toList(),
|
||||
'parts': parts.map((c) => c.toJson()).toList(),
|
||||
'sections': sections.map((s) => s.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -79,6 +79,9 @@ class _ProgramScreenState extends State<ProgramScreen> {
|
|||
// This will contain information on the last new work.
|
||||
WorkInfo workInfo;
|
||||
|
||||
// The index of the last displayed section.
|
||||
int lastSectionIndex;
|
||||
|
||||
for (var i = 0; i < playlist.length; i++) {
|
||||
// The widgets displayed for this track.
|
||||
List<Widget> children = [];
|
||||
|
|
@ -113,6 +116,18 @@ class _ProgramScreenState extends State<ProgramScreen> {
|
|||
for (final partId in partIds) {
|
||||
final partInfo = workInfo.parts[partId];
|
||||
|
||||
final sectionIndex = workInfo.sections
|
||||
.lastIndexWhere((s) => s.beforePartIndex <= partId);
|
||||
if (sectionIndex != lastSectionIndex) {
|
||||
lastSectionIndex = sectionIndex;
|
||||
children.add(Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 8.0,
|
||||
),
|
||||
child: Text(workInfo.sections[sectionIndex].title),
|
||||
));
|
||||
}
|
||||
|
||||
children.add(Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 8.0,
|
||||
|
|
@ -183,21 +198,20 @@ class _ProgramScreenState extends State<ProgramScreen> {
|
|||
},
|
||||
onLongPress: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SimpleDialog(
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
title: Text('Remove from playlist'),
|
||||
onTap: () {
|
||||
backend.playback.removeTrack(index);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
);
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SimpleDialog(
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
title: Text('Remove from playlist'),
|
||||
onTap: () {
|
||||
backend.playback.removeTrack(index);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -61,6 +61,15 @@ class _PlayerBarState extends State<PlayerBar> {
|
|||
|
||||
if (_partIds.isNotEmpty) {
|
||||
subtitleBuffer.write(': ');
|
||||
|
||||
final section = _workInfo.sections
|
||||
.lastWhere((s) => s.beforePartIndex <= _partIds[0]);
|
||||
|
||||
if (section != null) {
|
||||
subtitleBuffer.write(section.title);
|
||||
subtitleBuffer.write(': ');
|
||||
}
|
||||
|
||||
subtitleBuffer.write(
|
||||
_partIds.map((i) => _workInfo.parts[i].part.title).join(', '));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue