several new and sexy changes
This commit is contained in:
parent
10a5455cc7
commit
4700d4113e
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,2 @@
|
|||
connection.project.dir=..
|
||||
eclipse.preferences.version=1
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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)));
|
||||
}));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
975
lib/ui/menu.dart
975
lib/ui/menu.dart
File diff suppressed because it is too large
Load Diff
|
@ -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(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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(() {});
|
||||
},
|
||||
),
|
||||
|
|
1091
lib/ui/search.dart
1091
lib/ui/search.dart
File diff suppressed because it is too large
Load Diff
|
@ -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();
|
||||
},
|
||||
)));
|
||||
|
|
|
@ -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']);
|
||||
}
|
||||
|
|
136
pubspec.lock
136
pubspec.lock
|
@ -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"
|
||||
|
|
46
pubspec.yaml
46
pubspec.yaml
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue