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
This commit is contained in:
Pato05 2023-10-25 00:32:28 +02:00
parent 7a119d281c
commit 2862c9ec05
No known key found for this signature in database
GPG Key ID: ED4C6F9C3D574FB6
28 changed files with 804 additions and 753 deletions

View File

@ -109,24 +109,24 @@ class Cache {
static Future<Cache> 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<void> save() async {

View File

@ -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<bool>? _authorizing;
@ -93,7 +93,7 @@ class DeezerAPI {
Future<Map<dynamic, dynamic>> callApi(String method,
{Map<dynamic, dynamic>? params, String? gatewayInput}) async {
//Post
final res = await dio.post<Map>('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<String> 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<bool> rawAuthorize({Function? onError}) async {
try {
Map<dynamic, dynamic> 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'}
}

View File

@ -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 = <HomePageItem>[];
@ -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;

View File

@ -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<bool> _checkOffline(
(Album? album, Track? track, Playlist? playlist) message) async {
Future<bool> 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<bool> checkOffline({Album? album, Track? track, Playlist? playlist}) {
return compute(_checkOffline, (album, track, playlist));
}
//Offline search
Future<SearchResults> search(String? query) async {
SearchResults results =

View File

@ -1,4 +1,3 @@
import 'package:freezer/api/definitions.dart';
import 'package:isar/isar.dart';
@collection

View File

@ -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<bool> addOfflineTrack(Track track,
{bool private = true, BuildContext? context, isSingleton = false}) {
// TODO: implement addOfflineTrack

View File

@ -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<void> _logListenedTrack(MediaItem mediaItem) async {
Future<void> _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,
);
}

View File

@ -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<MainScreen>
child: _MainRouteNavigator(
navigatorKey: navigatorKey,
routes: {
Navigator.defaultRouteName: (context) =>
const HomeScreen(),
Navigator.defaultRouteName: (context) => HomeScreen(),
'/podcasts': (context) => HomePageScreen(
cacheable: true,
channel: const DeezerChannel(

View File

@ -39,7 +39,7 @@ const List<Language> languages = [
List<Locale> 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);
}

View File

@ -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<PlaylistDetails> {
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: <Widget>[

View File

@ -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<HomePageWidget> {
}
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<HomePageWidget> {
});
}
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<HomePageWidget> {
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<HomepageRowSection> createState() => _HomepageRowSectionState();
}
class _HomepageRowSectionState extends State<HomepageRowSection> {
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));
}),
],
);
}

View File

@ -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<SpotifyImporterV2Main> {
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

View File

@ -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<LibraryTracks> {
),
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: <Widget>[
@ -677,9 +677,10 @@ class _LibraryAlbumsState extends State<LibraryAlbums> {
),
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: <Widget>[
@ -886,9 +887,10 @@ class _LibraryArtistsState extends State<LibraryArtists> {
? 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: <Widget>[
@ -1047,9 +1049,10 @@ class _LibraryPlaylistsState extends State<LibraryPlaylists> {
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: <Widget>[
@ -1198,9 +1201,10 @@ class _HistoryScreenState extends State<HistoryScreen> {
)
],
),
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,

View File

@ -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<LoginWidget> {
}
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<LoginWidget> {
// style: ButtonStyle(
// foregroundColor:
// MaterialStateProperty.all(Colors.white)))),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
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: <Widget>[
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: <Widget>[
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: <Widget>[
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<EmailLogin> {
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'),
)
],
);
}

View File

@ -48,7 +48,7 @@ class _LyricsWidgetState extends State<LyricsWidget> {
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<LyricsWidget> {
_lyrics = lyrics;
});
_nextPosition = Duration.zero;
SchedulerBinding.instance.addPostFrameCallback(
(_) => _updatePosition(audioHandler.playbackState.value.position));
} catch (e) {
@ -96,7 +97,7 @@ class _LyricsWidgetState extends State<LyricsWidget> {
}
}
Future<void> _scrollToLyric() async {
void _scrollToLyric() {
if (!_controller.hasClients) return;
//Lyric height, screen height, appbar height
double scrollTo;
@ -111,27 +112,36 @@ class _LyricsWidgetState extends State<LyricsWidget> {
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();
}

View File

@ -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<MenuSheetOption> 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<MenuSheetOption> options, {TapDownDetails? details}) {
void show(List<MenuSheetOption> options, {TapUpDetails? details}) {
if (details != null) {
_showContextMenu(options, details: details);
return;
@ -185,7 +180,7 @@ class MenuSheet {
//===================
void showWithTrack(Track track, List<MenuSheetOption> 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<MenuSheetOption> options = const [],
Function? onRemove,
TapDownDetails? details,
TapUpDetails? details,
}) {
showWithTrack(
track,
@ -423,7 +419,7 @@ class MenuSheet {
void defaultAlbumMenu(Album album,
{List<MenuSheetOption> 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<MenuSheetOption> options = const [],
Function? onRemove,
TapDownDetails? details}) {
TapUpDetails? details}) {
show(details: details, [
artist.library!
? removeArtist(artist, onRemove: onRemove)
@ -529,7 +525,7 @@ class MenuSheet {
{List<MenuSheetOption> 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<MenuSheetOption> options = const [], TapDownDetails? details}) {
{List<MenuSheetOption> options = const [], TapUpDetails? details}) {
show(details: details, [
shareTile('episode', e.id),
shareShow(s.id),

View File

@ -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';

View File

@ -287,26 +287,26 @@ class PlayerScreenVertical extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
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: <Widget>[
SizedBox(
height: textSize * 1.25,
width: double.infinity,
child: FitOrScrollText(
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
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<SeekBar> {
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: <Widget>[
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)
],
);
}

View File

@ -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';

View File

@ -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<SettingsScreen> {
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<AppearanceSettings> {
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: <Widget>[
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: <Widget>[
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<GeneralSettings> {
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();
},
)

View File

@ -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<MediaItem?>(
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: <Widget>[
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: [

View File

@ -6,16 +6,12 @@
#include "generated_plugin_registrant.h"
#include <desktop_webview_window/desktop_webview_window_plugin.h>
#include <dynamic_color/dynamic_color_plugin.h>
#include <isar_flutter_libs/isar_flutter_libs_plugin.h>
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
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);

View File

@ -3,7 +3,6 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
desktop_webview_window
dynamic_color
isar_flutter_libs
media_kit_libs_linux

View File

@ -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"))

View File

@ -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:

View File

@ -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

View File

@ -7,7 +7,6 @@
#include "generated_plugin_registrant.h"
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
#include <desktop_webview_window/desktop_webview_window_plugin.h>
#include <dynamic_color/dynamic_color_plugin_c_api.h>
#include <isar_flutter_libs/isar_flutter_libs_plugin.h>
#include <media_kit_libs_windows_audio/media_kit_libs_windows_audio_plugin_c_api.h>
@ -18,8 +17,6 @@
void RegisterPlugins(flutter::PluginRegistry* registry) {
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
DesktopWebviewWindowPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("DesktopWebviewWindowPlugin"));
DynamicColorPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("DynamicColorPluginCApi"));
IsarFlutterLibsPluginRegisterWithRegistrar(

View File

@ -4,7 +4,6 @@
list(APPEND FLUTTER_PLUGIN_LIST
connectivity_plus
desktop_webview_window
dynamic_color
isar_flutter_libs
media_kit_libs_windows_audio