mirror of
https://github.com/johrpan/musicus_mobile.git
synced 2025-10-25 19:27:24 +02:00
common: Remove platform interface
This commit is contained in:
parent
8987735797
commit
b14dcd67f2
16 changed files with 44 additions and 204 deletions
|
|
@ -1,5 +1,4 @@
|
|||
export 'src/app.dart';
|
||||
export 'src/library.dart';
|
||||
export 'src/platform.dart';
|
||||
export 'src/playback.dart';
|
||||
export 'src/settings.dart';
|
||||
export 'src/settings.dart';
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'backend.dart';
|
||||
import 'screens/home.dart';
|
||||
import 'settings.dart';
|
||||
import 'platform.dart';
|
||||
import 'playback.dart';
|
||||
import 'widgets/player_bar.dart';
|
||||
|
||||
|
|
@ -24,14 +23,10 @@ class MusicusApp extends StatelessWidget {
|
|||
/// An object handling playback.
|
||||
final MusicusPlayback playback;
|
||||
|
||||
/// An object handling platform dependent functionality.
|
||||
final MusicusPlatform platform;
|
||||
|
||||
MusicusApp({
|
||||
@required this.dbPath,
|
||||
@required this.settingsStorage,
|
||||
@required this.playback,
|
||||
@required this.platform,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -39,7 +34,6 @@ class MusicusApp extends StatelessWidget {
|
|||
return MusicusBackend(
|
||||
settingsStorage: settingsStorage,
|
||||
playback: playback,
|
||||
platform: platform,
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
final backend = MusicusBackend.of(context);
|
||||
|
|
@ -91,7 +85,7 @@ class MusicusApp extends StatelessWidget {
|
|||
leading: const Icon(Icons.folder_open),
|
||||
title: Text('Choose path'),
|
||||
onTap: () async {
|
||||
final uri = await platform.chooseBasePath();
|
||||
final uri = await FilePicker.platform.getDirectoryPath();
|
||||
if (uri != null) {
|
||||
backend.settings.setMusicLibraryPath(uri);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import 'package:flutter/widgets.dart';
|
|||
import 'package:musicus_database/musicus_database.dart';
|
||||
|
||||
import 'library.dart';
|
||||
import 'platform.dart';
|
||||
import 'playback.dart';
|
||||
import 'settings.dart';
|
||||
|
||||
|
|
@ -43,9 +42,6 @@ class MusicusBackend extends StatefulWidget {
|
|||
/// An object handling playback.
|
||||
final MusicusPlayback playback;
|
||||
|
||||
/// An object handling platform dependent functionality.
|
||||
final MusicusPlatform platform;
|
||||
|
||||
/// The first child below the backend widget.
|
||||
///
|
||||
/// This widget should keep track of the current backend status and block
|
||||
|
|
@ -56,7 +52,6 @@ class MusicusBackend extends StatefulWidget {
|
|||
MusicusBackend({
|
||||
@required this.settingsStorage,
|
||||
@required this.playback,
|
||||
@required this.platform,
|
||||
@required this.child,
|
||||
});
|
||||
|
||||
|
|
@ -76,7 +71,6 @@ class MusicusBackendState extends State<MusicusBackend> {
|
|||
|
||||
MusicusPlayback playback;
|
||||
MusicusSettings settings;
|
||||
MusicusPlatform platform;
|
||||
MusicusLibrary library;
|
||||
|
||||
MusicusClientDatabase get db => library.db;
|
||||
|
|
@ -90,7 +84,6 @@ class MusicusBackendState extends State<MusicusBackend> {
|
|||
/// Initialize resources.
|
||||
Future<void> _load() async {
|
||||
playback = widget.playback;
|
||||
await playback.setup();
|
||||
|
||||
settings = MusicusSettings(widget.settingsStorage);
|
||||
await settings.load();
|
||||
|
|
@ -104,9 +97,6 @@ class MusicusBackendState extends State<MusicusBackend> {
|
|||
|
||||
final path = settings.musicLibraryPath.valueOrNull;
|
||||
|
||||
platform = widget.platform;
|
||||
platform.setBasePath(path);
|
||||
|
||||
// This will change the status for us.
|
||||
_updateMusicLibrary(path);
|
||||
}
|
||||
|
|
@ -118,9 +108,11 @@ class MusicusBackendState extends State<MusicusBackend> {
|
|||
status = MusicusBackendStatus.setup;
|
||||
});
|
||||
} else {
|
||||
platform.setBasePath(path);
|
||||
library = MusicusLibrary(path, platform);
|
||||
library = MusicusLibrary(path);
|
||||
|
||||
await library.load();
|
||||
await playback.setup(library);
|
||||
|
||||
setState(() {
|
||||
status = MusicusBackendStatus.ready;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ import 'package:drift/isolate.dart';
|
|||
import 'package:drift/native.dart';
|
||||
import 'package:musicus_database/musicus_database.dart';
|
||||
|
||||
import 'platform.dart';
|
||||
|
||||
/// Manager for all available tracks and their representation on disk.
|
||||
class MusicusLibrary {
|
||||
/// Starts the database isolate.
|
||||
|
|
@ -19,6 +17,7 @@ class MusicusLibrary {
|
|||
static void _dbIsolateEntrypoint(_IsolateStartRequest request) {
|
||||
final executor = NativeDatabase(File(request.path));
|
||||
|
||||
|
||||
final driftIsolate =
|
||||
DriftIsolate.inCurrent(() => DatabaseConnection.fromExecutor(executor));
|
||||
|
||||
|
|
@ -31,10 +30,7 @@ class MusicusLibrary {
|
|||
/// The actual music library database.
|
||||
MusicusClientDatabase db;
|
||||
|
||||
/// Access to platform dependent functionality.
|
||||
final MusicusPlatform platform;
|
||||
|
||||
MusicusLibrary(this.basePath, this.platform);
|
||||
MusicusLibrary(this.basePath);
|
||||
|
||||
/// Load all available tracks.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -1,82 +0,0 @@
|
|||
/// Object representing a document in Storage Access Framework terms.
|
||||
class Document {
|
||||
/// Unique ID for the document.
|
||||
///
|
||||
/// The platform implementation thould be able to get the content of the
|
||||
/// document based on this value.
|
||||
final String id;
|
||||
|
||||
/// Name of the document (i.e. file name).
|
||||
final String name;
|
||||
|
||||
/// Document ID of the parent document.
|
||||
final String parent;
|
||||
|
||||
/// Whether this document represents a directory.
|
||||
final bool isDirectory;
|
||||
|
||||
Document({
|
||||
this.id,
|
||||
this.name,
|
||||
this.parent,
|
||||
this.isDirectory,
|
||||
});
|
||||
|
||||
// Use Map<dynamic, dynamic> here, as we get casting errors otherwise. This
|
||||
// won't be typesafe anyway.
|
||||
Document.fromJson(Map<dynamic, dynamic> json)
|
||||
: id = json['id'],
|
||||
name = json['name'],
|
||||
parent = json['parent'],
|
||||
isDirectory = json['isDirectory'];
|
||||
}
|
||||
|
||||
/// Platform dependent code for the Musicus backend.
|
||||
abstract class MusicusPlatform {
|
||||
/// An identifier for the root directory of the music library.
|
||||
///
|
||||
/// This will be the string, that is stored as musicLibraryPath in the
|
||||
/// settings object.
|
||||
String basePath;
|
||||
|
||||
MusicusPlatform();
|
||||
|
||||
/// This will be called, when the music library path was changed.
|
||||
void setBasePath(String path) {
|
||||
basePath = path;
|
||||
}
|
||||
|
||||
/// Choose a root level directory for the music library.
|
||||
///
|
||||
/// This should return a string representation of the chosen directory
|
||||
/// suitable for storage as [basePath].
|
||||
Future<String> chooseBasePath();
|
||||
|
||||
/// Get all documents in a directory.
|
||||
///
|
||||
/// [parentId] will be the ID of the directory document. If [parentId] is
|
||||
/// null, the children of the root directory will be returned.
|
||||
Future<List<Document>> getChildren(String parentId);
|
||||
|
||||
/// Read the contents of a document by ID.
|
||||
Future<String> readDocument(String id);
|
||||
|
||||
/// Read from a document by name.
|
||||
///
|
||||
/// [parentId] is the document ID of the parent directory.
|
||||
Future<String> readDocumentByName(String parentId, String fileName);
|
||||
|
||||
/// Get a string identifying a document.
|
||||
///
|
||||
/// [parentId] is the document ID of the parent directory. The return value
|
||||
/// should be a string, that the playback object can use to find and play the
|
||||
/// file. It will be included in [InternalTrack] objects by the music
|
||||
/// library.
|
||||
Future<String> getIdentifier(String parentId, String fileName);
|
||||
|
||||
/// Write to a document by name.
|
||||
///
|
||||
/// [parentId] is the document ID of the parent directory.
|
||||
Future<void> writeDocumentByName(
|
||||
String parentId, String fileName, String contents);
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:meta/meta.dart';
|
||||
import 'package:musicus_database/musicus_database.dart';
|
||||
import 'package:musicus_common/musicus_common.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
/// Base class for Musicus playback.
|
||||
|
|
@ -13,7 +13,7 @@ abstract class MusicusPlayback {
|
|||
/// The current playlist.
|
||||
///
|
||||
/// If the player is not active, this will be an empty list.
|
||||
final playlist = BehaviorSubject.seeded(<Track>[]);
|
||||
final playlist = BehaviorSubject.seeded(<String>[]);
|
||||
|
||||
/// Index of the currently played (or paused) track within the playlist.
|
||||
///
|
||||
|
|
@ -23,7 +23,7 @@ abstract class MusicusPlayback {
|
|||
/// The currently played track.
|
||||
///
|
||||
/// This will be null, if there is no current track.
|
||||
final currentTrack = BehaviorSubject<Track>.seeded(null);
|
||||
final currentTrack = BehaviorSubject<String>.seeded(null);
|
||||
|
||||
/// Whether we are currently playing or not.
|
||||
///
|
||||
|
|
@ -46,10 +46,10 @@ abstract class MusicusPlayback {
|
|||
/// Initialize the player.
|
||||
///
|
||||
/// This will be called after the database was initialized.
|
||||
Future<void> setup();
|
||||
Future<void> setup(MusicusLibrary library);
|
||||
|
||||
/// Add a list of tracks to the players playlist.
|
||||
Future<void> addTracks(List<Track> tracks);
|
||||
Future<void> addTracks(List<String> tracks);
|
||||
|
||||
/// Remove the track at [index] from the playlist.
|
||||
Future<void> removeTrack(int index);
|
||||
|
|
@ -96,16 +96,15 @@ abstract class MusicusPlayback {
|
|||
/// Update [position] and [normalizedPosition].
|
||||
///
|
||||
/// Requires [duration] to be up to date
|
||||
void updatePosition(int positionMs) {
|
||||
position.add(Duration(milliseconds: positionMs));
|
||||
_setNormalizedPosition(positionMs / duration.value.inMilliseconds);
|
||||
void updatePosition(Duration pos) {
|
||||
position.add(pos);
|
||||
_setNormalizedPosition(pos.inMilliseconds / duration.value.inMilliseconds);
|
||||
}
|
||||
|
||||
/// Update [position], [duration] and [normalizedPosition].
|
||||
void updateDuration(int positionMs, int durationMs) {
|
||||
position.add(Duration(milliseconds: positionMs));
|
||||
duration.add(Duration(milliseconds: durationMs));
|
||||
_setNormalizedPosition(positionMs / durationMs);
|
||||
void updateDuration(Duration dur) {
|
||||
duration.add(dur);
|
||||
_setNormalizedPosition(position.value.inMilliseconds / dur.inMilliseconds);
|
||||
}
|
||||
|
||||
/// Update [normalizedPosition] ensuring its value is between 0.0 and 1.0.
|
||||
|
|
@ -124,6 +123,9 @@ abstract class MusicusPlayback {
|
|||
/// Requires [playlist] to be up to date.
|
||||
void updateCurrentTrack(int index) {
|
||||
currentIndex.add(index);
|
||||
currentTrack.add(playlist.value[index]);
|
||||
|
||||
if (playlist.value != null && index >= 0 && index < playlist.value.length) {
|
||||
currentTrack.add(playlist.value[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class _ProgramScreenState extends State<ProgramScreen> {
|
|||
|
||||
StreamSubscription<bool> playerActiveSubscription;
|
||||
|
||||
StreamSubscription<List<Track>> playlistSubscription;
|
||||
StreamSubscription<List<String>> playlistSubscription;
|
||||
List<Widget> widgets = [];
|
||||
|
||||
StreamSubscription<double> positionSubscription;
|
||||
|
|
@ -63,7 +63,7 @@ class _ProgramScreenState extends State<ProgramScreen> {
|
|||
}
|
||||
|
||||
/// Go through the tracks of [playlist] and preprocess them for displaying.
|
||||
Future<void> updateProgram(List<Track> playlist) async {
|
||||
Future<void> updateProgram(List<String> playlist) async {
|
||||
List<Widget> newWidgets = [];
|
||||
|
||||
// The following variables exist to adapt the resulting ProgramItem to its
|
||||
|
|
@ -84,7 +84,7 @@ class _ProgramScreenState extends State<ProgramScreen> {
|
|||
// The widgets displayed for this track.
|
||||
List<Widget> children = [];
|
||||
|
||||
final track = playlist[i];
|
||||
final track = await backend.db.tracksById(playlist[i]).getSingle();
|
||||
final recordingId = track.recording;
|
||||
final partIds = track.workParts;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../backend.dart';
|
||||
|
||||
class SettingsScreen extends StatelessWidget {
|
||||
static const _platform = MethodChannel('de.johrpan.musicus/platform');
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final backend = MusicusBackend.of(context);
|
||||
|
|
@ -25,7 +23,7 @@ class SettingsScreen extends StatelessWidget {
|
|||
subtitle: Text(snapshot.data ?? 'Choose folder'),
|
||||
isThreeLine: snapshot.hasData,
|
||||
onTap: () async {
|
||||
final uri = await backend.platform.chooseBasePath();
|
||||
final uri = await FilePicker.platform.getDirectoryPath();
|
||||
|
||||
if (uri != null) {
|
||||
settings.setMusicLibraryPath(uri);
|
||||
|
|
|
|||
|
|
@ -32,8 +32,9 @@ class WorkScreen extends StatelessWidget {
|
|||
performanceInfos: recordingInfo.performances,
|
||||
),
|
||||
onTap: () async {
|
||||
final tracks = await backend.db.tracksByRecording(recordingId).get();
|
||||
backend.playback.addTracks(tracks);
|
||||
final tracks =
|
||||
await backend.db.tracksByRecording(recordingId).get();
|
||||
backend.playback.addTracks(tracks.map((t) => t.id).toList());
|
||||
},
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,9 +2,6 @@ import 'dart:async';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../backend.dart';
|
||||
import '../widgets/texts.dart';
|
||||
|
||||
/// A list view supporting pagination and searching.
|
||||
///
|
||||
/// The [fetch] function will be called, when the user has scrolled to the end
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ class PlayerBar extends StatefulWidget {
|
|||
|
||||
class _PlayerBarState extends State<PlayerBar> {
|
||||
MusicusBackendState _backend;
|
||||
StreamSubscription<Track> _currentTrackSubscribtion;
|
||||
StreamSubscription<String> _currentTrackSubscribtion;
|
||||
WorkInfo _workInfo;
|
||||
List<int> _partIds;
|
||||
|
||||
|
|
@ -26,9 +26,10 @@ class _PlayerBarState extends State<PlayerBar> {
|
|||
_backend = MusicusBackend.of(context);
|
||||
|
||||
_currentTrackSubscribtion?.cancel();
|
||||
_currentTrackSubscribtion = _backend.playback.currentTrack.listen((track) {
|
||||
_currentTrackSubscribtion =
|
||||
_backend.playback.currentTrack.listen((track) async {
|
||||
if (track != null) {
|
||||
_setTrack(track);
|
||||
_setTrack(await _backend.db.tracksById(track).getSingle());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
name: musicus_common
|
||||
version: 0.1.0
|
||||
description: Common building blocks for Musicus client apps.
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: ">=2.3.0 <3.0.0"
|
||||
|
||||
dependencies:
|
||||
drift: ^1.0.0
|
||||
file_picker: ^4.5.1
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_markdown:
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import 'package:path/path.dart' as p;
|
|||
import 'package:path_provider/path_provider.dart' as pp;
|
||||
|
||||
import 'settings.dart';
|
||||
import 'platform.dart';
|
||||
import 'playback.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
|
|
@ -16,7 +15,6 @@ Future<void> main() async {
|
|||
runApp(MusicusApp(
|
||||
dbPath: dbPath,
|
||||
settingsStorage: SettingsStorage(),
|
||||
platform: MusicusDesktopPlatform(),
|
||||
playback: MusicusDesktopPlayback(),
|
||||
));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,58 +0,0 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:musicus_common/musicus_common.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
class MusicusDesktopPlatform extends MusicusPlatform {
|
||||
@override
|
||||
Future<String> chooseBasePath() async {
|
||||
return await FilePicker.platform.getDirectoryPath();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Document>> getChildren(String parentId) async {
|
||||
final List<Document> result = [];
|
||||
|
||||
final parent = Directory(parentId ?? basePath);
|
||||
await for (final fse in parent.list()) {
|
||||
result.add(Document(
|
||||
id: fse.path,
|
||||
name: p.basename(fse.path),
|
||||
parent: parentId,
|
||||
isDirectory: fse is Directory,
|
||||
));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> getIdentifier(String parentId, String fileName) async {
|
||||
return p.absolute(parentId, fileName);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> readDocument(String id) async {
|
||||
try {
|
||||
return await File(id).readAsString();
|
||||
} on FileSystemException {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> readDocumentByName(String parentId, String fileName) async {
|
||||
try {
|
||||
return await File(p.absolute(parentId, fileName)).readAsString();
|
||||
} on FileSystemException {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> writeDocumentByName(
|
||||
String parentId, String fileName, String contents) async {
|
||||
await File(p.absolute(parentId, fileName)).writeAsString(contents);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,12 @@
|
|||
import 'package:musicus_common/musicus_common.dart';
|
||||
import 'package:musicus_database/musicus_database.dart';
|
||||
|
||||
class MusicusDesktopPlayback extends MusicusPlayback {
|
||||
@override
|
||||
Future<void> setup() async {}
|
||||
Future<void> setup(MusicusLibrary library) async {}
|
||||
|
||||
@override
|
||||
Future<void> addTracks(List<Track> tracks) async {
|
||||
final List<Track> newPlaylist = List.from(playlist.value);
|
||||
Future<void> addTracks(List<String> tracks) async {
|
||||
final List<String> newPlaylist = List.from(playlist.value);
|
||||
newPlaylist.addAll(tracks);
|
||||
playlist.add(newPlaylist);
|
||||
active.add(true);
|
||||
|
|
@ -20,7 +19,7 @@ class MusicusDesktopPlayback extends MusicusPlayback {
|
|||
|
||||
@override
|
||||
Future<void> removeTrack(int index) async {
|
||||
final List<Track> tracks = List.from(playlist.value);
|
||||
final List<String> tracks = List.from(playlist.value);
|
||||
tracks.removeAt(index);
|
||||
playlist.add(tracks);
|
||||
}
|
||||
|
|
@ -29,7 +28,7 @@ class MusicusDesktopPlayback extends MusicusPlayback {
|
|||
Future<void> seekTo(double pos) async {
|
||||
if (active.value && pos >= 0.0 && pos <= 1.0) {
|
||||
final durationMs = duration.value.inMilliseconds;
|
||||
updatePosition((pos * durationMs).floor());
|
||||
updatePosition(Duration(milliseconds: (pos * durationMs).floor()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
name: musicus_desktop
|
||||
version: 0.1.0
|
||||
description: Desktop version of the classical music player and organizer.
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: ">=2.3.0 <3.0.0"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue