import 'package:flutter/material.dart'; import 'package:musicus_database/musicus_database.dart'; import '../backend.dart'; import '../selectors/instruments.dart'; import '../selectors/person.dart'; class PartData { final titleController = TextEditingController(); Person composer; List instruments; PartData({ String title, this.composer, this.instruments = const [], }) { titleController.text = title ?? ''; } } class WorkProperties extends StatelessWidget { final TextEditingController titleController; final Person composer; final List instruments; final void Function(Person) onComposerChanged; final void Function(List) 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: [ 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'), trailing: IconButton( icon: const Icon(Icons.delete), onPressed: () { onComposerChanged(null); }, ), 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'), trailing: IconButton( icon: const Icon(Icons.delete), onPressed: () { onInstrumentsChanged([]); }), onTap: () async { final List selection = await Navigator.push( context, MaterialPageRoute( builder: (context) => InstrumentsSelector( multiple: true, selection: instruments, ), fullscreenDialog: true, )); if (selection != null) { onInstrumentsChanged(selection); } }, ), ], ); } } class PartTile extends StatefulWidget { final PartData part; final void Function() onMore; final void Function() onDelete; PartTile({ Key key, @required this.part, @required this.onMore, @required this.onDelete, }) : super(key: key); @override _PartTileState createState() => _PartTileState(); } class _PartTileState extends State { @override Widget build(BuildContext context) { return Row( children: [ Padding( padding: const EdgeInsets.only(left: 16.0, right: 8.0), child: Icon( Icons.drag_handle, ), ), Expanded( child: TextField( controller: widget.part.titleController, decoration: InputDecoration( border: InputBorder.none, hintText: 'Part title', ), ), ), IconButton( icon: const Icon(Icons.more_horiz), onPressed: widget.onMore, ), IconButton( icon: const Icon(Icons.delete), onPressed: widget.onDelete, ), ], ); } } /// 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; WorkEditor({ this.work, }); @override _WorkEditorState createState() => _WorkEditorState(); } class _WorkEditorState extends State { final titleController = TextEditingController(); BackendState backend; String title = ''; Person composer; List instruments = []; List parts = []; @override void initState() { super.initState(); if (widget.work != null) { titleController.text = widget.work.title; } } @override void didChangeDependencies() { super.didChangeDependencies(); backend = Backend.of(context); 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; }); } }(); () async { final dbParts = await backend.db.workParts(widget.work.id).get(); for (final dbPart in dbParts) { final partInstruments = await backend.db.instrumentsByWork(dbPart.id).get(); Person partComposer; if (dbPart.composer != null) { partComposer = await backend.db.personById(dbPart.composer).getSingle(); } setState(() { parts.add(PartData( title: dbPart.title, composer: partComposer, instruments: partInstruments, )); }); } }(); } } @override Widget build(BuildContext context) { final List partTiles = []; for (var i = 0; i < parts.length; i++) { final part = parts[i]; partTiles.add(PartTile( key: Key(part.hashCode.toString()), part: part, onMore: () { showDialog( context: context, builder: (context) => StatefulBuilder( builder: (context, setState) => Dialog( child: ListView( shrinkWrap: true, children: [ WorkProperties( titleController: part.titleController, composer: part.composer, instruments: part.instruments, onComposerChanged: (composer) { setState(() { part.composer = composer; }); }, onInstrumentsChanged: (instruments) { setState(() { part.instruments = instruments; }); }, ), ], ), ), ), ); }, onDelete: () { setState(() { parts.removeAt(i); }); }, )); } return Scaffold( appBar: AppBar( title: Text('Work'), actions: [ FlatButton( child: Text('DONE'), onPressed: () async { final workId = widget.work?.id ?? generateId(); final data = WorkPartData( work: Work( id: workId, title: titleController.text, composer: composer?.id, ), instrumentIds: instruments.map((i) => i.id).toList(), ); final List partData = []; for (var i = 0; i < parts.length; i++) { final part = parts[i]; partData.add(WorkPartData( work: Work( id: generateId(), title: part.titleController.text, composer: part.composer?.id, partOf: workId, partIndex: i, ), instrumentIds: part.instruments.map((i) => i.id).toList(), )); } final workInfo = await backend.client.putWork(WorkData( data: data, partData: partData, )); Navigator.pop(context, workInfo); }, ), ], ), body: ReorderableListView( header: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ 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.subtitle1, ), ), ], ), children: partTiles, onReorder: (i1, i2) { setState(() { final part = parts.removeAt(i1); final newIndex = i2 > i1 ? i2 - 1 : i2; parts.insert(newIndex, part); }); }, ), floatingActionButton: FloatingActionButton.extended( icon: const Icon(Icons.add), label: Text('Add part'), onPressed: () { setState(() { parts.add(PartData()); }); }, ), ); } }