several new and sexy changes

This commit is contained in:
pato05 2021-08-30 00:25:18 +02:00
parent 10a5455cc7
commit 4700d4113e
23 changed files with 2411 additions and 2204 deletions

28
android/.project Normal file
View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>android</name>
<comment>Project android created by Buildship.</comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures>
<filteredResources>
<filter>
<id>1630077705303</id>
<name></name>
<type>30</type>
<matcher>
<id>org.eclipse.core.resources.regexFilterMatcher</id>
<arguments>node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
</matcher>
</filter>
</filteredResources>
</projectDescription>

6
android/app/.classpath Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11/"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="bin/default"/>
</classpath>

34
android/app/.project Normal file
View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>app</name>
<comment>Project app created by Buildship.</comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures>
<filteredResources>
<filter>
<id>1630077705310</id>
<name></name>
<type>30</type>
<matcher>
<id>org.eclipse.core.resources.regexFilterMatcher</id>
<arguments>node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
</matcher>
</filter>
</filteredResources>
</projectDescription>

View File

@ -0,0 +1,2 @@
connection.project.dir=..
eclipse.preferences.version=1

View File

@ -32,7 +32,7 @@ apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 29
compileSdkVersion 30
lintOptions {
disable 'InvalidPackage'
@ -62,6 +62,9 @@ android {
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.release
shrinkResources false
minifyEnabled true
}
debug {
minifyEnabled false
}
}
@ -78,6 +81,7 @@ dependencies {
implementation files('libs/jaudiotagger-2.2.3.jar')
implementation files('libs/extension-flac.aar')
implementation group: 'org.nanohttpd', name: 'nanohttpd', version: '2.3.1'
implementation group: 'androidx.core', name: 'core', version: '1.6.0'
}
flutter {

View File

@ -8,6 +8,7 @@ import android.content.ServiceConnection;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@ -15,6 +16,7 @@ import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
import androidx.core.view.WindowCompat;
import androidx.annotation.NonNull;
@ -291,6 +293,15 @@ public class MainActivity extends FlutterActivity {
connectService();
}
@Override
public void onPostResume() {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// Allow the app to background below system navbar
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
}
super.onPostResume();
}
@Override
protected void onStop() {
super.onStop();

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
@ -12,7 +12,7 @@
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@android:color/white</item>
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

File diff suppressed because it is too large Load Diff

View File

@ -21,11 +21,9 @@ import 'dart:async';
import 'dart:convert';
import 'dart:math';
PlayerHelper playerHelper = PlayerHelper();
class PlayerHelper {
StreamSubscription _customEventSubscription;
StreamSubscription _mediaItemSubscription;
StreamSubscription _playbackStateStreamSubscription;
@ -41,11 +39,15 @@ class PlayerHelper {
Stream get visualizerStream => _visualizerController.stream;
//Find queue index by id
int get queueIndex => AudioService.queue == null ? 0 : AudioService.queue.indexWhere((mi) => mi.id == AudioService.currentMediaItem?.id??'Random string so it returns -1');
int get queueIndex => AudioService.queue == null
? 0
: AudioService.queue
.indexWhere((mi) => mi.id == AudioService.currentMediaItem?.id);
Future start() async {
//Subscribe to custom events
_customEventSubscription = AudioService.customEventStream.listen((event) async {
//Subscribe to custom events
_customEventSubscription =
AudioService.customEventStream.listen((event) async {
if (!(event is Map)) return;
switch (event['action']) {
case 'onLoad':
@ -67,7 +69,8 @@ class PlayerHelper {
case 'screenAndroidAuto':
AndroidAuto androidAuto = AndroidAuto();
List<MediaItem> data = await androidAuto.getScreen(event['id']);
await AudioService.customAction('screenAndroidAuto', jsonEncode(data));
await AudioService.customAction(
'screenAndroidAuto', jsonEncode(data));
break;
case 'tracksAndroidAuto':
AndroidAuto androidAuto = AndroidAuto();
@ -78,8 +81,7 @@ class PlayerHelper {
//Save
_prevAudioSession = audioSession;
audioSession = event['id'];
if (audioSession == null)
break;
if (audioSession == null) break;
//Open EQ
if (!equalizerOpen) {
Equalizer.open(event['id']);
@ -88,7 +90,8 @@ class PlayerHelper {
}
//Change session id
if (_prevAudioSession != audioSession) {
if (_prevAudioSession != null) Equalizer.removeAudioSessionId(_prevAudioSession);
if (_prevAudioSession != null)
Equalizer.removeAudioSessionId(_prevAudioSession);
Equalizer.setAudioSessionId(audioSession);
}
break;
@ -97,13 +100,12 @@ class PlayerHelper {
_visualizerController.add(event['data']);
break;
}
});
_mediaItemSubscription = AudioService.currentMediaItemStream.listen((event) {
_mediaItemSubscription =
AudioService.currentMediaItemStream.listen((event) {
if (event == null) return;
//Load more flow if index-1 song
if (queueIndex == AudioService.queue.length-1)
onQueueEnd();
if (queueIndex == AudioService.queue.length - 1) onQueueEnd();
//Save queue
AudioService.customAction('saveQueue');
@ -116,8 +118,10 @@ class PlayerHelper {
//Logging listen timer
_timer = Timer.periodic(Duration(seconds: 2), (timer) async {
if (AudioService.currentMediaItem == null || !AudioService.playbackState.playing) return;
if (AudioService.playbackState.currentPosition.inSeconds > (AudioService.currentMediaItem.duration.inSeconds * 0.75)) {
if (AudioService.currentMediaItem == null ||
!AudioService.playbackState.playing) return;
if (AudioService.playbackState.currentPosition.inSeconds >
(AudioService.currentMediaItem.duration.inSeconds * 0.75)) {
if (cache.loggedTrackId == AudioService.currentMediaItem.id) return;
cache.loggedTrackId = AudioService.currentMediaItem.id;
await cache.save();
@ -127,7 +131,6 @@ class PlayerHelper {
deezerAPI.logListen(AudioService.currentMediaItem.id);
}
}
});
//Start audio_service
@ -136,44 +139,48 @@ class PlayerHelper {
Future startService() async {
if (AudioService.running && AudioService.connected) return;
if (!AudioService.connected)
await AudioService.connect();
if (!AudioService.connected) await AudioService.connect();
if (!AudioService.running)
await AudioService.start(
backgroundTaskEntrypoint: backgroundTaskEntrypoint,
androidEnableQueue: true,
androidStopForegroundOnPause: false,
androidNotificationOngoing: false,
androidNotificationClickStartsActivity: true,
androidNotificationChannelDescription: 'Freezer',
androidNotificationChannelName: 'Freezer',
androidNotificationIcon: 'drawable/ic_logo',
params: {'ignoreInterruptions': settings.ignoreInterruptions}
);
backgroundTaskEntrypoint: backgroundTaskEntrypoint,
androidEnableQueue: true,
androidStopForegroundOnPause: false,
androidNotificationOngoing: false,
androidNotificationClickStartsActivity: true,
androidNotificationChannelDescription: 'Freezer',
androidNotificationChannelName: 'Freezer',
androidNotificationIcon: 'drawable/ic_logo',
params: {'ignoreInterruptions': settings.ignoreInterruptions});
}
Future authorizeLastFM() async {
if (settings.lastFMUsername == null || settings.lastFMPassword == null) return;
await AudioService.customAction("authorizeLastFM", [settings.lastFMUsername, settings.lastFMPassword]);
if (settings.lastFMUsername == null || settings.lastFMPassword == null)
return;
await AudioService.customAction(
"authorizeLastFM", [settings.lastFMUsername, settings.lastFMPassword]);
}
Future toggleShuffle() async {
await AudioService.customAction('shuffle');
}
//Repeat toggle
Future changeRepeat() async {
//Change to next repeat type
switch (repeatType) {
case LoopMode.one:
repeatType = LoopMode.off; break;
repeatType = LoopMode.off;
break;
case LoopMode.all:
repeatType = LoopMode.one; break;
repeatType = LoopMode.one;
break;
default:
repeatType = LoopMode.all; break;
repeatType = LoopMode.all;
break;
}
//Set repeat type
await AudioService.customAction("repeatType", LoopMode.values.indexOf(repeatType));
await AudioService.customAction(
"repeatType", LoopMode.values.indexOf(repeatType));
}
//Executed before exit
@ -187,12 +194,12 @@ class PlayerHelper {
Future _loadQueuePlay(List<MediaItem> queue, String trackId) async {
await startService();
await settings.updateAudioServiceQuality();
await AudioService.customAction('setIndex', queue.indexWhere((m) => m.id == trackId));
await AudioService.customAction(
'setIndex', queue.indexWhere((m) => m.id == trackId));
await AudioService.updateQueue(queue);
// if (queue[0].id != trackId)
// await AudioService.skipToQueueItem(trackId);
if (!AudioService.playbackState.playing)
AudioService.play();
if (!AudioService.playbackState.playing) AudioService.play();
}
//Called when queue ends to load more tracks
@ -201,7 +208,7 @@ class PlayerHelper {
if (queueSource == null) return;
List<Track> tracks = [];
switch(queueSource.source) {
switch (queueSource.source) {
case 'flow':
tracks = await deezerAPI.flow();
break;
@ -211,7 +218,8 @@ class PlayerHelper {
break;
//Library shuffle
case 'libraryshuffle':
tracks = await deezerAPI.libraryShuffle(start: AudioService.queue.length);
tracks =
await deezerAPI.libraryShuffle(start: AudioService.queue.length);
break;
case 'mix':
tracks = await deezerAPI.playMix(queueSource.id);
@ -231,47 +239,45 @@ class PlayerHelper {
//Play track from album
Future playFromAlbum(Album album, String trackId) async {
await playFromTrackList(album.tracks, trackId, QueueSource(
id: album.id,
text: album.title,
source: 'album'
));
await playFromTrackList(album.tracks, trackId,
QueueSource(id: album.id, text: album.title, source: 'album'));
}
//Play mix by track
Future playMix(String trackId, String trackTitle) async {
List<Track> tracks = await deezerAPI.playMix(trackId);
playFromTrackList(tracks, tracks[0].id, QueueSource(
id: trackId,
text: 'Mix based on'.i18n + ' $trackTitle',
source: 'mix'
));
playFromTrackList(
tracks,
tracks[0].id,
QueueSource(
id: trackId,
text: 'Mix based on'.i18n + ' $trackTitle',
source: 'mix'));
}
//Play from artist top tracks
Future playFromTopTracks(List<Track> tracks, String trackId, Artist artist) async {
await playFromTrackList(tracks, trackId, QueueSource(
id: artist.id,
text: 'Top ${artist.name}',
source: 'topTracks'
));
Future playFromTopTracks(
List<Track> tracks, String trackId, Artist artist) async {
await playFromTrackList(
tracks,
trackId,
QueueSource(
id: artist.id, text: 'Top ${artist.name}', source: 'topTracks'));
}
Future playFromPlaylist(Playlist playlist, String trackId) async {
await playFromTrackList(playlist.tracks, trackId, QueueSource(
id: playlist.id,
text: playlist.title,
source: 'playlist'
));
await playFromTrackList(playlist.tracks, trackId,
QueueSource(id: playlist.id, text: playlist.title, source: 'playlist'));
}
//Play episode from show, load whole show as queue
Future playShowEpisode(Show show, List<ShowEpisode> episodes, {int index = 0}) async {
QueueSource queueSource = QueueSource(
id: show.id,
text: show.name,
source: 'show'
);
Future playShowEpisode(Show show, List<ShowEpisode> episodes,
{int index = 0}) async {
QueueSource queueSource =
QueueSource(id: show.id, text: show.name, source: 'show');
//Generate media items
List<MediaItem> queue = episodes.map<MediaItem>((e) => e.toMediaItem(show)).toList();
List<MediaItem> queue =
episodes.map<MediaItem>((e) => e.toMediaItem(show)).toList();
//Load and play
await startService();
@ -279,15 +285,16 @@ class PlayerHelper {
await setQueueSource(queueSource);
await AudioService.customAction('setIndex', index);
await AudioService.updateQueue(queue);
if (!AudioService.playbackState.playing)
AudioService.play();
if (!AudioService.playbackState.playing) AudioService.play();
}
//Load tracks as queue, play track id, set queue source
Future playFromTrackList(List<Track> tracks, String trackId, QueueSource queueSource) async {
Future playFromTrackList(
List<Track> tracks, String trackId, QueueSource queueSource) async {
await startService();
List<MediaItem> queue = tracks.map<MediaItem>((track) => track.toMediaItem()).toList();
List<MediaItem> queue =
tracks.map<MediaItem>((track) => track.toMediaItem()).toList();
await setQueueSource(queueSource);
await _loadQueuePlay(queue, trackId);
}
@ -298,10 +305,9 @@ class PlayerHelper {
if (stl.tracks == null || stl.tracks.length == 0) {
if (settings.offlineMode) {
Fluttertoast.showToast(
msg: "Offline mode, can't play flow or smart track lists.".i18n,
gravity: ToastGravity.BOTTOM,
toastLength: Toast.LENGTH_SHORT
);
msg: "Offline mode, can't play flow or smart track lists.".i18n,
gravity: ToastGravity.BOTTOM,
toastLength: Toast.LENGTH_SHORT);
return;
}
@ -313,13 +319,13 @@ class PlayerHelper {
}
}
QueueSource queueSource = QueueSource(
id: stl.id,
source: (stl.id == 'flow')?'flow':'smarttracklist',
text: stl.title??((stl.id == 'flow') ? 'Flow'.i18n : 'Smart track list'.i18n)
);
id: stl.id,
source: (stl.id == 'flow') ? 'flow' : 'smarttracklist',
text: stl.title ??
((stl.id == 'flow') ? 'Flow'.i18n : 'Smart track list'.i18n));
await playFromTrackList(stl.tracks, stl.tracks[0].id, queueSource);
}
Future setQueueSource(QueueSource queueSource) async {
await startService();
@ -336,11 +342,11 @@ class PlayerHelper {
Future startVisualizer() async {
await AudioService.customAction('startVisualizer');
}
//Stop visualizer
Future stopVisualizer() async {
await AudioService.customAction('stopVisualizer');
}
}
void backgroundTaskEntrypoint() async {
@ -382,7 +388,6 @@ class AudioPlayerTask extends BackgroundAudioTask {
@override
Future onStart(Map<String, dynamic> params) async {
final session = await AudioSession.instance;
session.configure(AudioSessionConfiguration.music());
@ -412,27 +417,28 @@ class AudioPlayerTask extends BackgroundAudioTask {
_broadcastState();
});
_player.processingStateStream.listen((state) {
switch(state) {
case ProcessingState.completed:
//Player ended, get more songs
if (_queueIndex == _queue.length - 1)
AudioServiceBackground.sendCustomEvent({
'action': 'queueEnd',
'queueSource': (queueSource??QueueSource()).toJson()
});
break;
case ProcessingState.ready:
//Ready to play
_skipState = null;
break;
default:
break;
}
switch (state) {
case ProcessingState.completed:
//Player ended, get more songs
if (_queueIndex == _queue.length - 1)
AudioServiceBackground.sendCustomEvent({
'action': 'queueEnd',
'queueSource': (queueSource ?? QueueSource()).toJson()
});
break;
case ProcessingState.ready:
//Ready to play
_skipState = null;
break;
default:
break;
}
});
//Audio session
_audioSessionSub = _player.androidAudioSessionIdStream.listen((event) {
AudioServiceBackground.sendCustomEvent({"action": 'audioSession', "id": event});
AudioServiceBackground.sendCustomEvent(
{"action": 'audioSession', "id": event});
});
//Load queue
@ -449,8 +455,8 @@ class AudioPlayerTask extends BackgroundAudioTask {
if (newIndex == -1) return;
//Update buffering state
_skipState = newIndex > _queueIndex
? AudioProcessingState.skippingToNext
: AudioProcessingState.skippingToPrevious;
? AudioProcessingState.skippingToNext
: AudioProcessingState.skippingToPrevious;
//Skip in player
await _player.seek(Duration.zero, index: newIndex);
@ -513,7 +519,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
@override
Future<void> onSkipToNext() async {
_lastPosition = null;
if (_queueIndex == _queue.length-1) return;
if (_queueIndex == _queue.length - 1) return;
//Update buffering state
_skipState = AudioProcessingState.skippingToNext;
_queueIndex++;
@ -535,14 +541,13 @@ class AudioPlayerTask extends BackgroundAudioTask {
@override
Future<List<MediaItem>> onLoadChildren(String parentMediaId) async {
AudioServiceBackground.sendCustomEvent({
'action': 'screenAndroidAuto',
'id': parentMediaId
});
AudioServiceBackground.sendCustomEvent(
{'action': 'screenAndroidAuto', 'id': parentMediaId});
//Wait for data from main thread
_androidAutoCallback = Completer();
List<MediaItem> data = (await _androidAutoCallback.future) as List<MediaItem>;
List<MediaItem> data =
(await _androidAutoCallback.future) as List<MediaItem>;
_androidAutoCallback = null;
return data;
}
@ -551,7 +556,9 @@ class AudioPlayerTask extends BackgroundAudioTask {
void _seekContinuously(bool begin, int direction) {
_seeker?.stop();
if (begin) {
_seeker = Seeker(_player, Duration(seconds: 10 * direction), Duration(seconds: 1), mediaItem)..start();
_seeker = Seeker(_player, Duration(seconds: 10 * direction),
Duration(seconds: 1), mediaItem)
..start();
}
}
@ -568,29 +575,27 @@ class AudioPlayerTask extends BackgroundAudioTask {
//Update state on all clients
Future _broadcastState() async {
await AudioServiceBackground.setState(
controls: [
MediaControl.skipToPrevious,
if (_player.playing) MediaControl.pause else MediaControl.play,
MediaControl.skipToNext,
//Stop
MediaControl(
androidIcon: 'drawable/ic_action_stop',
label: 'stop',
action: MediaAction.stop
),
],
systemActions: [
MediaAction.seekTo,
MediaAction.seekForward,
MediaAction.seekBackward,
MediaAction.stop
],
processingState: _getProcessingState(),
playing: _player.playing,
position: _player.position,
bufferedPosition: _player.bufferedPosition,
speed: _player.speed
);
controls: [
MediaControl.skipToPrevious,
if (_player.playing) MediaControl.pause else MediaControl.play,
MediaControl.skipToNext,
//Stop
MediaControl(
androidIcon: 'drawable/ic_action_stop',
label: 'stop',
action: MediaAction.stop),
],
systemActions: [
MediaAction.seekTo,
MediaAction.seekForward,
MediaAction.seekBackward,
MediaAction.stop
],
processingState: _getProcessingState(),
playing: _player.playing,
position: _player.position,
bufferedPosition: _player.bufferedPosition,
speed: _player.speed);
}
//just_audio state -> audio_service state. If skipping, use _skipState
@ -625,8 +630,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
//Filter duplicate IDs
List<MediaItem> queue = [];
for (MediaItem mi in q) {
if (queue.indexWhere((m) => mi.id == m.id) == -1)
queue.add(mi);
if (queue.indexWhere((m) => mi.id == m.id) == -1) queue.add(mi);
}
this._queue = queue;
AudioServiceBackground.setQueue(queue);
@ -641,16 +645,16 @@ class AudioPlayerTask extends BackgroundAudioTask {
int qi = _queueIndex;
List<AudioSource> sources = [];
for(int i=0; i<_queue.length; i++) {
for (int i = 0; i < _queue.length; i++) {
AudioSource s = await _mediaItemToAudioSource(_queue[i]);
if (s != null)
sources.add(s);
if (s != null) sources.add(s);
}
_audioSource = ConcatenatingAudioSource(children: sources);
//Load in just_audio
try {
await _player.setAudioSource(_audioSource, initialIndex: qi, initialPosition: Duration.zero);
await _player.setAudioSource(_audioSource,
initialIndex: qi, initialPosition: Duration.zero);
} catch (e) {
//Error loading tracks
}
@ -667,7 +671,8 @@ class AudioPlayerTask extends BackgroundAudioTask {
Future _getTrackUrl(MediaItem mediaItem, {int quality}) async {
//Check if offline
String _offlinePath = p.join((await getExternalStorageDirectory()).path, 'offline/');
String _offlinePath =
p.join((await getExternalStorageDirectory()).path, 'offline/');
File f = File(p.join(_offlinePath, mediaItem.id));
if (await f.exists()) {
//return f.path;
@ -676,8 +681,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
}
//Show episode direct link
if (mediaItem.extras['showUrl'] != null)
return mediaItem.extras['showUrl'];
if (mediaItem.extras['showUrl'] != null) return mediaItem.extras['showUrl'];
//Due to current limitations of just_audio, quality fallback moved to DeezerDataSource in ExoPlayer
//This just returns fake url that contains metadata
@ -687,9 +691,10 @@ class AudioPlayerTask extends BackgroundAudioTask {
quality = mobileQuality;
if (conn == ConnectivityResult.wifi) quality = wifiQuality;
if ((playbackDetails??[]).length < 2) return null;
if ((playbackDetails ?? []).length < 2) return null;
//String url = 'https://dzcdn.net/?md5=${playbackDetails[0]}&mv=${playbackDetails[1]}&q=${quality.toString()}#${mediaItem.id}';
String url = 'http://localhost:36958/?q=$quality&mv=${playbackDetails[1]}&md5origin=${playbackDetails[0]}&id=${mediaItem.id}';
String url =
'http://localhost:36958/?q=$quality&mv=${playbackDetails[1]}&md5origin=${playbackDetails[0]}&id=${mediaItem.id}';
return url;
}
@ -705,7 +710,8 @@ class AudioPlayerTask extends BackgroundAudioTask {
break;
//Update queue source
case 'queueSource':
this.queueSource = QueueSource.fromJson(Map<String, dynamic>.from(args));
this.queueSource =
QueueSource.fromJson(Map<String, dynamic>.from(args));
break;
//Looping
case 'repeatType':
@ -726,7 +732,6 @@ class AudioPlayerTask extends BackgroundAudioTask {
_shuffle = true;
_originalQueue = List.from(_queue);
_queue.shuffle();
} else {
_shuffle = false;
_queue = _originalQueue;
@ -746,16 +751,15 @@ class AudioPlayerTask extends BackgroundAudioTask {
//Android audio callback
case 'screenAndroidAuto':
if (_androidAutoCallback != null)
_androidAutoCallback.complete(jsonDecode(args).map<MediaItem>((m) => MediaItem.fromJson(m)).toList());
_androidAutoCallback.complete(jsonDecode(args)
.map<MediaItem>((m) => MediaItem.fromJson(m))
.toList());
break;
//Reorder tracks, args = [old, new]
case 'reorder':
await _audioSource.move(args[0], args[1]);
//Switch in queue
List<MediaItem> newQueue = List.from(_queue);
newQueue.removeAt(args[0]);
newQueue.insert(args[1], _queue[args[0]]);
_queue = newQueue;
_queue.reorder(args[0], args[1]);
//Update UI
AudioServiceBackground.setQueue(_queue);
_broadcastState();
@ -769,20 +773,20 @@ class AudioPlayerTask extends BackgroundAudioTask {
if (_visualizerSubscription != null) break;
_player.startVisualizer(
enableWaveform: false,
enableFft: true,
captureRate: 15000,
captureSize: 128
);
enableWaveform: false,
enableFft: true,
captureRate: 15000,
captureSize: 128);
_visualizerSubscription = _player.visualizerFftStream.listen((event) {
//Calculate actual values
List<double> out = [];
for (int i=0; i<event.length/2; i++) {
int rfk = event[i*2].toSigned(8);
int ifk = event[i*2+1].toSigned(8);
for (int i = 0; i < event.length / 2; i++) {
int rfk = event[i * 2].toSigned(8);
int ifk = event[i * 2 + 1].toSigned(8);
out.add(log(hypot(rfk, ifk) + 1) / 5.2);
}
AudioServiceBackground.sendCustomEvent({"action": "visualizer", "data": out});
AudioServiceBackground.sendCustomEvent(
{"action": "visualizer", "data": out});
});
break;
//Stop visualizer
@ -801,11 +805,12 @@ class AudioPlayerTask extends BackgroundAudioTask {
apiKey: 'b6ab5ae967bcd8b10b23f68f42493829',
apiSecret: '861b0dff9a8a574bec747f9dab8b82bf',
username: username,
passwordHash: password
);
passwordHash: password);
_scrobblenaut = Scrobblenaut(lastFM: lastFM);
_scrobblenautReady = true;
} catch (e) { print(e); }
} catch (e) {
print(e);
}
break;
case 'disableLastFM':
_scrobblenaut = null;
@ -849,15 +854,15 @@ class AudioPlayerTask extends BackgroundAudioTask {
String path = await _getQueuePath();
File f = File(path);
//Create if doesn't exist
if (! await File(path).exists()) {
if (!await File(path).exists()) {
f = await f.create();
}
Map data = {
'index': _queueIndex,
'queue': _queue.map<Map<String, dynamic>>((mi) => mi.toJson()).toList(),
'position': _player.position.inMilliseconds,
'queueSource': (queueSource??QueueSource()).toJson(),
'loopMode': LoopMode.values.indexOf(_loopMode??LoopMode.off)
'queueSource': (queueSource ?? QueueSource()).toJson(),
'loopMode': LoopMode.values.indexOf(_loopMode ?? LoopMode.off)
};
await f.writeAsString(jsonEncode(data));
}
@ -867,11 +872,13 @@ class AudioPlayerTask extends BackgroundAudioTask {
File f = File(await _getQueuePath());
if (await f.exists()) {
Map<String, dynamic> json = jsonDecode(await f.readAsString());
this._queue = (json['queue']??[]).map<MediaItem>((mi) => MediaItem.fromJson(mi)).toList();
this._queue = (json['queue'] ?? [])
.map<MediaItem>((mi) => MediaItem.fromJson(mi))
.toList();
this._queueIndex = json['index'] ?? 0;
this._lastPosition = Duration(milliseconds: json['position']??0);
this.queueSource = QueueSource.fromJson(json['queueSource']??{});
this._loopMode = LoopMode.values[(json['loopMode']??0)];
this._lastPosition = Duration(milliseconds: json['position'] ?? 0);
this.queueSource = QueueSource.fromJson(json['queueSource'] ?? {});
this._loopMode = LoopMode.values[(json['loopMode'] ?? 0)];
//Restore queue
if (_queue != null) {
await AudioServiceBackground.setQueue(_queue);
@ -882,7 +889,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
//Send restored queue source to ui
AudioServiceBackground.sendCustomEvent({
'action': 'onRestore',
'queueSource': (queueSource??QueueSource()).toJson(),
'queueSource': (queueSource ?? QueueSource()).toJson(),
'loopMode': LoopMode.values.indexOf(_loopMode)
});
return true;
@ -895,9 +902,8 @@ class AudioPlayerTask extends BackgroundAudioTask {
_queue.insert(index, mi);
await AudioServiceBackground.setQueue(_queue);
AudioSource _newSource = await _mediaItemToAudioSource(mi);
if (_newSource != null)
await _audioSource.insert(index,_newSource);
AudioSource _newSource = await _mediaItemToAudioSource(mi);
if (_newSource != null) await _audioSource.insert(index, _newSource);
_saveQueue();
}
@ -905,20 +911,17 @@ class AudioPlayerTask extends BackgroundAudioTask {
//Add at end of queue
@override
Future onAddQueueItem(MediaItem mi) async {
if (_queue.indexWhere((m) => m.id == mi.id) != -1)
return;
if (_queue.indexWhere((m) => m.id == mi.id) != -1) return;
_queue.add(mi);
await AudioServiceBackground.setQueue(_queue);
AudioSource _newSource = await _mediaItemToAudioSource(mi);
if (_newSource != null)
await _audioSource.add(_newSource);
AudioSource _newSource = await _mediaItemToAudioSource(mi);
if (_newSource != null) await _audioSource.add(_newSource);
_saveQueue();
}
@override
Future onPlayFromMediaId(String mediaId) async {
//Android auto load tracks
if (mediaId.startsWith(AndroidAuto.prefix)) {
AudioServiceBackground.sendCustomEvent({
@ -931,7 +934,6 @@ class AudioPlayerTask extends BackgroundAudioTask {
//Does the same thing
await this.onSkipToQueueItem(mediaId);
}
}
//Seeker from audio_service example (why reinvent the wheel?)
@ -959,4 +961,4 @@ class Seeker {
void stop() {
_running = false;
}
}
}

View File

@ -7,6 +7,7 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:freezer/api/cache.dart';
import 'package:freezer/api/definitions.dart';
import 'package:freezer/ui/library.dart';
@ -84,36 +85,39 @@ class _FreezerAppState extends State<FreezerApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Freezer',
shortcuts: <LogicalKeySet, Intent>{
...WidgetsApp.defaultShortcuts,
LogicalKeySet(LogicalKeyboardKey.select):
const ActivateIntent(), // DPAD center key, for remote controls
},
theme: settings.themeData,
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: supportedLocales,
home: WillPopScope(
onWillPop: () async {
//For some reason AudioServiceWidget caused the app to freeze after 2 back button presses. "fix"
if (navigatorKey.currentState.canPop()) {
await navigatorKey.currentState.maybePop();
return false;
}
await MoveToBackground.moveTaskToBack();
return false;
return ScreenUtilInit(
designSize: Size(1080, 720),
builder: () => MaterialApp(
title: 'Freezer',
shortcuts: <ShortcutActivator, Intent>{
...WidgetsApp.defaultShortcuts,
LogicalKeySet(LogicalKeyboardKey.select):
const ActivateIntent(), // DPAD center key, for remote controls
},
child: I18n(
initialLocale: _locale(),
child: LoginMainWrapper(),
theme: settings.themeData,
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: supportedLocales,
home: WillPopScope(
onWillPop: () async {
//For some reason AudioServiceWidget caused the app to freeze after 2 back button presses. "fix"
if (navigatorKey.currentState.canPop()) {
await navigatorKey.currentState.maybePop();
return false;
}
await MoveToBackground.moveTaskToBack();
return false;
},
child: I18n(
initialLocale: _locale(),
child: LoginMainWrapper(),
),
),
navigatorKey: mainNavigatorKey,
),
navigatorKey: mainNavigatorKey,
);
}
}
@ -183,7 +187,7 @@ class _MainScreenState extends State<MainScreen>
if (settings.displayMode != null && settings.displayMode >= 0) {
FlutterDisplayMode.supported.then((modes) async {
if (modes.length - 1 >= settings.displayMode)
FlutterDisplayMode.setMode(modes[settings.displayMode]);
FlutterDisplayMode.setPreferredMode(modes[settings.displayMode]);
});
}

View File

@ -6,9 +6,7 @@ import 'package:freezer/translations.i18n.dart';
ImagesDatabase imagesDatabase = ImagesDatabase();
class ImagesDatabase {
/*
!!! Using the wrappers so i don't have to rewrite most of the code, because of migration to cached network image
*/
@ -28,9 +26,10 @@ class ImagesDatabase {
Future<bool> isDark(String url) async {
PaletteGenerator paletteGenerator = await getPaletteGenerator(url);
return paletteGenerator.colors.first.computeLuminance() > 0.5 ? false : true;
return paletteGenerator.colors.first.computeLuminance() > 0.5
? false
: true;
}
}
class CachedImage extends StatefulWidget {
@ -41,7 +40,15 @@ class CachedImage extends StatefulWidget {
final bool fullThumb;
final bool rounded;
const CachedImage({Key key, this.url, this.height, this.width, this.circular = false, this.fullThumb = false, this.rounded = false}): super(key: key);
const CachedImage(
{Key key,
this.url,
this.height,
this.width,
this.circular = false,
this.fullThumb = false,
this.rounded = false})
: super(key: key);
@override
_CachedImageState createState() => _CachedImageState();
@ -50,15 +57,28 @@ class CachedImage extends StatefulWidget {
class _CachedImageState extends State<CachedImage> {
@override
Widget build(BuildContext context) {
if (widget.rounded)
return ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: CachedImage(
url: widget.url,
height: widget.height,
width: widget.width,
circular: false,
rounded: false,
fullThumb: widget.fullThumb),
);
if (widget.rounded) return ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: CachedImage(url: widget.url, height: widget.height, width: widget.width, circular: false, rounded: false, fullThumb: widget.fullThumb),
);
if (widget.circular) return ClipOval(
child: CachedImage(url: widget.url, height: widget.height, width: widget.width, circular: false, rounded: false, fullThumb: widget.fullThumb,)
);
if (widget.circular)
return ClipOval(
child: CachedImage(
url: widget.url,
height: widget.height,
width: widget.width,
circular: false,
rounded: false,
fullThumb: widget.fullThumb,
));
if (!widget.url.startsWith('http'))
return Image.asset(
@ -72,10 +92,19 @@ class _CachedImageState extends State<CachedImage> {
width: widget.width,
height: widget.height,
placeholder: (context, url) {
if (widget.fullThumb) return Image.asset('assets/cover.jpg', width: widget.width, height: widget.height,);
return Image.asset('assets/cover_thumb.jpg', width: widget.width, height: widget.height);
if (widget.fullThumb)
return Image.asset(
'assets/cover.jpg',
width: widget.width,
height: widget.height,
);
return Image.asset('assets/cover_thumb.jpg',
width: widget.width, height: widget.height);
},
errorWidget: (context, url, error) => Image.asset('assets/cover_thumb.jpg', width: widget.width, height: widget.height),
errorWidget: (context, url, error) => Image.asset(
'assets/cover_thumb.jpg',
width: widget.width,
height: widget.height),
);
}
}
@ -92,51 +121,50 @@ class ZoomableImage extends StatefulWidget {
}
class _ZoomableImageState extends State<ZoomableImage> {
BuildContext ctx;
PhotoViewController controller;
bool photoViewOpened = false;
@override
void initState() {
super.initState();
controller = PhotoViewController()
..outputStateStream.listen(listener);
controller = PhotoViewController()..outputStateStream.listen(listener);
}
// Listener of PhotoView scale changes. Used for closing PhotoView by pinch-in
void listener(PhotoViewControllerValue value) {
if (value.scale < 0.16 && photoViewOpened) {
Navigator.pop(ctx);
photoViewOpened = false; // to avoid multiple pop() when picture are being scaled out too slowly
Navigator.pop(context);
photoViewOpened =
false; // to avoid multiple pop() when picture are being scaled out too slowly
}
}
@override
Widget build(BuildContext context) {
ctx = context;
return TextButton(
child: Semantics(
child: CachedImage(
url: widget.url,
rounded: widget.rounded,
width: widget.width,
fullThumb: true,
),
label: "Album art".i18n,
return GestureDetector(
child: Semantics(
child: CachedImage(
url: widget.url,
rounded: widget.rounded,
width: widget.width,
fullThumb: true,
),
onPressed: () {
Navigator.of(context).push(PageRouteBuilder(
opaque: false, // transparent background
pageBuilder: (context, a, b) {
photoViewOpened = true;
return PhotoView(
imageProvider: CachedNetworkImageProvider(widget.url),
maxScale: 8.0,
minScale: 0.2,
controller: controller,
backgroundDecoration:
BoxDecoration(color: Color.fromARGB(0x90, 0, 0, 0)));
}));
});
label: "Album art".i18n,
),
onTap: () {
Navigator.of(context).push(PageRouteBuilder(
opaque: false, // transparent background
pageBuilder: (context, _, __) {
photoViewOpened = true;
return PhotoView(
imageProvider: CachedNetworkImageProvider(widget.url),
maxScale: 8.0,
minScale: 0.2,
controller: controller,
backgroundDecoration:
BoxDecoration(color: Color.fromARGB(0x90, 0, 0, 0)));
}));
},
);
}
}
}

View File

@ -13,13 +13,9 @@ import '../settings.dart';
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SafeArea(child: Container()),
Flexible(child: HomePageScreen(),)
],
return SafeArea(
child: SingleChildScrollView(
child: HomePageScreen(),
),
);
}
@ -40,10 +36,7 @@ class FreezerTitle extends StatelessWidget {
Image.asset('assets/icon.png', width: 64, height: 64),
Text(
'freezer',
style: TextStyle(
fontSize: 56,
fontWeight: FontWeight.w900
),
style: TextStyle(fontSize: 56, fontWeight: FontWeight.w900),
)
],
)
@ -53,20 +46,16 @@ class FreezerTitle extends StatelessWidget {
}
}
class HomePageScreen extends StatefulWidget {
final HomePage homePage;
final DeezerChannel channel;
HomePageScreen({this.homePage, this.channel, Key key}): super(key: key);
HomePageScreen({this.homePage, this.channel, Key key}) : super(key: key);
@override
_HomePageScreenState createState() => _HomePageScreenState();
}
class _HomePageScreenState extends State<HomePageScreen> {
HomePage _homePage;
bool _cancel = false;
bool _error = false;
@ -84,6 +73,7 @@ class _HomePageScreenState extends State<HomePageScreen> {
}
setState(() => _homePage = _hp);
}
void _loadHomePage() async {
//Load local
try {
@ -113,7 +103,8 @@ class _HomePageScreenState extends State<HomePageScreen> {
_loadHomePage();
return;
}
if (widget.homePage.sections == null || widget.homePage.sections.length == 0) {
if (widget.homePage.sections == null ||
widget.homePage.sections.length == 0) {
_loadHomePage();
return;
}
@ -136,91 +127,85 @@ class _HomePageScreenState extends State<HomePageScreen> {
@override
Widget build(BuildContext context) {
if (_homePage == null)
return Center(child: Padding(
return Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: CircularProgressIndicator(),
));
if (_error)
return ErrorScreen();
if (_error) return ErrorScreen();
return Column(
children: List.generate(_homePage.sections.length, (i) {
switch (_homePage.sections[i].layout) {
case HomePageSectionLayout.ROW:
return HomepageRowSection(_homePage.sections[i]);
case HomePageSectionLayout.GRID:
return HomePageGridSection(_homePage.sections[i]);
default:
return HomepageRowSection(_homePage.sections[i]);
}
},
children: List.generate(
_homePage.sections.length,
(i) {
switch (_homePage.sections[i].layout) {
case HomePageSectionLayout.ROW:
return HomepageRowSection(_homePage.sections[i]);
case HomePageSectionLayout.GRID:
return HomePageGridSection(_homePage.sections[i]);
default:
return HomepageRowSection(_homePage.sections[i]);
}
},
));
}
}
class HomepageRowSection extends StatelessWidget {
final HomePageSection section;
HomepageRowSection(this.section);
@override
Widget build(BuildContext context) {
return ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 4.0, vertical: 2.0),
title: Padding(
padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 6.0),
child: Text(
section.title??'',
textAlign: TextAlign.left,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.w900
contentPadding: EdgeInsets.symmetric(horizontal: 4.0, vertical: 2.0),
title: Padding(
padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 6.0),
child: Text(
section.title ?? '',
textAlign: TextAlign.left,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: 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: TextStyle(
fontSize: 20.0
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: TextStyle(fontSize: 20.0),
),
),
onPressed: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) => Scaffold(
appBar: FreezerAppBar(section.title),
body: SingleChildScrollView(
child: HomePageScreen(
channel: DeezerChannel(target: section.pagePath)
)
onPressed: () =>
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => Scaffold(
appBar: FreezerAppBar(section.title),
body: SingleChildScrollView(
child: HomePageScreen(
channel:
DeezerChannel(target: section.pagePath))),
),
),
)),
);
)),
);
}
return Container(height: 0, width: 0);
}
return Container(height: 0, width: 0);
}
//Show item
HomePageItem item = section.items[j];
return HomePageItemWidget(item);
}),
),
)
);
//Show item
HomePageItem item = section.items[j];
return HomePageItemWidget(item);
}),
),
));
}
}
class HomePageGridSection extends StatelessWidget {
final HomePageSection section;
HomePageGridSection(this.section);
@ -231,20 +216,16 @@ class HomePageGridSection extends StatelessWidget {
title: Padding(
padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 6.0),
child: Text(
section.title??'',
section.title ?? '',
textAlign: TextAlign.left,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.w900
),
style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.w900),
),
),
subtitle: Wrap(
alignment: WrapAlignment.spaceAround,
children: List.generate(section.items.length, (i) {
//Item
return HomePageItemWidget(section.items[i]);
}),
@ -253,17 +234,12 @@ class HomePageGridSection extends StatelessWidget {
}
}
class HomePageItemWidget extends StatelessWidget {
final HomePageItem item;
HomePageItemWidget(this.item);
@override
Widget build(BuildContext context) {
switch (item.type) {
case HomePageItemType.SMARTTRACKLIST:
return SmartTrackListTile(
@ -277,8 +253,7 @@ class HomePageItemWidget extends StatelessWidget {
item.value,
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => AlbumDetails(item.value)
));
builder: (context) => AlbumDetails(item.value)));
},
onHold: () {
MenuSheet m = MenuSheet(context);
@ -290,8 +265,7 @@ class HomePageItemWidget extends StatelessWidget {
item.value,
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ArtistDetails(item.value)
));
builder: (context) => ArtistDetails(item.value)));
},
onHold: () {
MenuSheet m = MenuSheet(context);
@ -303,8 +277,7 @@ class HomePageItemWidget extends StatelessWidget {
item.value,
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => PlaylistDetails(item.value)
));
builder: (context) => PlaylistDetails(item.value)));
},
onHold: () {
MenuSheet m = MenuSheet(context);
@ -317,12 +290,12 @@ class HomePageItemWidget extends StatelessWidget {
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => Scaffold(
appBar: FreezerAppBar(item.value.title.toString()),
body: SingleChildScrollView(
child: HomePageScreen(channel: item.value,)
),
)
));
appBar: FreezerAppBar(item.value.title.toString()),
body: SingleChildScrollView(
child: HomePageScreen(
channel: item.value,
)),
)));
},
);
case HomePageItemType.SHOW:
@ -330,8 +303,7 @@ class HomePageItemWidget extends StatelessWidget {
item.value,
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ShowScreen(item.value)
));
builder: (context) => ShowScreen(item.value)));
},
);
}

View File

@ -164,7 +164,7 @@ class _LoginWidgetState extends State<LoginWidget> {
if (event.logicalKey == LogicalKeyboardKey.select) {
goARL(node, _controller);
}
return true;
return KeyEventResult.handled;
});
if (settings.arl == null)
return Scaffold(
@ -271,7 +271,7 @@ class _LoginWidgetState extends State<LoginWidget> {
child: Text('Open in browser'.i18n),
onPressed: () {
InAppBrowser.openWithSystemBrowser(
url: 'https://deezer.com/register');
url: Uri.parse('https://deezer.com/register'));
},
),
),
@ -301,40 +301,34 @@ class LoginBrowser extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Expanded(
child: Container(
child: InAppWebView(
initialUrl: 'https://deezer.com/login',
onLoadStart:
(InAppWebViewController controller, String url) async {
//Offers URL
if (!url.contains('/login') && !url.contains('/register')) {
controller.evaluateJavascript(
source: 'window.location.href = "/open_app"');
}
//Parse arl from url
if (url.startsWith('intent://deezer.page.link')) {
try {
//Parse url
Uri uri = Uri.parse(url);
//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;
Navigator.of(context).pop();
updateParent();
}
} catch (e) {}
}
},
),
),
),
],
return SafeArea(
child: InAppWebView(
initialUrlRequest:
URLRequest(url: Uri.parse('https://deezer.com/login')),
onLoadStart: (InAppWebViewController controller, Uri uri) async {
//Offers URL
if (!uri.path.contains('/login') && !uri.path.contains('/register')) {
controller.evaluateJavascript(
source: 'window.location.href = "/open_app"');
}
print('scheme ${uri.scheme}, host: ${uri.host}');
//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;
Navigator.of(context).pop();
updateParent();
}
} catch (e) {
print(e);
}
}
},
),
);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -66,11 +66,6 @@ class PlayerBar extends StatelessWidget {
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) =>
PlayerScreen()));
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle(
systemNavigationBarColor:
settings.themeData.scaffoldBackgroundColor,
));
},
leading: CachedImage(
width: 50,
@ -125,7 +120,6 @@ class PrevNextButton extends StatelessWidget {
final double size;
final bool prev;
final bool hidePrev;
int i;
PrevNextButton(this.size, {this.prev = false, this.hidePrev = false});
@override
@ -134,53 +128,27 @@ class PrevNextButton extends StatelessWidget {
stream: AudioService.queueStream,
builder: (context, _snapshot) {
if (!prev) {
if (playerHelper.queueIndex ==
(AudioService.queue ?? []).length - 1) {
return IconButton(
icon: Icon(
Icons.skip_next,
semanticLabel: "Play next".i18n,
),
iconSize: size,
onPressed: null,
);
}
return IconButton(
icon: Icon(
Icons.skip_next,
semanticLabel: "Play next".i18n,
),
iconSize: size,
onPressed: () => AudioService.skipToNext(),
onPressed:
playerHelper.queueIndex == (AudioService.queue ?? []).length - 1
? null
: () => AudioService.skipToNext(),
);
}
if (prev) {
if (i == 0) {
if (hidePrev) {
return Container(
height: 0,
width: 0,
);
}
return IconButton(
icon: Icon(
Icons.skip_previous,
semanticLabel: "Play previous".i18n,
),
iconSize: size,
onPressed: null,
);
}
return IconButton(
icon: Icon(
Icons.skip_previous,
semanticLabel: "Play previous".i18n,
),
iconSize: size,
onPressed: () => AudioService.skipToPrevious(),
);
}
return Container();
if (hidePrev) return const SizedBox(width: 0.0, height: 0.0);
return IconButton(
icon: Icon(
Icons.skip_previous,
semanticLabel: "Play previous".i18n,
),
iconSize: size,
onPressed: () => AudioService.skipToPrevious(),
);
},
);
}

View File

@ -1,9 +1,11 @@
import 'dart:math';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:audio_service/audio_service.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/screenutil.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:freezer/api/cache.dart';
import 'package:freezer/api/deezer.dart';
@ -16,7 +18,6 @@ import 'package:freezer/ui/lyrics.dart';
import 'package:freezer/ui/menu.dart';
import 'package:freezer/ui/settings_screen.dart';
import 'package:freezer/ui/tiles.dart';
import 'package:async/async.dart';
import 'package:just_audio/just_audio.dart';
import 'package:marquee/marquee.dart';
import 'package:palette_generator/palette_generator.dart';
@ -36,6 +37,8 @@ bool pageViewLock = false;
Function updateColor;
class PlayerScreen extends StatefulWidget {
static const _blurStrength = 50.0;
@override
_PlayerScreenState createState() => _PlayerScreenState();
}
@ -43,41 +46,26 @@ class PlayerScreen extends StatefulWidget {
class _PlayerScreenState extends State<PlayerScreen> {
LinearGradient _bgGradient;
StreamSubscription _mediaItemSub;
StreamSubscription _playerStateSub;
ImageProvider _blurImage;
bool _wasConnected = true;
//Calculate background color
Future _updateColor() async {
if (!settings.colorGradientBackground && !settings.blurPlayerBackground)
return;
final imageProvider = CachedNetworkImageProvider(
AudioService.currentMediaItem.extras['thumb'] ??
AudioService.currentMediaItem.artUri);
//BG Image
if (settings.blurPlayerBackground)
setState(() {
_blurImage = NetworkImage(
AudioService.currentMediaItem.extras['thumb'] ??
AudioService.currentMediaItem.artUri);
});
setState(() => _blurImage = imageProvider);
//Run in isolate
PaletteGenerator palette = await PaletteGenerator.fromImageProvider(
CachedNetworkImageProvider(
AudioService.currentMediaItem.extras['thumb'] ??
AudioService.currentMediaItem.artUri));
if (settings.colorGradientBackground) {
//Run in isolate
PaletteGenerator palette =
await PaletteGenerator.fromImageProvider(imageProvider);
//Update notification
if (settings.blurPlayerBackground)
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor: palette.dominantColor.color.withOpacity(0.25),
systemNavigationBarColor: Color.alphaBlend(
palette.dominantColor.color.withOpacity(0.25),
Theme.of(context).scaffoldBackgroundColor),
systemNavigationBarIconBrightness: Brightness.dark));
//Color gradient
if (!settings.blurPlayerBackground) {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor: palette.dominantColor.color.withOpacity(0.7),
statusBarIconBrightness: Brightness.dark));
setState(() => _bgGradient = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
@ -92,12 +80,23 @@ class _PlayerScreenState extends State<PlayerScreen> {
}
}
void _playbackStateChanged() {
if (AudioService.currentMediaItem == null) {
playerHelper.startService();
setState(() => _wasConnected = false);
} else if (!_wasConnected) setState(() => _wasConnected = true);
}
@override
void initState() {
Future.delayed(Duration(milliseconds: 600), _updateColor);
_playbackStateChanged();
_mediaItemSub = AudioService.currentMediaItemStream.listen((event) {
_playbackStateChanged();
_updateColor();
});
_playerStateSub =
AudioService.playbackStateStream.listen((_) => _playbackStateChanged());
updateColor = this._updateColor;
super.initState();
@ -105,71 +104,66 @@ class _PlayerScreenState extends State<PlayerScreen> {
@override
void dispose() {
if (_mediaItemSub != null) _mediaItemSub.cancel();
//Fix bottom buttons
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
systemNavigationBarColor: settings.themeData.bottomAppBarColor,
statusBarColor: Colors.transparent));
_mediaItemSub.cancel();
_playerStateSub.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
//Responsive
ScreenUtil.init(context, allowFontScaling: true);
return Scaffold(
body: SafeArea(
child: Container(
decoration: BoxDecoration(
gradient:
settings.blurPlayerBackground ? null : _bgGradient),
child: Stack(
children: [
if (settings.blurPlayerBackground && _blurImage != null)
ClipRect(
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: _blurImage,
fit: BoxFit.fill,
colorFilter: ColorFilter.mode(
Colors.black.withOpacity(0.25),
BlendMode.dstATop))),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20),
child: Container(color: Colors.transparent),
),
),
),
StreamBuilder(
stream: StreamZip([
AudioService.playbackStateStream,
AudioService.currentMediaItemStream
]),
builder: (BuildContext context, AsyncSnapshot snapshot) {
//When disconnected
if (AudioService.currentMediaItem == null) {
playerHelper.startService();
return Center(
child: CircularProgressIndicator(),
);
}
return OrientationBuilder(
builder: (context, orientation) {
//Landscape
if (orientation == Orientation.landscape) {
return PlayerScreenHorizontal();
}
//Portrait
return PlayerScreenVertical();
},
);
},
final hasBackground =
settings.blurPlayerBackground || settings.colorGradientBackground;
final color = hasBackground
? Colors.transparent
: Theme.of(context).scaffoldBackgroundColor;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarColor: color,
statusBarBrightness: Brightness.light,
statusBarIconBrightness: Brightness.light,
systemNavigationBarIconBrightness: Brightness.light,
systemNavigationBarColor: color,
systemNavigationBarDividerColor: color,
),
child: Stack(
children: [
if (hasBackground)
Positioned.fill(
child: ImageFiltered(
imageFilter: ImageFilter.blur(
sigmaX: PlayerScreen._blurStrength,
sigmaY: PlayerScreen._blurStrength,
tileMode: TileMode.mirror),
child: DecoratedBox(
decoration: BoxDecoration(
gradient: _bgGradient,
image: _blurImage == null
? null
: DecorationImage(
image: _blurImage,
fit: BoxFit.cover,
colorFilter: ColorFilter.mode(
Colors.white.withOpacity(0.5),
BlendMode.dstATop))),
),
),
),
Scaffold(
backgroundColor: hasBackground ? Colors.transparent : null,
body: _wasConnected
? SafeArea(
child: OrientationBuilder(
builder: (context, orientation) =>
orientation == Orientation.landscape
? PlayerScreenHorizontal()
: PlayerScreenVertical(),
),
],
))));
)
: Center(child: CircularProgressIndicator()),
),
],
),
);
}
}
@ -239,9 +233,7 @@ class _PlayerScreenHorizontalState extends State<PlayerScreenHorizontal> {
fontSize: ScreenUtil().setSp(40),
fontWeight: FontWeight.bold),
)),
Container(
height: 4,
),
const SizedBox(height: 4.0),
Text(
AudioService.currentMediaItem.displaySubtitle ?? '',
maxLines: 1,
@ -311,17 +303,18 @@ class _PlayerScreenVerticalState extends State<PlayerScreenVertical> {
padding: EdgeInsets.fromLTRB(30, 4, 16, 0),
child: PlayerScreenTopRow()),
Padding(
padding: EdgeInsets.fromLTRB(16, 0, 16, 0),
child: Container(
height: ScreenUtil().setHeight(1000),
child: Stack(
children: <Widget>[
BigAlbumArt(),
],
),
padding: EdgeInsets.symmetric(horizontal: 16.0),
child: SizedBox(
child: BigAlbumArt(),
width: min(MediaQuery.of(context).size.width,
MediaQuery.of(context).size.height) *
0.9,
height: min(MediaQuery.of(context).size.width,
MediaQuery.of(context).size.height) *
0.9,
),
),
Container(height: 4.0),
const SizedBox(height: 4.0),
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
@ -346,9 +339,7 @@ class _PlayerScreenVerticalState extends State<PlayerScreenVertical> {
fontSize: ScreenUtil().setSp(64),
fontWeight: FontWeight.bold),
)),
Container(
height: 4,
),
const SizedBox(height: 4),
Text(
AudioService.currentMediaItem.displaySubtitle ?? '',
maxLines: 1,
@ -514,6 +505,7 @@ class RepeatButton extends StatefulWidget {
}
class _RepeatButtonState extends State<RepeatButton> {
// ignore: missing_return
Icon get repeatIcon {
switch (playerHelper.repeatType) {
case LoopMode.off:
@ -676,8 +668,11 @@ class _BigAlbumArtState extends State<BigAlbumArt> {
if (_animationLock) return;
AudioService.skipToQueueItem(AudioService.queue[index].id);
},
children: List.generate(AudioService.queue.length,
(i) => ZoomableImage(url: AudioService.queue[i].artUri)),
children: List.generate(
AudioService.queue.length,
(i) => ZoomableImage(
url: AudioService.queue[i].artUri,
)),
),
);
}
@ -720,17 +715,8 @@ class PlayerScreenTopRow extends StatelessWidget {
),
iconSize: this.iconSize ?? ScreenUtil().setSp(52),
splashRadius: this.iconSize ?? ScreenUtil().setWidth(52),
onPressed: () async {
//Fix bottom buttons
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
systemNavigationBarColor: settings.themeData.bottomAppBarColor,
statusBarColor: Colors.transparent));
//Navigate
await Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => QueueScreen()));
//Fix colors
updateColor();
},
onPressed: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => QueueScreen())),
),
],
);
@ -743,11 +729,10 @@ class SeekBar extends StatefulWidget {
}
class _SeekBarState extends State<SeekBar> {
bool _seeking = false;
double _pos;
double get position {
if (_seeking) return _pos;
if (_pos != null) return _pos;
if (AudioService.playbackState == null) return 0.0;
double p =
AudioService.playbackState.currentPosition.inMilliseconds.toDouble() ??
@ -776,7 +761,7 @@ class _SeekBarState extends State<SeekBar> {
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(vertical: 0.0, horizontal: 24.0),
padding: EdgeInsets.symmetric(horizontal: 24.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
@ -791,35 +776,30 @@ class _SeekBarState extends State<SeekBar> {
],
),
),
Container(
height: 32.0,
child: Slider(
focusNode: FocusNode(
canRequestFocus: false,
skipTraversal:
true), // Don't focus on Slider - it doesn't work (and not needed)
value: position,
max: duration,
onChangeStart: (double d) {
setState(() {
_seeking = true;
_pos = d;
});
},
onChanged: (double d) {
setState(() {
_pos = d;
});
},
onChangeEnd: (double d) async {
await AudioService.seekTo(Duration(milliseconds: d.round()));
setState(() {
_pos = d;
_seeking = false;
});
},
),
)
Slider(
focusNode: FocusNode(
canRequestFocus: false,
skipTraversal:
true), // Don't focus on Slider - it doesn't work (and not needed)
value: position,
max: duration,
onChangeStart: (double d) {
setState(() {
_pos = d;
});
},
onChanged: (double d) {
setState(() {
_pos = d;
});
},
onChangeEnd: (double d) async {
await AudioService.seekTo(Duration(milliseconds: d.round()));
setState(() {
_pos = null;
});
},
),
],
);
},
@ -835,9 +815,15 @@ class QueueScreen extends StatefulWidget {
class _QueueScreenState extends State<QueueScreen> {
StreamSubscription _queueSub;
/// Basically a simple list that keeps itself synchronized with [AudioService.queue],
/// so that the [ReorderableListView] is updated instanly (as it should be)
List<MediaItem> _queueCache = [];
@override
void initState() {
_queueCache = AudioService.queue;
_queueSub = AudioService.queueStream.listen((event) {
_queueCache = AudioService.queue;
setState(() {});
});
super.initState();
@ -870,28 +856,27 @@ class _QueueScreenState extends State<QueueScreen> {
body: ReorderableListView.builder(
onReorder: (int oldIndex, int newIndex) {
if (oldIndex == playerHelper.queueIndex) return;
playerHelper
.reorder(oldIndex, newIndex)
.then((value) => setState(() => null));
setState(() => _queueCache.reorder(oldIndex, newIndex));
playerHelper.reorder(oldIndex, newIndex);
},
itemCount: AudioService.queue.length,
itemCount: _queueCache.length,
itemBuilder: (BuildContext context, int i) {
Track t = Track.fromMediaItem(AudioService.queue[i]);
Track track = Track.fromMediaItem(AudioService.queue[i]);
return TrackTile(
t,
track,
onTap: () {
pageViewLock = true;
AudioService.skipToQueueItem(t.id)
AudioService.skipToQueueItem(track.id)
.then((value) => Navigator.of(context).pop());
},
key: Key(i.toString()),
key: Key(track.id),
trailing: IconButton(
icon: Icon(
Icons.close,
semanticLabel: "Close".i18n,
),
onPressed: () async {
await AudioService.removeQueueItem(t.toMediaItem());
await AudioService.removeQueueItem(track.toMediaItem());
setState(() {});
},
),

File diff suppressed because it is too large Load Diff

View File

@ -324,7 +324,8 @@ class _AppearanceSettingsState extends State<AppearanceSettings> {
onPressed: () async {
settings.displayMode = i;
await settings.save();
await FlutterDisplayMode.setMode(modes[i]);
await FlutterDisplayMode.setPreferredMode(
modes[i]);
Navigator.of(context).pop();
},
)));

View File

@ -16,14 +16,12 @@ import 'dart:convert';
import 'package:version/version.dart';
class UpdaterScreen extends StatefulWidget {
@override
_UpdaterScreenState createState() => _UpdaterScreenState();
}
class _UpdaterScreenState extends State<UpdaterScreen> {
bool _loading = true;
bool _error = false;
FreezerVersions _versions;
@ -39,8 +37,7 @@ class _UpdaterScreenState extends State<UpdaterScreen> {
//Get architecture
_arch = await DownloadManager.platform.invokeMethod("arch");
if (_arch == 'armv8l')
_arch = 'arm32';
if (_arch == 'armv8l') _arch = 'arm32';
//Load from website
try {
@ -57,23 +54,27 @@ class _UpdaterScreenState extends State<UpdaterScreen> {
}
FreezerDownload get _versionDownload {
return _versions.versions[0].downloads.firstWhere((d) => d.version.toLowerCase().contains(_arch.toLowerCase()), orElse: () => null);
return _versions.versions[0].downloads.firstWhere(
(d) => d.version.toLowerCase().contains(_arch.toLowerCase()),
orElse: () => null);
}
Future _download() async {
String url = _versionDownload.directUrl;
//Start request
http.Client client = new http.Client();
http.StreamedResponse res = await client.send(http.Request('GET', Uri.parse(url)));
http.StreamedResponse res =
await client.send(http.Request('GET', Uri.parse(url)));
int size = res.contentLength;
//Open file
String path = p.join((await getExternalStorageDirectory()).path, 'update.apk');
String path =
p.join((await getExternalStorageDirectory()).path, 'update.apk');
File file = File(path);
IOSink fileSink = file.openWrite();
//Update progress
Future.doWhile(() async {
int received = await file.length();
setState(() => _progress = received/size);
setState(() => _progress = received / size);
return received != size;
});
//Pipe
@ -84,7 +85,6 @@ class _UpdaterScreenState extends State<UpdaterScreen> {
setState(() => _buttonEnabled = true);
}
@override
void initState() {
_load();
@ -94,104 +94,92 @@ class _UpdaterScreenState extends State<UpdaterScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: FreezerAppBar('Updates'.i18n),
body: ListView(
children: [
if (_error)
ErrorScreen(),
if (_loading)
Padding(
padding: EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [CircularProgressIndicator()],
),
),
if (!_error && !_loading && Version.parse(_versions.latest) <= Version.parse(_current))
Center(
child: Padding(
appBar: FreezerAppBar('Updates'.i18n),
body: ListView(
children: [
if (_error) ErrorScreen(),
if (_loading)
Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'You are running latest version!'.i18n,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 26.0
)
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [CircularProgressIndicator()],
),
)
),
if (!_error && !_loading && Version.parse(_versions.latest) > Version.parse(_current))
Column(
children: [
Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'New update available!'.i18n + ' ' + _versions.latest,
),
if (!_error &&
!_loading &&
Version.parse(_versions.latest) <= Version.parse(_current))
Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text('You are running latest version!'.i18n,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold
style: TextStyle(fontSize: 26.0)),
)),
if (!_error &&
!_loading &&
Version.parse(_versions.latest) > Version.parse(_current))
Column(
children: [
Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'New update available!'.i18n + ' ' + _versions.latest,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20.0, fontWeight: FontWeight.bold),
),
),
),
Text(
'Current version: ' + _current,
style: TextStyle(
fontSize: 14.0,
fontStyle: FontStyle.italic
),
),
Container(height: 8.0),
FreezerDivider(),
Container(height: 8.0),
Text(
'Changelog',
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold
),
),
Padding(
padding: EdgeInsets.fromLTRB(16, 4, 16, 8),
child: Text(
_versions.versions[0].changelog,
style: TextStyle(fontSize: 16.0),
),
),
FreezerDivider(),
Container(height: 8.0),
//Available download
if (_versionDownload != null)
Column(children: [
ElevatedButton(
child: Text('Download'.i18n + ' (${_versionDownload.version})'),
onPressed: _buttonEnabled ? () {
setState(() => _buttonEnabled = false);
_download();
} : null
),
Padding(
padding: EdgeInsets.all(8.0),
child: LinearProgressIndicator(value: _progress),
)
]),
//Unsupported arch
if (_versionDownload == null)
Text(
'Unsupported platform!'.i18n + ' $_arch',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 16.0),
)
],
)
],
)
);
'Current version: ' + _current,
style:
TextStyle(fontSize: 14.0, fontStyle: FontStyle.italic),
),
Container(height: 8.0),
FreezerDivider(),
Container(height: 8.0),
Text(
'Changelog',
style:
TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold),
),
Padding(
padding: EdgeInsets.fromLTRB(16, 4, 16, 8),
child: Text(
_versions.versions[0].changelog,
style: TextStyle(fontSize: 16.0),
),
),
FreezerDivider(),
Container(height: 8.0),
//Available download
if (_versionDownload != null)
Column(children: [
ElevatedButton(
child: Text('Download'.i18n +
' (${_versionDownload.version})'),
onPressed: _buttonEnabled
? () {
setState(() => _buttonEnabled = false);
_download();
}
: null),
Padding(
padding: EdgeInsets.all(8.0),
child: LinearProgressIndicator(value: _progress),
)
]),
//Unsupported arch
if (_versionDownload == null)
Text(
'Unsupported platform!'.i18n + ' $_arch',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 16.0),
)
],
)
],
));
}
}
@ -202,13 +190,15 @@ class FreezerVersions {
FreezerVersions({this.latest, this.versions});
factory FreezerVersions.fromJson(Map data) => FreezerVersions(
latest: data['android']['latest'],
versions: data['android']['versions'].map<FreezerVersion>((v) => FreezerVersion.fromJson(v)).toList()
);
latest: data['android']['latest'],
versions: data['android']['versions']
.map<FreezerVersion>((v) => FreezerVersion.fromJson(v))
.toList());
//Fetch from website API
static Future<FreezerVersions> fetch() async {
http.Response response = await http.get('https://freezer.life/api/versions');
http.Response response =
await http.get('https://freezer.life/api/versions');
// http.Response response = await http.get('https://cum.freezerapp.workers.dev/api/versions');
return FreezerVersions.fromJson(jsonDecode(response.body));
}
@ -216,7 +206,8 @@ class FreezerVersions {
static Future checkUpdate() async {
//Check only each 24h
int updateDelay = 86400000;
if ((DateTime.now().millisecondsSinceEpoch - (cache.lastUpdateCheck??0)) < updateDelay) return;
if ((DateTime.now().millisecondsSinceEpoch - (cache.lastUpdateCheck ?? 0)) <
updateDelay) return;
cache.lastUpdateCheck = DateTime.now().millisecondsSinceEpoch;
await cache.save();
@ -228,19 +219,28 @@ class FreezerVersions {
//Get architecture
String _arch = await DownloadManager.platform.invokeMethod("arch");
if (_arch == 'armv8l')
_arch = 'arm32';
if (_arch == 'armv8l') _arch = 'arm32';
//Check compatible architecture
if (versions.versions[0].downloads.firstWhere((d) => d.version.toLowerCase().contains(_arch.toLowerCase()), orElse: () => null) == null) return;
if (versions.versions[0].downloads.firstWhere(
(d) => d.version.toLowerCase().contains(_arch.toLowerCase()),
orElse: () => null) ==
null) return;
//Show notification
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
const AndroidInitializationSettings androidInitializationSettings = AndroidInitializationSettings('drawable/ic_logo');
final InitializationSettings initializationSettings = InitializationSettings(androidInitializationSettings, null);
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
const AndroidInitializationSettings androidInitializationSettings =
AndroidInitializationSettings('drawable/ic_logo');
final InitializationSettings initializationSettings =
InitializationSettings(android: androidInitializationSettings);
await flutterLocalNotificationsPlugin.initialize(initializationSettings);
AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails('freezerupdates', 'Freezer Updates'.i18n, 'Freezer Updates'.i18n);
NotificationDetails notificationDetails = NotificationDetails(androidNotificationDetails, null);
await flutterLocalNotificationsPlugin.show(0, 'New update available!'.i18n, 'Update to latest version in the settings.'.i18n, notificationDetails);
AndroidNotificationDetails androidNotificationDetails =
AndroidNotificationDetails(
'freezerupdates', 'Freezer Updates'.i18n, 'Freezer Updates'.i18n);
NotificationDetails notificationDetails =
NotificationDetails(android: androidNotificationDetails);
await flutterLocalNotificationsPlugin.show(0, 'New update available!'.i18n,
'Update to latest version in the settings.'.i18n, notificationDetails);
}
}
@ -252,10 +252,11 @@ class FreezerVersion {
FreezerVersion({this.version, this.changelog, this.downloads});
factory FreezerVersion.fromJson(Map data) => FreezerVersion(
version: data['version'],
changelog: data['changelog'],
downloads: data['downloads'].map<FreezerDownload>((d) => FreezerDownload.fromJson(d)).toList()
);
version: data['version'],
changelog: data['changelog'],
downloads: data['downloads']
.map<FreezerDownload>((d) => FreezerDownload.fromJson(d))
.toList());
}
class FreezerDownload {
@ -265,7 +266,5 @@ class FreezerDownload {
FreezerDownload({this.version, this.directUrl});
factory FreezerDownload.fromJson(Map data) => FreezerDownload(
version: data['version'],
directUrl: data['links'].first['url']
);
version: data['version'], directUrl: data['links'].first['url']);
}

