mirror of
				https://github.com/johrpan/musicus_mobile.git
				synced 2025-10-26 18:57:25 +01:00 
			
		
		
		
	 e9f0bd03e7
			
		
	
	
		e9f0bd03e7
		
	
	
	
	
		
			
			Everything related to file system access has been rewritten to make use of the storage access framework. This means that the WRITE_EXTERNAL_STORAGE is no longer needed. Because of that, the dependency on permission_handler could be dropped and all code related to permission handling has been removed. To be able to open a whole document tree, the minSdkVersion was bumped to 21. Finally the file selector was rewritten using custom platform dependent code.
		
			
				
	
	
		
			141 lines
		
	
	
	
		
			4 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			141 lines
		
	
	
	
		
			4 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| import 'dart:async';
 | |
| 
 | |
| import 'package:flutter/material.dart';
 | |
| 
 | |
| import 'backend.dart';
 | |
| import 'screens/home.dart';
 | |
| import 'widgets/player_bar.dart';
 | |
| 
 | |
| class App extends StatelessWidget {
 | |
|   @override
 | |
|   Widget build(BuildContext context) {
 | |
|     final backend = Backend.of(context);
 | |
| 
 | |
|     return MaterialApp(
 | |
|       title: 'Musicus',
 | |
|       theme: ThemeData(
 | |
|         brightness: Brightness.dark,
 | |
|         accentColor: Colors.amber,
 | |
|         textSelectionColor: Colors.grey[600],
 | |
|         cursorColor: Colors.amber,
 | |
|         textSelectionHandleColor: Colors.amber,
 | |
|         toggleableActiveColor: Colors.amber,
 | |
|         // Added for sliders and FABs. Not everything seems to obey this.
 | |
|         colorScheme: ColorScheme.dark(
 | |
|           primary: Colors.amber,
 | |
|           secondary: Colors.amber,
 | |
|         ),
 | |
|         fontFamily: 'Libertinus Sans',
 | |
|       ),
 | |
|       home: Builder(
 | |
|         builder: (context) {
 | |
|           if (backend.status == BackendStatus.loading) {
 | |
|             return Material(
 | |
|               color: Theme.of(context).scaffoldBackgroundColor,
 | |
|             );
 | |
|           } else if (backend.status == BackendStatus.setup) {
 | |
|             return Material(
 | |
|               color: Theme.of(context).scaffoldBackgroundColor,
 | |
|               child: Column(
 | |
|                 mainAxisAlignment: MainAxisAlignment.center,
 | |
|                 children: <Widget>[
 | |
|                   Text(
 | |
|                     'Choose the base path for\nyour music library.',
 | |
|                     textAlign: TextAlign.center,
 | |
|                     style: Theme.of(context).textTheme.headline6,
 | |
|                   ),
 | |
|                   SizedBox(
 | |
|                     height: 16.0,
 | |
|                   ),
 | |
|                   ListTile(
 | |
|                     leading: const Icon(Icons.folder_open),
 | |
|                     title: Text('Choose path'),
 | |
|                     onTap: () {
 | |
|                       backend.chooseMusicLibraryUri();
 | |
|                     },
 | |
|                   ),
 | |
|                 ],
 | |
|               ),
 | |
|             );
 | |
|           } else {
 | |
|             return Content();
 | |
|           }
 | |
|         },
 | |
|       ),
 | |
|     );
 | |
|   }
 | |
| }
 | |
| 
 | |
| class Content extends StatefulWidget {
 | |
|   @override
 | |
|   _ContentState createState() => _ContentState();
 | |
| }
 | |
| 
 | |
| class _ContentState extends State<Content> with SingleTickerProviderStateMixin {
 | |
|   final nestedNavigator = GlobalKey<NavigatorState>();
 | |
| 
 | |
|   AnimationController playerBarAnimation;
 | |
|   BackendState backend;
 | |
|   StreamSubscription<bool> playerActiveSubscription;
 | |
| 
 | |
|   @override
 | |
|   void initState() {
 | |
|     super.initState();
 | |
| 
 | |
|     playerBarAnimation = AnimationController(
 | |
|       vsync: this,
 | |
|       duration: Duration(milliseconds: 300),
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   void didChangeDependencies() {
 | |
|     super.didChangeDependencies();
 | |
| 
 | |
|     backend = Backend.of(context);
 | |
|     playerBarAnimation.value = backend.playerActive.value ? 1.0 : 0.0;
 | |
| 
 | |
|     if (playerActiveSubscription != null) {
 | |
|       playerActiveSubscription.cancel();
 | |
|     }
 | |
| 
 | |
|     playerActiveSubscription = backend.playerActive.listen((active) =>
 | |
|         active ? playerBarAnimation.forward() : playerBarAnimation.reverse());
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context) {
 | |
|     // The nested Navigator is for every screen from which the player bar at
 | |
|     // the bottom should be accessible. The WillPopScope widget intercepts
 | |
|     // taps on the system back button and redirects them to the nested
 | |
|     // navigator.
 | |
|     return WillPopScope(
 | |
|       onWillPop: () async => !(await nestedNavigator.currentState.maybePop()),
 | |
|       child: Scaffold(
 | |
|         body: Navigator(
 | |
|           key: nestedNavigator,
 | |
|           onGenerateRoute: (settings) => settings.name == '/'
 | |
|               ? MaterialPageRoute(
 | |
|                   builder: (context) => HomeScreen(),
 | |
|                 )
 | |
|               : null,
 | |
|           initialRoute: '/',
 | |
|         ),
 | |
|         bottomNavigationBar: SizeTransition(
 | |
|           sizeFactor: CurvedAnimation(
 | |
|             curve: Curves.easeOut,
 | |
|             parent: playerBarAnimation,
 | |
|           ),
 | |
|           axisAlignment: -1.0,
 | |
|           child: PlayerBar(),
 | |
|         ),
 | |
|       ),
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   void dispose() {
 | |
|     super.dispose();
 | |
|     playerActiveSubscription.cancel();
 | |
|   }
 | |
| }
 |