mirror of
https://github.com/johrpan/musicus_mobile.git
synced 2025-10-26 18:57:25 +01:00
The work editor contains a ReorderableList view now. This list consists of instances of the new PartTile widget, which only supports editing the parts title and its level (meaning how deep it is down the tree) for now.
356 lines
9 KiB
Dart
356 lines
9 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import '../backend.dart';
|
|
import '../database.dart';
|
|
import '../selectors/instruments.dart';
|
|
import '../selectors/person.dart';
|
|
|
|
class PartData {
|
|
final titleController = TextEditingController();
|
|
|
|
int level;
|
|
Person composer;
|
|
List<Instrument> instruments = [];
|
|
|
|
PartData(this.level);
|
|
}
|
|
|
|
class WorkProperties extends StatelessWidget {
|
|
final TextEditingController titleController;
|
|
final Person composer;
|
|
final List<Instrument> instruments;
|
|
final void Function(Person) onComposerChanged;
|
|
final void Function(List<Instrument>) onInstrumentsChanged;
|
|
|
|
WorkProperties({
|
|
@required this.titleController,
|
|
@required this.composer,
|
|
@required this.instruments,
|
|
@required this.onComposerChanged,
|
|
@required this.onInstrumentsChanged,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Column(
|
|
children: <Widget>[
|
|
Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: TextField(
|
|
controller: titleController,
|
|
decoration: InputDecoration(
|
|
labelText: 'Title',
|
|
),
|
|
),
|
|
),
|
|
ListTile(
|
|
title: Text('Composer'),
|
|
subtitle: Text(composer != null
|
|
? '${composer.firstName} ${composer.lastName}'
|
|
: 'Select composer'),
|
|
onTap: () async {
|
|
final Person person = await Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (context) => PersonsSelector(),
|
|
fullscreenDialog: true,
|
|
));
|
|
|
|
if (person != null) {
|
|
onComposerChanged(person);
|
|
}
|
|
},
|
|
),
|
|
ListTile(
|
|
title: Text('Instruments'),
|
|
subtitle: Text(instruments.isNotEmpty
|
|
? instruments.map((i) => i.name).join(', ')
|
|
: 'Select instruments'),
|
|
onTap: () async {
|
|
final List<Instrument> selection = await Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (context) => InstrumentsSelector(
|
|
selection: instruments,
|
|
),
|
|
fullscreenDialog: true,
|
|
));
|
|
|
|
if (selection != null) {
|
|
onInstrumentsChanged(selection);
|
|
}
|
|
},
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class PartTile extends StatefulWidget {
|
|
final PartData part;
|
|
final void Function() onMore;
|
|
final void Function() onAdd;
|
|
final void Function() onDelete;
|
|
final void Function(int levels) onMove;
|
|
|
|
PartTile({
|
|
Key key,
|
|
@required this.part,
|
|
@required this.onMore,
|
|
@required this.onAdd,
|
|
@required this.onDelete,
|
|
@required this.onMove,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
_PartTileState createState() => _PartTileState();
|
|
}
|
|
|
|
class _PartTileState extends State<PartTile> {
|
|
static const unit = 16.0;
|
|
static const iconShrink = 4.0;
|
|
|
|
double dragStart;
|
|
double dragDelta = 0.0;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final padding = widget.part.level * unit + dragDelta;
|
|
final iconSize = 24 - widget.part.level * iconShrink;
|
|
|
|
return GestureDetector(
|
|
child: Padding(
|
|
padding: EdgeInsets.only(left: padding > 0.0 ? padding : 0.0),
|
|
child: Row(
|
|
children: <Widget>[
|
|
Padding(
|
|
padding: const EdgeInsets.only(left: 16.0, right: 8.0),
|
|
child: Icon(
|
|
Icons.drag_handle,
|
|
size: iconSize,
|
|
),
|
|
),
|
|
Expanded(
|
|
child: TextField(
|
|
controller: widget.part.titleController,
|
|
decoration: InputDecoration(
|
|
border: InputBorder.none,
|
|
hintText: 'Part title',
|
|
),
|
|
),
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.more_horiz),
|
|
iconSize: iconSize,
|
|
onPressed: widget.onMore,
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.add),
|
|
iconSize: iconSize,
|
|
onPressed: widget.onAdd,
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.delete),
|
|
iconSize: iconSize,
|
|
onPressed: widget.onDelete,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
onHorizontalDragStart: (details) {
|
|
dragStart = details.localPosition.dx;
|
|
},
|
|
onHorizontalDragUpdate: (details) {
|
|
setState(() {
|
|
dragDelta = details.localPosition.dx - dragStart;
|
|
});
|
|
},
|
|
onHorizontalDragEnd: (details) {
|
|
if (dragDelta.abs() >= unit) {
|
|
widget.onMove((dragDelta / unit).round());
|
|
}
|
|
setState(() {
|
|
dragDelta = 0.0;
|
|
});
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
class WorkEditor extends StatefulWidget {
|
|
final Work work;
|
|
|
|
WorkEditor({
|
|
this.work,
|
|
});
|
|
|
|
@override
|
|
_WorkEditorState createState() => _WorkEditorState();
|
|
}
|
|
|
|
class _WorkEditorState extends State<WorkEditor> {
|
|
final titleController = TextEditingController();
|
|
|
|
Backend backend;
|
|
|
|
String title = '';
|
|
Person composer;
|
|
List<Instrument> instruments = [];
|
|
List<PartData> parts = [];
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
if (widget.work != null) {
|
|
titleController.text = widget.work.title;
|
|
}
|
|
}
|
|
|
|
@override
|
|
void didChangeDependencies() {
|
|
super.didChangeDependencies();
|
|
|
|
backend = Backend.of(context);
|
|
|
|
// TODO: Initialize parts
|
|
if (widget.work != null) {
|
|
if (widget.work.composer != null) {
|
|
() async {
|
|
final person =
|
|
await backend.db.personById(widget.work.composer).getSingle();
|
|
|
|
// We don't want to override a newly selected composer.
|
|
if (composer != null) {
|
|
setState(() {
|
|
composer = person;
|
|
});
|
|
}
|
|
}();
|
|
}
|
|
|
|
() async {
|
|
final selection =
|
|
await backend.db.instrumentsByWork(widget.work.id).get();
|
|
|
|
// We don't want to override already selected instruments.
|
|
if (instruments.isEmpty) {
|
|
setState(() {
|
|
instruments = selection;
|
|
});
|
|
}
|
|
}();
|
|
}
|
|
}
|
|
|
|
void cleanLevels() {
|
|
var previousLevel = -1;
|
|
for (var i = 0; i < parts.length; i++) {
|
|
final part = parts[i];
|
|
if (part.level > previousLevel + 1) {
|
|
part.level = previousLevel + 1;
|
|
}
|
|
previousLevel = part.level;
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final List<Widget> partTiles = [];
|
|
for (var i = 0; i < parts.length; i++) {
|
|
final part = parts[i];
|
|
|
|
partTiles.add(PartTile(
|
|
key: Key(part.hashCode.toString()),
|
|
part: part,
|
|
// TODO: Make part details editable
|
|
onMore: () {},
|
|
onAdd: () {
|
|
setState(() {
|
|
parts.insert(i + 1, PartData(part.level + 1));
|
|
});
|
|
},
|
|
onDelete: () {
|
|
setState(() {
|
|
parts.removeAt(i);
|
|
cleanLevels();
|
|
});
|
|
},
|
|
onMove: (levels) {
|
|
if (levels > 0 && i > 0 && parts[i - 1].level >= part.level) {
|
|
setState(() {
|
|
part.level++;
|
|
});
|
|
} else if (levels < 0) {
|
|
final newLevel = part.level + levels;
|
|
setState(() {
|
|
part.level = newLevel > 0 ? newLevel : 0;
|
|
});
|
|
}
|
|
},
|
|
));
|
|
}
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: Text('Work'),
|
|
actions: <Widget>[
|
|
FlatButton(
|
|
child: Text('DONE'),
|
|
onPressed: () {},
|
|
),
|
|
],
|
|
),
|
|
body: ReorderableListView(
|
|
header: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: <Widget>[
|
|
WorkProperties(
|
|
titleController: titleController,
|
|
composer: composer,
|
|
instruments: instruments,
|
|
onComposerChanged: (newComposer) {
|
|
setState(() {
|
|
composer = newComposer;
|
|
});
|
|
},
|
|
onInstrumentsChanged: (newInstruments) {
|
|
setState(() {
|
|
instruments = newInstruments;
|
|
});
|
|
},
|
|
),
|
|
if (parts.length > 0)
|
|
Padding(
|
|
padding: const EdgeInsets.only(left: 16.0, top: 16.0),
|
|
child: Text(
|
|
'Parts',
|
|
style: Theme.of(context).textTheme.subhead,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
children: partTiles,
|
|
onReorder: (i1, i2) {
|
|
setState(() {
|
|
final part = parts.removeAt(i1);
|
|
final newIndex = i2 > i1 ? i2 - 1 : i2;
|
|
|
|
parts.insert(newIndex, part);
|
|
|
|
cleanLevels();
|
|
});
|
|
},
|
|
),
|
|
floatingActionButton: FloatingActionButton.extended(
|
|
icon: const Icon(Icons.add),
|
|
label: Text('Add part'),
|
|
onPressed: () {
|
|
setState(() {
|
|
parts.add(PartData(0));
|
|
});
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|