View File

@ -28,7 +28,7 @@ packages:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.6.1"
version: "2.7.0"
audio_service:
dependency: "direct main"
description:
@ -105,7 +105,7 @@ packages:
name: built_value
url: "https://pub.dartlang.org"
source: hosted
version: "8.1.1"
version: "8.1.2"
cached_network_image:
dependency: "direct main"
description:
@ -126,7 +126,7 @@ packages:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.3.1"
checked_yaml:
dependency: transitive
description:
@ -168,28 +168,28 @@ packages:
name: connectivity
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.9+5"
version: "3.0.6"
connectivity_for_web:
dependency: transitive
description:
name: connectivity_for_web
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.1+4"
version: "0.4.0+1"
connectivity_macos:
dependency: transitive
description:
name: connectivity_macos
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.0+7"
version: "0.2.1+2"
connectivity_platform_interface:
dependency: transitive
description:
name: connectivity_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.6"
version: "2.0.0"
convert:
dependency: transitive
description:
@ -203,7 +203,7 @@ packages:
name: cookie_jar
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
version: "3.0.1"
country_pickers:
dependency: "direct main"
description:
@ -224,7 +224,7 @@ packages:
name: csslib
url: "https://pub.dartlang.org"
source: hosted
version: "0.16.2"
version: "0.17.0"
custom_navigator:
dependency: "direct main"
description:
@ -254,14 +254,14 @@ packages:
name: disk_space
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.3"
version: "0.1.1"
draggable_scrollbar:
dependency: "direct main"
description:
name: draggable_scrollbar
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.4"
version: "0.1.0"
equalizer:
dependency: "direct main"
description:
@ -289,7 +289,7 @@ packages:
name: fading_edge_scrollview
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.4"
version: "2.0.0"
fake_async:
dependency: transitive
description:
@ -317,7 +317,7 @@ packages:
name: filesize
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.4"
version: "2.0.1"
fixnum:
dependency: transitive
description:
@ -350,14 +350,14 @@ packages:
name: flutter_displaymode
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.1"
version: "0.3.2"
flutter_inappwebview:
dependency: "direct main"
description:
name: flutter_inappwebview
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.0+4"
version: "5.3.2"
flutter_isolate:
dependency: transitive
description:
@ -371,14 +371,14 @@ packages:
name: flutter_local_notifications
url: "https://pub.dartlang.org"
source: hosted
version: "1.5.0+1"
version: "4.0.1+2"
flutter_local_notifications_platform_interface:
dependency: transitive
description:
name: flutter_local_notifications_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
version: "2.0.0+1"
flutter_localizations:
dependency: "direct main"
description: flutter
@ -390,14 +390,14 @@ packages:
name: flutter_material_color_picker
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.5"
version: "1.1.0+2"
flutter_screenutil:
dependency: "direct main"
description:
name: flutter_screenutil
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.1"
version: "5.0.0+2"
flutter_test:
dependency: "direct dev"
description: flutter
@ -414,14 +414,14 @@ packages:
name: fluttericon
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.7"
version: "2.0.0"
fluttertoast:
dependency: "direct main"
description:
name: fluttertoast
url: "https://pub.dartlang.org"
source: hosted
version: "7.0.4"
version: "8.0.8"
gettext_parser:
dependency: transitive
description:
@ -456,7 +456,7 @@ packages:
name: html
url: "https://pub.dartlang.org"
source: hosted
version: "0.14.0+4"
version: "0.15.0"
http:
dependency: "direct main"
description:
@ -485,13 +485,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.0"
import_js_library:
dependency: transitive
description:
name: import_js_library
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
infinite_listview:
dependency: transitive
description:
@ -568,7 +561,7 @@ packages:
name: marquee
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
version: "2.2.0"
matcher:
dependency: transitive
description:
@ -582,14 +575,14 @@ packages:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
version: "1.7.0"
mime:
dependency: transitive
description:
name: mime
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.7"
version: "1.0.0"
move_to_background:
dependency: "direct main"
description:
@ -603,7 +596,7 @@ packages:
name: numberpicker
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
version: "2.1.1"
oauth2:
dependency: transitive
description:
@ -638,14 +631,14 @@ packages:
name: package_info
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.3+4"
version: "2.0.2"
palette_generator:
dependency: "direct main"
description:
name: palette_generator
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.3"
version: "0.3.0"
path:
dependency: "direct main"
description:
@ -659,7 +652,7 @@ packages:
name: path_provider
url: "https://pub.dartlang.org"
source: hosted
version: "1.6.10"
version: "1.6.28"
path_provider_ex:
dependency: "direct main"
description:
@ -688,6 +681,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.4"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.5"
pedantic:
dependency: transitive
description:
@ -722,14 +722,14 @@ packages:
name: photo_view
url: "https://pub.dartlang.org"
source: hosted
version: "0.10.3"
version: "0.12.0"
platform:
dependency: transitive
description:
name: platform
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
version: "3.0.2"
plugin_platform_interface:
dependency: transitive
description:
@ -750,7 +750,7 @@ packages:
name: process
url: "https://pub.dartlang.org"
source: hosted
version: "4.2.1"
version: "4.2.3"
pub_semver:
dependency: transitive
description:
@ -771,7 +771,7 @@ packages:
name: quick_actions
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.0+10"
version: "0.5.0+1"
quiver:
dependency: transitive
description:
@ -806,7 +806,7 @@ packages:
name: share
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.5+4"
version: "2.0.4"
shelf:
dependency: transitive
description:
@ -916,7 +916,14 @@ packages:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.0"
version: "0.4.1"
timezone:
dependency: transitive
description:
name: timezone
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.1"
timing:
dependency: transitive
description:
@ -951,42 +958,42 @@ packages:
name: url_launcher
url: "https://pub.dartlang.org"
source: hosted
version: "5.7.10"
version: "6.0.5"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.1+4"
version: "2.0.1"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.1+9"
version: "2.0.1"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.9"
version: "2.0.1"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.5+3"
version: "2.0.4"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.1+3"
version: "2.0.2"
uuid:
dependency: transitive
description:
@ -1007,28 +1014,42 @@ packages:
name: version
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1"
version: "2.0.0"
wakelock:
dependency: "direct main"
description:
name: wakelock
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.1+1"
version: "0.5.3+3"
wakelock_macos:
dependency: transitive
description:
name: wakelock_macos
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.0+2"
wakelock_platform_interface:
dependency: transitive
description:
name: wakelock_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.0+1"
version: "0.2.1+2"
wakelock_web:
dependency: transitive
description:
name: wakelock_web
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.0+3"
version: "0.2.0+2"
wakelock_windows:
dependency: transitive
description:
name: wakelock_windows
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.0+1"
watcher:
dependency: transitive
description:
@ -1043,6 +1064,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
win32:
dependency: transitive
description:
name: win32
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.7"
xdg_directories:
dependency: transitive
description:
@ -1072,5 +1100,5 @@ packages:
source: hosted
version: "0.1.2"
sdks:
dart: ">=2.12.0 <3.0.0"
flutter: ">=1.22.2"
dart: ">=2.13.0 <3.0.0"
flutter: ">=2.0.0"

