mirror of
				https://github.com/johrpan/musicus_mobile.git
				synced 2025-10-26 02:37:25 +01: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