From 2862c9ec0532c27a6435a04d14ef6a31df739d4f Mon Sep 17 00:00:00 2001 From: Pato05 Date: Wed, 25 Oct 2023 00:32:28 +0200 Subject: [PATCH] remove browser login for desktop restore translations functionality make scrollViews handle mouse pointers like touch, so that pull to refresh functionality is available exit app if opening cache or settings fails (another instance running) remove draggable_scrollbar and use builtin widget instead fix email login better way to manage lyrics (less updates and lookups in the lyrics List) fix player_screen on mobile (too big -> just average :)) right click: use TapUp events instead desktop: show context menu on triple dots button also avoid showing connection error if the homepage is cached and available offline i'm probably forgetting something idk --- lib/api/cache.dart | 26 +- lib/api/deezer.dart | 75 ++-- lib/api/definitions.dart | 7 +- lib/api/download.dart | 12 +- lib/api/download_manager/database.dart | 1 - .../download_manager/download_manager.dart | 2 - lib/api/player/audio_handler.dart | 47 ++- lib/main.dart | 21 +- lib/translations.i18n.dart | 2 +- lib/ui/details_screens.dart | 6 +- lib/ui/home_screen.dart | 176 ++++++--- lib/ui/importer_screen.dart | 16 +- lib/ui/library.dart | 26 +- lib/ui/login_screen.dart | 342 ++++++++---------- lib/ui/lyrics_screen.dart | 28 +- lib/ui/menu.dart | 110 +++--- lib/ui/player_bar.dart | 1 - lib/ui/player_screen.dart | 186 ++++++---- lib/ui/queue_screen.dart | 1 - lib/ui/settings_screen.dart | 142 ++++---- lib/ui/tiles.dart | 14 +- linux/flutter/generated_plugin_registrant.cc | 4 - linux/flutter/generated_plugins.cmake | 1 - macos/Flutter/GeneratedPluginRegistrant.swift | 2 - pubspec.lock | 16 - pubspec.yaml | 289 ++++++++------- .../flutter/generated_plugin_registrant.cc | 3 - windows/flutter/generated_plugins.cmake | 1 - 28 files changed, 804 insertions(+), 753 deletions(-) diff --git a/lib/api/cache.dart b/lib/api/cache.dart index 964fd0f..76139ce 100644 --- a/lib/api/cache.dart +++ b/lib/api/cache.dart @@ -109,24 +109,24 @@ class Cache { static Future load([int tryN = 0]) async { final box = await _box; - if (tryN > 2) { + if (tryN > 1) { // recursion error, something's wrong throw Exception("can't load cache"); } - //Doesn't exist, create new - if (box.isEmpty) { - final c = Cache(); - await c.save(); - return c; - } - try { - return (await box.get(0))!; - } catch (e) { - // invalidate cache on error - await box.clear(); - return await load(tryN + 1); + if (box.isNotEmpty) { + try { + return (await box.get(0))!; + } catch (e) { + // invalidate cache on error + await box.clear(); + } } + + // create cache if non-existent or error occurred + final c = Cache(); + await c.save(); + return c; } Future save() async { diff --git a/lib/api/deezer.dart b/lib/api/deezer.dart index daa7850..0eaa41c 100644 --- a/lib/api/deezer.dart +++ b/lib/api/deezer.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:cookie_jar/cookie_jar.dart'; import 'package:crypto/crypto.dart'; import 'package:dio/dio.dart'; @@ -60,9 +58,11 @@ class DeezerAPI { late bool canStreamHQ; final cookieJar = DefaultCookieJar(); - late final dio = - Dio(BaseOptions(headers: headers, responseType: ResponseType.json)) - ..interceptors.add(CookieManager(cookieJar)); + late final dio = Dio(BaseOptions( + headers: headers, + responseType: ResponseType.json, + validateStatus: (status) => true)) + ..interceptors.add(CookieManager(cookieJar)); Future? _authorizing; @@ -93,7 +93,7 @@ class DeezerAPI { Future> callApi(String method, {Map? params, String? gatewayInput}) async { //Post - final res = await dio.post('https://www.deezer.com/ajax/gw-light.php', + final res = await dio.post('https://www.deezer.com/ajax/gw-light.php', queryParameters: { 'api_version': '1.0', 'api_token': token, @@ -145,21 +145,25 @@ class DeezerAPI { }, options: Options(responseType: ResponseType.json)); final accessToken = res.data['access_token'] as String?; - print(res.data); if (accessToken == null) { throw Exception('login failed, access token is null'); } + print(accessToken); + return getArlByAccessToken(accessToken); } // FROM DEEMIX-JS Future getArlByAccessToken(String accessToken) async { //Get SID in cookieJar - await dio.get("https://api.deezer.com/platform/generic/track/3135556", - options: Options(headers: { - 'Authorization': 'Bearer $accessToken', - })); + final res = + await dio.get("https://api.deezer.com/platform/generic/track/3135556", + options: Options(headers: { + 'Authorization': 'Bearer $accessToken', + 'User-Agent': userAgent, + })); + print(res.data); //Get ARL final arlRes = await dio.get("https://www.deezer.com/ajax/gw-light.php", queryParameters: { @@ -170,7 +174,6 @@ class DeezerAPI { }, options: Options(responseType: ResponseType.json)); final arl = arlRes.data["results"]; - print(arlRes.data); if (arl == null) throw Exception('couldn\'t obtain ARL'); return arl; } @@ -178,19 +181,20 @@ class DeezerAPI { //Authorize, bool = success Future rawAuthorize({Function? onError}) async { try { - Map data = await callApi('deezer.getUserData'); - if (data['results']['USER']['USER_ID'] == 0) { + final data = await callApi('deezer.getUserData'); + if (data['results']?['USER']?['USER_ID'] == null || + data['results']['USER']['USER_ID'] == 0) { return false; } else { token = data['results']['checkForm']; userId = data['results']['USER']['USER_ID'].toString(); userName = data['results']['USER']['BLOG_NAME']; favoritesPlaylistId = data['results']['USER']['LOVEDTRACKS_ID']; - canStreamHQ = data['results']['USER']['OPTIONS']['web_hq'] || - data['results']['USER']['OPTIONS']['mobile_hq']; - canStreamLossless = data['results']['USER']['OPTIONS'] - ['web_lossless'] || - data['results']['USER']['OPTIONS']['mobile_lossless']; + canStreamHQ = data['results']['USER']['OPTIONS']['web_hq'] as bool || + data['results']['USER']['OPTIONS']['mobile_hq'] as bool; + canStreamLossless = + data['results']['USER']['OPTIONS']['web_lossless'] as bool || + data['results']['USER']['OPTIONS']['mobile_lossless'] as bool; licenseToken = data['results']['USER']['OPTIONS']['license_token'] as String; @@ -531,18 +535,39 @@ class DeezerAPI { return HomePage.fromPrivateJson(data['results']); } - //Log song listen to deezer - Future logListen(String trackId, - {int seek = 0, int pause = 0, int sync = 1, int? timestamp}) async { + /// Log song listen to deezer + Future logListen( + String trackId, { + /// Amount of times the track's been seeked + int seek = 0, + + /// Amount of times the track's been paused + int pause = 0, + + /// 1 if the track was listened in sync, 0 otherwise + int sync = 1, + + /// If the track is skipped to the next song + bool next = false, + + /// If the track is skipped to the previous song + bool prev = false, + + /// When the timestamp has begun as UTC int (in SECONDS) + int? timestamp, + }) async { await callApi('log.listen', params: { 'params': { - 'timestamp': timestamp ?? DateTime.now().millisecondsSinceEpoch, - 'ts_listen': DateTime.now().millisecondsSinceEpoch, + 'timestamp': + timestamp ?? (DateTime.now().millisecondsSinceEpoch) ~/ 1000, + 'ts_listen': DateTime.now().millisecondsSinceEpoch ~/ 1000, 'type': 1, 'stat': { 'seek': seek, // amount of times seeked 'pause': pause, // amount of times paused - 'sync': sync + 'sync': sync, + if (next) 'next': true, + if (prev) 'prev': true, }, 'media': {'id': trackId, 'type': 'song', 'format': 'MP3_128'} } diff --git a/lib/api/definitions.dart b/lib/api/definitions.dart index 160edb1..3686683 100644 --- a/lib/api/definitions.dart +++ b/lib/api/definitions.dart @@ -10,7 +10,6 @@ import 'package:freezer/page_routes/blur_slide.dart'; import 'package:freezer/page_routes/fade.dart'; import 'package:freezer/settings.dart'; import 'package:hive_flutter/hive_flutter.dart'; -import 'package:isar/isar.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:intl/intl.dart'; import 'package:just_audio/just_audio.dart'; @@ -966,6 +965,8 @@ class HomePageSection { }[json['layout'] ?? '']; if (layout == null) { _logger.warning('UNKNOWN LAYOUT: ${json['layout']}'); + _logger.warning('LAYOUT DATA:'); + _logger.warning(json); return null; } final items = []; @@ -1552,6 +1553,10 @@ extension GetId on FlowConfig { } } +extension LastChars on String { + String withoutLast(int n) => substring(0, length - n); +} + class TrackUrlSource { final String provider; final String url; diff --git a/lib/api/download.dart b/lib/api/download.dart index 8007649..f872258 100644 --- a/lib/api/download.dart +++ b/lib/api/download.dart @@ -1,11 +1,9 @@ -import 'package:flutter/foundation.dart'; import 'package:freezer/main.dart'; import 'package:sqflite/sqflite.dart'; import 'package:disk_space_plus/disk_space_plus.dart'; import 'package:filesize/filesize.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:freezer/api/deezer.dart'; import 'package:freezer/api/definitions.dart'; @@ -488,12 +486,10 @@ class DownloadManager { } //Check if album, track or playlist is offline - Future _checkOffline( - (Album? album, Track? track, Playlist? playlist) message) async { + Future checkOffline( + {Album? album, Track? track, Playlist? playlist}) async { if (!isSupported) return false; - final (album, track, playlist) = message; - //Track if (track != null) { List res = await db.query('Tracks', @@ -518,10 +514,6 @@ class DownloadManager { return false; } - Future checkOffline({Album? album, Track? track, Playlist? playlist}) { - return compute(_checkOffline, (album, track, playlist)); - } - //Offline search Future search(String? query) async { SearchResults results = diff --git a/lib/api/download_manager/database.dart b/lib/api/download_manager/database.dart index d59d7a7..744361a 100644 --- a/lib/api/download_manager/database.dart +++ b/lib/api/download_manager/database.dart @@ -1,4 +1,3 @@ -import 'package:freezer/api/definitions.dart'; import 'package:isar/isar.dart'; @collection diff --git a/lib/api/download_manager/download_manager.dart b/lib/api/download_manager/download_manager.dart index abbc78e..e5024da 100644 --- a/lib/api/download_manager/download_manager.dart +++ b/lib/api/download_manager/download_manager.dart @@ -7,7 +7,6 @@ import 'package:freezer/api/definitions.dart'; import 'package:freezer/api/download_manager/download_service.dart'; import 'package:freezer/api/download_manager/service_interface.dart'; import 'package:freezer/translations.i18n.dart'; -import '../download.dart' as dl; class DownloadManager { //implements dl.DownloadManager { @@ -62,7 +61,6 @@ class DownloadManager { FlutterBackgroundService().invoke(method, args); } - @override Future addOfflineTrack(Track track, {bool private = true, BuildContext? context, isSingleton = false}) { // TODO: implement addOfflineTrack diff --git a/lib/api/player/audio_handler.dart b/lib/api/player/audio_handler.dart index 5b8c75c..29a46f3 100644 --- a/lib/api/player/audio_handler.dart +++ b/lib/api/player/audio_handler.dart @@ -1,8 +1,6 @@ import 'package:audio_service/audio_service.dart'; import 'package:audio_session/audio_session.dart'; -import 'package:equalizer/equalizer.dart'; import 'package:flutter/foundation.dart'; -import 'package:fluttertoast/fluttertoast.dart'; import 'package:freezer/api/cache.dart'; import 'package:freezer/api/deezer.dart'; import 'package:freezer/api/deezer_audio_source.dart'; @@ -18,11 +16,8 @@ import 'package:just_audio_media_kit/just_audio_media_kit.dart'; import 'package:logging/logging.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; -import 'package:freezer/translations.i18n.dart'; import 'package:collection/collection.dart'; import 'package:scrobblenaut/scrobblenaut.dart'; -import 'package:rxdart/rxdart.dart'; -import 'package:media_kit/media_kit.dart' show MPVLogLevel; import '../definitions.dart'; import '../../settings.dart'; @@ -122,6 +117,9 @@ class AudioPlayerTask extends BaseAudioHandler { /// Last logged track id (to avoid duplicates) String? _loggedTrackId; + /// Last track's id + String? _lastTrackId; + // deezer track logging /// Whether we should send log requests to deezer late bool _shouldLogTracks; @@ -132,7 +130,7 @@ class AudioPlayerTask extends BaseAudioHandler { /// Amount of times the song's been seeked int _amountSeeked = 0; - /// When playback begun + /// When playback begun (in SECONDS) int? _timestamp; MediaItem get currentMediaItem => queue.value[_queueIndex]; @@ -155,7 +153,6 @@ class AudioPlayerTask extends BaseAudioHandler { // Linux/Windows specific options JustAudioMediaKit.title = 'Freezer'; JustAudioMediaKit.protocolWhitelist = const ['http']; - JustAudioMediaKit.mpvLogLevel = MPVLogLevel.debug; _deezerAPI = initArgs.deezerAPI; _androidAuto = AndroidAuto(deezerAPI: _deezerAPI); @@ -180,12 +177,22 @@ class AudioPlayerTask extends BaseAudioHandler { //Update track index _player.currentIndexStream.listen((index) { - _amountSeeked = 0; - _amountPaused = 0; - _timestamp = DateTime.now().millisecondsSinceEpoch; if (index != null && queue.value.isNotEmpty) { _queueIndex = index; mediaItem.add(currentMediaItem); + + // log previous track + if (index != 0 && + _lastTrackId != null && + _lastTrackId! != currentMediaItem.id) { + _logListenedTrack(_lastTrackId!, + sync: _amountPaused == 0 && _amountSeeked == 0); + } + + _lastTrackId = currentMediaItem.id; + _amountSeeked = 0; + _amountPaused = 0; + _timestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000; } if (index == queue.value.length - 1) { @@ -290,7 +297,7 @@ class AudioPlayerTask extends BaseAudioHandler { Future skipToQueueItem(int index) async { _lastPosition = null; - unawaited(_logListenedTrack(currentMediaItem)); + unawaited(_logListenedTrack(currentMediaItem.id, sync: false)); //Skip in player await _player.seek(Duration.zero, index: index); _queueIndex = index; @@ -396,7 +403,7 @@ class AudioPlayerTask extends BaseAudioHandler { _lastPosition = null; if (_queueIndex == queue.value.length - 1) return; //Update buffering state - unawaited(_logListenedTrack(currentMediaItem)); + unawaited(_logListenedTrack(currentMediaItem.id, sync: false, next: true)); _queueIndex++; await _player.seekToNext(); _broadcastState(); @@ -408,24 +415,30 @@ class AudioPlayerTask extends BaseAudioHandler { //Update buffering state //_skipState = AudioProcessingState.skippingToPrevious; //Normal skip to previous - unawaited(_logListenedTrack(currentMediaItem)); + unawaited(_logListenedTrack(currentMediaItem.id, sync: false, prev: true)); _queueIndex--; await _player.seekToPrevious(); //_skipState = null; } - Future _logListenedTrack(MediaItem mediaItem) async { + Future _logListenedTrack(String trackId, + {required bool sync, bool next = false, bool prev = false}) async { + if (_loggedTrackId == trackId) return; + _loggedTrackId = trackId; + print( - 'logging: seek: $_amountSeeked, pause: $_amountPaused, timestamp: $_timestamp (elapsed: ${DateTime.now().millisecondsSinceEpoch - _timestamp!}ms)'); + 'logging: seek: $_amountSeeked, pause: $_amountPaused, sync: $sync, next: $next, prev: $prev, timestamp: $_timestamp (elapsed: ${DateTime.now().millisecondsSinceEpoch ~/ 1000 - _timestamp!}s)'); if (!_shouldLogTracks) return; //Log to Deezer _deezerAPI.logListen( - mediaItem.id, + trackId, seek: _amountSeeked, pause: _amountPaused, - sync: _amountSeeked == 0 && _amountPaused == 0 ? 1 : 0, + sync: sync ? 1 : 0, timestamp: _timestamp, + prev: prev, + next: next, ); } diff --git a/lib/main.dart b/lib/main.dart index 74b76ea..652608c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -97,10 +97,17 @@ void main() async { Hive.init(await Paths.dataDirectory()); //Initialize globals - settings = await Settings.load(); - settings.save(); + try { + cache = await Cache.load(); + settings = await Settings.load(); + settings.save(); + } catch (e) { + // ignore: avoid_print + print( + 'Cannot load cache or settings box. Maybe another instance of the app is running?'); + exit(1); + } downloadManager.init(); - cache = await Cache.load(); // photos cacheManager = DefaultCacheManager(); // cacheManager = HiveCacheManager( @@ -109,9 +116,8 @@ void main() async { deezerAPI.favoritesPlaylistId = cache.favoritesPlaylistId; Logger.root.onRecord.listen((record) { - if (kDebugMode) { - print('${record.level.name}: ${record.time}: ${record.message}'); - } + // ignore: avoid_print + print('${record.level.name}: ${record.time}: ${record.message}'); }); if (kDebugMode) { @@ -699,8 +705,7 @@ class MainScreenState extends State child: _MainRouteNavigator( navigatorKey: navigatorKey, routes: { - Navigator.defaultRouteName: (context) => - const HomeScreen(), + Navigator.defaultRouteName: (context) => HomeScreen(), '/podcasts': (context) => HomePageScreen( cacheable: true, channel: const DeezerChannel( diff --git a/lib/translations.i18n.dart b/lib/translations.i18n.dart index 4c3c980..7c95ce3 100644 --- a/lib/translations.i18n.dart +++ b/lib/translations.i18n.dart @@ -39,7 +39,7 @@ const List languages = [ List get supportedLocales => languages.map((l) => l.getLocale).toList(); extension Localization on String { - static const _t = Translations.from("en_US", {...language_en_us, ...crowdin}); + static final _t = Translations.byLocale("en_US") + language_en_us + crowdin; String get i18n => localize(this, _t); } diff --git a/lib/ui/details_screens.dart b/lib/ui/details_screens.dart index 432e16e..db6bd69 100644 --- a/lib/ui/details_screens.dart +++ b/lib/ui/details_screens.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:math'; -import 'package:draggable_scrollbar/draggable_scrollbar.dart'; import 'package:flutter/material.dart'; import 'package:fluttericon/font_awesome5_icons.dart'; import 'package:freezer/api/cache.dart'; @@ -864,9 +863,10 @@ class _PlaylistDetailsState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(playlist!.title ?? '')), - body: DraggableScrollbar.rrect( + body: Scrollbar( + interactive: true, controller: _scrollController, - backgroundColor: Theme.of(context).primaryColor, + thickness: 8.0, child: ListView( controller: _scrollController, children: [ diff --git a/lib/ui/home_screen.dart b/lib/ui/home_screen.dart index 3a081aa..39b70a5 100644 --- a/lib/ui/home_screen.dart +++ b/lib/ui/home_screen.dart @@ -1,7 +1,9 @@ +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:freezer/api/deezer.dart'; import 'package:freezer/api/definitions.dart'; import 'package:freezer/api/player/audio_handler.dart'; +import 'package:freezer/main.dart'; import 'package:freezer/ui/error.dart'; import 'package:freezer/ui/menu.dart'; import 'package:freezer/translations.i18n.dart'; @@ -138,8 +140,10 @@ class _HomePageWidgetState extends State { } if (!mounted) return; if (homePage == null) { - //On error - setState(() => _error = true); + if (_homePage == null) { + //On error + setState(() => _error = true); + } return; } if (homePage.sections.isEmpty) return; @@ -183,6 +187,16 @@ class _HomePageWidgetState extends State { }); } + Widget getSectionChild(HomePageSection section) { + switch (section.layout) { + case HomePageSectionLayout.GRID: + return HomePageGridSection(section); + case HomePageSectionLayout.ROW: + default: + return HomepageRowSection(section); + } + } + @override Widget build(BuildContext context) { if (_error) return const ErrorScreen(); @@ -190,74 +204,102 @@ class _HomePageWidgetState extends State { if (_homePage != null) { sections = _homePage!.sections; } - return RefreshIndicator( - key: _indicatorKey, - onRefresh: _load, - child: _homePage == null - ? const SizedBox.expand(child: SingleChildScrollView()) - : ListView.builder( - itemBuilder: (context, index) { - final section = sections![index]; - switch (section.layout) { - case HomePageSectionLayout.GRID: - return HomePageGridSection(section); - case HomePageSectionLayout.ROW: - default: - return HomepageRowSection(section); - } - }, - itemCount: sections!.length, - padding: const EdgeInsets.symmetric(horizontal: 8.0), - ), + + final actualScrollConfiguration = ScrollConfiguration.of(context); + return ScrollConfiguration( + behavior: actualScrollConfiguration.copyWith( + dragDevices: { + PointerDeviceKind.mouse, + PointerDeviceKind.stylus, + PointerDeviceKind.touch, + PointerDeviceKind.trackpad + }, + ), + child: RefreshIndicator( + key: _indicatorKey, + onRefresh: _load, + child: _homePage == null + ? const SizedBox.expand(child: SingleChildScrollView()) + : ListView.builder( + itemBuilder: (context, index) { + return Padding( + padding: index == 0 + ? EdgeInsets.zero + : const EdgeInsets.only(top: 16.0), + child: getSectionChild(sections![index])); + }, + itemCount: sections!.length, + padding: const EdgeInsets.symmetric(horizontal: 8.0), + ), + ), ); } } -class HomepageRowSection extends StatelessWidget { +class HomepageRowSection extends StatefulWidget { final HomePageSection section; const HomepageRowSection(this.section, {super.key}); + @override + State createState() => _HomepageRowSectionState(); +} + +class _HomepageRowSectionState extends State { + final _controller = ScrollController(); + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { return ListTile( title: Text( - section.title ?? '', + widget.section.title ?? '', textAlign: TextAlign.left, maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 20.0, fontWeight: FontWeight.w900), ), - subtitle: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: List.generate(section.items!.length + 1, (j) { - //Has more items - if (j == section.items!.length) { - if (section.hasMore ?? false) { - return TextButton( - child: Text( - 'Show more'.i18n, - textAlign: TextAlign.center, - style: const TextStyle(fontSize: 20.0), - ), - onPressed: () => Navigator.of(context).pushRoute( - builder: (context) => HomePageScreen( - title: section.title!, - channel: DeezerChannel(target: section.pagePath), + subtitle: Scrollbar( + controller: _controller, + thickness: MainScreen.of(context).isDesktop ? null : 1.0, + child: SingleChildScrollView( + controller: _controller, + scrollDirection: Axis.horizontal, + child: Row( + children: List.generate(widget.section.items!.length + 1, (j) { + //Has more items + if (j == widget.section.items!.length) { + if (widget.section.hasMore ?? false) { + return TextButton( + child: Text( + 'Show more'.i18n, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 20.0), ), - ), - ); + onPressed: () => Navigator.of(context).pushRoute( + builder: (context) => HomePageScreen( + title: widget.section.title!, + channel: + DeezerChannel(target: widget.section.pagePath), + ), + ), + ); + } + return const SizedBox(); } - return const SizedBox(); - } - //Show item - HomePageItem item = section.items![j]; - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 6.0), - child: HomePageItemWidget(item), - ); - }), + //Show item + HomePageItem item = widget.section.items![j]; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 6.0), + child: HomePageItemWidget(item), + ); + }), + ), ), )); } @@ -265,7 +307,14 @@ class HomepageRowSection extends StatelessWidget { class HomePageGridSection extends StatelessWidget { final HomePageSection section; - const HomePageGridSection(this.section, {super.key}); + final double horizontalSpacing; + final double verticalSpacing; + const HomePageGridSection( + this.section, { + this.horizontalSpacing = 6.0, + this.verticalSpacing = 6.0, + super.key, + }); @override Widget build(BuildContext context) { @@ -284,13 +333,22 @@ class HomePageGridSection extends StatelessWidget { const TextStyle(fontSize: 20.0, fontWeight: FontWeight.w900), ), ), - Wrap( - spacing: 4.0, - runSpacing: 4.0, - alignment: WrapAlignment.spaceEvenly, - children: section.items! - .map((e) => HomePageItemWidget(e)) - .toList(growable: false)), + LayoutBuilder(builder: (context, constraints) { + final amountThatCanFit = + constraints.maxWidth ~/ (150.0 + horizontalSpacing); + final widthOfEach = + constraints.maxWidth / amountThatCanFit - horizontalSpacing; + return Wrap( + spacing: horizontalSpacing, + runSpacing: verticalSpacing, + alignment: WrapAlignment.start, + children: section.items! + .map((e) => SizedBox( + width: widthOfEach, + child: HomePageItemWidget(e), + )) + .toList(growable: false)); + }), ], ); } diff --git a/lib/ui/importer_screen.dart b/lib/ui/importer_screen.dart index 0634c21..6e09e5c 100644 --- a/lib/ui/importer_screen.dart +++ b/lib/ui/importer_screen.dart @@ -9,7 +9,6 @@ import 'package:freezer/api/spotify.dart'; import 'package:freezer/ui/elements.dart'; import 'package:freezer/translations.i18n.dart'; import 'package:spotify/spotify.dart' as spotify; -import 'package:url_launcher/url_launcher.dart'; import 'dart:async'; @@ -576,15 +575,18 @@ class _SpotifyImporterV2MainState extends State { await importer.start(context, title, description, tracks); if (!context.mounted) return; - //Route - Navigator.of(context).pop(); - Navigator.of(context).pushReplacement(MaterialPageRoute( - builder: (context) => const ImporterStatusScreen())); - } catch (e) { + } catch (e, st) { + print(e); + print(st); ScaffoldMessenger.of(context).snack(e.toString()); - Navigator.of(context).pop(); + Navigator.of(context, rootNavigator: true).pop(); return; } + + //Route + Navigator.of(context, rootNavigator: true).pop(); + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (context) => const ImporterStatusScreen())); } @override diff --git a/lib/ui/library.dart b/lib/ui/library.dart index 0d00751..7f5e5e4 100644 --- a/lib/ui/library.dart +++ b/lib/ui/library.dart @@ -13,7 +13,6 @@ import 'package:freezer/ui/error.dart'; import 'package:freezer/ui/importer_screen.dart'; import 'package:freezer/ui/tiles.dart'; import 'package:freezer/translations.i18n.dart'; -import 'package:draggable_scrollbar/draggable_scrollbar.dart'; import 'menu.dart'; import '../api/download.dart'; @@ -471,9 +470,10 @@ class _LibraryTracksState extends State { ), body: _loading && allTracks.isEmpty ? const Center(child: CircularProgressIndicator()) - : DraggableScrollbar.rrect( + : Scrollbar( + interactive: true, controller: _scrollController, - backgroundColor: Theme.of(context).primaryColor, + thickness: 8.0, child: ListView( controller: _scrollController, children: [ @@ -677,9 +677,10 @@ class _LibraryAlbumsState extends State { ), body: !settings.offlineMode && _albums == null ? const Center(child: CircularProgressIndicator()) - : DraggableScrollbar.rrect( + : Scrollbar( + interactive: true, controller: _scrollController, - backgroundColor: Theme.of(context).primaryColor, + thickness: 8.0, child: ListView( controller: _scrollController, children: [ @@ -886,9 +887,10 @@ class _LibraryArtistsState extends State { ? const Center(child: CircularProgressIndicator()) : _error ? const Center(child: ErrorScreen()) - : DraggableScrollbar.rrect( + : Scrollbar( + interactive: true, controller: _scrollController, - backgroundColor: Theme.of(context).primaryColor, + thickness: 8.0, child: ListView( controller: _scrollController, children: [ @@ -1047,9 +1049,10 @@ class _LibraryPlaylistsState extends State { Container(width: 8.0), ], ), - body: DraggableScrollbar.rrect( + body: Scrollbar( + interactive: true, controller: _scrollController, - backgroundColor: Theme.of(context).primaryColor, + thickness: 8.0, child: ListView( controller: _scrollController, children: [ @@ -1198,9 +1201,10 @@ class _HistoryScreenState extends State { ) ], ), - body: DraggableScrollbar.rrect( + body: Scrollbar( + interactive: true, controller: _scrollController, - backgroundColor: Theme.of(context).primaryColor, + thickness: 8.0, child: ListView.builder( controller: _scrollController, itemCount: cache.history.length, diff --git a/lib/ui/login_screen.dart b/lib/ui/login_screen.dart index df12ab6..5744bb4 100644 --- a/lib/ui/login_screen.dart +++ b/lib/ui/login_screen.dart @@ -1,13 +1,11 @@ import 'dart:io'; -import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:freezer/api/deezer.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:freezer/translations.i18n.dart'; import 'package:url_launcher/url_launcher_string.dart'; -import 'package:desktop_webview_window/desktop_webview_window.dart'; import '../settings.dart'; import '../api/definitions.dart'; @@ -149,73 +147,6 @@ class _LoginWidgetState extends State { } void _loginBrowser() async { - if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { - // TODO: find a way to read arl from webview - if (!await WebviewWindow.isWebviewAvailable()) { - showDialog( - context: context, - builder: (ctx) => AlertDialog( - title: Text('Can\'t login via browser'.i18n), - content: RichText( - text: TextSpan(children: [ - TextSpan( - text: - 'Your system doesn\'t have a WebView implementation.\n\u2022 If you are on Windows,' - .i18n), - TextSpan( - text: - 'make sure it is available on your system.\n'.i18n, - style: const TextStyle(color: Colors.blue), - recognizer: TapGestureRecognizer() - ..onTap = () => launchUrlString( - 'https://developer.microsoft.com/en-us/microsoft-edge/webview2')), - TextSpan( - text: - '\u2022 If you are on Linux, make sure webkit2gtk is installed.\n' - .i18n), - TextSpan( - text: - '\nAlternatively, you can login in your browser on deezer.com, open your developer console with F12 and logging in with the ARL token' - .i18n), - ])), - )); - return; - } - - Navigator.of(context) - .pushRoute(builder: (context) => LoadingWindowWait(update: _update)); - final webview = - await WebviewWindow.create(configuration: CreateConfiguration()); - webview.launch('https://deezer.com/login'); - webview.onClose.then((_) { - Navigator.pop(context); - }); - - webview.addOnUrlRequestCallback((url) { - if (!url.contains('/login') && url.contains('/register')) { - webview.evaluateJavaScript('window.location.href = "/open_app"'); - } - - final uri = Uri.parse(url); - //Parse arl from url - if (uri.scheme == 'intent' && uri.host == 'deezer.page.link') { - try { - //Actual url is in `link` query parameter - Uri linkUri = Uri.parse(uri.queryParameters['link']!); - String? arl = linkUri.queryParameters['arl']; - if (arl != null) { - settings.arl = arl; - webview.close(); - _update(); - } - } catch (e) { - print(e); - } - } - }); - - return; - } Navigator.of(context) .pushRoute(builder: (context) => LoginBrowser(_update)); } @@ -256,106 +187,114 @@ class _LoginWidgetState extends State { // style: ButtonStyle( // foregroundColor: // MaterialStateProperty.all(Colors.white)))), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 32.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const FreezerTitle(), - Container(height: 16.0), - Text( - "Please login using your Deezer account.".i18n, - textAlign: TextAlign.center, - style: const TextStyle(fontSize: 16.0), - ), - Container( - height: 16.0, - ), - //Email login dialog - OutlinedButton( - child: Text( - 'Login using email'.i18n, - ), - onPressed: () { - showDialog( - context: context, - builder: (context) => EmailLogin(_update)); - }, - ), - OutlinedButton( - onPressed: _loginBrowser, - child: Text('Login using browser'.i18n), - ), - OutlinedButton( - child: Text('Login using token'.i18n), - onPressed: () { - showDialog( - context: context, - builder: (context) { - Future.delayed( - const Duration(seconds: 1), - () => { - focusNode.requestFocus() - }); // autofocus doesn't work - it's replacement - return AlertDialog( - title: Text('Enter ARL'.i18n), - content: TextField( - onChanged: (String s) => _arl = s, - decoration: InputDecoration( - labelText: 'Token (ARL)'.i18n), - focusNode: focusNode, - controller: controller, - onSubmitted: (String s) { - goARL(focusNode, controller); - }, - ), - actions: [ - TextButton( - child: Text('Save'.i18n), - onPressed: () => - goARL(null, controller), - ) - ], - ); - }); - }, - ), - ]))), - Container(height: 16.0), - Text( - "If you don't have account, you can register on deezer.com for free." - .i18n, - textAlign: TextAlign.center, - style: const TextStyle(fontSize: 16.0), + child: Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 700.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 32.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const FreezerTitle(), + const SizedBox(height: 16.0), + Text( + "Please login using your Deezer account." + .i18n, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 16.0), + ), + const SizedBox(height: 16.0), + //Email login dialog + ElevatedButton( + child: Text( + 'Login using email'.i18n, + ), + onPressed: () { + showDialog( + context: context, + builder: (context) => + EmailLogin(_update)); + }, + ), + const SizedBox(height: 2.0), + + // only supported on android + if (Platform.isAndroid) + ElevatedButton( + onPressed: _loginBrowser, + child: Text('Login using browser'.i18n), + ), + const SizedBox(height: 2.0), + ElevatedButton( + child: Text('Login using token'.i18n), + onPressed: () { + showDialog( + context: context, + builder: (context) { + Future.delayed( + const Duration(seconds: 1), + () => { + focusNode.requestFocus() + }); // autofocus doesn't work - it's replacement + return AlertDialog( + title: Text('Enter ARL'.i18n), + content: TextField( + onChanged: (String s) => _arl = s, + decoration: InputDecoration( + labelText: + 'Token (ARL)'.i18n), + focusNode: focusNode, + controller: controller, + onSubmitted: (String s) { + goARL(focusNode, controller); + }, + ), + actions: [ + TextButton( + child: Text('Save'.i18n), + onPressed: () => + goARL(null, controller), + ) + ], + ); + }); + }, + ), + ]))), + const SizedBox(height: 16.0), + Text( + "If you don't have account, you can register on deezer.com for free." + .i18n, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 16.0), + ), + const SizedBox(height: 8.0), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32.0), + child: ElevatedButton( + child: Text('Open in browser'.i18n), + onPressed: () { + launchUrlString('https://deezer.com/register'); + }, + ), + ), + const SizedBox(height: 8.0), + const Divider(), + const SizedBox(height: 8.0), + Text( + "By using this app, you don't agree with the Deezer ToS" + .i18n, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 14.0), + ) + ], ), - Container(height: 8.0), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 32.0), - child: OutlinedButton( - child: Text('Open in browser'.i18n), - onPressed: () { - launchUrlString('https://deezer.com/register'); - }, - ), - ), - Container( - height: 8.0, - ), - const Divider(), - Container( - height: 8.0, - ), - Text( - "By using this app, you don't agree with the Deezer ToS".i18n, - textAlign: TextAlign.center, - style: const TextStyle(fontSize: 14.0), - ) - ], + ), ), ), )); @@ -486,37 +425,48 @@ class _EmailLoginState extends State { Widget build(BuildContext context) { return AlertDialog( title: Text('Email Login'.i18n), - content: Column( - mainAxisSize: MainAxisSize.min, - children: _loading - ? [const CircularProgressIndicator()] - : [ - TextFormField( - decoration: InputDecoration(labelText: 'Email'.i18n), - controller: _emailController, - ), - const SizedBox(height: 8.0), - TextFormField( - obscureText: true, - decoration: InputDecoration(labelText: "Password".i18n), - controller: _passwordController, - ) + content: AutofillGroup( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextFormField( + enabled: !_loading, + decoration: InputDecoration(labelText: 'Email'.i18n), + controller: _emailController, + autofillHints: const [ + AutofillHints.email, ], + ), + const SizedBox(height: 8.0), + TextFormField( + enabled: !_loading, + obscureText: true, + decoration: InputDecoration(labelText: "Password".i18n), + controller: _passwordController, + autofillHints: const [ + AutofillHints.password, + ], + ) + ], + ), ), actions: [ - if (!_loading) - TextButton( - child: const Text('Login'), - onPressed: () async { - if (_emailController.text.isNotEmpty && - _passwordController.text.isNotEmpty) { - await _login(); - } else { - ScaffoldMessenger.of(context) - .snack("Missing email or password!".i18n); - } - }, - ) + TextButton( + onPressed: _loading + ? null + : () async { + if (_emailController.text.isNotEmpty && + _passwordController.text.isNotEmpty) { + await _login(); + } else { + ScaffoldMessenger.of(context) + .snack("Missing email or password!".i18n); + } + }, + child: _loading + ? const CircularProgressIndicator() + : const Text('Login'), + ) ], ); } diff --git a/lib/ui/lyrics_screen.dart b/lib/ui/lyrics_screen.dart index 47f4bae..8b133e8 100644 --- a/lib/ui/lyrics_screen.dart +++ b/lib/ui/lyrics_screen.dart @@ -48,7 +48,7 @@ class _LyricsWidgetState extends State { late StreamSubscription _mediaItemSub; late StreamSubscription _playbackStateSub; int? _currentIndex = -1; - int? _prevIndex = -1; + Duration _nextPosition = Duration.zero; final ScrollController _controller = ScrollController(); final double height = 90; BoxConstraints? _widgetConstraints; @@ -86,6 +86,7 @@ class _LyricsWidgetState extends State { _lyrics = lyrics; }); + _nextPosition = Duration.zero; SchedulerBinding.instance.addPostFrameCallback( (_) => _updatePosition(audioHandler.playbackState.value.position)); } catch (e) { @@ -96,7 +97,7 @@ class _LyricsWidgetState extends State { } } - Future _scrollToLyric() async { + void _scrollToLyric() { if (!_controller.hasClients) return; //Lyric height, screen height, appbar height double scrollTo; @@ -111,27 +112,36 @@ class _LyricsWidgetState extends State { print( '${height * _currentIndex!}, ${MediaQuery.of(context).size.height / 2}'); - if (0 > scrollTo) return; + if (scrollTo < 0.0) scrollTo = 0.0; if (scrollTo > _controller.position.maxScrollExtent) { scrollTo = _controller.position.maxScrollExtent; } _animatedScroll = true; - await _controller.animateTo(scrollTo, - duration: const Duration(milliseconds: 250), curve: Curves.ease); - _animatedScroll = false; + _controller + .animateTo(scrollTo, + duration: const Duration(milliseconds: 250), curve: Curves.ease) + .then((_) => _animatedScroll = false); } void _updatePosition(Duration position) { if (_loading) return; if (!_syncedLyrics) return; + if (position < _nextPosition) return; + _currentIndex = _lyrics?.lyrics?.lastIndexWhere((l) => l.offset! <= position); //Scroll to current lyric if (_currentIndex! < 0) return; - if (_prevIndex == _currentIndex) return; //Update current lyric index - setState(() {}); - _prevIndex = _currentIndex; + if (_currentIndex! < _lyrics!.lyrics!.length) { + // update nextPosition + _nextPosition = _lyrics!.lyrics![_currentIndex! + 1].offset!; + } else { + // dummy position so that the before-hand condition always returns false + _nextPosition = const Duration(days: 69); + } + + setState(() => _currentIndex); if (_freeScroll) return; _scrollToLyric(); } diff --git a/lib/ui/menu.dart b/lib/ui/menu.dart index ebe5ec7..ed7f8a4 100644 --- a/lib/ui/menu.dart +++ b/lib/ui/menu.dart @@ -1,8 +1,6 @@ import 'dart:async'; import 'package:freezer/main.dart'; -import 'package:freezer/ui/fancy_scaffold.dart'; -import 'package:freezer/ui/player_bar.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:flutter/material.dart'; import 'package:freezer/api/cache.dart'; @@ -108,43 +106,40 @@ class MenuSheetOption { class MenuSheet { final BuildContext context; final Function? navigateCallback; - bool _contextMenuOpen = false; MenuSheet(this.context, {this.navigateCallback}); void _showContextMenu(List options, - {required TapDownDetails details}) { + {required TapUpDetails details}) { final overlay = Overlay.of(context).context.findRenderObject() as RenderBox; final actualPosition = overlay.globalToLocal(details.globalPosition); - _contextMenuOpen = true; showMenu( - clipBehavior: Clip.antiAlias, - elevation: 4.0, - context: context, - constraints: const BoxConstraints(maxWidth: 300.0), - position: - RelativeRect.fromSize(actualPosition & Size.zero, overlay.size), - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(28.0))), - items: options - .map((option) => PopupMenuItem( - onTap: option.onTap, - child: option.icon == null - ? option.label - : Row(mainAxisSize: MainAxisSize.min, children: [ - option.icon!, - const SizedBox(width: 8.0), - Flexible(child: option.label), - ]))) - .toList(growable: false)) - .then((_) => _contextMenuOpen = false); + clipBehavior: Clip.antiAlias, + elevation: 4.0, + context: context, + constraints: const BoxConstraints(maxWidth: 300.0), + position: + RelativeRect.fromSize(actualPosition & Size.zero, overlay.size), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(28.0))), + items: options + .map((option) => PopupMenuItem( + onTap: option.onTap, + child: option.icon == null + ? option.label + : Row(mainAxisSize: MainAxisSize.min, children: [ + option.icon!, + const SizedBox(width: 8.0), + Flexible(child: option.label), + ]))) + .toList(growable: false)); } //=================== // DEFAULT //=================== - void show(List options, {TapDownDetails? details}) { + void show(List options, {TapUpDetails? details}) { if (details != null) { _showContextMenu(options, details: details); return; @@ -185,7 +180,7 @@ class MenuSheet { //=================== void showWithTrack(Track track, List options, - {TapDownDetails? details}) { + {TapUpDetails? details}) { if (details != null) { _showContextMenu(options, details: details); return; @@ -197,37 +192,38 @@ class MenuSheet { isScrollControlled: true, enableDrag: false, showDragHandle: false, - useSafeArea: true, elevation: 0.0, builder: (BuildContext context) { return DraggableScrollableSheet( initialChildSize: 0.5, minChildSize: 0.45, maxChildSize: 0.75, - builder: (context, scrollController) => Material( - type: MaterialType.card, - clipBehavior: Clip.antiAlias, - borderRadius: - const BorderRadius.vertical(top: Radius.circular(20.0)), - child: CustomScrollView( - controller: scrollController, - slivers: [ - SliverPersistentHeader( - pinned: true, - delegate: SliverTrackPersistentHeader(track, - extent: 128.0 + 16.0 + 16.0)), - SliverList( - delegate: SliverChildListDelegate.fixed(options - .map((option) => ListTile( - title: option.label, - leading: option.icon, - onTap: () { - option.onTap.call(); - Navigator.pop(context); - }, - )) - .toList(growable: false))), - ], + builder: (context, scrollController) => SafeArea( + child: Material( + type: MaterialType.card, + clipBehavior: Clip.antiAlias, + borderRadius: + const BorderRadius.vertical(top: Radius.circular(20.0)), + child: CustomScrollView( + controller: scrollController, + slivers: [ + SliverPersistentHeader( + pinned: true, + delegate: SliverTrackPersistentHeader(track, + extent: 128.0 + 16.0 + 16.0)), + SliverList( + delegate: SliverChildListDelegate.fixed(options + .map((option) => ListTile( + title: option.label, + leading: option.icon, + onTap: () { + option.onTap.call(); + Navigator.pop(context); + }, + )) + .toList(growable: false))), + ], + ), ), ), ); @@ -239,7 +235,7 @@ class MenuSheet { Track track, { List options = const [], Function? onRemove, - TapDownDetails? details, + TapUpDetails? details, }) { showWithTrack( track, @@ -423,7 +419,7 @@ class MenuSheet { void defaultAlbumMenu(Album album, {List options = const [], Function? onRemove, - TapDownDetails? details}) { + TapUpDetails? details}) { show([ album.library! ? removeAlbum(album, onRemove: onRemove) @@ -486,7 +482,7 @@ class MenuSheet { void defaultArtistMenu(Artist artist, {List options = const [], Function? onRemove, - TapDownDetails? details}) { + TapUpDetails? details}) { show(details: details, [ artist.library! ? removeArtist(artist, onRemove: onRemove) @@ -529,7 +525,7 @@ class MenuSheet { {List options = const [], Function? onRemove, Function? onUpdate, - TapDownDetails? details}) { + TapUpDetails? details}) { show(details: details, [ if (playlist.library != null) playlist.library! @@ -614,7 +610,7 @@ class MenuSheet { //=================== defaultShowEpisodeMenu(Show s, ShowEpisode e, - {List options = const [], TapDownDetails? details}) { + {List options = const [], TapUpDetails? details}) { show(details: details, [ shareTile('episode', e.id), shareShow(s.id), diff --git a/lib/ui/player_bar.dart b/lib/ui/player_bar.dart index dc6d0b7..d35c160 100644 --- a/lib/ui/player_bar.dart +++ b/lib/ui/player_bar.dart @@ -4,7 +4,6 @@ import 'package:audio_service/audio_service.dart'; import 'package:flutter/material.dart'; import 'package:freezer/settings.dart'; import 'package:freezer/translations.i18n.dart'; -import 'package:freezer/ui/fancy_scaffold.dart'; import 'package:rxdart/rxdart.dart'; import '../api/player/audio_handler.dart'; diff --git a/lib/ui/player_screen.dart b/lib/ui/player_screen.dart index 3d3a2d2..fa3b5c0 100644 --- a/lib/ui/player_screen.dart +++ b/lib/ui/player_screen.dart @@ -287,26 +287,26 @@ class PlayerScreenVertical extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Padding( - padding: EdgeInsets.symmetric(horizontal: 4.0), + padding: const EdgeInsets.symmetric(horizontal: 4.0), child: PlayerScreenTopRow( - textSize: 20.spMax, - iconSize: 24.spMax, + textSize: 14.spMax, + iconSize: 20.spMax, ), ), - Flexible(child: const BigAlbumArt()), + const Flexible(child: BigAlbumArt()), Padding( padding: const EdgeInsets.symmetric(horizontal: 24.0), - child: PlayerTextSubtext(textSize: 32.spMax), + child: PlayerTextSubtext(textSize: 24.spMax), ), - SeekBar(textSize: 20.spMax), + SeekBar(textSize: 14.spMax), Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: PlaybackControls(40.spMax), + child: PlaybackControls(32.spMax), ), Padding( padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 16.0), - child: BottomBarControls(size: 28.spMax), + child: BottomBarControls(size: 22.spMax), ) ], )); @@ -350,7 +350,7 @@ class PlayerScreenDesktop extends StatelessWidget { const EdgeInsets.symmetric(vertical: 0, horizontal: 16.0), child: BottomBarControls( size: 16.sp, - showLyricsButton: false, + desktopMode: true, ), ) ]), @@ -359,7 +359,7 @@ class PlayerScreenDesktop extends StatelessWidget { const Expanded( flex: 2, child: Padding( - padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 24.0), + padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0), child: _DesktopTabView(), )), ]); @@ -395,6 +395,7 @@ class _DesktopTabView extends StatelessWidget { child: TabBarView(children: [ QueueListWidget( closePlayer: FancyScaffold.of(context)!.closePanel, + isInsidePlayer: true, ), const LyricsWidget(), ]), @@ -478,53 +479,50 @@ class PlayerTextSubtext extends StatelessWidget { return const SizedBox(); } final currentMediaItem = snapshot.data!; - return Column(mainAxisSize: MainAxisSize.min, children: [ - SizedBox( - height: textSize * 1.25, - width: double.infinity, - child: FitOrScrollText( + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + FitOrScrollText( key: Key(currentMediaItem.displayTitle!), text: currentMediaItem.displayTitle!, maxLines: 1, style: TextStyle( - fontSize: textSize, fontWeight: FontWeight.bold))), - // child: currentMediaItem.displayTitle!.length >= 26 - // ? Marquee( - // key: Key(currentMediaItem.displayTitle!), - // text: currentMediaItem.displayTitle!, - // style: TextStyle( - // fontSize: textSize, fontWeight: FontWeight.bold), - // blankSpace: 32.0, - // startPadding: 0.0, - // accelerationDuration: const Duration(seconds: 1), - // pauseAfterRound: const Duration(seconds: 2), - // crossAxisAlignment: CrossAxisAlignment.start, - // fadingEdgeEndFraction: 0.05, - // fadingEdgeStartFraction: 0.05, - // ) - // : Text( - // currentMediaItem.displayTitle!, - // maxLines: 1, - // overflow: TextOverflow.ellipsis, - // textAlign: TextAlign.start, - // style: TextStyle( - // fontSize: textSize, fontWeight: FontWeight.bold), - // )), - const SizedBox(height: 4), - SizedBox( - width: double.infinity, - child: Text( - currentMediaItem.displaySubtitle ?? '', - maxLines: 1, - textAlign: TextAlign.start, - overflow: TextOverflow.clip, - style: TextStyle( - fontSize: textSize * 0.8, // 20% smaller - color: Theme.of(context).colorScheme.primary, + fontSize: textSize, fontWeight: FontWeight.bold)), + // child: currentMediaItem.displayTitle!.length >= 26 + // ? Marquee( + // key: Key(currentMediaItem.displayTitle!), + // text: currentMediaItem.displayTitle!, + // style: TextStyle( + // fontSize: textSize, fontWeight: FontWeight.bold), + // blankSpace: 32.0, + // startPadding: 0.0, + // accelerationDuration: const Duration(seconds: 1), + // pauseAfterRound: const Duration(seconds: 2), + // crossAxisAlignment: CrossAxisAlignment.start, + // fadingEdgeEndFraction: 0.05, + // fadingEdgeStartFraction: 0.05, + // ) + // : Text( + // currentMediaItem.displayTitle!, + // maxLines: 1, + // overflow: TextOverflow.ellipsis, + // textAlign: TextAlign.start, + // style: TextStyle( + // fontSize: textSize, fontWeight: FontWeight.bold), + // )), + const SizedBox(height: 2.0), + Text( + currentMediaItem.displaySubtitle ?? '', + maxLines: 1, + textAlign: TextAlign.start, + overflow: TextOverflow.clip, + style: TextStyle( + fontSize: textSize * 0.8, // 20% smaller + color: Theme.of(context).colorScheme.primary, + ), ), - ), - ), - ]); + ]); }); } } @@ -560,6 +558,47 @@ class QualityInfoWidget extends StatelessWidget { } } +void Function([TapUpDetails?]) _onMenuPressedCallback(BuildContext context) { + return ([details]) { + final currentMediaItem = audioHandler.mediaItem.value!; + Track t = Track.fromMediaItem(currentMediaItem); + MenuSheet m = MenuSheet(context, navigateCallback: () { + // close player + FancyScaffold.of(context)?.closePanel(); + }); + if (currentMediaItem.extras!['show'] == null) { + m.defaultTrackMenu(t, + options: [m.sleepTimer(), m.wakelock()], details: details); + } else { + m.defaultShowEpisodeMenu(Show.fromJson(currentMediaItem.extras!['show']), + ShowEpisode.fromMediaItem(currentMediaItem), + options: [m.sleepTimer(), m.wakelock()], details: details); + } + }; +} + +class PlayerMenuButtonDesktop extends StatelessWidget { + final double size; + + const PlayerMenuButtonDesktop({super.key, required this.size}); + + @override + Widget build(BuildContext context) { + return InkWell( + customBorder: const CircleBorder(), + onTapUp: _onMenuPressedCallback(context), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Icon( + Icons.more_vert, + semanticLabel: "Options".i18n, + size: size, + ), + ), + ); + } +} + class PlayerMenuButton extends StatelessWidget { final double size; const PlayerMenuButton({super.key, required this.size}); @@ -567,28 +606,12 @@ class PlayerMenuButton extends StatelessWidget { @override Widget build(BuildContext context) { return IconButton( - iconSize: size, - icon: Icon( - Icons.more_vert, - semanticLabel: "Options".i18n, - ), - onPressed: () { - final currentMediaItem = audioHandler.mediaItem.value!; - Track t = Track.fromMediaItem(currentMediaItem); - MenuSheet m = MenuSheet(context, navigateCallback: () { - // close player - FancyScaffold.of(context)?.closePanel(); - }); - if (currentMediaItem.extras!['show'] == null) { - m.defaultTrackMenu(t, options: [m.sleepTimer(), m.wakelock()]); - } else { - m.defaultShowEpisodeMenu( - Show.fromJson(currentMediaItem.extras!['show']), - ShowEpisode.fromMediaItem(currentMediaItem), - options: [m.sleepTimer(), m.wakelock()]); - } - }, - ); + iconSize: size, + icon: Icon( + Icons.more_vert, + semanticLabel: "Options".i18n, + ), + onPressed: _onMenuPressedCallback(context)); } } @@ -767,7 +790,7 @@ class PlaybackControls extends StatelessWidget { : Theme.of(context).brightness == Brightness.light ? provider.dominantColor! : darken(provider.dominantColor!); - return PlayPauseButton(size * 2.0, + return PlayPauseButton(size * 2.25, filled: true, color: color, iconColor: Color.lerp( @@ -975,7 +998,8 @@ class PlayerScreenTopRow extends StatelessWidget { text: TextSpan(children: [ if (!short) TextSpan( - text: 'PLAYING FROM\n'.i18n, + text: + '${'Playing from:'.i18n.toUpperCase().withoutLast(1)}\n', style: TextStyle( fontWeight: FontWeight.bold, letterSpacing: 1.5, @@ -1118,11 +1142,11 @@ class _SeekBarState extends State { class BottomBarControls extends StatelessWidget { final double size; final bool - showLyricsButton; // removed in desktop mode, because there's a tabbed view which includes it + desktopMode; // removed in desktop mode, because there's a tabbed view which includes it const BottomBarControls({ super.key, required this.size, - this.showLyricsButton = true, + this.desktopMode = false, }); @override @@ -1145,7 +1169,7 @@ class BottomBarControls extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, children: [ QualityInfoWidget(textSize: size * 0.75), - if (showLyricsButton) + if (!desktopMode) IconButton( iconSize: size, icon: Icon( @@ -1185,7 +1209,9 @@ class BottomBarControls extends StatelessWidget { // }, // ), FavoriteButton(size: size * 0.85), - PlayerMenuButton(size: size) + desktopMode + ? PlayerMenuButtonDesktop(size: size) + : PlayerMenuButton(size: size) ], ); } diff --git a/lib/ui/queue_screen.dart b/lib/ui/queue_screen.dart index b9a2c65..1562ddb 100644 --- a/lib/ui/queue_screen.dart +++ b/lib/ui/queue_screen.dart @@ -6,7 +6,6 @@ import 'package:flutter/services.dart'; import 'package:freezer/api/definitions.dart'; import 'package:freezer/api/player/audio_handler.dart'; import 'package:freezer/translations.i18n.dart'; -import 'package:freezer/ui/fancy_scaffold.dart'; import 'package:freezer/ui/menu.dart'; import 'package:freezer/ui/tiles.dart'; diff --git a/lib/ui/settings_screen.dart b/lib/ui/settings_screen.dart index c1c1995..598c408 100644 --- a/lib/ui/settings_screen.dart +++ b/lib/ui/settings_screen.dart @@ -1,4 +1,3 @@ -import 'dart:io'; import 'dart:math'; import 'package:country_pickers/country.dart'; @@ -8,12 +7,10 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_displaymode/flutter_displaymode.dart'; -import 'package:flutter_material_color_picker/flutter_material_color_picker.dart'; import 'package:fluttericon/font_awesome5_icons.dart'; import 'package:fluttericon/web_symbols_icons.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:freezer/api/definitions.dart'; -import 'package:google_fonts/google_fonts.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:scrobblenaut/scrobblenaut.dart'; @@ -96,8 +93,7 @@ class _SettingsScreenState extends State { title: Text(l.name), subtitle: Text("${l.locale}-${l.country}"), onTap: () async { - setState(() => settings.language = - "${l.locale}_${l.country}"); + settings.language = "${l.locale}_${l.country}"; await settings.save(); showDialog( context: context, @@ -173,55 +169,54 @@ class _AppearanceSettingsState extends State { subtitle: Text( '${'Currently'.i18n}: ${settings.theme.toString().split('.').lastItem}'), leading: const Icon(Icons.color_lens), - onTap: settings.materialYouAccent - ? null - : () { - showDialog( - context: context, - builder: (context) { - return SimpleDialog( - title: Text('Select theme'.i18n), - children: [ - SimpleDialogOption( - child: Text('Light'.i18n), - onPressed: () { - settings.theme = Themes.Light; - settings.save(); - updateTheme(context); - Navigator.of(context).pop(); - }, - ), - SimpleDialogOption( - child: Text('Dark'.i18n), - onPressed: () { - settings.theme = Themes.Dark; - settings.save(); - updateTheme(context); - Navigator.of(context).pop(); - }, - ), - SimpleDialogOption( - child: Text('Black (AMOLED)'.i18n), - onPressed: () { - settings.theme = Themes.Black; - settings.save(); - updateTheme(context); - Navigator.of(context).pop(); - }, - ), - SimpleDialogOption( - child: Text('Deezer (Dark)'.i18n), - onPressed: () { - settings.theme = Themes.Deezer; - settings.save(); - updateTheme(context); - Navigator.of(context).pop(); - }, - ), - ], - ); - }); - }, + enabled: !settings.materialYouAccent, + onTap: () { + showDialog( + context: context, + builder: (context) { + return SimpleDialog( + title: Text('Select theme'.i18n), + children: [ + SimpleDialogOption( + child: Text('Light'.i18n), + onPressed: () { + settings.theme = Themes.Light; + settings.save(); + updateTheme(context); + Navigator.of(context).pop(); + }, + ), + SimpleDialogOption( + child: Text('Dark'.i18n), + onPressed: () { + settings.theme = Themes.Dark; + settings.save(); + updateTheme(context); + Navigator.of(context).pop(); + }, + ), + SimpleDialogOption( + child: Text('Black (AMOLED)'.i18n), + onPressed: () { + settings.theme = Themes.Black; + settings.save(); + updateTheme(context); + Navigator.of(context).pop(); + }, + ), + SimpleDialogOption( + child: Text('Deezer (Dark)'.i18n), + onPressed: () { + settings.theme = Themes.Deezer; + settings.save(); + updateTheme(context); + Navigator.of(context).pop(); + }, + ), + ], + ); + }); + }, ), SwitchListTile( title: Text('Use system theme'.i18n), @@ -512,21 +507,23 @@ class _ColorPickerState extends State<_ColorPicker> { content: SizedBox( height: 600.0, width: min(MediaQuery.of(context).size.width * 0.9, 600.0), - child: ColorPicker( - width: 56.0, - height: 56.0, - borderRadius: 50.0, - onColorChanged: (color) => setState(() => this.color = color), - color: color, - showColorCode: true, - pickersEnabled: const { - ColorPickerType.both: false, - ColorPickerType.primary: true, - ColorPickerType.accent: false, - ColorPickerType.bw: false, - ColorPickerType.custom: true, - ColorPickerType.wheel: true, - }, + child: SingleChildScrollView( + child: ColorPicker( + width: 56.0, + height: 56.0, + borderRadius: 50.0, + onColorChanged: (color) => setState(() => this.color = color), + color: color, + showColorCode: true, + pickersEnabled: const { + ColorPickerType.both: false, + ColorPickerType.primary: true, + ColorPickerType.accent: false, + ColorPickerType.bw: false, + ColorPickerType.custom: true, + ColorPickerType.wheel: true, + }, + ), ), ), actions: [ @@ -1476,11 +1473,12 @@ class _GeneralSettingsState extends State { child: Text('Log out & Exit'.i18n), onPressed: () async { try { - audioHandler.stop(); + await audioHandler.stop(); + await DownloadManager.platform + .invokeMethod("kill"); } catch (e) {} await logOut(); - await DownloadManager.platform - .invokeMethod("kill"); + SystemNavigator.pop(); }, ) diff --git a/lib/ui/tiles.dart b/lib/ui/tiles.dart index c535c78..1e96218 100644 --- a/lib/ui/tiles.dart +++ b/lib/ui/tiles.dart @@ -13,7 +13,7 @@ import 'cached_image.dart'; import 'dart:async'; -typedef SecondaryTapCallback = void Function(TapDownDetails?); +typedef SecondaryTapCallback = void Function(TapUpDetails?); VoidCallback? normalizeSecondary(SecondaryTapCallback? callback) { if (callback == null) return null; @@ -90,7 +90,7 @@ class TrackTile extends StatelessWidget { @override Widget build(BuildContext context) { return GestureDetector( - onSecondaryTapDown: onSecondary, + onSecondaryTapUp: onSecondary, child: ListTile( title: StreamBuilder( stream: audioHandler.mediaItem, @@ -175,7 +175,7 @@ class AlbumTile extends StatelessWidget { @override Widget build(BuildContext context) { return GestureDetector( - onSecondaryTapDown: onSecondary, + onSecondaryTapUp: onSecondary, child: ListTile( title: Text( album!.title!, @@ -212,7 +212,7 @@ class ArtistTile extends StatelessWidget { borderRadius: const BorderRadius.all(Radius.circular(4.0)), onTap: onTap, onLongPress: normalizeSecondary(onSecondary), - onSecondaryTapDown: onSecondary, + onSecondaryTapUp: onSecondary, child: Column(mainAxisSize: MainAxisSize.min, children: [ const SizedBox(height: 4.0), CachedImage( @@ -256,7 +256,7 @@ class PlaylistTile extends StatelessWidget { @override Widget build(BuildContext context) { return GestureDetector( - onSecondaryTapDown: onSecondary, + onSecondaryTapUp: onSecondary, child: ListTile( title: Text( playlist!.title!, @@ -317,7 +317,7 @@ class PlaylistCardTile extends StatelessWidget { @override Widget build(BuildContext context) { return GestureDetector( - onSecondaryTapDown: onSecondary, + onSecondaryTapUp: onSecondary, child: SizedBox( height: 180.0, child: InkWell( @@ -814,7 +814,7 @@ class ShowEpisodeTile extends StatelessWidget { return InkWell( onTap: onTap, onLongPress: normalizeSecondary(onSecondary), - onSecondaryTapDown: onSecondary, + onSecondaryTapUp: onSecondary, child: Column( mainAxisSize: MainAxisSize.min, children: [ diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index f847d24..606c5a6 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,16 +6,12 @@ #include "generated_plugin_registrant.h" -#include #include #include #include #include void fl_register_plugins(FlPluginRegistry* registry) { - g_autoptr(FlPluginRegistrar) desktop_webview_window_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopWebviewWindowPlugin"); - desktop_webview_window_plugin_register_with_registrar(desktop_webview_window_registrar); g_autoptr(FlPluginRegistrar) dynamic_color_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "DynamicColorPlugin"); dynamic_color_plugin_register_with_registrar(dynamic_color_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 9f7f4e8..f1b77ef 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,7 +3,6 @@ # list(APPEND FLUTTER_PLUGIN_LIST - desktop_webview_window dynamic_color isar_flutter_libs media_kit_libs_linux diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index c9c340a..fcd3f4c 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,7 +8,6 @@ import Foundation import audio_service import audio_session import connectivity_plus -import desktop_webview_window import dynamic_color import flutter_local_notifications import isar_flutter_libs @@ -24,7 +23,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin")) AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin")) - DesktopWebviewWindowPlugin.register(with: registry.registrar(forPlugin: "DesktopWebviewWindowPlugin")) DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) IsarFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "IsarFlutterLibsPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 44f27b8..25ff371 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -322,14 +322,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.8" - desktop_webview_window: - dependency: "direct main" - description: - name: desktop_webview_window - sha256: "57cf20d81689d5cbb1adfd0017e96b669398a669d927906073b0e42fc64111c0" - url: "https://pub.dev" - source: hosted - version: "0.2.3" dio: dependency: "direct main" description: @@ -354,14 +346,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.3" - draggable_scrollbar: - dependency: "direct main" - description: - name: draggable_scrollbar - sha256: a906e27fc1ee056e2942d66989dd0cb5b70a361e7d44af566cfa1b584054eac3 - url: "https://pub.dev" - source: hosted - version: "0.1.0" dynamic_color: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index fa5b1df..2b8bd72 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: Freezer # The following line prevents the package from being accidentally published to # pub.dev using `pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +publish_to: "none" # Remove this line if you wish to publish to pub.dev # The following defines the version and build number for your application. # A version number is three numbers separated by dots, like 1.2.43 @@ -18,163 +18,162 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 0.6.14+1 environment: - sdk: '>=3.0.0 <4.0.0' + sdk: ">=3.0.0 <4.0.0" dependencies: - flutter: - sdk: flutter - flutter_localizations: - sdk: flutter - spotify: ^0.12.0 - flutter_displaymode: ^0.6.0 - crypto: ^3.0.3 - http: ^1.1.0 - cookie_jar: ^4.0.8 - json_annotation: ^4.8.1 - path_provider: ^2.0.15 - path: ^1.6.4 - sqflite: ^2.0.0+3 - permission_handler: ^10.4.3 - intl: ^0.18.0 - filesize: ^2.0.1 - fluttertoast: ^8.0.8 - palette_generator: ^0.3.0 - flutter_material_color_picker: ^1.0.5 - flutter_inappwebview: ^5.3.2 - country_pickers: ^2.0.0 - move_to_background: ^1.0.1 - flutter_local_notifications: ^15.1.0+1 - collection: ^1.17.1 - random_string: ^2.0.1 - async: ^2.6.1 - html: ^0.15.0 - flutter_screenutil: ^5.0.0+2 - marquee: ^2.2.0 - flutter_cache_manager: ^3.0.0 - cached_network_image: ^3.1.0 - i18n_extension: ^9.0.2 - fluttericon: ^2.0.0 - url_launcher: ^6.0.5 - uni_links: ^0.5.1 - numberpicker: ^2.1.1 - quick_actions: ^1.0.5 - photo_view: ^0.14.0 - draggable_scrollbar: ^0.1.0 - scrobblenaut: - git: - url: https://github.com/Pato05/Scrobblenaut.git - ref: main - open_file: ^3.0.3 - version: ^3.0.2 - wakelock_plus: ^1.1.1 - google_fonts: ^5.1.0 - equalizer: - git: https://github.com/gladson97/equalizer.git - audio_session: ^0.1.6 - audio_service: ^0.18.1 - provider: ^6.0.0 - hive_flutter: ^1.1.0 - connectivity_plus: ^4.0.1 - share_plus: ^7.0.2 - disk_space_plus: ^0.2.3 - dynamic_color: ^1.6.6 - package_info_plus: ^4.0.2 - encrypt: ^5.0.1 - dart_blowfish: - git: - url: https://github.com/Pato05/dart_blowfish.git - ref: main - logging: ^1.2.0 - just_audio: ^0.9.35 - just_audio_media_kit: - git: https://github.com/Pato05/just_audio_media_kit.git - rxdart: ^0.27.7 - flutter_isolate: ^2.0.4 - isar: ^3.1.0+1 - isar_flutter_libs: ^3.1.0+1 - flutter_background_service: ^5.0.1 - audio_service_mpris: ^0.1.0 - desktop_webview_window: ^0.2.3 - dio: ^5.3.3 - dio_cookie_manager: ^3.1.1 - flutter_cache_manager_hive: - git: https://github.com/Pato05/flutter_cache_manager_hive.git - flex_color_picker: ^3.3.0 - - #deezcryptor: - #path: deezcryptor/ + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter + spotify: ^0.12.0 + flutter_displaymode: ^0.6.0 + crypto: ^3.0.3 + http: ^1.1.0 + cookie_jar: ^4.0.8 + json_annotation: ^4.8.1 + path_provider: ^2.0.15 + path: ^1.6.4 + sqflite: ^2.0.0+3 + permission_handler: ^10.4.3 + intl: ^0.18.0 + filesize: ^2.0.1 + fluttertoast: ^8.0.8 + palette_generator: ^0.3.0 + flutter_material_color_picker: ^1.0.5 + flutter_inappwebview: ^5.3.2 + country_pickers: ^2.0.0 + move_to_background: ^1.0.1 + flutter_local_notifications: ^15.1.0+1 + collection: ^1.17.1 + random_string: ^2.0.1 + async: ^2.6.1 + html: ^0.15.0 + flutter_screenutil: ^5.0.0+2 + marquee: ^2.2.0 + flutter_cache_manager: ^3.0.0 + cached_network_image: ^3.1.0 + i18n_extension: ^9.0.2 + fluttericon: ^2.0.0 + url_launcher: ^6.0.5 + uni_links: ^0.5.1 + numberpicker: ^2.1.1 + quick_actions: ^1.0.5 + photo_view: ^0.14.0 + scrobblenaut: + git: + url: https://github.com/Pato05/Scrobblenaut.git + ref: main + open_file: ^3.0.3 + version: ^3.0.2 + wakelock_plus: ^1.1.1 + google_fonts: ^5.1.0 + equalizer: + git: https://github.com/gladson97/equalizer.git + audio_session: ^0.1.6 + audio_service: ^0.18.1 + provider: ^6.0.0 + hive_flutter: ^1.1.0 + connectivity_plus: ^4.0.1 + share_plus: ^7.0.2 + disk_space_plus: ^0.2.3 + dynamic_color: ^1.6.6 + package_info_plus: ^4.0.2 + encrypt: ^5.0.1 + dart_blowfish: + git: + url: https://github.com/Pato05/dart_blowfish.git + ref: main + logging: ^1.2.0 + just_audio: ^0.9.35 + just_audio_media_kit: + git: https://github.com/Pato05/just_audio_media_kit.git + rxdart: ^0.27.7 + flutter_isolate: ^2.0.4 + isar: ^3.1.0+1 + isar_flutter_libs: ^3.1.0+1 + flutter_background_service: ^5.0.1 + audio_service_mpris: ^0.1.0 + dio: ^5.3.3 + dio_cookie_manager: ^3.1.1 + flutter_cache_manager_hive: + git: https://github.com/Pato05/flutter_cache_manager_hive.git + flex_color_picker: + ^3.3.0 + + #deezcryptor: + #path: deezcryptor/ dev_dependencies: - flutter_test: - sdk: flutter - json_serializable: ^6.0.1 - build_runner: ^2.4.6 - hive_generator: ^2.0.0 - flutter_lints: ^2.0.3 - isar_generator: ^3.1.0+1 + flutter_test: + sdk: flutter + json_serializable: ^6.0.1 + build_runner: ^2.4.6 + hive_generator: ^2.0.0 + flutter_lints: ^2.0.3 + isar_generator: ^3.1.0+1 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter. flutter: - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. - uses-material-design: true + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg - assets: - - assets/cover.jpg - - assets/cover_thumb.jpg - - assets/icon.png - - assets/favorites_thumb.jpg - - assets/browse_icon.png + assets: + - assets/cover.jpg + - assets/cover_thumb.jpg + - assets/icon.png + - assets/favorites_thumb.jpg + - assets/browse_icon.png - fonts: - # - family: Montserrat - # fonts: - # - asset: assets/fonts/Montserrat-Regular.ttf - # - asset: assets/fonts/Montserrat-Bold.ttf - # weight: 700 - # - asset: assets/fonts/Montserrat-Italic.ttf - # style: italic - - family: MabryPro - fonts: - - asset: assets/fonts/MabryPro.otf - - asset: assets/fonts/MabryProItalic.otf - style: italic - - asset: assets/fonts/MabryProBold.otf - weight: 700 - - asset: assets/fonts/MabryProBlack.otf - weight: 900 + fonts: + # - family: Montserrat + # fonts: + # - asset: assets/fonts/Montserrat-Regular.ttf + # - asset: assets/fonts/Montserrat-Bold.ttf + # weight: 700 + # - asset: assets/fonts/Montserrat-Italic.ttf + # style: italic + - family: MabryPro + fonts: + - asset: assets/fonts/MabryPro.otf + - asset: assets/fonts/MabryProItalic.otf + style: italic + - asset: assets/fonts/MabryProBold.otf + weight: 700 + - asset: assets/fonts/MabryProBlack.otf + weight: 900 - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 7349940..1dc0921 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,7 +7,6 @@ #include "generated_plugin_registrant.h" #include -#include #include #include #include @@ -18,8 +17,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { ConnectivityPlusWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); - DesktopWebviewWindowPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("DesktopWebviewWindowPlugin")); DynamicColorPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); IsarFlutterLibsPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 56ca898..79c3036 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,7 +4,6 @@ list(APPEND FLUTTER_PLUGIN_LIST connectivity_plus - desktop_webview_window dynamic_color isar_flutter_libs media_kit_libs_windows_audio