From 777c89fed4348b2cd8c8c69c3b5b390d02b97fc2 Mon Sep 17 00:00:00 2001 From: Elias Projahn Date: Fri, 6 May 2022 13:56:10 +0200 Subject: [PATCH] common: Adapt to dependencies and remove editing --- common/lib/src/backend.dart | 74 ++-- common/lib/src/editors/ensemble.dart | 106 ----- common/lib/src/editors/instrument.dart | 106 ----- common/lib/src/editors/performance.dart | 131 ------ common/lib/src/editors/person.dart | 118 ------ common/lib/src/editors/recording.dart | 231 ----------- common/lib/src/editors/tracks.dart | 165 -------- common/lib/src/editors/work.dart | 414 ------------------- common/lib/src/screens/about.dart | 4 +- common/lib/src/screens/account_settings.dart | 265 ------------ common/lib/src/screens/delete_account.dart | 97 ----- common/lib/src/screens/email.dart | 103 ----- common/lib/src/screens/home.dart | 45 +- common/lib/src/screens/password.dart | 117 ------ common/lib/src/screens/person.dart | 28 +- common/lib/src/screens/program.dart | 2 +- common/lib/src/screens/register.dart | 163 -------- common/lib/src/screens/server_settings.dart | 110 ----- common/lib/src/screens/settings.dart | 51 --- common/lib/src/screens/work.dart | 28 +- common/lib/src/selectors/ensemble.dart | 41 -- common/lib/src/selectors/files.dart | 211 ---------- common/lib/src/selectors/instruments.dart | 136 ------ common/lib/src/selectors/person.dart | 41 -- common/lib/src/selectors/recording.dart | 90 ---- common/lib/src/selectors/work.dart | 65 --- common/lib/src/settings.dart | 75 ---- common/lib/src/widgets/lists.dart | 226 +--------- common/lib/src/widgets/player_bar.dart | 2 +- common/lib/src/widgets/recording_tile.dart | 2 +- common/lib/src/widgets/texts.dart | 4 +- common/pubspec.yaml | 11 +- 32 files changed, 45 insertions(+), 3217 deletions(-) delete mode 100644 common/lib/src/editors/ensemble.dart delete mode 100644 common/lib/src/editors/instrument.dart delete mode 100644 common/lib/src/editors/performance.dart delete mode 100644 common/lib/src/editors/person.dart delete mode 100644 common/lib/src/editors/recording.dart delete mode 100644 common/lib/src/editors/tracks.dart delete mode 100644 common/lib/src/editors/work.dart delete mode 100644 common/lib/src/screens/account_settings.dart delete mode 100644 common/lib/src/screens/delete_account.dart delete mode 100644 common/lib/src/screens/email.dart delete mode 100644 common/lib/src/screens/password.dart delete mode 100644 common/lib/src/screens/register.dart delete mode 100644 common/lib/src/screens/server_settings.dart delete mode 100644 common/lib/src/selectors/ensemble.dart delete mode 100644 common/lib/src/selectors/files.dart delete mode 100644 common/lib/src/selectors/instruments.dart delete mode 100644 common/lib/src/selectors/person.dart delete mode 100644 common/lib/src/selectors/recording.dart delete mode 100644 common/lib/src/selectors/work.dart diff --git a/common/lib/src/backend.dart b/common/lib/src/backend.dart index 1236554..0f5edef 100644 --- a/common/lib/src/backend.dart +++ b/common/lib/src/backend.dart @@ -2,11 +2,11 @@ import 'dart:io'; import 'dart:isolate'; import 'dart:ui'; +import 'package:drift/drift.dart'; +import 'package:drift/isolate.dart'; +import 'package:drift/native.dart'; import 'package:flutter/widgets.dart'; -import 'package:moor/isolate.dart'; -import 'package:moor/moor.dart'; -import 'package:moor_ffi/moor_ffi.dart'; -import 'package:musicus_client/musicus_client.dart'; +import 'package:musicus_database/musicus_database.dart'; import 'library.dart'; import 'platform.dart'; @@ -79,15 +79,17 @@ class MusicusBackend extends StatefulWidget { } class MusicusBackendState extends State { - /// Starts the Moor isolate. + /// Starts the database isolate. /// /// It will create a database connection for [request.path] and will send the - /// Moor send port through [request.sendPort]. - static void _moorIsolateEntrypoint(_IsolateStartRequest request) { - final executor = VmDatabase(File(request.path)); - final moorIsolate = - MoorIsolate.inCurrent(() => DatabaseConnection.fromExecutor(executor)); - request.sendPort.send(moorIsolate.connectPort); + /// drift send port through [request.sendPort]. + static void _dbIsolateEntrypoint(_IsolateStartRequest request) { + final executor = NativeDatabase(File(request.path)); + + final driftIsolate = + DriftIsolate.inCurrent(() => DatabaseConnection.fromExecutor(executor)); + + request.sendPort.send(driftIsolate.connectPort); } /// The current backend status. @@ -99,7 +101,6 @@ class MusicusBackendState extends State { MusicusClientDatabase db; MusicusPlayback playback; MusicusSettings settings; - MusicusClient client; MusicusPlatform platform; MusicusLibrary library; @@ -111,17 +112,22 @@ class MusicusBackendState extends State { /// Initialize resources. Future _load() async { - SendPort moorPort = IsolateNameServer.lookupPortByName('moor'); - if (moorPort == null) { + SendPort driftPort = IsolateNameServer.lookupPortByName('moor'); + + if (driftPort == null) { final receivePort = ReceivePort(); - await Isolate.spawn(_moorIsolateEntrypoint, + + await Isolate.spawn(_dbIsolateEntrypoint, _IsolateStartRequest(receivePort.sendPort, widget.dbPath)); - moorPort = await receivePort.first; - IsolateNameServer.registerPortWithName(moorPort, 'moor'); + + driftPort = await receivePort.first; + IsolateNameServer.registerPortWithName(driftPort, 'drift'); } - final moorIsolate = MoorIsolate.fromConnectPort(moorPort); - db = MusicusClientDatabase.connect(connection: await moorIsolate.connect()); + final driftIsolate = DriftIsolate.fromConnectPort(driftPort); + db = MusicusClientDatabase.connect( + connection: await driftIsolate.connect(), + ); playback = widget.playback; await playback.setup(); @@ -136,18 +142,7 @@ class MusicusBackendState extends State { _updateMusicLibrary(path); }); - settings.server.listen((serverSettings) { - _updateClient(serverSettings); - }); - - settings.account.listen((credentials) { - client.credentials = credentials; - }); - - // This will also check for existing account settings. - _updateClient(settings.server.value); - - final path = settings.musicLibraryPath.value; + final path = settings.musicLibraryPath.valueOrNull; platform = widget.platform; platform.setBasePath(path); @@ -172,20 +167,6 @@ class MusicusBackendState extends State { } } - /// Create a new client based on [serverSettings]. - void _updateClient(MusicusServerSettings serverSettings) { - client?.dispose(); - client = MusicusClient( - host: serverSettings.host, - port: serverSettings.port, - basePath: serverSettings.apiPath, - credentials: settings.account.value, - ); - - // TODO: Maybe don't change the client in the middle of synchronization. - db.client = client; - } - @override Widget build(BuildContext context) { return _InheritedBackend( @@ -202,11 +183,10 @@ class MusicusBackendState extends State { /// We don't stop the Moor isolate, because it can be used elsewhere. db.close(); - client.dispose(); } } -/// Bundles arguments for the moor isolate. +/// Bundles arguments for the database isolate. class _IsolateStartRequest { final SendPort sendPort; final String path; diff --git a/common/lib/src/editors/ensemble.dart b/common/lib/src/editors/ensemble.dart deleted file mode 100644 index 5222d63..0000000 --- a/common/lib/src/editors/ensemble.dart +++ /dev/null @@ -1,106 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:musicus_client/musicus_client.dart'; - -import '../backend.dart'; - -class EnsembleEditor extends StatefulWidget { - final Ensemble ensemble; - - EnsembleEditor({ - this.ensemble, - }); - - @override - _EnsembleEditorState createState() => _EnsembleEditorState(); -} - -class _EnsembleEditorState extends State { - final nameController = TextEditingController(); - - bool uploading = false; - bool _sync = true; - - @override - void initState() { - super.initState(); - - if (widget.ensemble != null) { - nameController.text = widget.ensemble.name; - _sync = widget.ensemble.sync; - } - } - - @override - Widget build(BuildContext context) { - final backend = MusicusBackend.of(context); - - return Scaffold( - appBar: AppBar( - title: Text('Ensemble'), - actions: [ - 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; - }); - - final ensemble = Ensemble( - id: widget.ensemble?.id ?? generateId(), - name: nameController.text, - sync: _sync, - synced: false, - ); - - await backend.db.updateEnsemble(ensemble); - - setState(() { - uploading = false; - }); - - Navigator.pop(context, ensemble); - }, - ), - ], - ), - body: ListView( - children: [ - SwitchListTile( - title: Text('Synchronize changes'), - subtitle: Text(_sync - ? 'Publish changes on the server' - : 'Keep changes private'), - value: _sync, - onChanged: (value) { - setState(() { - _sync = value; - }); - }, - ), - Padding( - padding: const EdgeInsets.all(16.0), - child: TextField( - controller: nameController, - decoration: InputDecoration( - labelText: 'Name', - ), - ), - ), - ], - ), - ); - } -} diff --git a/common/lib/src/editors/instrument.dart b/common/lib/src/editors/instrument.dart deleted file mode 100644 index ab939e1..0000000 --- a/common/lib/src/editors/instrument.dart +++ /dev/null @@ -1,106 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:musicus_client/musicus_client.dart'; - -import '../backend.dart'; - -class InstrumentEditor extends StatefulWidget { - final Instrument instrument; - - InstrumentEditor({ - this.instrument, - }); - - @override - _InstrumentEditorState createState() => _InstrumentEditorState(); -} - -class _InstrumentEditorState extends State { - final nameController = TextEditingController(); - - bool uploading = false; - bool _sync = true; - - @override - void initState() { - super.initState(); - - if (widget.instrument != null) { - nameController.text = widget.instrument.name; - _sync = widget.instrument.sync; - } - } - - @override - Widget build(BuildContext context) { - final backend = MusicusBackend.of(context); - - return Scaffold( - appBar: AppBar( - title: Text('Instrument/Role'), - actions: [ - 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; - }); - - final instrument = Instrument( - id: widget.instrument?.id ?? generateId(), - name: nameController.text, - sync: _sync, - synced: false, - ); - - await backend.db.updateInstrument(instrument); - - setState(() { - uploading = false; - }); - - Navigator.pop(context, instrument); - }, - ), - ], - ), - body: ListView( - children: [ - SwitchListTile( - title: Text('Synchronize changes'), - subtitle: Text(_sync - ? 'Publish changes on the server' - : 'Keep changes private'), - value: _sync, - onChanged: (value) { - setState(() { - _sync = value; - }); - }, - ), - Padding( - padding: const EdgeInsets.all(16.0), - child: TextField( - controller: nameController, - decoration: InputDecoration( - labelText: 'Name', - ), - ), - ), - ], - ), - ); - } -} diff --git a/common/lib/src/editors/performance.dart b/common/lib/src/editors/performance.dart deleted file mode 100644 index d5fd906..0000000 --- a/common/lib/src/editors/performance.dart +++ /dev/null @@ -1,131 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:musicus_client/musicus_client.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 { - 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: [ - FlatButton( - child: Text('DONE'), - onPressed: () => Navigator.pop( - context, - PerformanceInfo( - person: person, - ensemble: ensemble, - role: role, - ), - ), - ), - ], - ), - body: ListView( - children: [ - 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; - ensemble = null; - }); - } - }, - ), - 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; - person = null; - }); - } - }, - ), - ListTile( - title: Text('Role'), - subtitle: Text(role?.name ?? 'Select instrument/role'), - trailing: role != null - ? IconButton( - icon: const Icon(Icons.delete), - onPressed: () { - setState(() { - role = null; - }); - }, - ) - : null, - onTap: () async { - final Instrument newRole = await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => InstrumentsSelector(), - fullscreenDialog: true, - ), - ); - - if (newRole != null) { - setState(() { - role = newRole; - }); - } - }, - ), - ], - ), - ); - } -} diff --git a/common/lib/src/editors/person.dart b/common/lib/src/editors/person.dart deleted file mode 100644 index 89bc558..0000000 --- a/common/lib/src/editors/person.dart +++ /dev/null @@ -1,118 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:musicus_client/musicus_client.dart'; - -import '../backend.dart'; - -class PersonEditor extends StatefulWidget { - final Person person; - - PersonEditor({ - this.person, - }); - - @override - _PersonEditorState createState() => _PersonEditorState(); -} - -class _PersonEditorState extends State { - final firstNameController = TextEditingController(); - final lastNameController = TextEditingController(); - - bool uploading = false; - bool _sync = true; - - @override - void initState() { - super.initState(); - - if (widget.person != null) { - firstNameController.text = widget.person.firstName; - lastNameController.text = widget.person.lastName; - _sync = widget.person.sync; - } - } - - @override - Widget build(BuildContext context) { - final backend = MusicusBackend.of(context); - - return Scaffold( - appBar: AppBar( - title: Text('Person'), - actions: [ - 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; - }); - - final person = Person( - id: widget.person?.id ?? generateId(), - firstName: firstNameController.text, - lastName: lastNameController.text, - sync: _sync, - synced: false, - ); - - await backend.db.updatePerson(person); - - setState(() { - uploading = false; - }); - - Navigator.pop(context, person); - }, - ), - ], - ), - body: ListView( - children: [ - SwitchListTile( - title: Text('Synchronize changes'), - subtitle: Text(_sync - ? 'Publish changes on the server' - : 'Keep changes private'), - value: _sync, - onChanged: (value) { - setState(() { - _sync = value; - }); - }, - ), - Padding( - padding: const EdgeInsets.all(16.0), - child: TextField( - controller: firstNameController, - decoration: InputDecoration( - labelText: 'First name', - ), - ), - ), - Padding( - padding: const EdgeInsets.all(16.0), - child: TextField( - controller: lastNameController, - decoration: InputDecoration( - labelText: 'Last name', - ), - ), - ), - ], - ), - ); - } -} diff --git a/common/lib/src/editors/recording.dart b/common/lib/src/editors/recording.dart deleted file mode 100644 index f72b458..0000000 --- a/common/lib/src/editors/recording.dart +++ /dev/null @@ -1,231 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:musicus_client/musicus_client.dart'; - -import '../backend.dart'; -import '../editors/performance.dart'; -import '../selectors/recording.dart'; -import '../selectors/work.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 { - /// The recording to edit. - /// - /// If this is null, a new recording will be created. - final RecordingInfo recordingInfo; - - RecordingEditor({ - this.recordingInfo, - }); - - @override - _RecordingEditorState createState() => _RecordingEditorState(); -} - -class _RecordingEditorState extends State { - final _commentController = TextEditingController(); - - MusicusBackendState _backend; - bool _uploading = false; - bool _sync = true; - WorkInfo _workInfo; - List _performanceInfos = []; - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - - _backend = MusicusBackend.of(context); - if (widget.recordingInfo != null && - _workInfo == null && - _performanceInfos.isEmpty) { - _init(); - } - } - - Future _init() async { - _workInfo = await _backend.db.getWork(widget.recordingInfo.recording.work); - _performanceInfos = List.from(widget.recordingInfo.performances); - - setState(() { - this._workInfo = _workInfo; - this._performanceInfos = _performanceInfos; - _sync = widget.recordingInfo.recording.sync; - }); - } - - @override - Widget build(BuildContext context) { - Future selectWork() async { - final WorkInfo newWorkInfo = await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => WorkSelector(), - fullscreenDialog: true, - )); - - if (newWorkInfo != null) { - setState(() { - _workInfo = newWorkInfo; - }); - } - } - - final List 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( - appBar: AppBar( - title: Text('Recording'), - actions: [ - _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; - }); - - final recordingInfo = RecordingInfo( - recording: Recording( - id: widget?.recordingInfo?.recording?.id ?? - generateId(), - work: _workInfo.work.id, - comment: _commentController.text, - sync: _sync, - synced: false, - ), - performances: _performanceInfos, - ); - - await _backend.db.updateRecording(recordingInfo); - - setState(() { - _uploading = false; - }); - - Navigator.pop( - context, - RecordingSelectorResult( - workInfo: _workInfo, - recordingInfo: recordingInfo, - ), - ); - }, - ), - ], - ), - body: ListView( - children: [ - SwitchListTile( - title: Text('Synchronize changes'), - subtitle: Text(_sync - ? 'Publish changes on the server' - : 'Keep changes private'), - value: _sync, - onChanged: (value) { - setState(() { - _sync = value; - }); - }, - ), - _workInfo != null - ? ListTile( - title: Text(_workInfo.work.title), - subtitle: Text(_workInfo.composers - .map((p) => '${p.firstName} ${p.lastName}') - .join(', ')), - onTap: selectWork, - ) - : ListTile( - title: Text('Work'), - subtitle: Text('Select work'), - onTap: selectWork, - ), - Padding( - padding: const EdgeInsets.only( - left: 16.0, - right: 16.0, - top: 0.0, - bottom: 16.0, - ), - child: TextField( - controller: _commentController, - decoration: InputDecoration( - labelText: 'Comment', - ), - ), - ), - ListTile( - title: Text('Performers'), - trailing: IconButton( - icon: const Icon(Icons.add), - onPressed: () async { - final PerformanceInfo model = await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => PerformanceEditor(), - fullscreenDialog: true, - )); - - if (model != null) { - setState(() { - _performanceInfos.add(model); - }); - } - }, - ), - ), - ...performanceTiles, - ], - ), - ); - } -} diff --git a/common/lib/src/editors/tracks.dart b/common/lib/src/editors/tracks.dart deleted file mode 100644 index 462d0af..0000000 --- a/common/lib/src/editors/tracks.dart +++ /dev/null @@ -1,165 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:musicus_client/musicus_client.dart'; - -import '../backend.dart'; -import '../library.dart'; -import '../selectors/files.dart'; -import '../selectors/recording.dart'; -import '../widgets/recording_tile.dart'; - -class TrackModel { - int workPartIndex; - String workPartTitle; - String fileName; - - TrackModel(this.fileName); -} - -class TracksEditor extends StatefulWidget { - @override - _TracksEditorState createState() => _TracksEditorState(); -} - -class _TracksEditorState extends State { - MusicusBackendState backend; - WorkInfo workInfo; - RecordingInfo recordingInfo; - String parentId; - List trackModels = []; - - @override - Widget build(BuildContext context) { - backend = MusicusBackend.of(context); - - return Scaffold( - appBar: AppBar( - title: Text('Tracks'), - actions: [ - FlatButton( - child: Text('DONE'), - onPressed: () async { - final List tracks = []; - - for (var i = 0; i < trackModels.length; i++) { - final trackModel = trackModels[i]; - - tracks.add(Track( - fileName: trackModel.fileName, - recordingId: recordingInfo.recording.id, - index: i, - partIds: trackModel.workPartIndex != null - ? [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. - backend.db.updateWork(workInfo); - backend.db.updateRecording(recordingInfo); - - backend.library.addTracks(parentId, tracks); - - Navigator.pop(context); - }, - ), - ], - ), - body: ReorderableListView( - header: Column( - children: [ - ListTile( - title: recordingInfo != null - ? RecordingTile( - workInfo: workInfo, - recordingInfo: recordingInfo, - ) - : Text('Select recording'), - onTap: selectRecording, - ), - ListTile( - title: Text('Files'), - trailing: IconButton( - icon: const Icon(Icons.edit), - onPressed: () async { - final FilesSelectorResult result = await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => FilesSelector(), - ), - ); - - if (result != null) { - final List newTrackModels = []; - - for (final document in result.selection) { - newTrackModels.add(TrackModel(document.name)); - } - - setState(() { - parentId = result.parentId; - trackModels = newTrackModels; - }); - - if (recordingInfo != null) { - updateAutoParts(); - } - } - }, - ), - ), - ], - ), - children: trackModels - .map((t) => ListTile( - key: Key(t.hashCode.toString()), - leading: const Icon(Icons.drag_handle), - title: Text(t.workPartTitle ?? 'Set work part'), - subtitle: Text(t.fileName), - )) - .toList(), - onReorder: (i1, i2) { - setState(() { - final track = trackModels.removeAt(i1); - final newIndex = i2 > i1 ? i2 - 1 : i2; - trackModels.insert(newIndex, track); - }); - }, - ), - ); - } - - Future selectRecording() async { - final RecordingSelectorResult result = await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => RecordingSelector(), - ), - ); - - if (result != null) { - setState(() { - workInfo = result.workInfo; - recordingInfo = result.recordingInfo; - }); - - updateAutoParts(); - } - } - - /// Automatically associate the tracks with work parts. - Future updateAutoParts() async { - setState(() { - for (var i = 0; i < trackModels.length; i++) { - if (i >= workInfo.parts.length) { - trackModels[i].workPartIndex = null; - trackModels[i].workPartTitle = null; - } else { - trackModels[i].workPartIndex = workInfo.parts[i].part.partIndex; - trackModels[i].workPartTitle = workInfo.parts[i].part.title; - } - } - }); - } -} diff --git a/common/lib/src/editors/work.dart b/common/lib/src/editors/work.dart deleted file mode 100644 index 082821c..0000000 --- a/common/lib/src/editors/work.dart +++ /dev/null @@ -1,414 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:musicus_client/musicus_client.dart'; - -import '../backend.dart'; -import '../selectors/instruments.dart'; -import '../selectors/person.dart'; - -class PartData { - final bool isSection; - final titleController = TextEditingController(); - - Person composer; - List instruments; - - PartData({ - this.isSection = false, - 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, - this.onMore, - @required this.onDelete, - }) : super(key: key); - - @override - _PartTileState createState() => _PartTileState(); -} - -class _PartTileState extends State { - @override - Widget build(BuildContext context) { - final isSection = widget.part.isSection; - - return Row( - children: [ - Padding( - padding: EdgeInsets.only(left: isSection ? 8.0 : 24.0, right: 8.0), - child: Icon( - Icons.drag_handle, - ), - ), - Expanded( - child: TextField( - controller: widget.part.titleController, - decoration: InputDecoration( - border: InputBorder.none, - hintText: isSection ? 'Section title' : 'Part title', - ), - ), - ), - if (!isSection) - 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 { - /// The work to edit. - /// - /// If this is null, a new work will be created. - final WorkInfo workInfo; - - WorkEditor({ - this.workInfo, - }); - - @override - _WorkEditorState createState() => _WorkEditorState(); -} - -class _WorkEditorState extends State { - final titleController = TextEditingController(); - - bool uploading = false; - bool _sync = true; - Person composer; - List instruments = []; - List parts = []; - - @override - void initState() { - super.initState(); - - if (widget.workInfo != null) { - titleController.text = widget.workInfo.work.title; - // TODO: Theoretically this includes the composers of all parts. - composer = widget.workInfo.composers.first; - instruments = List.from(widget.workInfo.instruments); - - for (final partInfo in widget.workInfo.parts) { - parts.add(PartData( - title: partInfo.part.title, - composer: partInfo.composer, - instruments: List.from(partInfo.instruments), - )); - } - - _sync = widget.workInfo.work.sync; - } - } - - @override - Widget build(BuildContext context) { - final backend = MusicusBackend.of(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: [ - 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; - }); - - final workId = widget?.workInfo?.work?.id ?? generateId(); - - List partInfos = []; - List sections = []; - int sectionCount = 0; - for (var i = 0; i < parts.length; i++) { - final part = parts[i]; - if (part.isSection) { - sections.add(WorkSection( - id: generateId(), - work: workId, - title: part.titleController.text, - 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( - work: Work( - id: workId, - title: titleController.text, - composer: composer?.id, - sync: _sync, - synced: false, - ), - instruments: instruments, - // TODO: Theoretically, this should include all composers - // from the parts. - composers: [composer], - parts: partInfos, - sections: sections, - ); - - await backend.db.updateWork(workInfo); - - setState(() { - uploading = false; - }); - - Navigator.pop(context, workInfo); - }, - ), - ], - ), - body: ReorderableListView( - header: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SwitchListTile( - title: Text('Synchronize changes'), - subtitle: Text(_sync - ? 'Publish changes on the server' - : 'Keep changes private'), - value: _sync, - onChanged: (value) { - setState(() { - _sync = value; - }); - }, - ), - WorkProperties( - titleController: titleController, - composer: composer, - instruments: instruments, - onComposerChanged: (newComposer) { - setState(() { - composer = newComposer; - }); - }, - onInstrumentsChanged: (newInstruments) { - setState(() { - instruments = newInstruments; - }); - }, - ), - Padding( - padding: const EdgeInsets.only(left: 16.0, top: 16.0), - child: Row( - children: [ - 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, - onReorder: (i1, i2) { - setState(() { - final part = parts.removeAt(i1); - final newIndex = i2 > i1 ? i2 - 1 : i2; - - parts.insert(newIndex, part); - }); - }, - ), - ); - } -} diff --git a/common/lib/src/screens/about.dart b/common/lib/src/screens/about.dart index 0004fa7..467476b 100644 --- a/common/lib/src/screens/about.dart +++ b/common/lib/src/screens/about.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; -import 'package:url_launcher/url_launcher.dart' as url; +import 'package:url_launcher/url_launcher.dart'; class AboutScreen extends StatelessWidget { @override @@ -29,7 +29,7 @@ class AboutScreen extends StatelessWidget { decoration: TextDecoration.underline, ), ), - onTapLink: (link) => url.launch(link), + onTapLink: (text, href, title) => launchUrl(Uri.parse(href)), ); } else { return Container(); diff --git a/common/lib/src/screens/account_settings.dart b/common/lib/src/screens/account_settings.dart deleted file mode 100644 index a597cc6..0000000 --- a/common/lib/src/screens/account_settings.dart +++ /dev/null @@ -1,265 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:musicus_client/musicus_client.dart'; - -import '../backend.dart'; - -import 'delete_account.dart'; -import 'email.dart'; -import 'password.dart'; -import 'register.dart'; - -class AccountSettingsScreen extends StatefulWidget { - @override - _AccountSettingsScreenState createState() => _AccountSettingsScreenState(); -} - -class _AccountSettingsScreenState extends State { - final _usernameController = TextEditingController(); - final _passwordController = TextEditingController(); - - MusicusBackendState _backend; - StreamSubscription _accountSubscription; - bool _loading = false; - bool _loggedIn = false; - String _username; - String _email; - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - - _backend = MusicusBackend.of(context); - - final credentials = _backend.settings.account.value; - if (credentials != null) { - _setCredentials(credentials); - _getDetails(); - } - - _accountSubscription = _backend.settings.account.listen((credentials) { - _setCredentials(credentials); - }); - } - - Future _setCredentials(MusicusAccountCredentials credentials) async { - if (mounted) { - if (credentials != null) { - setState(() { - _loggedIn = true; - _username = credentials.username; - }); - } else { - setState(() { - _loggedIn = false; - }); - } - } - } - - Future _getDetails() async { - setState(() { - _email = null; - }); - - final email = (await _backend.client.getAccountDetails()).email; - - if (mounted) { - setState(() { - _email = email; - }); - } - } - - @override - Widget build(BuildContext context) { - List children; - - if (_loggedIn) { - children = [ - Material( - elevation: 2.0, - child: ListTile( - title: Text('Logged in as: $_username'), - ), - ), - ListTile( - title: Text('E-mail address'), - subtitle: Text( - _email != null ? _email.isNotEmpty ? _email : 'Not set' : '...'), - trailing: const Icon(Icons.chevron_right), - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => EmailScreen( - email: _email, - ), - ), - ); - - _getDetails(); - }, - ), - ListTile( - title: Text('Change password'), - trailing: const Icon(Icons.chevron_right), - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => PasswordScreen(), - ), - ); - }, - ), - ListTile( - title: Text('Delete this account'), - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => DeleteAccountScreen(), - ), - ); - }, - ), - ListTile( - title: Text('Logout'), - onTap: () async { - await _backend.settings.clearAccount(); - Navigator.pop(context); - }, - ), - ]; - } else { - children = [ - Padding( - padding: const EdgeInsets.only( - left: 16.0, - right: 16.0, - top: 16.0, - bottom: 8.0, - ), - child: Text( - 'Enter your Musicus account credentials:', - style: Theme.of(context).textTheme.subtitle1, - ), - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - ), - child: TextField( - controller: _usernameController, - decoration: InputDecoration( - labelText: 'User name', - ), - ), - ), - SizedBox( - height: 16.0, - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - ), - child: TextField( - controller: _passwordController, - obscureText: true, - decoration: InputDecoration( - labelText: 'Password', - ), - ), - ), - SizedBox( - height: 32.0, - ), - ListTile( - title: Text('Create a new account'), - onTap: () { - Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) => RegisterScreen( - username: _usernameController.text, - password: _passwordController.text, - ), - ), - ); - }, - ), - ]; - } - - return Scaffold( - appBar: AppBar( - title: Text('Musicus account'), - actions: [ - Builder( - builder: (context) { - if (_loggedIn) { - return Container(); - } else if (_loading) { - return Padding( - padding: const EdgeInsets.all(16.0), - child: Center( - child: SizedBox( - width: 24.0, - height: 24.0, - child: CircularProgressIndicator( - strokeWidth: 2.0, - ), - ), - ), - ); - } else { - return FlatButton( - onPressed: () async { - setState(() { - _loading = true; - }); - - final credentials = MusicusAccountCredentials( - username: _usernameController.text, - password: _passwordController.text, - ); - - _backend.client.credentials = credentials; - - try { - await _backend.client.login(); - await _backend.settings.setAccount(credentials); - Navigator.pop(context); - } on MusicusLoginFailedException { - Scaffold.of(context).showSnackBar( - SnackBar( - content: Text('Login failed'), - ), - ); - } - - setState(() { - _loading = false; - }); - }, - child: Text('LOGIN'), - ); - } - }, - ), - ], - ), - body: ListView( - children: children, - ), - ); - } - - @override - void dispose() { - super.dispose(); - _accountSubscription.cancel(); - } -} diff --git a/common/lib/src/screens/delete_account.dart b/common/lib/src/screens/delete_account.dart deleted file mode 100644 index e0ef887..0000000 --- a/common/lib/src/screens/delete_account.dart +++ /dev/null @@ -1,97 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../backend.dart'; - -class DeleteAccountScreen extends StatefulWidget { - @override - _DeleteAccountScreenState createState() => _DeleteAccountScreenState(); -} - -class _DeleteAccountScreenState extends State { - final _passwordController = TextEditingController(); - - bool _loading = false; - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text('Delete account'), - actions: [ - Builder( - builder: (context) { - if (_loading) { - return Padding( - padding: const EdgeInsets.all(16.0), - child: Center( - child: SizedBox( - width: 24.0, - height: 24.0, - child: CircularProgressIndicator( - strokeWidth: 2.0, - ), - ), - ), - ); - } else { - return FlatButton( - onPressed: () async { - final backend = MusicusBackend.of(context); - - if (_passwordController.text == - backend.settings.account.value.password) { - setState(() { - _loading = true; - }); - - await backend.client.deleteAccount(); - await backend.settings.clearAccount(); - - setState(() { - _loading = false; - }); - - Navigator.pop(context); - } else { - Scaffold.of(context).showSnackBar(SnackBar( - content: Text('Wrong password'), - )); - } - }, - child: Text('DELETE'), - ); - } - }, - ), - ], - ), - body: ListView( - children: [ - Padding( - padding: const EdgeInsets.only( - left: 16.0, - right: 16.0, - top: 16.0, - bottom: 8.0, - ), - child: Text( - 'If you really want to delete your account, enter your password ' - 'below.', - style: Theme.of(context).textTheme.subtitle1, - ), - ), - Padding( - padding: const EdgeInsets.all(16.0), - child: TextField( - controller: _passwordController, - obscureText: true, - decoration: InputDecoration( - labelText: 'Password', - ), - ), - ), - ], - ), - ); - } -} diff --git a/common/lib/src/screens/email.dart b/common/lib/src/screens/email.dart deleted file mode 100644 index edcc552..0000000 --- a/common/lib/src/screens/email.dart +++ /dev/null @@ -1,103 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../backend.dart'; - -class EmailScreen extends StatefulWidget { - final String email; - - EmailScreen({ - this.email, - }); - - @override - _EmailScreenState createState() => _EmailScreenState(); -} - -class _EmailScreenState extends State { - final _emailController = TextEditingController(); - - bool _loading = false; - - @override - void initState() { - super.initState(); - - if (widget.email != null) { - _emailController.text = widget.email; - } - } - - Future _setEmail(String email) async { - setState(() { - _loading = true; - }); - - final backend = MusicusBackend.of(context); - - await backend.client.updateAccount( - newEmail: email, - ); - - setState(() { - _loading = false; - }); - - Navigator.pop(context); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text('E-mail address'), - actions: [ - Builder( - builder: (context) { - if (_loading) { - return Padding( - padding: const EdgeInsets.all(16.0), - child: Center( - child: SizedBox( - width: 24.0, - height: 24.0, - child: CircularProgressIndicator( - strokeWidth: 2.0, - ), - ), - ), - ); - } else { - return FlatButton( - onPressed: () { - _setEmail(_emailController.text); - }, - child: Text('DONE'), - ); - } - }, - ), - ], - ), - body: ListView( - children: [ - Padding( - padding: const EdgeInsets.all(16.0), - child: TextField( - controller: _emailController, - keyboardType: TextInputType.emailAddress, - decoration: InputDecoration( - labelText: 'E-mail', - ), - ), - ), - ListTile( - title: Text('Delete E-mail address'), - onTap: () { - _setEmail(''); - }, - ), - ], - ), - ); - } -} diff --git a/common/lib/src/screens/home.dart b/common/lib/src/screens/home.dart index 2a12df8..451afba 100644 --- a/common/lib/src/screens/home.dart +++ b/common/lib/src/screens/home.dart @@ -1,9 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:musicus_client/musicus_client.dart'; +import 'package:musicus_database/musicus_database.dart'; import '../backend.dart'; -import '../editors/person.dart'; -import '../editors/tracks.dart'; import '../icons.dart'; import '../widgets/lists.dart'; @@ -46,34 +44,22 @@ class _HomeScreenState extends State { itemBuilder: (context) => [ PopupMenuItem( value: 1, - child: Text('Add tracks'), - ), - PopupMenuItem( - value: 2, child: Text('Settings'), ), PopupMenuItem( - value: 3, + value: 2, child: Text('About'), ), ], onSelected: (selected) { if (selected == 1) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => TracksEditor(), - fullscreenDialog: true, - ), - ); - } else if (selected == 2) { Navigator.push( context, MaterialPageRoute( builder: (context) => SettingsScreen(), ), ); - } else if (selected == 3) { + } else if (selected == 2) { Navigator.push( context, MaterialPageRoute( @@ -100,31 +86,6 @@ class _HomeScreenState extends State { ), ), ), - onLongPress: () { - showDialog( - context: context, - builder: (context) { - return SimpleDialog( - children: [ - ListTile( - title: Text('Edit person'), - onTap: () async { - Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) => PersonEditor( - person: person, - ), - fullscreenDialog: true, - ), - ); - }, - ), - ], - ); - }, - ); - }, ), ), ); diff --git a/common/lib/src/screens/password.dart b/common/lib/src/screens/password.dart deleted file mode 100644 index 317ae31..0000000 --- a/common/lib/src/screens/password.dart +++ /dev/null @@ -1,117 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:musicus_client/musicus_client.dart'; - -import '../backend.dart'; - -class PasswordScreen extends StatefulWidget { - @override - _PasswordScreenState createState() => _PasswordScreenState(); -} - -class _PasswordScreenState extends State { - final _oldPasswordController = TextEditingController(); - final _newPasswordController = TextEditingController(); - final _repeatController = TextEditingController(); - - bool _loading = false; - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text('Change password'), - actions: [ - Builder( - builder: (context) { - if (_loading) { - return Padding( - padding: const EdgeInsets.all(16.0), - child: Center( - child: SizedBox( - width: 24.0, - height: 24.0, - child: CircularProgressIndicator( - strokeWidth: 2.0, - ), - ), - ), - ); - } else { - return FlatButton( - onPressed: () async { - final backend = MusicusBackend.of(context); - final password = _newPasswordController.text; - - if (_oldPasswordController.text == - backend.settings.account.value.password && - password.isNotEmpty && - password == _repeatController.text) { - setState(() { - _loading = true; - }); - - await backend.client.updateAccount( - newPassword: password, - ); - - await backend.settings - .setAccount(MusicusAccountCredentials( - username: backend.settings.account.value.username, - password: password, - )); - - setState(() { - _loading = false; - }); - - Navigator.pop(context); - } else { - Scaffold.of(context).showSnackBar(SnackBar( - content: Text('Invalid inputs'), - )); - } - }, - child: Text('DONE'), - ); - } - }, - ), - ], - ), - body: ListView( - children: [ - Padding( - padding: const EdgeInsets.all(16.0), - child: TextField( - controller: _oldPasswordController, - obscureText: true, - decoration: InputDecoration( - labelText: 'Old password', - ), - ), - ), - Padding( - padding: const EdgeInsets.all(16.0), - child: TextField( - controller: _newPasswordController, - obscureText: true, - decoration: InputDecoration( - labelText: 'New password', - ), - ), - ), - Padding( - padding: const EdgeInsets.all(16.0), - child: TextField( - controller: _repeatController, - obscureText: true, - decoration: InputDecoration( - labelText: 'New password (repeat)', - ), - ), - ), - ], - ), - ); - } -} diff --git a/common/lib/src/screens/person.dart b/common/lib/src/screens/person.dart index cc28a8d..bc496b5 100644 --- a/common/lib/src/screens/person.dart +++ b/common/lib/src/screens/person.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:musicus_client/musicus_client.dart'; +import 'package:musicus_database/musicus_database.dart'; import '../backend.dart'; -import '../editors/work.dart'; import '../widgets/lists.dart'; import 'work.dart'; @@ -55,31 +54,6 @@ class _PersonScreenState extends State { ), ), ), - onLongPress: () { - showDialog( - context: context, - builder: (context) { - return SimpleDialog( - children: [ - ListTile( - title: Text('Edit work'), - onTap: () async { - Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) => WorkEditor( - workInfo: workInfo, - ), - fullscreenDialog: true, - ), - ); - }, - ), - ], - ); - }, - ); - }, ), ), ); diff --git a/common/lib/src/screens/program.dart b/common/lib/src/screens/program.dart index b61ba84..cbf614c 100644 --- a/common/lib/src/screens/program.dart +++ b/common/lib/src/screens/program.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:musicus_client/musicus_client.dart'; +import 'package:musicus_database/musicus_database.dart'; import '../backend.dart'; import '../library.dart'; diff --git a/common/lib/src/screens/register.dart b/common/lib/src/screens/register.dart deleted file mode 100644 index 33b7264..0000000 --- a/common/lib/src/screens/register.dart +++ /dev/null @@ -1,163 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:musicus_client/musicus_client.dart'; - -import '../backend.dart'; - -/// A screen for creating a new Musicus account. -class RegisterScreen extends StatefulWidget { - final String username; - final String password; - - RegisterScreen({ - this.username, - this.password, - }); - - @override - _RegisterScreenState createState() => _RegisterScreenState(); -} - -class _RegisterScreenState extends State { - final nameController = TextEditingController(); - final emailController = TextEditingController(); - final passwordController = TextEditingController(); - final repeatController = TextEditingController(); - - bool _loading = false; - - @override - void initState() { - super.initState(); - - if (widget.username != null) { - nameController.text = widget.username; - } - - if (widget.password != null) { - passwordController.text = widget.password; - } - } - - @override - Widget build(BuildContext context) { - final backend = MusicusBackend.of(context); - - return Scaffold( - appBar: AppBar( - title: Text('Create account'), - actions: [ - Builder( - builder: (context) { - if (!_loading) { - return FlatButton( - onPressed: () async { - if (_verify()) { - setState(() { - _loading = true; - }); - - final success = await backend.client.registerAccount( - username: nameController.text, - email: emailController.text, - password: passwordController.text, - ); - - setState(() { - _loading = false; - }); - - if (success) { - await backend.settings - .setAccount(MusicusAccountCredentials( - username: nameController.text, - password: passwordController.text, - )); - - Navigator.pop(context); - } else { - Scaffold.of(context).showSnackBar( - SnackBar( - content: Text('Failed to create account'), - ), - ); - } - } else { - Scaffold.of(context).showSnackBar( - SnackBar( - content: Text('Invalid inputs'), - ), - ); - } - }, - child: Text('REGISTER'), - ); - } else { - return Padding( - padding: const EdgeInsets.all(16.0), - child: Center( - child: SizedBox( - width: 24.0, - height: 24.0, - child: CircularProgressIndicator( - strokeWidth: 2.0, - ), - ), - ), - ); - } - }, - ), - ], - ), - body: ListView( - children: [ - Padding( - padding: const EdgeInsets.all(16.0), - child: TextField( - controller: nameController, - decoration: InputDecoration( - labelText: 'User name', - ), - ), - ), - Padding( - padding: const EdgeInsets.all(16.0), - child: TextField( - controller: emailController, - decoration: InputDecoration( - labelText: 'E-mail address (optional)', - ), - ), - ), - Padding( - padding: const EdgeInsets.all(16.0), - child: TextField( - controller: passwordController, - obscureText: true, - decoration: InputDecoration( - labelText: 'Password', - ), - ), - ), - Padding( - padding: const EdgeInsets.all(16.0), - child: TextField( - controller: repeatController, - obscureText: true, - decoration: InputDecoration( - labelText: 'Password (repeat)', - ), - ), - ), - ], - ), - ); - } - - /// Check whether all requirements are met. - bool _verify() { - return nameController.text.isNotEmpty && - passwordController.text.isNotEmpty && - passwordController.text == repeatController.text; - } -} diff --git a/common/lib/src/screens/server_settings.dart b/common/lib/src/screens/server_settings.dart deleted file mode 100644 index 3ce7854..0000000 --- a/common/lib/src/screens/server_settings.dart +++ /dev/null @@ -1,110 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:musicus_common/musicus_common.dart'; - -import '../backend.dart'; - -class ServerSettingsScreen extends StatefulWidget { - @override - _ServerSettingsScreenState createState() => _ServerSettingsScreenState(); -} - -class _ServerSettingsScreenState extends State { - final hostController = TextEditingController(); - final portController = TextEditingController(); - final apiPathController = TextEditingController(); - - MusicusBackendState backend; - StreamSubscription serverSubscription; - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - - backend = MusicusBackend.of(context); - - if (serverSubscription != null) { - serverSubscription.cancel(); - } - - _settingsChanged(backend.settings.server.value); - serverSubscription = backend.settings.server.listen((settings) { - _settingsChanged(settings); - }); - } - - void _settingsChanged(MusicusServerSettings settings) { - hostController.text = settings.host; - portController.text = settings.port.toString(); - apiPathController.text = settings.apiPath; - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text('Server settings'), - actions: [ - IconButton( - icon: const Icon(Icons.restore), - tooltip: 'Reset to default', - onPressed: () { - backend.settings.resetServer(); - }, - ), - FlatButton( - onPressed: () async { - await backend.settings.setServer(MusicusServerSettings( - host: hostController.text, - port: int.parse(portController.text), - apiPath: apiPathController.text, - )); - - Navigator.pop(context); - }, - child: Text('DONE'), - ), - ], - ), - body: ListView( - children: [ - Padding( - padding: const EdgeInsets.all(16.0), - child: TextField( - controller: hostController, - decoration: InputDecoration( - labelText: 'Host', - ), - ), - ), - Padding( - padding: const EdgeInsets.all(16.0), - child: TextField( - controller: portController, - keyboardType: TextInputType.number, - decoration: InputDecoration( - labelText: 'Port', - ), - ), - ), - Padding( - padding: const EdgeInsets.all(16.0), - child: TextField( - controller: apiPathController, - decoration: InputDecoration( - labelText: 'API path', - ), - ), - ), - ], - ), - ); - } - - @override - void dispose() { - super.dispose(); - serverSubscription.cancel(); - } -} diff --git a/common/lib/src/screens/settings.dart b/common/lib/src/screens/settings.dart index 8067536..0b09e48 100644 --- a/common/lib/src/screens/settings.dart +++ b/common/lib/src/screens/settings.dart @@ -1,13 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:musicus_client/musicus_client.dart'; -import 'package:musicus_common/musicus_common.dart'; import '../backend.dart'; -import 'account_settings.dart'; -import 'server_settings.dart'; - class SettingsScreen extends StatelessWidget { static const _platform = MethodChannel('de.johrpan.musicus/platform'); @@ -39,52 +34,6 @@ class SettingsScreen extends StatelessWidget { ); }, ), - StreamBuilder( - stream: settings.server, - builder: (context, snapshot) { - final s = snapshot.data; - - return ListTile( - title: Text('Musicus server'), - subtitle: - Text(s != null ? '${s.host}:${s.port}${s.apiPath}' : '...'), - trailing: const Icon(Icons.chevron_right), - onTap: () async { - final MusicusServerSettings result = await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ServerSettingsScreen(), - ), - ); - - if (result != null) { - settings.setServer(result); - } - }, - ); - }, - ), - StreamBuilder( - stream: settings.account, - builder: (context, snapshot) { - final credentials = snapshot.data; - - return ListTile( - title: Text('Account settings'), - subtitle: Text( - credentials != null ? credentials.username : 'No account'), - trailing: const Icon(Icons.chevron_right), - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => AccountSettingsScreen(), - ), - ); - }, - ); - }, - ), ], ), ); diff --git a/common/lib/src/screens/work.dart b/common/lib/src/screens/work.dart index 4e56008..553b2cf 100644 --- a/common/lib/src/screens/work.dart +++ b/common/lib/src/screens/work.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:musicus_client/musicus_client.dart'; +import 'package:musicus_database/musicus_database.dart'; import '../backend.dart'; -import '../editors/recording.dart'; import '../widgets/lists.dart'; import '../widgets/texts.dart'; @@ -37,31 +36,6 @@ class WorkScreen extends StatelessWidget { tracks.sort((t1, t2) => t1.track.index.compareTo(t2.track.index)); backend.playback.addTracks(tracks); }, - onLongPress: () { - showDialog( - context: context, - builder: (context) { - return SimpleDialog( - children: [ - ListTile( - title: Text('Edit recording'), - onTap: () { - Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) => RecordingEditor( - recordingInfo: recordingInfo, - ), - fullscreenDialog: true, - ), - ); - }, - ), - ], - ); - }, - ); - }, ); }, ), diff --git a/common/lib/src/selectors/ensemble.dart b/common/lib/src/selectors/ensemble.dart deleted file mode 100644 index a24024d..0000000 --- a/common/lib/src/selectors/ensemble.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:musicus_client/musicus_client.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); - } - }, - ), - ); - } -} diff --git a/common/lib/src/selectors/files.dart b/common/lib/src/selectors/files.dart deleted file mode 100644 index 24bd8b9..0000000 --- a/common/lib/src/selectors/files.dart +++ /dev/null @@ -1,211 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../backend.dart'; -import '../platform.dart'; - -/// Result of the user's interaction with the files selector. -/// -/// This will be given back when popping the navigator. -class FilesSelectorResult { - /// Document ID of the parent directory of the selected files. - /// - /// This will be null, if they are in the toplevel directory. - final String parentId; - - /// Selected files. - final Set selection; - - FilesSelectorResult(this.parentId, this.selection); -} - -/// A screen for selecting files. -/// -/// This returns a [FilesSelectorResult] when pooping the navigator. If -/// [chooseDirectory] is true, the user will select a directory instead. In -/// that case, the document ID of the directory will be returned directly. -/// If that value is null, this means that the toplevel directory was selected. -class FilesSelector extends StatefulWidget { - /// Choose a directory instead of multiple files. - final bool chooseDirectory; - - FilesSelector({ - this.chooseDirectory = false, - }); - - @override - _FilesSelectorState createState() => _FilesSelectorState(); -} - -class _FilesSelectorState extends State { - final _searchController = TextEditingController(); - - MusicusBackendState backend; - List history = []; - List children = []; - Set selection = {}; - - String _search = ''; - - @override - void initState() { - super.initState(); - - _searchController.addListener(() { - setState(() { - _search = _searchController.text; - }); - }); - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - - backend = MusicusBackend.of(context); - loadChildren(); - } - - @override - Widget build(BuildContext context) { - final title = history.isNotEmpty ? history.last.name : 'base path'; - final filteredChildren = children - .where((d) => d.name.toLowerCase().contains(_search.toLowerCase())) - .toList(); - - return WillPopScope( - child: Scaffold( - appBar: AppBar( - title: Text( - widget.chooseDirectory ? 'Choose directory' : 'Choose files'), - leading: IconButton( - icon: const Icon(Icons.close), - onPressed: () { - Navigator.pop(context); - }, - ), - actions: [ - FlatButton( - child: Text(widget.chooseDirectory ? 'SELECT' : 'DONE'), - onPressed: () { - final parentId = history.isNotEmpty ? history.last.id : null; - - Navigator.pop( - context, - widget.chooseDirectory - ? parentId - : FilesSelectorResult(parentId, selection), - ); - }, - ), - ], - ), - body: Column( - children: [ - Material( - elevation: 2.0, - child: ListTile( - leading: IconButton( - icon: const Icon(Icons.arrow_upward), - onPressed: history.isNotEmpty ? up : null, - ), - title: TextField( - autofocus: true, - controller: _searchController, - decoration: InputDecoration.collapsed( - hintText: 'Search in $title...'), - ), - ), - ), - Expanded( - child: ListView.builder( - itemCount: filteredChildren.length, - itemBuilder: (context, index) { - final document = filteredChildren[index]; - - if (document.isDirectory) { - return ListTile( - leading: const Icon(Icons.folder), - title: Text(document.name), - onTap: () { - _searchController.text = ''; - setState(() { - history.add(document); - }); - loadChildren(); - }, - ); - } else { - if (widget.chooseDirectory) { - return ListTile( - leading: const Icon(Icons.insert_drive_file), - title: Text(document.name), - ); - } else { - return CheckboxListTile( - controlAffinity: ListTileControlAffinity.trailing, - secondary: const Icon(Icons.insert_drive_file), - title: Text(document.name), - value: selection.contains(document), - onChanged: (selected) { - setState(() { - if (selected) { - selection.add(document); - } else { - selection.remove(document); - } - }); - }, - ); - } - } - }, - ), - ), - ], - ), - ), - onWillPop: () => Future.value(up()), - ); - } - - Future loadChildren() async { - setState(() { - children = []; - - // We reset the selection here, because the user should not be able to - // select files from multiple directories for now. - selection = {}; - }); - - final newChildren = await backend.platform - .getChildren(history.isNotEmpty ? history.last.id : null); - - newChildren.sort((d1, d2) { - if (d1.isDirectory != d2.isDirectory) { - return d1.isDirectory ? -1 : 1; - } else { - return d1.name.compareTo(d2.name); - } - }); - - if (mounted) { - setState(() { - children = newChildren; - }); - } - } - - bool up() { - if (history.isNotEmpty) { - setState(() { - history.removeLast(); - }); - - loadChildren(); - - return false; - } else { - return true; - } - } -} diff --git a/common/lib/src/selectors/instruments.dart b/common/lib/src/selectors/instruments.dart deleted file mode 100644 index e348226..0000000 --- a/common/lib/src/selectors/instruments.dart +++ /dev/null @@ -1,136 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:musicus_client/musicus_client.dart'; - -import '../backend.dart'; -import '../editors/instrument.dart'; -import '../widgets/lists.dart'; - -class InstrumentsSelector extends StatefulWidget { - final bool multiple; - final List selection; - - InstrumentsSelector({ - this.multiple = false, - this.selection, - }); - - @override - _InstrumentsSelectorState createState() => _InstrumentsSelectorState(); -} - -class _InstrumentsSelectorState extends State { - final _list = GlobalKey>(); - - Set selection = {}; - String _search; - - @override - void initState() { - super.initState(); - - if (widget.selection != null) { - selection = widget.selection.toSet(); - } - } - - @override - Widget build(BuildContext context) { - final backend = MusicusBackend.of(context); - - return Scaffold( - appBar: AppBar( - title: Text(widget.multiple - ? 'Select instruments/roles' - : 'Select instrument/role'), - actions: widget.multiple - ? [ - FlatButton( - child: Text('DONE'), - onPressed: () => Navigator.pop(context, selection.toList()), - ), - ] - : null, - ), - body: Column( - children: [ - Material( - elevation: 2.0, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 4.0, - ), - child: TextField( - autofocus: true, - onChanged: (text) { - setState(() { - _search = text; - }); - }, - decoration: InputDecoration.collapsed( - hintText: 'Search by name...', - ), - ), - ), - ), - Expanded( - child: PagedListView( - key: _list, - search: _search, - fetch: (page, search) async { - return await backend.client.getInstruments(page, search); - }, - builder: (context, instrument) { - if (widget.multiple) { - return CheckboxListTile( - title: Text(instrument.name), - value: selection.contains(instrument), - checkColor: Colors.black, - onChanged: (selected) { - setState(() { - if (selected) { - selection.add(instrument); - } else { - selection.remove(instrument); - } - }); - }, - ); - } else { - return ListTile( - title: Text(instrument.name), - onTap: () => Navigator.pop(context, instrument), - ); - } - }, - ), - ), - ], - ), - floatingActionButton: FloatingActionButton( - child: const Icon(Icons.add), - onPressed: () async { - final Instrument instrument = await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => InstrumentEditor(), - fullscreenDialog: true, - )); - - if (instrument != null) { - if (widget.multiple) { - setState(() { - selection.add(instrument); - }); - - // We need to rebuild the list view, because we added an item. - _list.currentState.update(); - } else { - Navigator.pop(context, instrument); - } - } - }, - ), - ); - } -} diff --git a/common/lib/src/selectors/person.dart b/common/lib/src/selectors/person.dart deleted file mode 100644 index 31ed4ef..0000000 --- a/common/lib/src/selectors/person.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:musicus_client/musicus_client.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 { - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text('Select person'), - ), - body: PersonsList( - onSelected: (person) { - Navigator.pop(context, person); - }, - ), - floatingActionButton: FloatingActionButton( - child: const Icon(Icons.add), - onPressed: () async { - final Person person = await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => PersonEditor(), - fullscreenDialog: true, - ), - ); - - if (person != null) { - Navigator.pop(context, person); - } - }, - ), - ); - } -} diff --git a/common/lib/src/selectors/recording.dart b/common/lib/src/selectors/recording.dart deleted file mode 100644 index cf97ea6..0000000 --- a/common/lib/src/selectors/recording.dart +++ /dev/null @@ -1,90 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:musicus_client/musicus_client.dart'; - -import '../editors/recording.dart'; -import '../widgets/lists.dart'; - -class RecordingSelectorResult { - final WorkInfo workInfo; - final RecordingInfo recordingInfo; - - RecordingSelectorResult({ - this.workInfo, - this.recordingInfo, - }); -} - -/// A screen to select a recording. -/// -/// 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(); -} - -class _RecordingSelectorState extends State { - Person person; - WorkInfo workInfo; - - @override - Widget build(BuildContext context) { - Widget body; - - if (person == null) { - body = PersonsList( - onSelected: (newPerson) { - setState(() { - person = newPerson; - }); - }, - ); - } 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, - ), - ); - }, - ); - } - - return Scaffold( - appBar: AppBar( - title: Text('Select recording'), - ), - body: body, - floatingActionButton: FloatingActionButton( - child: const Icon(Icons.add), - onPressed: () async { - final RecordingSelectorResult result = await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => RecordingEditor(), - fullscreenDialog: true, - ), - ); - - if (result != null) { - Navigator.pop(context, result); - } - }, - ), - ); - } -} diff --git a/common/lib/src/selectors/work.dart b/common/lib/src/selectors/work.dart deleted file mode 100644 index a3a52dc..0000000 --- a/common/lib/src/selectors/work.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:musicus_client/musicus_client.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 { - Person person; - - @override - Widget build(BuildContext 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( - appBar: AppBar( - title: Text('Select work'), - ), - body: body, - floatingActionButton: FloatingActionButton( - child: const Icon(Icons.add), - onPressed: () async { - final WorkInfo workInfo = await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => WorkEditor(), - fullscreenDialog: true, - ), - ); - - if (workInfo != null) { - Navigator.pop(context, workInfo); - } - }, - ), - ); - } -} diff --git a/common/lib/src/settings.dart b/common/lib/src/settings.dart index 0293cdc..b4fb6e3 100644 --- a/common/lib/src/settings.dart +++ b/common/lib/src/settings.dart @@ -1,7 +1,4 @@ -import 'dart:convert'; - import 'package:meta/meta.dart'; -import 'package:musicus_client/musicus_client.dart'; import 'package:rxdart/rxdart.dart'; /// Interface for persisting settings. @@ -54,12 +51,6 @@ class MusicusSettings { /// Android storage access framework. final musicLibraryPath = BehaviorSubject(); - /// Musicus server to connect to. - final server = BehaviorSubject(); - - /// Credentials for the Musicus account to login as. - final account = BehaviorSubject(); - /// Create a settings instance. MusicusSettings(this.storage); @@ -71,26 +62,6 @@ class MusicusSettings { if (path != null) { musicLibraryPath.add(path); } - - final host = await storage.getString('serverHost') ?? defaultHost; - final port = await storage.getInt('serverPort') ?? defaultPort; - final apiPath = await storage.getString('serverApiPath') ?? defaultApiPath; - - server.add(MusicusServerSettings( - host: host, - port: port, - apiPath: apiPath, - )); - - final username = await storage.getString('accountUsername'); - final passwordBase64 = await storage.getString('accountPassword'); - - if (username != null) { - account.add(MusicusAccountCredentials( - username: username, - password: utf8.decode(base64Decode(passwordBase64)), - )); - } } /// Set a new music library path. @@ -101,54 +72,8 @@ class MusicusSettings { musicLibraryPath.add(path); } - /// Update the server settings. - /// - /// This will persist the new values and update the stream. - Future setServer(MusicusServerSettings serverSettings) async { - await storage.setString('serverHost', serverSettings.host); - await storage.setInt('serverPort', serverSettings.port); - await storage.setString('severApiPath', serverSettings.apiPath); - server.add(serverSettings); - } - - /// Reset the server settings to their defaults. - Future resetServer() async { - await setServer(MusicusServerSettings( - host: defaultHost, - port: defaultPort, - apiPath: defaultApiPath, - )); - } - - /// Update the account credentials. - /// - /// This will persist the new values and update the stream. - Future setAccount(MusicusAccountCredentials credentials) async { - await storage.setString('accountUsername', credentials.username); - - // IMPORTANT NOTE: We encode the password using Base64 to defend just the - // simplest of simplest attacks. This provides no additional security - // besides the fact that the password looks a little bit encrypted. - await storage.setString( - 'accountPassword', - base64Encode(utf8.encode(credentials.password)), - ); - - account.add(credentials); - } - - /// Delete the current account credentials. - Future clearAccount() async { - await storage.setString('accountUsername', null); - await storage.setString('accountPassword', null); - - account.add(null); - } - /// Tidy up. void dispose() { musicLibraryPath.close(); - server.close(); - account.close(); } } diff --git a/common/lib/src/widgets/lists.dart b/common/lib/src/widgets/lists.dart index 52a4689..84136b4 100644 --- a/common/lib/src/widgets/lists.dart +++ b/common/lib/src/widgets/lists.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:musicus_client/musicus_client.dart'; import '../backend.dart'; import '../widgets/texts.dart'; @@ -97,7 +96,7 @@ class PagedListViewState extends State> { } /// Update the content manually. - /// + /// /// This will reset the current page to zero and call the provided fetch() /// method. void update() { @@ -142,226 +141,3 @@ class PagedListViewState extends State> { ); } } - -/// A list of persons. -class PersonsList extends StatefulWidget { - /// Called, when the user has selected a person. - final void Function(Person person) onSelected; - - PersonsList({ - @required this.onSelected, - }); - - @override - _PersonsListState createState() => _PersonsListState(); -} - -class _PersonsListState extends State { - String _search; - - @override - Widget build(BuildContext context) { - final backend = MusicusBackend.of(context); - - return Column( - children: [ - Material( - elevation: 2.0, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 4.0, - ), - child: TextField( - autofocus: true, - onChanged: (text) { - setState(() { - _search = text; - }); - }, - decoration: InputDecoration.collapsed( - hintText: 'Search by last name...', - ), - ), - ), - ), - Expanded( - child: PagedListView( - search: _search, - fetch: (page, search) async { - return await backend.client.getPersons(page, search); - }, - builder: (context, person) => ListTile( - title: Text('${person.lastName}, ${person.firstName}'), - onTap: () { - widget.onSelected(person); - }, - ), - ), - ), - ], - ); - } -} - -/// A list of ensembles. -class EnsemblesList extends StatefulWidget { - /// Called, when the user has selected an ensemble. - final void Function(Ensemble ensemble) onSelected; - - EnsemblesList({ - @required this.onSelected, - }); - - @override - _EnsemblesListState createState() => _EnsemblesListState(); -} - -class _EnsemblesListState extends State { - String _search; - - @override - Widget build(BuildContext context) { - final backend = MusicusBackend.of(context); - - return Column( - children: [ - Material( - elevation: 2.0, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 4.0, - ), - child: TextField( - autofocus: true, - onChanged: (text) { - setState(() { - _search = text; - }); - }, - decoration: InputDecoration.collapsed( - hintText: 'Search by name...', - ), - ), - ), - ), - Expanded( - child: PagedListView( - search: _search, - fetch: (page, search) async { - return await backend.client.getEnsembles(page, search); - }, - builder: (context, ensemble) => ListTile( - title: Text(ensemble.name), - onTap: () { - widget.onSelected(ensemble); - }, - ), - ), - ), - ], - ); - } -} - -/// A list of works by one composer. -class WorksList extends StatefulWidget { - /// 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 - _WorksListState createState() => _WorksListState(); -} - -class _WorksListState extends State { - String _search; - - @override - Widget build(BuildContext context) { - final backend = MusicusBackend.of(context); - - return Column( - children: [ - Material( - elevation: 2.0, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 4.0, - ), - child: TextField( - autofocus: true, - onChanged: (text) { - setState(() { - _search = text; - }); - }, - decoration: InputDecoration.collapsed( - hintText: 'Search by title...', - ), - ), - ), - ), - Expanded( - child: PagedListView( - search: _search, - fetch: (page, search) async { - return await backend.client - .getWorks(widget.personId, page, search); - }, - builder: (context, workInfo) => ListTile( - title: Text(workInfo.work.title), - onTap: () { - widget.onSelected(workInfo); - }, - ), - ), - ), - ], - ); - } -} - -/// 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 = MusicusBackend.of(context); - - return PagedListView( - fetch: (page, _) async { - return await backend.client.getRecordings(workId, page); - }, - builder: (context, recordingInfo) => ListTile( - title: PerformancesText( - performanceInfos: recordingInfo.performances, - ), - onTap: () { - if (onSelected != null) { - onSelected(recordingInfo); - } - }, - ), - ); - } -} diff --git a/common/lib/src/widgets/player_bar.dart b/common/lib/src/widgets/player_bar.dart index 8b10e01..6ad5420 100644 --- a/common/lib/src/widgets/player_bar.dart +++ b/common/lib/src/widgets/player_bar.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:musicus_client/musicus_client.dart'; +import 'package:musicus_database/musicus_database.dart'; import '../backend.dart'; import '../library.dart'; diff --git a/common/lib/src/widgets/recording_tile.dart b/common/lib/src/widgets/recording_tile.dart index 697a86f..257f38f 100644 --- a/common/lib/src/widgets/recording_tile.dart +++ b/common/lib/src/widgets/recording_tile.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:musicus_client/musicus_client.dart'; +import 'package:musicus_database/musicus_database.dart'; import 'texts.dart'; diff --git a/common/lib/src/widgets/texts.dart b/common/lib/src/widgets/texts.dart index 0db58dc..e74b47c 100644 --- a/common/lib/src/widgets/texts.dart +++ b/common/lib/src/widgets/texts.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:musicus_client/musicus_client.dart'; +import 'package:musicus_database/musicus_database.dart'; /// A widget showing information on a list of performances. class PerformancesText extends StatelessWidget { @@ -34,7 +34,7 @@ class PerformancesText extends StatelessWidget { performanceTexts.add(buffer.toString()); } - + return Text(performanceTexts.join(', ')); } } diff --git a/common/pubspec.yaml b/common/pubspec.yaml index ce71fa2..7607570 100644 --- a/common/pubspec.yaml +++ b/common/pubspec.yaml @@ -6,16 +6,15 @@ environment: sdk: ">=2.3.0 <3.0.0" dependencies: + drift: ^1.0.0 flutter: sdk: flutter flutter_markdown: meta: - moor: - moor_ffi: - musicus_client: - path: ../client + musicus_database: + path: ../database rxdart: - url_launcher: + url_launcher: ^6.1.0 flutter: uses-material-design: true @@ -31,4 +30,4 @@ flutter: style: italic - family: Musicus Icons fonts: - - asset: fonts/musicus_icons.ttf \ No newline at end of file + - asset: fonts/musicus_icons.ttf