mirror of
https://github.com/johrpan/musicus_mobile.git
synced 2025-10-26 10:47:25 +01:00
common: Adapt to database changes
This commit is contained in:
parent
9e485eac11
commit
84b700236b
9 changed files with 85 additions and 270 deletions
|
|
@ -37,7 +37,6 @@ class MusicusApp extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MusicusBackend(
|
return MusicusBackend(
|
||||||
dbPath: dbPath,
|
|
||||||
settingsStorage: settingsStorage,
|
settingsStorage: settingsStorage,
|
||||||
playback: playback,
|
playback: playback,
|
||||||
platform: platform,
|
platform: platform,
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,3 @@
|
||||||
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:flutter/widgets.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_database/musicus_database.dart';
|
||||||
|
|
||||||
|
|
@ -44,9 +37,6 @@ enum MusicusBackendStatus {
|
||||||
/// The backend maintains a Musicus database within a Moor isolate. The connect
|
/// The backend maintains a Musicus database within a Moor isolate. The connect
|
||||||
/// port will be registered as 'moor' in the [IsolateNameServer].
|
/// port will be registered as 'moor' in the [IsolateNameServer].
|
||||||
class MusicusBackend extends StatefulWidget {
|
class MusicusBackend extends StatefulWidget {
|
||||||
/// Path to the database file.
|
|
||||||
final String dbPath;
|
|
||||||
|
|
||||||
/// An object to persist the settings.
|
/// An object to persist the settings.
|
||||||
final MusicusSettingsStorage settingsStorage;
|
final MusicusSettingsStorage settingsStorage;
|
||||||
|
|
||||||
|
|
@ -64,7 +54,6 @@ class MusicusBackend extends StatefulWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
MusicusBackend({
|
MusicusBackend({
|
||||||
@required this.dbPath,
|
|
||||||
@required this.settingsStorage,
|
@required this.settingsStorage,
|
||||||
@required this.playback,
|
@required this.playback,
|
||||||
@required this.platform,
|
@required this.platform,
|
||||||
|
|
@ -79,31 +68,19 @@ class MusicusBackend extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class MusicusBackendState extends State<MusicusBackend> {
|
class MusicusBackendState extends State<MusicusBackend> {
|
||||||
/// Starts the database isolate.
|
|
||||||
///
|
|
||||||
/// It will create a database connection for [request.path] and will send the
|
|
||||||
/// 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.
|
/// The current backend status.
|
||||||
///
|
///
|
||||||
/// If this is not [MusicusBackendStatus.ready], the [child] widget should
|
/// If this is not [MusicusBackendStatus.ready], the [child] widget should
|
||||||
/// prevent all access to the backend.
|
/// prevent all access to the backend.
|
||||||
MusicusBackendStatus status = MusicusBackendStatus.loading;
|
MusicusBackendStatus status = MusicusBackendStatus.loading;
|
||||||
|
|
||||||
MusicusClientDatabase db;
|
|
||||||
MusicusPlayback playback;
|
MusicusPlayback playback;
|
||||||
MusicusSettings settings;
|
MusicusSettings settings;
|
||||||
MusicusPlatform platform;
|
MusicusPlatform platform;
|
||||||
MusicusLibrary library;
|
MusicusLibrary library;
|
||||||
|
|
||||||
|
MusicusClientDatabase get db => library.db;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
@ -112,23 +89,6 @@ class MusicusBackendState extends State<MusicusBackend> {
|
||||||
|
|
||||||
/// Initialize resources.
|
/// Initialize resources.
|
||||||
Future<void> _load() async {
|
Future<void> _load() async {
|
||||||
SendPort driftPort = IsolateNameServer.lookupPortByName('moor');
|
|
||||||
|
|
||||||
if (driftPort == null) {
|
|
||||||
final receivePort = ReceivePort();
|
|
||||||
|
|
||||||
await Isolate.spawn(_dbIsolateEntrypoint,
|
|
||||||
_IsolateStartRequest(receivePort.sendPort, widget.dbPath));
|
|
||||||
|
|
||||||
driftPort = await receivePort.first;
|
|
||||||
IsolateNameServer.registerPortWithName(driftPort, 'drift');
|
|
||||||
}
|
|
||||||
|
|
||||||
final driftIsolate = DriftIsolate.fromConnectPort(driftPort);
|
|
||||||
db = MusicusClientDatabase.connect(
|
|
||||||
connection: await driftIsolate.connect(),
|
|
||||||
);
|
|
||||||
|
|
||||||
playback = widget.playback;
|
playback = widget.playback;
|
||||||
await playback.setup();
|
await playback.setup();
|
||||||
|
|
||||||
|
|
@ -186,14 +146,6 @@ class MusicusBackendState extends State<MusicusBackend> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Bundles arguments for the database isolate.
|
|
||||||
class _IsolateStartRequest {
|
|
||||||
final SendPort sendPort;
|
|
||||||
final String path;
|
|
||||||
|
|
||||||
_IsolateStartRequest(this.sendPort, this.path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper widget passing the current backend state down the widget tree.
|
/// Helper widget passing the current backend state down the widget tree.
|
||||||
class _InheritedBackend extends InheritedWidget {
|
class _InheritedBackend extends InheritedWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
|
||||||
|
|
@ -1,123 +1,39 @@
|
||||||
import 'dart:convert';
|
import 'dart:io';
|
||||||
|
import 'dart:isolate';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift/isolate.dart';
|
||||||
|
import 'package:drift/native.dart';
|
||||||
|
import 'package:musicus_database/musicus_database.dart';
|
||||||
|
|
||||||
import 'platform.dart';
|
import 'platform.dart';
|
||||||
|
|
||||||
/// Bundles a [Track] with information on how to find the corresponding file.
|
|
||||||
class InternalTrack {
|
|
||||||
/// The represented track.
|
|
||||||
final Track track;
|
|
||||||
|
|
||||||
/// A string identifying the track for playback.
|
|
||||||
///
|
|
||||||
/// This will be the result of calling the platform objects getIdentifier()
|
|
||||||
/// function with the file name of the track.
|
|
||||||
final String identifier;
|
|
||||||
|
|
||||||
InternalTrack({
|
|
||||||
this.track,
|
|
||||||
this.identifier,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory InternalTrack.fromJson(Map<String, dynamic> json) => InternalTrack(
|
|
||||||
track: Track.fromJson(json['track']),
|
|
||||||
identifier: json['identifier'],
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
|
||||||
'track': track.toJson(),
|
|
||||||
'identifier': identifier,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Description of a concrete audio file.
|
|
||||||
///
|
|
||||||
/// This gets stored in the folder of the audio file and links the audio file
|
|
||||||
/// to a recording in the database.
|
|
||||||
class Track {
|
|
||||||
/// The name of the file that contains the track's audio.
|
|
||||||
///
|
|
||||||
/// This corresponds to a document ID in terms of the Android Storage Access
|
|
||||||
/// Framework.
|
|
||||||
final String fileName;
|
|
||||||
|
|
||||||
/// Index within the list of tracks for the corresponding recording.
|
|
||||||
final int index;
|
|
||||||
|
|
||||||
/// Of which recording this track is a part of.
|
|
||||||
final int recordingId;
|
|
||||||
|
|
||||||
/// Which work parts of the recorded work are contained in this track.
|
|
||||||
final List<int> partIds;
|
|
||||||
|
|
||||||
Track({
|
|
||||||
this.fileName,
|
|
||||||
this.index,
|
|
||||||
this.recordingId,
|
|
||||||
this.partIds,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory Track.fromJson(Map<String, dynamic> json) => Track(
|
|
||||||
fileName: json['fileName'],
|
|
||||||
index: json['index'],
|
|
||||||
recordingId: json['recording'],
|
|
||||||
partIds: List.from(json['parts']),
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
|
||||||
'fileName': fileName,
|
|
||||||
'index': index,
|
|
||||||
'recording': recordingId,
|
|
||||||
'parts': partIds,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Representation of all tracked audio files in one folder.
|
|
||||||
class MusicusFile {
|
|
||||||
/// Current version of the Musicus file format.
|
|
||||||
///
|
|
||||||
/// If incompatible changes are made, this will be increased by one.
|
|
||||||
static const currentVersion = 0;
|
|
||||||
|
|
||||||
/// Musicus file format version in use.
|
|
||||||
///
|
|
||||||
/// This will be used in the future, if incompatible changes are made.
|
|
||||||
final int version;
|
|
||||||
|
|
||||||
/// List of [Track] objects.
|
|
||||||
final List<Track> tracks;
|
|
||||||
|
|
||||||
MusicusFile({
|
|
||||||
this.version = currentVersion,
|
|
||||||
List<Track> tracks,
|
|
||||||
}) : tracks = tracks ?? [];
|
|
||||||
|
|
||||||
factory MusicusFile.fromJson(Map<String, dynamic> json) => MusicusFile(
|
|
||||||
version: json['version'],
|
|
||||||
tracks: json['tracks']
|
|
||||||
.map<Track>((trackJson) => Track.fromJson(trackJson))
|
|
||||||
.toList(growable: true),
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
|
||||||
'version': version,
|
|
||||||
'tracks': tracks.map((t) => t.toJson()).toList(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Manager for all available tracks and their representation on disk.
|
/// Manager for all available tracks and their representation on disk.
|
||||||
class MusicusLibrary {
|
class MusicusLibrary {
|
||||||
|
/// Starts the database isolate.
|
||||||
|
///
|
||||||
|
/// It will create a database connection for [request.path] and will send the
|
||||||
|
/// 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);
|
||||||
|
}
|
||||||
|
|
||||||
/// String representing the music library base path.
|
/// String representing the music library base path.
|
||||||
final String basePath;
|
final String basePath;
|
||||||
|
|
||||||
|
/// The actual music library database.
|
||||||
|
MusicusClientDatabase db;
|
||||||
|
|
||||||
/// Access to platform dependent functionality.
|
/// Access to platform dependent functionality.
|
||||||
final MusicusPlatform platform;
|
final MusicusPlatform platform;
|
||||||
|
|
||||||
/// Map of all available tracks by recording ID.
|
|
||||||
///
|
|
||||||
/// These are [InternalTrack] objects to store the URI of the corresponding
|
|
||||||
/// audio file alongside the real [Track] object.
|
|
||||||
final Map<int, List<InternalTrack>> tracks = {};
|
|
||||||
|
|
||||||
MusicusLibrary(this.basePath, this.platform);
|
MusicusLibrary(this.basePath, this.platform);
|
||||||
|
|
||||||
/// Load all available tracks.
|
/// Load all available tracks.
|
||||||
|
|
@ -126,62 +42,32 @@ class MusicusLibrary {
|
||||||
/// content of all files called musicus.json and stores all track information
|
/// content of all files called musicus.json and stores all track information
|
||||||
/// that it found.
|
/// that it found.
|
||||||
Future<void> load() async {
|
Future<void> load() async {
|
||||||
// TODO: Consider capping the recursion somewhere.
|
SendPort driftPort = IsolateNameServer.lookupPortByName('drift');
|
||||||
Future<void> recurse([String parentId]) async {
|
|
||||||
final children = await platform.getChildren(parentId);
|
|
||||||
|
|
||||||
for (final child in children) {
|
if (driftPort == null) {
|
||||||
if (child.isDirectory) {
|
final receivePort = ReceivePort();
|
||||||
recurse(child.id);
|
|
||||||
} else if (child.name == 'musicus.json') {
|
|
||||||
final content = await platform.readDocument(child.id);
|
|
||||||
final musicusFile = MusicusFile.fromJson(jsonDecode(content));
|
|
||||||
for (final track in musicusFile.tracks) {
|
|
||||||
_indexTrack(parentId, track);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await recurse();
|
await Isolate.spawn(
|
||||||
}
|
_dbIsolateEntrypoint,
|
||||||
|
_IsolateStartRequest(
|
||||||
/// Add a list of new tracks to the music library.
|
receivePort.sendPort, p.join(basePath, 'musicus.db')),
|
||||||
///
|
|
||||||
/// They are stored in this instance and on disk in the directory denoted by
|
|
||||||
/// [parentId].
|
|
||||||
Future<void> addTracks(String parentId, List<Track> newTracks) async {
|
|
||||||
MusicusFile musicusFile;
|
|
||||||
|
|
||||||
final oldContent =
|
|
||||||
await platform.readDocumentByName(parentId, 'musicus.json');
|
|
||||||
|
|
||||||
if (oldContent != null) {
|
|
||||||
musicusFile = MusicusFile.fromJson(jsonDecode(oldContent));
|
|
||||||
} else {
|
|
||||||
musicusFile = MusicusFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final track in newTracks) {
|
|
||||||
_indexTrack(parentId, track);
|
|
||||||
musicusFile.tracks.add(track);
|
|
||||||
}
|
|
||||||
|
|
||||||
await platform.writeDocumentByName(
|
|
||||||
parentId, 'musicus.json', jsonEncode(musicusFile.toJson()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a track to the map of available tracks.
|
|
||||||
Future<void> _indexTrack(String parentId, Track track) async {
|
|
||||||
final iTrack = InternalTrack(
|
|
||||||
track: track,
|
|
||||||
identifier: await platform.getIdentifier(parentId, track.fileName),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (tracks.containsKey(track.recordingId)) {
|
driftPort = await receivePort.first;
|
||||||
tracks[track.recordingId].add(iTrack);
|
IsolateNameServer.registerPortWithName(driftPort, 'drift');
|
||||||
} else {
|
}
|
||||||
tracks[track.recordingId] = [iTrack];
|
|
||||||
|
final driftIsolate = DriftIsolate.fromConnectPort(driftPort);
|
||||||
|
db = MusicusClientDatabase.connect(
|
||||||
|
connection: await driftIsolate.connect(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Bundles arguments for the database isolate.
|
||||||
|
class _IsolateStartRequest {
|
||||||
|
final SendPort sendPort;
|
||||||
|
final String path;
|
||||||
|
|
||||||
|
_IsolateStartRequest(this.sendPort, this.path);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:musicus_database/musicus_database.dart';
|
||||||
import 'package:rxdart/rxdart.dart';
|
import 'package:rxdart/rxdart.dart';
|
||||||
|
|
||||||
import 'library.dart';
|
|
||||||
|
|
||||||
/// Base class for Musicus playback.
|
/// Base class for Musicus playback.
|
||||||
abstract class MusicusPlayback {
|
abstract class MusicusPlayback {
|
||||||
/// Whether the player is active.
|
/// Whether the player is active.
|
||||||
|
|
@ -14,7 +13,7 @@ abstract class MusicusPlayback {
|
||||||
/// The current playlist.
|
/// The current playlist.
|
||||||
///
|
///
|
||||||
/// If the player is not active, this will be an empty list.
|
/// If the player is not active, this will be an empty list.
|
||||||
final playlist = BehaviorSubject.seeded(<InternalTrack>[]);
|
final playlist = BehaviorSubject.seeded(<Track>[]);
|
||||||
|
|
||||||
/// Index of the currently played (or paused) track within the playlist.
|
/// Index of the currently played (or paused) track within the playlist.
|
||||||
///
|
///
|
||||||
|
|
@ -24,7 +23,7 @@ abstract class MusicusPlayback {
|
||||||
/// The currently played track.
|
/// The currently played track.
|
||||||
///
|
///
|
||||||
/// This will be null, if there is no current track.
|
/// This will be null, if there is no current track.
|
||||||
final currentTrack = BehaviorSubject<InternalTrack>.seeded(null);
|
final currentTrack = BehaviorSubject<Track>.seeded(null);
|
||||||
|
|
||||||
/// Whether we are currently playing or not.
|
/// Whether we are currently playing or not.
|
||||||
///
|
///
|
||||||
|
|
@ -50,7 +49,7 @@ abstract class MusicusPlayback {
|
||||||
Future<void> setup();
|
Future<void> setup();
|
||||||
|
|
||||||
/// Add a list of tracks to the players playlist.
|
/// Add a list of tracks to the players playlist.
|
||||||
Future<void> addTracks(List<InternalTrack> tracks);
|
Future<void> addTracks(List<Track> tracks);
|
||||||
|
|
||||||
/// Remove the track at [index] from the playlist.
|
/// Remove the track at [index] from the playlist.
|
||||||
Future<void> removeTrack(int index);
|
Future<void> removeTrack(int index);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_database/musicus_database.dart';
|
||||||
|
|
||||||
import '../backend.dart';
|
import '../backend.dart';
|
||||||
import '../library.dart';
|
|
||||||
import '../widgets/play_pause_button.dart';
|
import '../widgets/play_pause_button.dart';
|
||||||
import '../widgets/recording_tile.dart';
|
import '../widgets/recording_tile.dart';
|
||||||
|
|
||||||
|
|
@ -18,7 +17,7 @@ class _ProgramScreenState extends State<ProgramScreen> {
|
||||||
|
|
||||||
StreamSubscription<bool> playerActiveSubscription;
|
StreamSubscription<bool> playerActiveSubscription;
|
||||||
|
|
||||||
StreamSubscription<List<InternalTrack>> playlistSubscription;
|
StreamSubscription<List<Track>> playlistSubscription;
|
||||||
List<Widget> widgets = [];
|
List<Widget> widgets = [];
|
||||||
|
|
||||||
StreamSubscription<double> positionSubscription;
|
StreamSubscription<double> positionSubscription;
|
||||||
|
|
@ -64,7 +63,7 @@ class _ProgramScreenState extends State<ProgramScreen> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Go through the tracks of [playlist] and preprocess them for displaying.
|
/// Go through the tracks of [playlist] and preprocess them for displaying.
|
||||||
Future<void> updateProgram(List<InternalTrack> playlist) async {
|
Future<void> updateProgram(List<Track> playlist) async {
|
||||||
List<Widget> newWidgets = [];
|
List<Widget> newWidgets = [];
|
||||||
|
|
||||||
// The following variables exist to adapt the resulting ProgramItem to its
|
// The following variables exist to adapt the resulting ProgramItem to its
|
||||||
|
|
@ -72,25 +71,22 @@ class _ProgramScreenState extends State<ProgramScreen> {
|
||||||
|
|
||||||
// If the previous recording was the same, we won't need to include the
|
// If the previous recording was the same, we won't need to include the
|
||||||
// recording data again.
|
// recording data again.
|
||||||
int lastRecordingId;
|
String lastRecordingId;
|
||||||
|
|
||||||
// If the previous work was the same, we won't need to retrieve its parts
|
// If the previous work was the same, we won't need to retrieve its parts
|
||||||
// from the database again.
|
// from the database again.
|
||||||
int lastWorkId;
|
String lastWorkId;
|
||||||
|
|
||||||
// This will contain information on the last new work.
|
// This will contain information on the last new work.
|
||||||
WorkInfo workInfo;
|
WorkInfo workInfo;
|
||||||
|
|
||||||
// The index of the last displayed section.
|
|
||||||
int lastSectionIndex;
|
|
||||||
|
|
||||||
for (var i = 0; i < playlist.length; i++) {
|
for (var i = 0; i < playlist.length; i++) {
|
||||||
// The widgets displayed for this track.
|
// The widgets displayed for this track.
|
||||||
List<Widget> children = [];
|
List<Widget> children = [];
|
||||||
|
|
||||||
final track = playlist[i];
|
final track = playlist[i];
|
||||||
final recordingId = track.track.recordingId;
|
final recordingId = track.recording;
|
||||||
final partIds = track.track.partIds;
|
final partIds = track.workParts;
|
||||||
|
|
||||||
// If the recording is the same, the work will also be the same, so
|
// If the recording is the same, the work will also be the same, so
|
||||||
// workInfo doesn't have to be updated either.
|
// workInfo doesn't have to be updated either.
|
||||||
|
|
@ -102,7 +98,6 @@ class _ProgramScreenState extends State<ProgramScreen> {
|
||||||
if (recordingInfo.recording.work != lastWorkId) {
|
if (recordingInfo.recording.work != lastWorkId) {
|
||||||
lastWorkId = recordingInfo.recording.work;
|
lastWorkId = recordingInfo.recording.work;
|
||||||
workInfo = await backend.db.getWork(lastWorkId);
|
workInfo = await backend.db.getWork(lastWorkId);
|
||||||
lastSectionIndex = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
children.addAll([
|
children.addAll([
|
||||||
|
|
@ -116,27 +111,20 @@ class _ProgramScreenState extends State<ProgramScreen> {
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final partId in partIds) {
|
for (final part_id_unparsed in partIds.split(',')) {
|
||||||
final partInfo = workInfo.parts[partId];
|
if (part_id_unparsed.isEmpty) {
|
||||||
|
continue;
|
||||||
final sectionIndex = workInfo.sections
|
|
||||||
.lastIndexWhere((s) => s.beforePartIndex <= partId);
|
|
||||||
if (sectionIndex != lastSectionIndex && sectionIndex >= 0) {
|
|
||||||
lastSectionIndex = sectionIndex;
|
|
||||||
children.add(Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
bottom: 8.0,
|
|
||||||
),
|
|
||||||
child: Text(workInfo.sections[sectionIndex].title),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final partId = int.parse(part_id_unparsed);
|
||||||
|
final partInfo = workInfo.parts[partId];
|
||||||
|
|
||||||
children.add(Padding(
|
children.add(Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
left: 8.0,
|
left: 8.0,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
partInfo.part.title,
|
partInfo.title,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontStyle: FontStyle.italic,
|
fontStyle: FontStyle.italic,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,8 @@ class WorkScreen extends StatelessWidget {
|
||||||
title: PerformancesText(
|
title: PerformancesText(
|
||||||
performanceInfos: recordingInfo.performances,
|
performanceInfos: recordingInfo.performances,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
final tracks = backend.library.tracks[recordingId];
|
final tracks = await backend.db.tracksByRecording(recordingId).get();
|
||||||
tracks.sort((t1, t2) => t1.track.index.compareTo(t2.track.index));
|
|
||||||
backend.playback.addTracks(tracks);
|
backend.playback.addTracks(tracks);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_database/musicus_database.dart';
|
||||||
|
|
||||||
import '../backend.dart';
|
import '../backend.dart';
|
||||||
import '../library.dart';
|
|
||||||
import '../screens/program.dart';
|
import '../screens/program.dart';
|
||||||
|
|
||||||
import 'play_pause_button.dart';
|
import 'play_pause_button.dart';
|
||||||
|
|
@ -16,7 +15,7 @@ class PlayerBar extends StatefulWidget {
|
||||||
|
|
||||||
class _PlayerBarState extends State<PlayerBar> {
|
class _PlayerBarState extends State<PlayerBar> {
|
||||||
MusicusBackendState _backend;
|
MusicusBackendState _backend;
|
||||||
StreamSubscription<InternalTrack> _currentTrackSubscribtion;
|
StreamSubscription<Track> _currentTrackSubscribtion;
|
||||||
WorkInfo _workInfo;
|
WorkInfo _workInfo;
|
||||||
List<int> _partIds;
|
List<int> _partIds;
|
||||||
|
|
||||||
|
|
@ -29,16 +28,22 @@ class _PlayerBarState extends State<PlayerBar> {
|
||||||
_currentTrackSubscribtion?.cancel();
|
_currentTrackSubscribtion?.cancel();
|
||||||
_currentTrackSubscribtion = _backend.playback.currentTrack.listen((track) {
|
_currentTrackSubscribtion = _backend.playback.currentTrack.listen((track) {
|
||||||
if (track != null) {
|
if (track != null) {
|
||||||
_setTrack(track.track);
|
_setTrack(track);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _setTrack(Track track) async {
|
Future<void> _setTrack(Track track) async {
|
||||||
final recording =
|
final recording =
|
||||||
await _backend.db.recordingById(track.recordingId).getSingle();
|
await _backend.db.recordingById(track.recording).getSingle();
|
||||||
|
|
||||||
final workInfo = await _backend.db.getWork(recording.work);
|
final workInfo = await _backend.db.getWork(recording.work);
|
||||||
final partIds = track.partIds;
|
|
||||||
|
final partIds = track.workParts
|
||||||
|
.split(',')
|
||||||
|
.where((p) => p.isNotEmpty)
|
||||||
|
.map((p) => int.parse(p))
|
||||||
|
.toList();
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
@ -54,27 +59,14 @@ class _PlayerBarState extends State<PlayerBar> {
|
||||||
String subtitle;
|
String subtitle;
|
||||||
|
|
||||||
if (_workInfo != null) {
|
if (_workInfo != null) {
|
||||||
title = _workInfo.composers
|
title = '${_workInfo.composer.firstName} ${_workInfo.composer.lastName}';
|
||||||
.map((p) => '${p.firstName} ${p.lastName}')
|
|
||||||
.join(', ');
|
|
||||||
|
|
||||||
final subtitleBuffer = StringBuffer(_workInfo.work.title);
|
final subtitleBuffer = StringBuffer(_workInfo.work.title);
|
||||||
|
|
||||||
if (_partIds.isNotEmpty) {
|
if (_partIds.isNotEmpty) {
|
||||||
subtitleBuffer.write(': ');
|
subtitleBuffer.write(': ');
|
||||||
|
subtitleBuffer
|
||||||
final section = _workInfo.sections.lastWhere(
|
.write(_partIds.map((i) => _workInfo.parts[i].title).join(', '));
|
||||||
(s) => s.beforePartIndex <= _partIds[0],
|
|
||||||
orElse: () => null,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (section != null) {
|
|
||||||
subtitleBuffer.write(section.title);
|
|
||||||
subtitleBuffer.write(': ');
|
|
||||||
}
|
|
||||||
|
|
||||||
subtitleBuffer.write(
|
|
||||||
_partIds.map((i) => _workInfo.parts[i].part.title).join(', '));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
subtitle = subtitleBuffer.toString();
|
subtitle = subtitleBuffer.toString();
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,8 @@ class RecordingTile extends StatelessWidget {
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
DefaultTextStyle(
|
DefaultTextStyle(
|
||||||
style: textTheme.subtitle1,
|
style: textTheme.subtitle1,
|
||||||
child: Text(workInfo.composers
|
child: Text(
|
||||||
.map((p) => '${p.firstName} ${p.lastName}')
|
'${workInfo.composer.firstName} ${workInfo.composer.lastName}'),
|
||||||
.join(', ')),
|
|
||||||
),
|
),
|
||||||
DefaultTextStyle(
|
DefaultTextStyle(
|
||||||
style: textTheme.headline6,
|
style: textTheme.headline6,
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ dependencies:
|
||||||
meta:
|
meta:
|
||||||
musicus_database:
|
musicus_database:
|
||||||
path: ../database
|
path: ../database
|
||||||
|
path: ^1.8.1
|
||||||
rxdart:
|
rxdart:
|
||||||
url_launcher: ^6.1.0
|
url_launcher: ^6.1.0
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue