diff --git a/mobile/lib/app.dart b/mobile/lib/app.dart index fc77eb2..b395303 100644 --- a/mobile/lib/app.dart +++ b/mobile/lib/app.dart @@ -25,6 +25,13 @@ class App extends StatelessWidget { primary: Colors.amber, secondary: Colors.amber, ), + snackBarTheme: SnackBarThemeData( + backgroundColor: Colors.grey[800], + contentTextStyle: TextStyle( + color: Colors.white, + ), + behavior: SnackBarBehavior.floating, + ), fontFamily: 'Libertinus Sans', ), home: Builder( diff --git a/mobile/lib/editors/ensemble.dart b/mobile/lib/editors/ensemble.dart index 0556062..8fbae4d 100644 --- a/mobile/lib/editors/ensemble.dart +++ b/mobile/lib/editors/ensemble.dart @@ -17,6 +17,8 @@ class EnsembleEditor extends StatefulWidget { class _EnsembleEditorState extends State { final nameController = TextEditingController(); + bool uploading = false; + @override void initState() { super.initState(); @@ -34,18 +36,46 @@ class _EnsembleEditorState extends State { appBar: AppBar( title: Text('Ensemble'), actions: [ - FlatButton( - child: Text('DONE'), - onPressed: () async { - final ensemble = Ensemble( - id: widget.ensemble?.id ?? generateId(), - name: nameController.text, - ); + uploading + ? Padding( + padding: const EdgeInsets.all(16.0), + child: Center( + child: SizedBox( + width: 24.0, + height: 24.0, + child: CircularProgressIndicator( + strokeWidth: 2.0, + ), + ), + ), + ) + : FlatButton( + child: Text('DONE'), + onPressed: () async { + setState(() { + uploading = true; + }); - await backend.client.putEnsemble(ensemble); - Navigator.pop(context, ensemble); - }, - ) + final ensemble = Ensemble( + id: widget.ensemble?.id ?? generateId(), + name: nameController.text, + ); + + final success = await backend.client.putEnsemble(ensemble); + + setState(() { + uploading = false; + }); + + if (success) { + Navigator.pop(context, ensemble); + } else { + Scaffold.of(context).showSnackBar(SnackBar( + content: Text('Failed to upload'), + )); + } + }, + ), ], ), body: ListView( diff --git a/mobile/lib/editors/instrument.dart b/mobile/lib/editors/instrument.dart index f67c361..b95c349 100644 --- a/mobile/lib/editors/instrument.dart +++ b/mobile/lib/editors/instrument.dart @@ -17,6 +17,8 @@ class InstrumentEditor extends StatefulWidget { class _InstrumentEditorState extends State { final nameController = TextEditingController(); + bool uploading = false; + @override void initState() { super.initState(); @@ -34,18 +36,47 @@ class _InstrumentEditorState extends State { appBar: AppBar( title: Text('Instrument/Role'), actions: [ - FlatButton( - child: Text('DONE'), - onPressed: () async { - final instrument = Instrument( - id: widget.instrument?.id ?? generateId(), - name: nameController.text, - ); + uploading + ? Padding( + padding: const EdgeInsets.all(16.0), + child: Center( + child: SizedBox( + width: 24.0, + height: 24.0, + child: CircularProgressIndicator( + strokeWidth: 2.0, + ), + ), + ), + ) + : FlatButton( + child: Text('DONE'), + onPressed: () async { + setState(() { + uploading = true; + }); - await backend.client.putInstrument(instrument); - Navigator.pop(context, instrument); - }, - ) + final instrument = Instrument( + id: widget.instrument?.id ?? generateId(), + name: nameController.text, + ); + + final success = + await backend.client.putInstrument(instrument); + + setState(() { + uploading = false; + }); + + if (success) { + Navigator.pop(context, instrument); + } else { + Scaffold.of(context).showSnackBar(SnackBar( + content: Text('Failed to upload'), + )); + } + }, + ), ], ), body: ListView( diff --git a/mobile/lib/editors/person.dart b/mobile/lib/editors/person.dart index a0bfcee..68d5059 100644 --- a/mobile/lib/editors/person.dart +++ b/mobile/lib/editors/person.dart @@ -18,6 +18,8 @@ class _PersonEditorState extends State { final firstNameController = TextEditingController(); final lastNameController = TextEditingController(); + bool uploading = false; + @override void initState() { super.initState(); @@ -36,19 +38,47 @@ class _PersonEditorState extends State { appBar: AppBar( title: Text('Person'), actions: [ - FlatButton( - child: Text('DONE'), - onPressed: () async { - final person = Person( - id: widget.person?.id ?? generateId(), - firstName: firstNameController.text, - lastName: lastNameController.text, - ); + uploading + ? Padding( + padding: const EdgeInsets.all(16.0), + child: Center( + child: SizedBox( + width: 24.0, + height: 24.0, + child: CircularProgressIndicator( + strokeWidth: 2.0, + ), + ), + ), + ) + : FlatButton( + child: Text('DONE'), + onPressed: () async { + setState(() { + uploading = true; + }); - await backend.client.putPerson(person); - Navigator.pop(context, person); - }, - ), + final person = Person( + id: widget.person?.id ?? generateId(), + firstName: firstNameController.text, + lastName: lastNameController.text, + ); + + final success = await backend.client.putPerson(person); + + setState(() { + uploading = false; + }); + + if (success) { + Navigator.pop(context, person); + } else { + Scaffold.of(context).showSnackBar(SnackBar( + content: Text('Failed to upload'), + )); + } + }, + ), ], ), body: ListView( diff --git a/mobile/lib/editors/recording.dart b/mobile/lib/editors/recording.dart index c98ccec..cb74ff3 100644 --- a/mobile/lib/editors/recording.dart +++ b/mobile/lib/editors/recording.dart @@ -12,7 +12,7 @@ import '../selectors/work.dart'; /// navigator as a [RecordingSelectorResult] object. class RecordingEditor extends StatefulWidget { /// The recording to edit. - /// + /// /// If this is null, a new recording will be created. final RecordingInfo recordingInfo; @@ -27,6 +27,7 @@ class RecordingEditor extends StatefulWidget { class _RecordingEditorState extends State { final commentController = TextEditingController(); + bool uploading = false; WorkInfo workInfo; List performanceInfos = []; @@ -103,29 +104,58 @@ class _RecordingEditorState extends State { appBar: AppBar( title: Text('Recording'), actions: [ - FlatButton( - child: Text('DONE'), - onPressed: () async { - final recordingInfo = RecordingInfo( - recording: Recording( - id: widget?.recordingInfo?.recording?.id ?? generateId(), - work: workInfo.work.id, - comment: commentController.text, - ), - performances: performanceInfos, - ); + uploading + ? Padding( + padding: const EdgeInsets.all(16.0), + child: Center( + child: SizedBox( + width: 24.0, + height: 24.0, + child: CircularProgressIndicator( + strokeWidth: 2.0, + ), + ), + ), + ) + : FlatButton( + child: Text('DONE'), + onPressed: () async { + setState(() { + uploading = true; + }); - await backend.client.putRecording(recordingInfo); + final recordingInfo = RecordingInfo( + recording: Recording( + id: widget?.recordingInfo?.recording?.id ?? + generateId(), + work: workInfo.work.id, + comment: commentController.text, + ), + performances: performanceInfos, + ); - Navigator.pop( - context, - RecordingSelectorResult( - workInfo: workInfo, - recordingInfo: recordingInfo, + final success = + await backend.client.putRecording(recordingInfo); + + setState(() { + uploading = false; + }); + + if (success) { + Navigator.pop( + context, + RecordingSelectorResult( + workInfo: workInfo, + recordingInfo: recordingInfo, + ), + ); + } else { + Scaffold.of(context).showSnackBar(SnackBar( + content: Text('Failed to upload'), + )); + } + }, ), - ); - }, - ) ], ), body: ListView( diff --git a/mobile/lib/editors/work.dart b/mobile/lib/editors/work.dart index 8396ec1..fe371db 100644 --- a/mobile/lib/editors/work.dart +++ b/mobile/lib/editors/work.dart @@ -173,6 +173,7 @@ class WorkEditor extends StatefulWidget { class _WorkEditorState extends State { final titleController = TextEditingController(); + bool uploading = false; Person composer; List instruments = []; List parts = []; @@ -249,45 +250,72 @@ class _WorkEditorState extends State { appBar: AppBar( title: Text('Work'), actions: [ - FlatButton( - child: Text('DONE'), - onPressed: () async { - final workId = widget?.workInfo?.work?.id ?? generateId(); - - List partInfos = []; - for (var i = 0; i < parts.length; i++) { - final part = parts[i]; - partInfos.add(PartInfo( - work: Work( - id: generateId(), - title: part.titleController.text, - composer: part.composer?.id, - partOf: workId, - partIndex: i, + uploading + ? Padding( + padding: const EdgeInsets.all(16.0), + child: Center( + child: SizedBox( + width: 24.0, + height: 24.0, + child: CircularProgressIndicator( + strokeWidth: 2.0, + ), + ), ), - instruments: part.instruments, - composer: part.composer, - )); - } + ) + : FlatButton( + child: Text('DONE'), + onPressed: () async { + setState(() { + uploading = true; + }); - final workInfo = WorkInfo( - work: Work( - id: workId, - title: titleController.text, - composer: composer?.id, + final workId = widget?.workInfo?.work?.id ?? generateId(); + + List partInfos = []; + for (var i = 0; i < parts.length; i++) { + final part = parts[i]; + partInfos.add(PartInfo( + work: Work( + id: generateId(), + title: part.titleController.text, + composer: part.composer?.id, + partOf: workId, + partIndex: i, + ), + instruments: part.instruments, + composer: part.composer, + )); + } + + final workInfo = WorkInfo( + work: Work( + id: workId, + title: titleController.text, + composer: composer?.id, + ), + instruments: instruments, + // TODO: Theoretically, this should include all composers + // from the parts. + composers: [composer], + parts: partInfos, + ); + + final success = await backend.client.putWork(workInfo); + + setState(() { + uploading = false; + }); + + if (success) { + Navigator.pop(context, workInfo); + } else { + Scaffold.of(context).showSnackBar(SnackBar( + content: Text('Failed to upload'), + )); + } + }, ), - instruments: instruments, - // TODO: Theoretically, this should include all composers from - // the parts. - composers: [composer], - parts: partInfos, - ); - - await backend.client.putWork(workInfo); - - Navigator.pop(context, workInfo); - }, - ), ], ), body: ReorderableListView(