View File

@ -28,50 +28,50 @@ dependencies:
sdk: flutter
spotify: ^0.5.1
flutter_displaymode: ^0.1.1
flutter_displaymode: ^0.3.2
crypto: ^2.1.5
http: ^0.12.2
cookie_jar: ^1.0.1
cookie_jar: ^3.0.1
json_annotation: ^3.0.1
path_provider: 1.6.10
path_provider: ^1.6.28
path: ^1.6.4
sqflite: ^1.3.0+1
ext_storage: ^1.0.3
permission_handler: ^5.0.0+hotfix.6
connectivity: ^0.4.8+6
connectivity: ^3.0.6
intl: ^0.17.0
filesize: ^1.0.4
fluttertoast: 7.0.4
palette_generator: ^0.2.3
filesize: ^2.0.1
fluttertoast: ^8.0.8
palette_generator: ^0.3.0
flutter_material_color_picker: ^1.0.5
flutter_inappwebview: ^4.0.0
flutter_inappwebview: ^5.3.2
country_pickers: ^2.0.0
package_info: ^0.4.1
package_info: ^2.0.2
move_to_background: ^1.0.1
flutter_local_notifications: ^1.4.4+1
flutter_local_notifications: ^4.0.1+2
collection: ^1.14.12
disk_space: ^0.0.3
disk_space: ^0.1.1
path_provider_ex: ^1.0.1
random_string: ^2.0.1
async: ^2.4.1
html: ^0.14.0+3
flutter_screenutil: ^2.3.0
marquee: ^1.5.2
html: ^0.15.0
flutter_screenutil: ^5.0.0+2
marquee: ^2.2.0
flutter_cache_manager: ^1.4.1
cached_network_image: ^2.3.2+1
i18n_extension: ^4.0.0
fluttericon: ^1.0.7
url_launcher: ^5.7.2
fluttericon: ^2.0.0
url_launcher: ^6.0.5
uni_links: ^0.4.0
share: ^0.6.5+2
numberpicker: ^1.2.1
quick_actions: 0.4.0+10
photo_view: ^0.10.2
draggable_scrollbar: ^0.0.4
share: ^2.0.4
numberpicker: ^2.1.1
quick_actions: ^0.5.0+1
photo_view: ^0.12.0
draggable_scrollbar: ^0.1.0
scrobblenaut: ^2.0.4
open_file: ^3.0.3
version: ^1.2.0
wakelock: ^0.2.1+1
version: ^2.0.0
wakelock: ^0.5.3+3
google_fonts: ^1.1.2
equalizer: ^0.0.2+2
extended_math: ^0.0.29+1