changed lots of different stuff i don't remember

pre-running dart fix
This commit is contained in:
Pato05 2023-10-08 12:53:22 +02:00
parent c23e38b82c
commit 7769fd8287
No known key found for this signature in database
GPG Key ID: F53CA394104BA0CB
130 changed files with 5135 additions and 1646 deletions

3
.gitmodules vendored
View File

@ -0,0 +1,3 @@
[submodule "dart_blowfish"]
path = dart_blowfish
url = https://github.com/titoo-dev/dart_blowfish

View File

@ -1,10 +1,45 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: f7a6a7906be96d2288f5d63a5a54c515a6e987fe
channel: stable
project_type: app
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled.
version:
revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
channel: stable
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
- platform: android
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
- platform: ios
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
- platform: linux
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
- platform: macos
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
- platform: web
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
- platform: windows
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

29
analysis_options.yaml Normal file
View File

@ -0,0 +1,29 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View File

@ -1,6 +0,0 @@
<?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>

View File

@ -1,34 +0,0 @@
<?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>1690499516319</id>
<name></name>
<type>30</type>
<matcher>
<id>org.eclipse.core.resources.regexFilterMatcher</id>
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
</matcher>
</filter>
</filteredResources>
</projectDescription>

View File

@ -32,6 +32,7 @@ apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
namespace 'f.f.freezer'
compileSdkVersion 33
lintOptions {
@ -42,7 +43,7 @@ android {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "f.f.freezer"
minSdkVersion 21
targetSdkVersion 31
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
@ -58,13 +59,13 @@ android {
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
// TODO: Put back signingConfig.release
signingConfig signingConfigs.debug
shrinkResources false
minifyEnabled true
}
debug {
applicationIdSuffix ".debug"
shrinkResources false
minifyEnabled false
}
@ -80,7 +81,7 @@ android {
dependencies {
//implementation group: 'org', name: 'jaudiotagger', version: '2.0.3'
implementation files('libs/jaudiotagger-2.2.3.jar')
implementation files('libs/extension-flac.aar')
// 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'
}

View File

@ -17,7 +17,6 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-feature
android:name="android.software.LEANBACK"
@ -117,23 +116,24 @@
<service
android:name="com.ryanheise.audioservice.AudioService"
android:foregroundServiceType="mediaPlayback"
android:exported="true"
tools:ignore="Instantiatable"
>
<intent-filter>
<action
android:name="android.media.browse.MediaBrowserService"
/>
</intent-filter>
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
<receiver
android:name="com.ryanheise.audioservice.MediaButtonReceiver"
android:exported="true"
tools:ignore="Instantiatable"
>
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
<meta-data
android:name="com.google.android.gms.car.application"

View File

@ -80,8 +80,8 @@ public class Download {
}
//Used to send data to Flutter
HashMap toHashMap() {
HashMap map = new HashMap();
HashMap<String, Object> toHashMap() {
HashMap<String, Object> map = new HashMap<>();
map.put("id", id);
map.put("path", path);
map.put("private", priv);

View File

@ -90,7 +90,7 @@ public class MainActivity extends AudioServiceActivity {
//TX
db.beginTransaction();
ArrayList<HashMap> downloads = call.arguments();
ArrayList<HashMap<String, Object>> downloads = call.arguments();
for (int i=0; i<downloads.size(); i++) {
//Check if exists
Cursor cursor = db.rawQuery("SELECT id, state, quality FROM Downloads WHERE trackId == ? AND path == ?",
@ -128,7 +128,7 @@ public class MainActivity extends AudioServiceActivity {
//Get all downloads from DB
if (call.method.equals("getDownloads")) {
Cursor cursor = db.query("Downloads", null, null, null, null, null, null);
ArrayList downloads = new ArrayList();
ArrayList<HashMap<String, Object>> downloads = new ArrayList<>();
//Parse downloads
while (cursor.moveToNext()) {
Download download = Download.fromSQL(cursor);
@ -258,6 +258,8 @@ public class MainActivity extends AudioServiceActivity {
eventSink = null;
}
}));
super.configureFlutterEngine(flutterEngine);
}
@Override
@ -280,6 +282,7 @@ public class MainActivity extends AudioServiceActivity {
break;
}
}
super.onActivityResult(requestCode, resultCode, data);
}
@ -305,26 +308,28 @@ public class MainActivity extends AudioServiceActivity {
DownloadsDatabase dbHelper = new DownloadsDatabase(getApplicationContext());
db = dbHelper.getWritableDatabase();
//Trust all SSL Certs - Credits to Kilowatt36
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
SSLContext sc;
try {
sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (NoSuchAlgorithmException | KeyManagementException e) {
Log.e(this.getLocalClassName(), e.getMessage());
}
// what the fuck?
// //Trust all SSL Certs - Credits to Kilowatt36
// TrustManager[] trustAllCerts = new TrustManager[]{
// new X509TrustManager() {
// public java.security.cert.X509Certificate[] getAcceptedIssuers() {
// return null;
// }
// public void checkClientTrusted(X509Certificate[] certs, String authType) {
// }
// public void checkServerTrusted(X509Certificate[] certs, String authType) {
// }
// }
// };
// SSLContext sc;
// try {
// sc = SSLContext.getInstance("SSL");
// sc.init(null, trustAllCerts, new java.security.SecureRandom());
// HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
// } catch (NoSuchAlgorithmException | KeyManagementException e) {
// Log.e(this.getLocalClassName(), e.getMessage());
// }
}
@Override
@ -395,10 +400,10 @@ public class MainActivity extends AudioServiceActivity {
if (eventSink == null) break;
if (msg.getData().getParcelableArrayList("downloads").size() > 0) {
//Generate HashMap ArrayList for sending to flutter
ArrayList<HashMap> data = new ArrayList<>();
ArrayList<HashMap<String, Number>> data = new ArrayList<>();
for (int i=0; i<msg.getData().getParcelableArrayList("downloads").size(); i++) {
Bundle bundle = (Bundle) msg.getData().getParcelableArrayList("downloads").get(i);
HashMap out = new HashMap();
HashMap<String, Number> out = new HashMap<>();
out.put("id", bundle.getInt("id"));
out.put("state", bundle.getInt("state"));
out.put("received", bundle.getLong("received"));
@ -407,7 +412,7 @@ public class MainActivity extends AudioServiceActivity {
data.add(out);
}
//Wrapper
HashMap out = new HashMap();
HashMap<String, Object> out = new HashMap<>();
out.put("action", "onProgress");
out.put("data", data);
eventSink.success(out);
@ -418,7 +423,7 @@ public class MainActivity extends AudioServiceActivity {
case DownloadService.SERVICE_ON_STATE_CHANGE:
if (eventSink == null) break;
Bundle b = msg.getData();
HashMap out = new HashMap();
HashMap<String, Object> out = new HashMap<>();
out.put("running", b.getBoolean("running"));
out.put("queueSize", b.getInt("queueSize"));

View File

@ -23,7 +23,7 @@ public class StreamServer {
public HashMap<String, StreamInfo> streams = new HashMap<>();
private WebServer server;
private String host = "0.0.0.0";
private String host = "127.0.0.1";
private int port = 36958;
private String offlinePath;

View File

@ -0,0 +1,6 @@
package f.f.freezer
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}

View File

@ -1,18 +1,18 @@
buildscript {
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.3.0'
classpath 'com.android.tools.build:gradle:7.4.2'
}
}
allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}

View File

@ -1,5 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
#android.enableR8=true
android.useAndroidX=true
android.enableJetifier=true
android.enableDexingArtifactTransform=false

1
dart_blowfish Submodule

@ -0,0 +1 @@
Subproject commit c874b07df26b7be681f5241773f4305cc40b2974

66
ios/.gitignore vendored
View File

@ -1,32 +1,34 @@
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

View File

@ -1,26 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>8.0</string>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>11.0</string>
</dict>
</plist>

View File

@ -1 +1 @@
#include "Generated.xcconfig"
#include "Generated.xcconfig"

View File

@ -1 +1 @@
#include "Generated.xcconfig"
#include "Generated.xcconfig"

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@ -1,91 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@ -1,13 +1,13 @@
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

View File

@ -1,122 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 564 B

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,23 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -1,5 +1,5 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.

View File

@ -1,37 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>

View File

@ -1,26 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

View File

@ -1,45 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>freezer</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Freezer</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>freezer</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>

View File

@ -1 +1 @@
#import "GeneratedPluginRegistrant.h"
#import "GeneratedPluginRegistrant.h"

View File

@ -0,0 +1,12 @@
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}

@ -1 +0,0 @@
Subproject commit c29d7a5a4b34927d67b6e3b72c35aedeb8f35bc9

View File

@ -416,13 +416,18 @@ class DeezerAPI {
}
//Log song listen to deezer
Future logListen(String trackId) async {
Future logListen(String trackId,
{int seek = 0, int pause = 0, int sync = 1, int? timestamp}) async {
await callApi('log.listen', params: {
'params': {
'timestamp': DateTime.now().millisecondsSinceEpoch,
'timestamp': timestamp ?? DateTime.now().millisecondsSinceEpoch,
'ts_listen': DateTime.now().millisecondsSinceEpoch,
'type': 1,
'stat': {'seek': 0, 'pause': 0, 'sync': 1},
'stat': {
'seek': seek, // amount of times seeked
'pause': pause, // amount of times paused
'sync': sync
},
'media': {'id': trackId, 'type': 'song', 'format': 'MP3_128'}
}
});

View File

@ -6,6 +6,7 @@ import 'dart:typed_data';
import 'package:encrypt/encrypt.dart';
import 'package:freezer/api/deezer.dart';
import 'package:freezer/api/definitions.dart';
import 'package:freezer/settings.dart';
import 'package:just_audio/just_audio.dart';
import 'package:crypto/crypto.dart';
import 'package:http/http.dart' as http;
@ -17,15 +18,15 @@ import 'package:logging/logging.dart';
class DeezerAudioSource extends StreamAudioSource {
final _logger = Logger("DeezerAudioSource");
late int _quality;
late int _initialQuality;
late AudioQuality? _quality;
late AudioQuality? _initialQuality;
late String _trackId;
late String _md5origin;
late String _mediaVersion;
final StreamInfoCallback? onStreamObtained;
DeezerAudioSource({
required int quality,
required AudioQuality quality,
required String trackId,
required String md5origin,
required String mediaVersion,
@ -38,7 +39,7 @@ class DeezerAudioSource extends StreamAudioSource {
this._mediaVersion = mediaVersion;
}
int get quality => _quality;
AudioQuality? get quality => _quality;
String get trackId => _trackId;
String get md5origin => _md5origin;
String get mediaVersion => _mediaVersion;
@ -115,15 +116,16 @@ class DeezerAudioSource extends StreamAudioSource {
_logger.warning(
"quality fallback, response code: $rc, current quality: ${this.quality}");
switch (_quality) {
case Quality.MP3_128:
_quality = -1;
case AudioQuality.FLAC:
_quality = AudioQuality.MP3_320;
break;
case AudioQuality.MP3_320:
_quality = AudioQuality.MP3_128;
break;
case AudioQuality.MP3_128:
default:
_quality = null;
throw QualityException("No quality to fallback to!");
case Quality.MP3_320:
_quality = Quality.MP3_128;
break;
case Quality.FLAC:
_quality = Quality.MP3_320;
break;
}
return await _qualityFallback();
@ -138,7 +140,7 @@ class DeezerAudioSource extends StreamAudioSource {
final step1 = <int>[
...md5origin.codeUnits,
magic,
...quality.toString().codeUnits,
...quality!.toDeezerQualityInt().toString().codeUnits,
magic,
...trackId.codeUnits,
magic,
@ -232,7 +234,7 @@ class DeezerAudioSource extends StreamAudioSource {
_logger
.finest("request sent! rc: $rc, content-length: ${res.contentLength}");
this.onStreamObtained?.call(StreamQualityInfo(
format: quality == Quality.FLAC ? Format.FLAC : Format.MP3,
format: quality == AudioQuality.FLAC ? Format.FLAC : Format.MP3,
source: Source.stream,
size: start + res.contentLength!,
quality: quality,
@ -306,6 +308,7 @@ class DeezerAudioSource extends StreamAudioSource {
contentLength: cl,
offset: start,
stream: controller.stream,
contentType: quality == 9 ? "audio/flac" : "audio/mpeg");
contentType:
quality == AudioQuality.FLAC ? "audio/flac" : "audio/mpeg");
}
}

View File

@ -5,7 +5,6 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:audio_service/audio_service.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:freezer/api/cache.dart';
import 'package:freezer/page_routes/blur_slide.dart';
import 'package:freezer/page_routes/fade.dart';
@ -459,6 +458,7 @@ class Playlist extends DeezerMediaItem {
@HiveField(2)
List<Track>? tracks;
@HiveField(3)
@JsonImageDetailsConverter()
ImageDetails? image;
@HiveField(4)
Duration? duration;
@ -563,11 +563,29 @@ class User {
Map<String, dynamic> toJson() => _$UserToJson(this);
}
abstract class ImageDetails {
String get full => throw UnimplementedError("get full is not implemented");
String get thumb => throw UnimplementedError("get full is not implemented");
class JsonImageDetailsConverter
extends JsonConverter<ImageDetails, Map<String, dynamic>> {
const JsonImageDetailsConverter();
Map<String, dynamic> toJson(ImageDetails details) => details.toJson();
ImageDetails fromJson(Map<String, dynamic> json) {
if (json.containsKey('full') || json.containsKey('thumb')) {
return UrlImageDetails.fromJson(json);
} else {
return DeezerImageDetails.fromJson(json);
}
}
}
abstract class ImageDetails {
String get full => throw UnimplementedError("get full is not implemented");
String get thumb => throw UnimplementedError("get thumb is not implemented");
Map<String, dynamic> toJson() =>
throw UnimplementedError("toJson() is not implemented");
}
@JsonSerializable()
class UrlImageDetails extends ImageDetails {
final String full;
final String thumb;
@ -575,6 +593,10 @@ class UrlImageDetails extends ImageDetails {
factory UrlImageDetails.single(String url) =>
UrlImageDetails(full: url, thumb: url);
factory UrlImageDetails.fromJson(Map<String, dynamic> json) =>
_$UrlImageDetailsFromJson(json);
Map<String, dynamic> toJson() => _$UrlImageDetailsToJson(this);
}
// TODO: migrate to Uri instead of String
@ -604,8 +626,8 @@ class DeezerImageDetails extends ImageDetails {
DeezerImageDetails(json['md5'].split('-').first, type: json['type']);
factory DeezerImageDetails.fromJson(Map<String, dynamic> json) =>
_$ImageDetailsFromJson(json);
Map<String, dynamic> toJson() => _$ImageDetailsToJson(this);
_$DeezerImageDetailsFromJson(json);
Map<String, dynamic> toJson() => _$DeezerImageDetailsToJson(this);
}
class SearchResults {
@ -830,7 +852,7 @@ class HomePage {
static Future<bool> exists(String channel) async {
if (!await Hive.boxExists(_boxName)) return false;
return (await Hive.openLazyBox(_boxName)).containsKey(channel);
return (await Hive.openLazyBox<HomePage>(_boxName)).containsKey(channel);
}
Future<void> save(String channel) async {
@ -843,7 +865,7 @@ class HomePage {
static Future<HomePage?> local(String channel) async {
final LazyBox<HomePage> box;
try {
box = await Hive.openLazyBox(_boxName);
box = await Hive.openLazyBox<HomePage>(_boxName);
} catch (e) {
print(e);
return null;
@ -1290,7 +1312,7 @@ class StreamQualityInfo {
// file size
final int? size;
// not available if offline
final int? quality;
final AudioQuality? quality;
final Source source;
StreamQualityInfo({
@ -1401,25 +1423,27 @@ extension ToLoopMode on AudioServiceRepeatMode {
// }
extension PushRoute on NavigatorState {
Future<T?> pushRoute<T extends Object?>({required WidgetBuilder builder}) {
Future<T?> pushRoute<T extends Object?>(
{required WidgetBuilder builder, RouteSettings? s}) {
final PageRoute<T> route;
switch (settings.navigatorRouteType) {
case NavigatorRouteType.blur_slide:
route = BlurSlidePageRoute<T>(builder: builder);
route = BlurSlidePageRoute<T>(builder: builder, settings: s);
break;
case NavigatorRouteType.material:
route = MaterialPageRoute<T>(builder: builder);
route = MaterialPageRoute<T>(builder: builder, settings: s);
break;
case NavigatorRouteType.cupertino:
route = CupertinoPageRoute<T>(builder: builder);
route = CupertinoPageRoute<T>(builder: builder, settings: s);
break;
case NavigatorRouteType.fade:
route = FadePageRoute<T>(builder: builder);
route = FadePageRoute<T>(builder: builder, settings: s);
break;
case NavigatorRouteType.fade_blur:
route = FadePageRoute<T>(builder: builder, blur: true);
route = FadePageRoute<T>(builder: builder, settings: s, blur: true);
break;
}
return push(route);
}
}
@ -1468,12 +1492,6 @@ class QualityException implements Exception {
String get message => _message ?? "$runtimeType";
}
class Quality {
static const int MP3_128 = 1;
static const int MP3_320 = 3;
static const int FLAC = 9;
}
// different kinds of flow (scrapped from web)
enum FlowConfig {
motivation,

View File

@ -224,7 +224,7 @@ class PlaylistAdapter extends TypeAdapter<Playlist> {
id: fields[0] as String,
title: fields[1] as String?,
tracks: (fields[2] as List?)?.cast<Track>(),
image: fields[3] as DeezerImageDetails?,
image: fields[3] as ImageDetails?,
trackCount: fields[5] as int?,
duration: fields[4] as Duration?,
user: fields[6] as User?,
@ -311,7 +311,7 @@ class UserAdapter extends TypeAdapter<User> {
typeId == other.typeId;
}
class ImageDetailsAdapter extends TypeAdapter<DeezerImageDetails> {
class DeezerImageDetailsAdapter extends TypeAdapter<DeezerImageDetails> {
@override
final int typeId = 6;
@ -343,7 +343,7 @@ class ImageDetailsAdapter extends TypeAdapter<DeezerImageDetails> {
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is ImageDetailsAdapter &&
other is DeezerImageDetailsAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
@ -1254,9 +1254,8 @@ Playlist _$PlaylistFromJson(Map<String, dynamic> json) => Playlist(
tracks: (json['tracks'] as List<dynamic>?)
?.map((e) => Track.fromJson(e as Map<String, dynamic>))
.toList(),
image: json['image'] == null
? null
: DeezerImageDetails.fromJson(json['image'] as Map<String, dynamic>),
image: _$JsonConverterFromJson<Map<String, dynamic>, ImageDetails>(
json['image'], const JsonImageDetailsConverter().fromJson),
trackCount: json['trackCount'] as int?,
duration: json['duration'] == null
? null
@ -1273,7 +1272,8 @@ Map<String, dynamic> _$PlaylistToJson(Playlist instance) => <String, dynamic>{
'id': instance.id,
'title': instance.title,
'tracks': instance.tracks,
'image': instance.image,
'image': _$JsonConverterToJson<Map<String, dynamic>, ImageDetails>(
instance.image, const JsonImageDetailsConverter().toJson),
'duration': instance.duration?.inMicroseconds,
'trackCount': instance.trackCount,
'user': instance.user,
@ -1282,6 +1282,18 @@ Map<String, dynamic> _$PlaylistToJson(Playlist instance) => <String, dynamic>{
'description': instance.description,
};
Value? _$JsonConverterFromJson<Json, Value>(
Object? json,
Value? Function(Json json) fromJson,
) =>
json == null ? null : fromJson(json as Json);
Json? _$JsonConverterToJson<Json, Value>(
Value? value,
Json? Function(Value value) toJson,
) =>
value == null ? null : toJson(value);
User _$UserFromJson(Map<String, dynamic> json) => User(
id: json['id'] as String?,
name: json['name'] as String?,
@ -1297,13 +1309,25 @@ Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
'picture': instance.picture,
};
DeezerImageDetails _$ImageDetailsFromJson(Map<String, dynamic> json) =>
UrlImageDetails _$UrlImageDetailsFromJson(Map<String, dynamic> json) =>
UrlImageDetails(
full: json['full'] as String,
thumb: json['thumb'] as String,
);
Map<String, dynamic> _$UrlImageDetailsToJson(UrlImageDetails instance) =>
<String, dynamic>{
'full': instance.full,
'thumb': instance.thumb,
};
DeezerImageDetails _$DeezerImageDetailsFromJson(Map<String, dynamic> json) =>
DeezerImageDetails(
json['md5'] as String,
type: json['type'] as String? ?? 'cover',
);
Map<String, dynamic> _$ImageDetailsToJson(DeezerImageDetails instance) =>
Map<String, dynamic> _$DeezerImageDetailsToJson(DeezerImageDetails instance) =>
<String, dynamic>{
'type': instance.type,
'md5': instance.md5,
@ -1315,12 +1339,14 @@ Lyrics _$LyricsFromJson(Map<String, dynamic> json) => Lyrics(
lyrics: (json['lyrics'] as List<dynamic>?)
?.map((e) => Lyric.fromJson(e as Map<String, dynamic>))
.toList(),
sync: json['sync'] as bool? ?? true,
);
Map<String, dynamic> _$LyricsToJson(Lyrics instance) => <String, dynamic>{
'id': instance.id,
'writers': instance.writers,
'lyrics': instance.lyrics,
'sync': instance.sync,
};
Lyric _$LyricFromJson(Map<String, dynamic> json) => Lyric(
@ -1490,7 +1516,7 @@ Show _$ShowFromJson(Map<String, dynamic> json) => Show(
Map<String, dynamic> _$ShowToJson(Show instance) => <String, dynamic>{
'name': instance.name,
'description': instance.description,
'art': instance.art?.toJson(),
'art': instance.art,
'id': instance.id,
};
@ -1523,7 +1549,7 @@ StreamQualityInfo _$StreamQualityInfoFromJson(Map<String, dynamic> json) =>
StreamQualityInfo(
format: $enumDecode(_$FormatEnumMap, json['format']),
source: $enumDecode(_$SourceEnumMap, json['source']),
quality: json['quality'] as int?,
quality: $enumDecodeNullable(_$AudioQualityEnumMap, json['quality']),
size: json['size'] as int?,
);
@ -1531,7 +1557,7 @@ Map<String, dynamic> _$StreamQualityInfoToJson(StreamQualityInfo instance) =>
<String, dynamic>{
'format': _$FormatEnumMap[instance.format]!,
'size': instance.size,
'quality': instance.quality,
'quality': _$AudioQualityEnumMap[instance.quality],
'source': _$SourceEnumMap[instance.source]!,
};
@ -1544,3 +1570,10 @@ const _$SourceEnumMap = {
Source.offline: 'offline',
Source.stream: 'stream',
};
const _$AudioQualityEnumMap = {
AudioQuality.MP3_128: 'MP3_128',
AudioQuality.MP3_320: 'MP3_320',
AudioQuality.FLAC: 'FLAC',
AudioQuality.ASK: 'ASK',
};

View File

@ -737,8 +737,8 @@ class Download {
"md5origin": t.playbackDetails![0],
"mediaVersion": t.playbackDetails![1],
"quality": private
? settings.getQualityInt(settings.offlineQuality)
: settings.getQualityInt((quality ?? settings.downloadQuality)),
? settings.offlineQuality.toDeezerQualityInt()
: (quality ?? settings.downloadQuality).toDeezerQualityInt(),
"title": t.title,
"path": path,
"image": t.albumArt?.thumb

View File

@ -35,7 +35,6 @@ class PlayerHelper {
late StreamSubscription _playbackStateStreamSubscription;
QueueSource? queueSource;
AudioServiceRepeatMode repeatType = AudioServiceRepeatMode.none;
Timer? _timer;
int? audioSession;
int? _prevAudioSession;
bool equalizerOpen = false;
@ -64,11 +63,11 @@ class PlayerHelper {
int get queueIndex => _queueIndex;
Future<void> initAudioHandler() async {
final initArgs = AudioPlayerTaskInitArguments.from(
settings: settings, deezerAPI: deezerAPI);
// initialize our audiohandler instance
audioHandler = await AudioService.init(
builder: () => AudioPlayerTask(
ignoreInterruptions: settings.ignoreInterruptions,
deezerAPI: deezerAPI),
builder: () => AudioPlayerTask(initArgs),
config: AudioServiceConfig(
notificationColor: settings.primaryColor,
androidStopForegroundOnPause: false,
@ -87,14 +86,12 @@ class PlayerHelper {
_started = true;
//Subscribe to custom events
_customEventSubscription = audioHandler.customEvent.listen((event) async {
if (!(event is Map)) return;
Logger('PlayerHelper').fine("event received: " + event['action']);
if (event is! Map) return;
Logger('PlayerHelper').fine("event received: ${event['action']}");
switch (event['action']) {
case 'onLoad':
//After audio_service is loaded, load queue, set quality
await settings.updateAudioServiceQuality();
await audioHandler.customAction('load');
await authorizeLastFM();
break;
case 'onRestore':
//Load queueSource from isolate
@ -102,13 +99,6 @@ class PlayerHelper {
repeatType = event['repeatMode'] as AudioServiceRepeatMode;
_queueIndex = getQueueIndex();
break;
case 'screenAndroidAuto':
List<MediaItem> data = await androidAuto.getScreen(event['id']);
await audioHandler.customAction('screenAndroidAuto', {'value': data});
break;
case 'tracksAndroidAuto':
await androidAuto.playItem(event['id']);
break;
case 'audioSession':
if (!settings.enableEqualizer) break;
//Save
@ -123,8 +113,9 @@ class PlayerHelper {
}
//Change session id
if (_prevAudioSession != audioSession) {
if (_prevAudioSession != null)
if (_prevAudioSession != null) {
Equalizer.removeAudioSessionId(_prevAudioSession!);
}
Equalizer.setAudioSessionId(audioSession!);
}
break;
@ -146,36 +137,21 @@ class PlayerHelper {
//Save queue
await audioHandler.customAction('saveQueue', {});
//Add to history
if (cache.history.length > 0 && cache.history.last.id == mediaItem.id)
if (cache.history.isNotEmpty && cache.history.last.id == mediaItem.id) {
return;
}
cache.history.add(Track.fromMediaItem(mediaItem));
cache.save();
});
//Logging listen timer
_timer = Timer.periodic(Duration(seconds: 2), (timer) async {
if (audioHandler.mediaItem.value == null ||
!audioHandler.playbackState.value.playing) return;
if (audioHandler.playbackState.value.position.inSeconds >
(audioHandler.mediaItem.value!.duration!.inSeconds * 0.75)) {
if (cache.loggedTrackId == audioHandler.mediaItem.value!.id) return;
cache.loggedTrackId = audioHandler.mediaItem.value!.id;
await cache.save();
//Log to Deezer
if (settings.logListen) {
deezerAPI.logListen(audioHandler.mediaItem.value!.id);
}
}
});
//Start audio_service
// await startService(); it is already ready, there is no need to start it
}
Future authorizeLastFM() async {
if (settings.lastFMUsername == null || settings.lastFMPassword == null)
Future<void> authorizeLastFM() async {
if (settings.lastFMUsername == null || settings.lastFMPassword == null) {
return;
}
await audioHandler.customAction('authorizeLastFM', {
'username': settings.lastFMUsername,
'password': settings.lastFMPassword
@ -237,7 +213,7 @@ class PlayerHelper {
tracks[0].id,
QueueSource(
id: trackId,
text: 'Mix based on'.i18n + ' $trackTitle',
text: '${'Mix based on'.i18n} $trackTitle',
source: 'mix'));
}
@ -332,6 +308,41 @@ class PlayerHelper {
// }
}
class AudioPlayerTaskInitArguments {
final bool ignoreInterruptions;
final DeezerAPI deezerAPI;
final bool logListen;
final String? lastFMUsername;
final String? lastFMPassword;
AudioPlayerTaskInitArguments({
required this.deezerAPI,
required this.ignoreInterruptions,
required this.logListen,
required this.lastFMUsername,
required this.lastFMPassword,
});
static AudioPlayerTaskInitArguments from(
{required Settings settings, required DeezerAPI deezerAPI}) {
return AudioPlayerTaskInitArguments(
deezerAPI: deezerAPI,
logListen: settings.logListen,
ignoreInterruptions: settings.ignoreInterruptions,
lastFMUsername: settings.lastFMUsername,
lastFMPassword: settings.lastFMPassword);
}
static Future<AudioPlayerTaskInitArguments> loadSettings() async {
final settings = await Settings.load();
final deezerAPI = DeezerAPI(arl: settings.arl);
await deezerAPI.authorize();
return from(settings: settings, deezerAPI: deezerAPI);
}
}
class AudioPlayerTask extends BaseAudioHandler {
late AudioPlayer _player;
late DeezerAPI _deezerAPI;
@ -349,28 +360,41 @@ class AudioPlayerTask extends BaseAudioHandler {
StreamSubscription? _audioSessionSub;
StreamSubscription? _visualizerSubscription;
late final AndroidAuto _androidAuto;
//Loaded from file/frontendjust
int? mobileQuality;
int? wifiQuality;
AudioQuality mobileQuality = AudioQuality.MP3_128;
AudioQuality wifiQuality = AudioQuality.MP3_128;
QueueSource? queueSource;
Duration? _lastPosition;
AudioServiceRepeatMode _repeatMode = AudioServiceRepeatMode.none;
Completer<List<MediaItem>>? _androidAutoCallback;
Scrobblenaut? _scrobblenaut;
// Last logged track id
String? _loggedTrackId;
// track logging
bool _shouldLogTracks = false;
int _amountPaused = 0;
int _amountSeeked = 0;
int? _timestamp;
MediaItem get currentMediaItem => queue.value[_queueIndex];
late final LazyBox _box;
AudioPlayerTask(
{bool ignoreInterruptions = false, required DeezerAPI deezerAPI}) {
_deezerAPI = deezerAPI;
unawaited(_init(ignoreInterruptions));
AudioPlayerTask([AudioPlayerTaskInitArguments? initArgs]) {
if (initArgs == null) {
unawaited(AudioPlayerTaskInitArguments.loadSettings().then(_init));
return;
}
unawaited(_init(initArgs));
}
Future<void> _init(bool ignoreInterruptions) async {
Future<void> _init(AudioPlayerTaskInitArguments initArgs) async {
_deezerAPI = initArgs.deezerAPI;
_androidAuto = AndroidAuto(deezerAPI: _deezerAPI);
_shouldLogTracks = initArgs.logListen;
final session = await AudioSession.instance;
session.configure(AudioSessionConfiguration.music());
@ -378,7 +402,7 @@ class AudioPlayerTask extends BaseAudioHandler {
path: (await getExternalCacheDirectories())?[0].path ??
(await getExternalStorageDirectory())?.path);
if (ignoreInterruptions) {
if (initArgs.ignoreInterruptions) {
_player = AudioPlayer(handleInterruptions: false);
session.interruptionEventStream.listen((_) {});
session.becomingNoisyEventStream.listen((_) {});
@ -388,6 +412,7 @@ class AudioPlayerTask extends BaseAudioHandler {
//Update track index
_player.currentIndexStream.listen((index) {
_timestamp = DateTime.now().millisecondsSinceEpoch;
if (index != null && queue.value.isNotEmpty) {
_queueIndex = index;
mediaItem.add(currentMediaItem);
@ -399,13 +424,6 @@ class AudioPlayerTask extends BaseAudioHandler {
});
//Update state on all clients on change
_eventSub = _player.playbackEventStream.listen((event) {
//Quality string
if (_queueIndex != -1 && _queueIndex < queue.value.length) {
Map extras = currentMediaItem.extras!;
extras['qualityString'] = '';
queue.value[_queueIndex] =
currentMediaItem.copyWith(extras: extras as Map<String, dynamic>?);
}
//Update
_broadcastState();
});
@ -413,11 +431,12 @@ class AudioPlayerTask extends BaseAudioHandler {
switch (state) {
case ProcessingState.completed:
//Player ended, get more songs
if (_queueIndex == queue.value.length - 1)
if (_queueIndex == queue.value.length - 1) {
customEvent.add({
'action': 'queueEnd',
'queueSource': (queueSource ?? QueueSource()).toJson()
});
}
break;
default:
break;
@ -431,6 +450,14 @@ class AudioPlayerTask extends BaseAudioHandler {
//Load queue
// queue.add(_queue);
await _loadQueueFile();
if (initArgs.lastFMUsername != null && initArgs.lastFMPassword != null) {
await _authorizeLastFM(
initArgs.lastFMUsername!, initArgs.lastFMPassword!);
}
customEvent.add({'action': 'onLoad'});
}
@ -438,6 +465,7 @@ class AudioPlayerTask extends BaseAudioHandler {
Future skipToQueueItem(int index) async {
_lastPosition = null;
unawaited(_logListenedTrack());
//Skip in player
await _player.seek(Duration.zero, index: index);
_queueIndex = index;
@ -461,15 +489,22 @@ class AudioPlayerTask extends BaseAudioHandler {
track: currentMediaItem.title,
artist: currentMediaItem.artist!,
album: currentMediaItem.album,
duration: currentMediaItem.duration,
);
}
}
@override
Future pause() => _player.pause();
Future<void> pause() {
_amountPaused++;
return _player.pause();
}
@override
Future seek(Duration? pos) => _player.seek(pos);
Future<void> seek(Duration? pos) {
_amountSeeked++;
return _player.seek(pos);
}
@override
Future<void> fastForward() =>
@ -507,6 +542,7 @@ class AudioPlayerTask extends BaseAudioHandler {
_lastPosition = null;
if (_queueIndex == queue.value.length - 1) return;
//Update buffering state
unawaited(_logListenedTrack());
_queueIndex++;
await _player.seekToNext();
_broadcastState();
@ -518,21 +554,29 @@ class AudioPlayerTask extends BaseAudioHandler {
//Update buffering state
//_skipState = AudioProcessingState.skippingToPrevious;
//Normal skip to previous
unawaited(_logListenedTrack());
_queueIndex--;
await _player.seekToPrevious();
//_skipState = null;
}
Future<void> _logListenedTrack() async {
if (!_shouldLogTracks) return;
//Log to Deezer
deezerAPI.logListen(
currentMediaItem.id,
seek: _amountSeeked,
pause: _amountPaused,
sync: _amountSeeked == 0 && _amountPaused == 0 ? 1 : 0,
timestamp: _timestamp,
);
}
@override
Future<List<MediaItem>> getChildren(String parentMediaId,
[Map<String, dynamic>? options]) async {
customEvent.add({'action': 'screenAndroidAuto', 'id': parentMediaId});
//Wait for data from main thread
_androidAutoCallback = Completer<List<MediaItem>>();
final data = await _androidAutoCallback!.future;
_androidAutoCallback = null;
return data;
return await _androidAuto.getScreen(parentMediaId);
}
//While seeking, jump 10s every 1s
@ -550,8 +594,9 @@ class AudioPlayerTask extends BaseAudioHandler {
Duration newPos = _player.position + offset;
//Out of bounds check
if (newPos < Duration.zero) newPos = Duration.zero;
if (newPos > currentMediaItem.duration!)
if (newPos > currentMediaItem.duration!) {
newPos = currentMediaItem.duration!;
}
await _player.seek(newPos);
}
@ -565,21 +610,17 @@ class AudioPlayerTask extends BaseAudioHandler {
_player.playing ? MediaControl.pause : MediaControl.play,
/*if (_queueIndex != _queue!.length - 1)*/ MediaControl
.skipToNext,
//Stop
MediaControl(
androidIcon: 'drawable/ic_action_stop',
label: 'stop',
action: MediaAction.stop),
//Stop -- USELESS.
// MediaControl(
// androidIcon: 'drawable/ic_action_stop',
// label: 'stop',
// action: MediaAction.stop),
// i mean, the user can just swipe the notification away to stop
]
: [
MediaControl.rewind,
_player.playing ? MediaControl.pause : MediaControl.play,
MediaControl.fastForward,
MediaControl.rewind,
MediaControl(
androidIcon: 'drawable/ic_action_stop',
label: 'stop',
action: MediaAction.stop),
],
systemActions: queueSource?.source != 'show'
? const {
@ -675,23 +716,25 @@ class AudioPlayerTask extends BaseAudioHandler {
}
//Show episode direct link
if (mediaItem.extras!['showUrl'] != null)
if (mediaItem.extras!['showUrl'] != null) {
return UrlAudioSource(
Uri.parse(mediaItem.extras!['showUrl']),
onStreamObtained: (qualityInfo) =>
customEvent.add({'action': 'streamInfo', 'data': qualityInfo}),
);
}
//Due to current limitations of just_audio, quality fallback moved to DeezerDataSource in ExoPlayer
//This just returns fake url that contains metadata
List? playbackDetails = jsonDecode(mediaItem.extras!['playbackDetails']);
//Quality
ConnectivityResult conn = await Connectivity().checkConnectivity();
int? quality = mobileQuality;
AudioQuality quality = mobileQuality;
if (conn == ConnectivityResult.wifi) quality = wifiQuality;
if ((playbackDetails ?? []).length < 2)
if ((playbackDetails ?? []).length < 2) {
throw Exception('not enough playback details');
}
//String url = 'https://dzcdn.net/?md5=${playbackDetails[0]}&mv=${playbackDetails[1]}&q=${quality.toString()}#${mediaItem.id}';
// final uri = Uri.http('localhost:36958', '', {
@ -721,8 +764,8 @@ class AudioPlayerTask extends BaseAudioHandler {
case 'updateQuality':
//Pass wifi & mobile quality by custom action
//Isolate can't access globals
wifiQuality = args!['wifiQuality'];
mobileQuality = args['mobileQuality'];
wifiQuality = args!['wifiQuality'] as AudioQuality;
mobileQuality = args['mobileQuality'] as AudioQuality;
break;
//Update queue source
case 'queueSource':
@ -737,15 +780,6 @@ class AudioPlayerTask extends BaseAudioHandler {
case 'saveQueue':
await _saveQueue();
break;
//Load queue after some initialization in frontend
case 'load':
await _loadQueueFile();
break;
//Android audio callback
case 'screenAndroidAuto':
_androidAutoCallback?.complete((args!['value'] as List<MediaItem>?));
break;
//Reorder tracks, args = [old, new]
case 'reorder':
final oldIndex = args!['oldIndex']! as int;
@ -790,16 +824,7 @@ class AudioPlayerTask extends BaseAudioHandler {
case 'authorizeLastFM':
final username = args!['username']! as String;
final password = args['password']! as String;
try {
final lastFM = await LastFM.authenticateWithPasswordHash(
apiKey: 'b6ab5ae967bcd8b10b23f68f42493829',
apiSecret: '861b0dff9a8a574bec747f9dab8b82bf',
username: username,
passwordHash: password);
_scrobblenaut = Scrobblenaut(lastFM: lastFM);
} catch (e) {
print(e);
}
await _authorizeLastFM(username, password);
break;
case 'disableLastFM':
_scrobblenaut = null;
@ -905,10 +930,7 @@ class AudioPlayerTask extends BaseAudioHandler {
[Map<String, dynamic>? args]) async {
//Android auto load tracks
if (mediaId.startsWith(AndroidAuto.prefix)) {
customEvent.add({
'action': 'tracksAndroidAuto',
'id': mediaId.replaceFirst(AndroidAuto.prefix, '')
});
await _androidAuto.playItem(mediaId.substring(AndroidAuto.prefix.length));
return;
}
@ -993,6 +1015,15 @@ class AudioPlayerTask extends BaseAudioHandler {
tracks.map<Future<MediaItem>>((t) => t.toMediaItem()));
await addQueueItems(mi);
}
Future<void> _authorizeLastFM(String username, String password) async {
_scrobblenaut = Scrobblenaut(
lastFM: await LastFM.authenticateWithPasswordHash(
apiKey: 'b6ab5ae967bcd8b10b23f68f42493829',
apiSecret: '861b0dff9a8a574bec747f9dab8b82bf',
username: username,
passwordHash: password));
}
}
//Seeker from audio_service example (why reinvent the wheel?)

View File

@ -3,6 +3,7 @@ import 'dart:io';
import 'package:audio_service/audio_service.dart';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart';
@ -10,6 +11,8 @@ 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/page_routes/blur_slide.dart';
import 'package:freezer/page_routes/fade.dart';
import 'package:freezer/page_routes/scale_fade.dart';
import 'package:freezer/type_adapters/uri.dart';
import 'package:freezer/ui/downloads_screen.dart';
@ -21,7 +24,6 @@ import 'package:freezer/ui/settings_screen.dart';
import 'package:hive_flutter/adapters.dart';
import 'package:i18n_extension/i18n_widget.dart';
import 'package:logging/logging.dart';
import 'package:move_to_background/move_to_background.dart';
import 'package:freezer/translations.i18n.dart';
import 'package:quick_actions/quick_actions.dart';
import 'package:uni_links/uni_links.dart';
@ -56,7 +58,7 @@ void main() async {
..registerAdapter(ArtistAdapter())
..registerAdapter(PlaylistAdapter())
..registerAdapter(UserAdapter())
..registerAdapter(ImageDetailsAdapter())
..registerAdapter(DeezerImageDetailsAdapter())
..registerAdapter(LyricsAdapter())
..registerAdapter(LyricAdapter())
..registerAdapter(SmartTrackListAdapter())
@ -102,7 +104,6 @@ void main() async {
}());
//Do on BG
playerHelper.authorizeLastFM();
await playerHelper.initAudioHandler();
runApp(FreezerApp());
@ -250,7 +251,8 @@ class _LoginMainWrapperState extends State<LoginMainWrapper> {
: SystemUiOverlayStyle.light)
.copyWith(
statusBarColor: Colors.transparent,
systemNavigationBarColor: Theme.of(context).scaffoldBackgroundColor,
systemNavigationBarColor:
Colors.transparent, // Theme.of(context).scaffoldBackgroundColor,
statusBarIconBrightness: Brightness.light,
),
child: LoginWidget(
@ -261,22 +263,22 @@ class _LoginMainWrapperState extends State<LoginMainWrapper> {
}
}
class _PlayerBarSliverDelegate extends SliverPersistentHeaderDelegate {
@override
// TODO: implement minExtent
double get minExtent => 59.0;
double get maxExtent => 59.0;
@override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) =>
false;
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return PlayerBar();
}
}
// class _PlayerBarSliverDelegate extends SliverPersistentHeaderDelegate {
// @override
// // TODO: implement minExtent
// double get minExtent => 59.0;
// double get maxExtent => 59.0;
//
// @override
// bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) =>
// false;
//
// @override
// Widget build(
// BuildContext context, double shrinkOffset, bool overlapsContent) {
// return PlayerBar();
// }
// }
class MainScreen extends StatefulWidget {
@override
@ -290,6 +292,7 @@ class _MainScreenState extends State<MainScreen>
0: '/',
1: '/podcasts',
2: '/library',
3: '/search',
};
StreamSubscription? _urlLinkStream;
int _keyPressed = 0;
@ -478,66 +481,49 @@ class _MainScreenState extends State<MainScreen>
return RawKeyboardListener(
focusNode: FocusNode(),
onKey: _handleKey,
child: Scaffold(
// bottomSheet: DraggableScrollableSheet(
// minChildSize: 0.2,
// maxChildSize: 1.0,
// snap: true,
// snapSizes: [0.2, 1.0],
// initialChildSize: 0.2,
// builder: (context, scrollController) => NestedScrollView(
// headerSliverBuilder: (context, innerBoxIsScrolled) => [
// SliverPersistentHeader(
// delegate: _PlayerBarSliverDelegate(),
// pinned: true,
// floating: true,
// )
// ],
// body: SizedBox.fromSize(
// size: MediaQuery.of(context).size,
// child: PlayerScreen(),
// ))),
bottomNavigationBar: SafeArea(
child: FocusScope(
node: navigationBarFocusNode,
child:
Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
PlayerBar(),
ValueListenableBuilder<int>(
valueListenable: _selected,
builder: (context, value, _) {
return NavigationBar(
selectedIndex: value,
onDestinationSelected: (int s) async {
//Pop all routes until home screen
navigatorKey.currentState!
.popUntil((route) => route.isFirst);
navigatorKey.currentState!
.pushReplacementNamed(_destinations[s]!);
child: FancyScaffold(
bottomNavigationBar: FocusScope(
node: navigationBarFocusNode,
child: ValueListenableBuilder<int>(
valueListenable: _selected,
builder: (context, value, _) {
return NavigationBar(
selectedIndex: value,
onDestinationSelected: (int s) async {
//Pop all routes until home screen
navigatorKey.currentState!
.popUntil((route) => route.isFirst);
navigatorKey.currentState!
.pushReplacementNamed(_destinations[s]!);
if (_selected.value != s) _selected.value = s;
},
destinations: <NavigationDestination>[
NavigationDestination(
icon: const Icon(Icons.home_outlined),
selectedIcon: const Icon(Icons.home),
label: 'Home'.i18n),
NavigationDestination(
icon: const Icon(Icons.podcasts),
label: 'Podcasts'.i18n),
// NavigationDestination(
// icon: const Icon(Icons.search),
// label: 'Search'.i18n),
NavigationDestination(
icon: const Icon(Icons.library_music_outlined),
selectedIcon: const Icon(Icons.library_music),
label: 'Library'.i18n)
],
);
})
]),
),
if (_selected.value != s) _selected.value = s;
},
destinations: <NavigationDestination>[
NavigationDestination(
icon: const Icon(Icons.home_outlined),
selectedIcon: const Icon(Icons.home),
label: 'Home'.i18n),
NavigationDestination(
icon: const Icon(Icons.podcasts),
label: 'Podcasts'.i18n),
NavigationDestination(
icon: const Icon(Icons.library_music_outlined),
selectedIcon: const Icon(Icons.library_music),
label: 'Library'.i18n),
// NavigationDestination(
// icon: const Icon(Icons.search),
// label: 'Search'.i18n),
],
);
}),
),
bottomPanel: PlayerBar(
shouldHandleClicks: false,
shouldHaveHero: false,
),
bottomPanelHeight: 68.0,
expandedPanel: PlayerScreen(),
body: Focus(
focusNode: screenFocusNode,
skipTraversal: true,
@ -545,23 +531,21 @@ class _MainScreenState extends State<MainScreen>
child: _MainRouteNavigator(
navigatorKey: navigatorKey,
routes: {
'/': (context) => HomeScreen(),
'/podcasts': (context) => Scaffold(
appBar: AppBar(title: Text('Podcasts'.i18n)),
body: HomePageScreen(
cacheable: true,
channel: DeezerChannel(target: 'channels/podcasts'),
),
'/': (context) => const HomeScreen(),
'/podcasts': (context) => HomePageScreen(
cacheable: true,
channel: DeezerChannel(target: 'channels/podcasts'),
title: 'Podcasts'.i18n,
),
'/library': (context) => LibraryScreen(),
'/library/tracks': (context) => LibraryTracks(),
'/library/albums': (context) => LibraryAlbums(),
'/library/artists': (context) => LibraryArtists(),
'/library/playlists': (context) => LibraryPlaylists(),
'/library/history': (context) => HistoryScreen(),
'/search': (context) => SearchScreen(),
'/settings': (context) => SettingsScreen(),
'/downloads': (context) => DownloadsScreen(),
'/library': (context) => const LibraryScreen(),
'/library/tracks': (context) => const LibraryTracks(),
'/library/albums': (context) => const LibraryAlbums(),
'/library/artists': (context) => const LibraryArtists(),
'/library/playlists': (context) => const LibraryPlaylists(),
'/library/history': (context) => const HistoryScreen(),
'/search': (context) => const SearchScreen(),
'/settings': (context) => const SettingsScreen(),
'/downloads': (context) => const DownloadsScreen(),
},
))));
}
@ -600,16 +584,30 @@ class _MainRouteNavigator extends StatelessWidget with WidgetsBindingObserver {
);
}
Route<dynamic>? _onGenerateRoute(RouteSettings settings) {
final routeBuilder = routes[settings.name];
Route<T>? _onGenerateRoute<T>(RouteSettings s) {
final routeBuilder = routes[s.name];
if (routeBuilder == null) return null;
return ScaleFadePageRoute(builder: routeBuilder, settings: settings);
if (!navigatorKey.currentState!.canPop())
return ScaleFadePageRoute<T>(builder: routeBuilder, settings: s);
switch (settings.navigatorRouteType) {
case NavigatorRouteType.blur_slide:
return BlurSlidePageRoute<T>(builder: routeBuilder, settings: s);
case NavigatorRouteType.material:
return MaterialPageRoute<T>(builder: routeBuilder, settings: s);
case NavigatorRouteType.cupertino:
return CupertinoPageRoute<T>(builder: routeBuilder, settings: s);
case NavigatorRouteType.fade:
return FadePageRoute<T>(builder: routeBuilder, settings: s);
case NavigatorRouteType.fade_blur:
return FadePageRoute<T>(builder: routeBuilder, settings: s, blur: true);
}
}
}
// class FreezerDrawer extends StatelessWidget {
// const FreezerDrawer({Key? key}) : super(key: key);
//
//
// @override
// Widget build(BuildContext context) {
// return Drawer(
@ -651,7 +649,7 @@ class _MainRouteNavigator extends StatelessWidget with WidgetsBindingObserver {
// );
// }
// }
//
//
// class FreezerDrawerTile extends StatelessWidget {
// final Widget? icon;
// final String title;
@ -659,7 +657,7 @@ class _MainRouteNavigator extends StatelessWidget with WidgetsBindingObserver {
// const FreezerDrawerTile(
// {Key? key, this.icon, required this.title, required this.route})
// : super(key: key);
//
//
// @override
// Widget build(BuildContext context) {
// print(route);

View File

@ -15,9 +15,12 @@ class BlurSlidePageRoute<T> extends BasicPageRoute<T> {
this.animationCurve = Curves.linearToEaseOut,
transitionDuration = const Duration(milliseconds: 300),
maintainState = true,
RouteSettings? settings,
}) : super(
transitionDuration: transitionDuration,
maintainState: maintainState);
transitionDuration: transitionDuration,
maintainState: maintainState,
settings: settings,
);
@override
Widget buildPage(BuildContext context, Animation<double> animation,

View File

@ -10,9 +10,12 @@ class FadePageRoute<T> extends BasicPageRoute<T> {
this.blur = false,
transitionDuration = const Duration(milliseconds: 300),
maintainState = true,
RouteSettings? settings,
}) : super(
transitionDuration: transitionDuration,
maintainState: maintainState);
transitionDuration: transitionDuration,
maintainState: maintainState,
settings: settings,
);
@override
Widget buildPage(BuildContext context, Animation<double> animation,

View File

@ -6,7 +6,6 @@ import 'package:google_fonts/google_fonts.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;
import 'package:flutter/material.dart';
import 'dart:convert';
@ -249,25 +248,8 @@ class Settings {
Future<void> updateAudioServiceQuality() async {
//Send wifi & mobile quality to audio service isolate
await audioHandler.customAction('updateQuality', {
'mobileQuality': getQualityInt(mobileQuality),
'wifiQuality': getQualityInt(wifiQuality)
});
}
//AudioQuality to deezer int
int getQualityInt(AudioQuality? q) {
switch (q) {
case AudioQuality.MP3_128:
return 1;
case AudioQuality.MP3_320:
return 3;
case AudioQuality.FLAC:
return 9;
//Deezer default
default:
return 8;
}
await audioHandler.customAction('updateQuality',
{'mobileQuality': mobileQuality, 'wifiQuality': wifiQuality});
}
// MaterialColor get _primarySwatch =>
@ -389,6 +371,17 @@ enum AudioQuality {
ASK
}
extension ToDeezerInt on AudioQuality {
int toDeezerQualityInt() {
return const {
AudioQuality.MP3_128: 1,
AudioQuality.MP3_320: 3,
AudioQuality.FLAC: 9,
}[this] ??
8;
}
}
@HiveType(typeId: 28)
enum Themes {
@HiveField(0)

View File

@ -55,7 +55,6 @@ class SettingsAdapter extends TypeAdapter<Settings> {
? NavigatorRouteType.material
: fields[33] as NavigatorRouteType
..primaryColor = fields[34] == null ? Colors.blue : fields[34] as Color
..materialYouAccent = fields[45] == null ? false : fields[45] as bool
..useArtColor = fields[35] as bool
..deezerLanguage = fields[36] as String
..deezerCountry = fields[37] as String
@ -65,13 +64,16 @@ class SettingsAdapter extends TypeAdapter<Settings> {
..lastFMPassword = fields[41] as String?
..spotifyClientId = fields[42] as String?
..spotifyClientSecret = fields[43] as String?
..spotifyCredentials = fields[44] as SpotifyCredentialsSave?;
..spotifyCredentials = fields[44] as SpotifyCredentialsSave?
..materialYouAccent = fields[45] == null ? false : fields[45] as bool
..playerAlbumArtDropShadow =
fields[46] == null ? true : fields[46] as bool;
}
@override
void write(BinaryWriter writer, Settings obj) {
writer
..writeByte(46)
..writeByte(47)
..writeByte(0)
..write(obj.language)
..writeByte(1)
@ -142,8 +144,6 @@ class SettingsAdapter extends TypeAdapter<Settings> {
..write(obj.navigatorRouteType)
..writeByte(34)
..write(obj.primaryColor)
..writeByte(45)
..write(obj.materialYouAccent)
..writeByte(35)
..write(obj.useArtColor)
..writeByte(36)
@ -163,7 +163,11 @@ class SettingsAdapter extends TypeAdapter<Settings> {
..writeByte(43)
..write(obj.spotifyClientSecret)
..writeByte(44)
..write(obj.spotifyCredentials);
..write(obj.spotifyCredentials)
..writeByte(45)
..write(obj.materialYouAccent)
..writeByte(46)
..write(obj.playerAlbumArtDropShadow);
}
@override
@ -362,7 +366,6 @@ Settings _$SettingsFromJson(Map<String, dynamic> json) => Settings()
..navigatorRouteType =
$enumDecode(_$NavigatorRouteTypeEnumMap, json['navigatorRouteType'])
..primaryColor = Settings._colorFromJson(json['primaryColor'] as int?)
..materialYouAccent = json['materialYouAccent'] as bool
..useArtColor = json['useArtColor'] as bool
..deezerLanguage = json['deezerLanguage'] as String
..deezerCountry = json['deezerCountry'] as String
@ -375,7 +378,9 @@ Settings _$SettingsFromJson(Map<String, dynamic> json) => Settings()
..spotifyCredentials = json['spotifyCredentials'] == null
? null
: SpotifyCredentialsSave.fromJson(
json['spotifyCredentials'] as Map<String, dynamic>);
json['spotifyCredentials'] as Map<String, dynamic>)
..materialYouAccent = json['materialYouAccent'] as bool
..playerAlbumArtDropShadow = json['playerAlbumArtDropShadow'] as bool;
Map<String, dynamic> _$SettingsToJson(Settings instance) => <String, dynamic>{
'language': instance.language,
@ -414,7 +419,6 @@ Map<String, dynamic> _$SettingsToJson(Settings instance) => <String, dynamic>{
'navigatorRouteType':
_$NavigatorRouteTypeEnumMap[instance.navigatorRouteType]!,
'primaryColor': Settings._colorToJson(instance.primaryColor),
'materialYouAccent': instance.materialYouAccent,
'useArtColor': instance.useArtColor,
'deezerLanguage': instance.deezerLanguage,
'deezerCountry': instance.deezerCountry,
@ -425,6 +429,8 @@ Map<String, dynamic> _$SettingsToJson(Settings instance) => <String, dynamic>{
'spotifyClientId': instance.spotifyClientId,
'spotifyClientSecret': instance.spotifyClientSecret,
'spotifyCredentials': instance.spotifyCredentials,
'materialYouAccent': instance.materialYouAccent,
'playerAlbumArtDropShadow': instance.playerAlbumArtDropShadow,
};
const _$AudioQualityEnumMap = {

View File

@ -4,15 +4,14 @@ import 'package:freezer/api/definitions.dart';
import 'package:freezer/api/player.dart';
import 'package:freezer/translations.i18n.dart';
final androidAuto = AndroidAuto._();
class AndroidAuto {
AndroidAuto._();
final DeezerAPI deezerAPI;
AndroidAuto({required this.deezerAPI});
//Prefix for "playable" MediaItem
static const prefix = '_aa_';
//Get media items for parent id
Future<List<MediaItem>> getScreen(String? parentId) async {
Future<List<MediaItem>> getScreen(String parentId) async {
print(parentId);
//Homescreen
@ -92,7 +91,7 @@ class AndroidAuto {
if (parentId == 'homescreen') {
HomePage hp = await deezerAPI.homePage();
List<MediaItem> out = [];
for (HomePageSection section in hp.sections!) {
for (HomePageSection section in hp.sections) {
for (int i = 0; i < section.items!.length; i++) {
//Limit to max 5 items
if (i == 5) break;
@ -154,7 +153,7 @@ class AndroidAuto {
}
//Load virtual mediaItem
Future playItem(String? id) async {
Future<void> playItem(String id) async {
print(id);
//Play flow

View File

@ -11,6 +11,8 @@ import 'cached_image.dart';
import 'dart:async';
class DownloadsScreen extends StatefulWidget {
const DownloadsScreen();
@override
_DownloadsScreenState createState() => _DownloadsScreenState();
}

View File

@ -7,7 +7,6 @@ import 'package:freezer/ui/menu.dart';
import 'package:freezer/translations.i18n.dart';
import 'tiles.dart';
import 'details_screens.dart';
import '../settings.dart';
class _SearchHeaderDelegate extends SliverPersistentHeaderDelegate {
@override
@ -38,6 +37,8 @@ class _SearchHeaderDelegate extends SliverPersistentHeaderDelegate {
}
class HomeScreen extends StatelessWidget {
const HomeScreen();
@override
Widget build(BuildContext context) {
return SafeArea(
@ -79,19 +80,46 @@ class FreezerTitle extends StatelessWidget {
}
}
class HomePageScreen extends StatefulWidget {
class HomePageScreen extends StatelessWidget {
final String title;
final HomePage? homePage;
final bool cacheable;
final DeezerChannel channel;
const HomePageScreen({
super.key,
required this.title,
required this.channel,
this.homePage,
this.cacheable = false,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: HomePageWidget(
homePage: homePage,
channel: channel,
cacheable: cacheable,
),
);
}
}
class HomePageWidget extends StatefulWidget {
final HomePage? homePage;
final bool cacheable;
final DeezerChannel? channel;
HomePageScreen(
HomePageWidget(
{this.homePage, this.channel, this.cacheable = false, Key? key})
: super(key: key);
@override
_HomePageScreenState createState() => _HomePageScreenState();
_HomePageWidgetState createState() => _HomePageWidgetState();
}
class _HomePageScreenState extends State<HomePageScreen> {
class _HomePageWidgetState extends State<HomePageWidget> {
HomePage? _homePage;
bool _error = false;
bool _loadExplicitlyRequested = false;
@ -115,7 +143,7 @@ class _HomePageScreenState extends State<HomePageScreen> {
setState(() => _error = true);
return;
}
if (_hp!.sections.isEmpty) return;
if (_hp.sections.isEmpty) return;
if (widget.cacheable) _hp.save(widget.channel?.target ?? '');
setState(() => _homePage = _hp!);
}
@ -161,7 +189,7 @@ class _HomePageScreenState extends State<HomePageScreen> {
if (_error) return ErrorScreen();
List<HomePageSection>? sections;
if (_homePage != null) {
sections = _homePage!.sections!;
sections = _homePage!.sections;
}
return RefreshIndicator(
key: _indicatorKey,
@ -214,13 +242,11 @@ class HomepageRowSection extends StatelessWidget {
style: TextStyle(fontSize: 20.0),
),
onPressed: () => Navigator.of(context).pushRoute(
builder: (context) => Scaffold(
appBar: AppBar(title: Text(section.title!)),
body: HomePageScreen(
channel:
DeezerChannel(target: section.pagePath),
),
)),
builder: (context) => HomePageScreen(
title: section.title!,
channel: DeezerChannel(target: section.pagePath),
),
),
);
}
return const SizedBox();
@ -324,10 +350,11 @@ class HomePageItemWidget extends StatelessWidget {
item.value,
onTap: () {
Navigator.of(context).pushRoute(
builder: (context) => Scaffold(
appBar: AppBar(title: Text(item.value.title.toString())),
body: HomePageScreen(channel: item.value),
));
builder: (context) => HomePageScreen(
channel: item.value,
title: item.value.title.toString(),
),
);
},
);
case HomePageItemType.SHOW:

View File

@ -34,20 +34,14 @@ class LibraryAppBar extends StatelessWidget implements PreferredSizeWidget {
Icons.file_download,
semanticLabel: "Download".i18n,
),
onPressed: () {
Navigator.of(context)
.pushRoute(builder: (context) => DownloadsScreen());
},
onPressed: () => Navigator.pushNamed(context, '/downloads'),
),
IconButton(
icon: Icon(
Icons.settings,
semanticLabel: "Settings".i18n,
),
onPressed: () {
Navigator.of(context)
.pushRoute(builder: (context) => SettingsScreen());
},
onPressed: () => Navigator.pushNamed(context, '/settings'),
),
],
);
@ -55,6 +49,8 @@ class LibraryAppBar extends StatelessWidget implements PreferredSizeWidget {
}
class LibraryScreen extends StatelessWidget {
const LibraryScreen();
@override
Widget build(BuildContext context) {
return Scaffold(
@ -178,7 +174,7 @@ class LibraryScreen extends StatelessWidget {
children: <Widget>[CircularProgressIndicator()],
),
);
List<String> data = snapshot.data! as List<String>;
List<String> data = snapshot.data!;
return Column(
children: <Widget>[
ListTile(
@ -219,6 +215,8 @@ class LibraryScreen extends StatelessWidget {
}
class LibraryTracks extends StatefulWidget {
const LibraryTracks();
@override
_LibraryTracksState createState() => _LibraryTracksState();
}
@ -535,6 +533,8 @@ class _LibraryTracksState extends State<LibraryTracks> {
}
class LibraryAlbums extends StatefulWidget {
const LibraryAlbums();
@override
_LibraryAlbumsState createState() => _LibraryAlbumsState();
}
@ -685,7 +685,7 @@ class _LibraryAlbumsState extends State<LibraryAlbums> {
builder: (context, snapshot) {
if (snapshot.hasError ||
!snapshot.hasData ||
(snapshot.data! as List).isEmpty)
(snapshot.data!).isEmpty)
return Container(
height: 0,
width: 0,
@ -731,6 +731,8 @@ class _LibraryAlbumsState extends State<LibraryAlbums> {
}
class LibraryArtists extends StatefulWidget {
const LibraryArtists();
@override
_LibraryArtistsState createState() => _LibraryArtistsState();
}
@ -886,6 +888,8 @@ class _LibraryArtistsState extends State<LibraryArtists> {
}
class LibraryPlaylists extends StatefulWidget {
const LibraryPlaylists();
@override
_LibraryPlaylistsState createState() => _LibraryPlaylistsState();
}
@ -1137,6 +1141,8 @@ class _LibraryPlaylistsState extends State<LibraryPlaylists> {
}
class HistoryScreen extends StatefulWidget {
const HistoryScreen();
@override
_HistoryScreenState createState() => _HistoryScreenState();
}

View File

@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:freezer/api/deezer.dart';
import 'package:freezer/api/player.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:freezer/translations.i18n.dart';

View File

@ -306,7 +306,7 @@ class MenuSheet {
Widget offlineTrack(Track track) => FutureBuilder(
future: downloadManager.checkOffline(track: track),
builder: (context, snapshot) {
bool isOffline = (snapshot.data as bool?) ?? (track.offline ?? false);
bool isOffline = snapshot.data ?? (track.offline ?? false);
return ListTile(
title: Text(isOffline ? 'Remove offline'.i18n : 'Offline'.i18n),
leading: Icon(Icons.offline_pin),
@ -747,7 +747,7 @@ class _SelectPlaylistDialogState extends State<SelectPlaylistDialog> {
),
);
List<Playlist> playlists = snapshot.data! as List<Playlist>;
List<Playlist> playlists = snapshot.data!;
return SingleChildScrollView(
child: Column(mainAxisSize: MainAxisSize.min, children: [
...List.generate(

View File

@ -3,7 +3,6 @@ import 'dart:async';
import 'package:audio_service/audio_service.dart';
import 'package:flutter/material.dart';
import 'package:freezer/api/definitions.dart';
import 'package:freezer/page_routes/fade.dart';
import 'package:freezer/settings.dart';
import 'package:freezer/translations.i18n.dart';
import 'package:rxdart/rxdart.dart';
@ -12,6 +11,185 @@ import '../api/player.dart';
import 'cached_image.dart';
import 'player_screen.dart';
class FancyScaffold extends StatefulWidget {
final Widget bottomPanel;
final double bottomPanelHeight;
final Widget expandedPanel;
final Widget bottomNavigationBar;
final Widget body;
const FancyScaffold({
required this.bottomPanel,
required this.bottomPanelHeight,
required this.expandedPanel,
required this.bottomNavigationBar,
required this.body,
super.key,
});
@override
State<FancyScaffold> createState() => _FancyScaffoldState();
}
class _FancyScaffoldState extends State<FancyScaffold>
with TickerProviderStateMixin {
// goes from 0 to 1 (double)
// 0 = preview, 1 = expanded
late final AnimationController _dragController;
final _status = ValueNotifier<AnimationStatus>(AnimationStatus.dismissed);
@override
void initState() {
_dragController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 500));
_dragController.addStatusListener((status) => _status.value = status);
super.initState();
}
@override
void dispose() {
_dragController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final systemPadding = MediaQuery.of(context).viewPadding;
final defaultBottomPadding = 80.0 + systemPadding.bottom;
final screenHeight = MediaQuery.of(context).size.height;
print('height: $screenHeight, padding: $systemPadding');
final _sizeAnimation = Tween<double>(
begin: widget.bottomPanelHeight / MediaQuery.of(context).size.height,
end: 1.0,
).animate(_dragController);
print('route: ' + (ModalRoute.of(context) == null ? 'no' : 'yes'));
return WillPopScope(
onWillPop: () {
print('BACK PRESSED! ${_dragController.value}');
if (_status.value == AnimationStatus.completed ||
_status.value == AnimationStatus.reverse) {
print('flinging without popping!');
_dragController.fling(velocity: -1.0);
return Future.value(false);
}
print('fuck this, no');
return Future.value(true);
},
child: Stack(
children: [
Positioned.fill(
child: Scaffold(
body: widget.body,
bottomNavigationBar: Column(
children: [
SizedBox(height: widget.bottomPanelHeight),
SizeTransition(
axisAlignment: -1.0,
sizeFactor:
Tween(begin: 1.0, end: 0.0).animate(_sizeAnimation),
child: widget.bottomNavigationBar,
),
],
),
),
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: AnimatedBuilder(
animation: _sizeAnimation,
builder: (context, child) {
final x = 1.0 - _sizeAnimation.value;
return Padding(
padding: EdgeInsets.only(
bottom: (defaultBottomPadding /*+ 8.0*/) * x,
//right: 8.0 * x,
//left: 8.0 * x,
),
child: child,
);
},
child: ValueListenableBuilder(
valueListenable: _status,
builder: (context, state, child) {
return GestureDetector(
onVerticalDragEnd: _onVerticalDragEnd,
onVerticalDragUpdate: _onVerticalDragUpdate,
onTap: state == AnimationStatus.dismissed ? _onTap : null,
child: child,
);
},
child: SizeTransition(
sizeFactor: _sizeAnimation,
axisAlignment: -1.0,
axis: Axis.vertical,
child: SizedBox(
height: screenHeight,
width: MediaQuery.of(context).size.width,
child: ValueListenableBuilder(
valueListenable: _status,
builder: (context, state, _) => Stack(
children: [
if (state != AnimationStatus.dismissed)
Positioned.fill(
child: widget.expandedPanel,
key: Key('player_screen'),
),
if (state != AnimationStatus.completed)
Positioned(
top: 0,
right: 0,
left: 0,
child: FadeTransition(
opacity: Tween(begin: 1.0, end: 0.0)
.animate(_dragController),
child: SizedBox(
height: widget.bottomPanelHeight,
child: widget.bottomPanel),
),
key: Key('player_bar'),
),
],
),
),
)),
),
),
),
],
),
);
}
void _onTap() {
_dragController.fling();
}
void _onVerticalDragUpdate(DragUpdateDetails details) {
_dragController.value -=
details.delta.dy / MediaQuery.of(context).size.height;
}
void _onVerticalDragEnd(DragEndDetails details) {
// snap widget to size
// this should be also handled by drag velocity and not only with bare size.
const double minFlingVelocity = 365.0;
if (details.velocity.pixelsPerSecond.dy.abs() > minFlingVelocity) {
_dragController.fling(
velocity: -details.velocity.pixelsPerSecond.dy /
MediaQuery.of(context).size.height);
return;
}
_dragController.fling(velocity: _dragController.value > 0.5 ? 1.0 : -1.0);
}
}
class PlayerBar extends StatefulWidget {
final bool shouldHandleClicks;
final bool shouldHaveHero;
@ -67,104 +245,107 @@ class _PlayerBarState extends State<PlayerBar> {
@override
Widget build(BuildContext context) {
return _isNothingPlaying
? const SizedBox()
: GestureDetector(
onHorizontalDragUpdate: (details) async {
if (_gestureRegistered) return;
final double sensitivity = 12.69;
//Right swipe
_gestureRegistered = true;
if (details.delta.dx > sensitivity) {
await audioHandler.skipToPrevious();
}
//Left
if (details.delta.dx < -sensitivity) {
await audioHandler.skipToNext();
}
_gestureRegistered = false;
return;
},
child: Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
StreamBuilder<MediaItem?>(
stream: audioHandler.mediaItem,
initialData: audioHandler.mediaItem.valueOrNull,
builder: (context, snapshot) {
if (!snapshot.hasData) return const SizedBox();
final currentMediaItem = snapshot.data!;
final image = CachedImage(
width: 50,
height: 50,
url: currentMediaItem.extras!['thumb'] ??
currentMediaItem.artUri.toString(),
);
final leadingWidget = widget.shouldHaveHero
? Hero(tag: currentMediaItem.id, child: image)
: image;
return Material(
// For Android TV: indicate focus by grey
color: focusNode.hasFocus
? Color.lerp(backgroundColor, Colors.grey, 0.26)
: backgroundColor,
child: ListTile(
dense: true,
focusNode: focusNode,
contentPadding:
EdgeInsets.symmetric(horizontal: 8.0),
onTap: widget.shouldHandleClicks
? _pushPlayerScreen
: null,
leading: AnimatedSwitcher(
duration: const Duration(milliseconds: 250),
child: leadingWidget),
title: Text(
currentMediaItem.displayTitle!,
overflow: TextOverflow.clip,
maxLines: 1,
),
subtitle: Text(
currentMediaItem.displaySubtitle ?? '',
overflow: TextOverflow.clip,
maxLines: 1,
),
trailing: IconTheme(
data: IconThemeData(
color: settings.isDark
? Colors.white
: Colors.grey[600]),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
PrevNextButton(
iconSize,
prev: true,
return SizedBox(
height: 68.0,
child: _isNothingPlaying
? null
: GestureDetector(
onHorizontalDragUpdate: (details) async {
if (_gestureRegistered) return;
final double sensitivity = 12.69;
//Right swipe
_gestureRegistered = true;
if (details.delta.dx > sensitivity) {
await audioHandler.skipToPrevious();
}
//Left
if (details.delta.dx < -sensitivity) {
await audioHandler.skipToNext();
}
_gestureRegistered = false;
return;
},
child: Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
Expanded(
child: StreamBuilder<MediaItem?>(
stream: audioHandler.mediaItem,
initialData: audioHandler.mediaItem.valueOrNull,
builder: (context, snapshot) {
if (!snapshot.hasData) return const SizedBox();
final currentMediaItem = snapshot.data!;
final image = CachedImage(
width: 50,
height: 50,
url: currentMediaItem.extras!['thumb'] ??
currentMediaItem.artUri.toString(),
);
final leadingWidget = widget.shouldHaveHero
? Hero(tag: currentMediaItem.id, child: image)
: image;
return Material(
child: ListTile(
// For Android TV: indicate focus by grey
tileColor: focusNode.hasFocus
? Color.lerp(
backgroundColor, Colors.grey, 0.26)
: backgroundColor,
dense: true,
focusNode: focusNode,
contentPadding:
EdgeInsets.symmetric(horizontal: 8.0),
onTap: widget.shouldHandleClicks
? _pushPlayerScreen
: null,
leading: AnimatedSwitcher(
duration: const Duration(milliseconds: 250),
child: leadingWidget),
title: Text(
currentMediaItem.displayTitle!,
overflow: TextOverflow.clip,
maxLines: 1,
),
subtitle: Text(
currentMediaItem.displaySubtitle ?? '',
overflow: TextOverflow.clip,
maxLines: 1,
),
trailing: IconTheme(
data: IconThemeData(
color: settings.isDark
? Colors.white
: Colors.grey[600]),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
PrevNextButton(
iconSize,
prev: true,
),
PlayPauseButton(iconSize),
PrevNextButton(iconSize)
],
),
PlayPauseButton(iconSize),
PrevNextButton(iconSize)
],
),
)));
}),
SizedBox(
height: 3.0,
child: StreamBuilder<Duration>(
stream: AudioService.position,
builder: (context, snapshot) {
return LinearProgressIndicator(
value: parsePosition(snapshot.data ?? Duration.zero),
);
}),
),
]),
);
)));
}),
),
SizedBox(
height: 3.0,
child: StreamBuilder<Duration>(
stream: AudioService.position,
builder: (context, snapshot) {
return LinearProgressIndicator(
value: parsePosition(snapshot.data ?? Duration.zero),
);
}),
),
]),
),
);
}
void _pushPlayerScreen() {
final builder = (BuildContext context) => PlayerScreen();
if (settings.blurPlayerBackground) {
Navigator.of(context).push(FadePageRoute(builder: builder));
return;
}
Navigator.of(context).pushRoute(builder: builder);
}
}
@ -292,7 +473,7 @@ class _PlayPauseButtonState extends State<PlayPauseButton>
iconSize: widget.size,
onPressed: _playPause);
child = InkWell(
customBorder: CircleBorder(),
customBorder: const CircleBorder(),
child: IconTheme.merge(
child: Center(child: icon),
data: IconThemeData(
@ -313,11 +494,16 @@ class _PlayPauseButtonState extends State<PlayPauseButton>
if (widget.filled)
return SizedBox.square(
dimension: widget.size,
child: Card(
color: widget.color,
elevation: 2.0,
shape: CircleBorder(),
child: child));
child: Material(
type: MaterialType.canvas,
shape: const CircleBorder(),
elevation: 2.0,
child: AnimatedContainer(
duration: const Duration(seconds: 1),
decoration:
BoxDecoration(shape: BoxShape.circle, color: widget.color),
child: child),
));
else
return SizedBox.square(dimension: widget.size, child: child);
}

View File

@ -1,16 +1,13 @@
import 'dart:ui';
import 'dart:convert';
import 'dart:async';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:audio_service/audio_service.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:freezer/api/cache.dart';
import 'package:freezer/api/deezer.dart';
import 'package:freezer/api/definitions.dart';
import 'package:freezer/api/download.dart';
import 'package:freezer/api/player.dart';
import 'package:freezer/page_routes/fade.dart';
import 'package:freezer/settings.dart';
@ -21,14 +18,12 @@ import 'package:freezer/ui/menu.dart';
import 'package:freezer/ui/player_bar.dart';
import 'package:freezer/ui/queue_screen.dart';
import 'package:freezer/ui/settings_screen.dart';
import 'package:logging/logging.dart';
import 'package:marquee/marquee.dart';
import 'package:palette_generator/palette_generator.dart';
import 'package:photo_view/photo_view.dart';
import 'package:provider/provider.dart';
//Changing item in queue view and pressing back causes the pageView to skip song
bool pageViewLock = false;
const _blurStrength = 90.0;
/// A simple [ChangeNotifier] that listens to the [AudioHandler.mediaItem] stream and
@ -38,12 +33,13 @@ class BackgroundProvider extends ChangeNotifier {
Color? _dominantColor;
ImageProvider? _imageProvider;
StreamSubscription? _mediaItemSub;
bool _isDisposed = false;
BackgroundProvider();
/// Calculate background color from [mediaItem]
///
/// Warning: this function is expensive to call, and should only be called when songs change!
Future _updateColor(MediaItem mediaItem) async {
Future<void> _updateColor(MediaItem mediaItem) async {
if (!settings.colorGradientBackground &&
!settings.blurPlayerBackground &&
!settings.enableFilledPlayButton &&
@ -54,7 +50,7 @@ class BackgroundProvider extends ChangeNotifier {
_palette = await PaletteGenerator.fromImageProvider(imageProvider);
_dominantColor = _palette!.dominantColor!.color;
_imageProvider = settings.blurPlayerBackground ? imageProvider : null;
notifyListeners();
if (!_isDisposed) notifyListeners();
}
@override
@ -77,6 +73,7 @@ class BackgroundProvider extends ChangeNotifier {
@override
void dispose() {
_isDisposed = true;
_mediaItemSub?.cancel();
super.dispose();
}
@ -121,21 +118,25 @@ class PlayerScreenBackground extends StatelessWidget {
if (provider.imageProvider != null || settings.colorGradientBackground)
Positioned.fill(
child: provider.imageProvider != null
? ImageFiltered(
imageFilter: ImageFilter.blur(
sigmaX: _blurStrength,
sigmaY: _blurStrength,
),
child: DecoratedBox(
decoration: BoxDecoration(
image: DecorationImage(
image: provider.imageProvider!,
fit: BoxFit.cover,
colorFilter: ColorFilter.mode(
Colors.white
.withOpacity(settings.isDark ? 0.5 : 0.8),
BlendMode.dstATop),
)),
? DecoratedBox(
decoration: BoxDecoration(color: Colors.black),
child: ImageFiltered(
imageFilter: ImageFilter.blur(
tileMode: TileMode.decal,
sigmaX: _blurStrength,
sigmaY: _blurStrength,
),
child: DecoratedBox(
decoration: BoxDecoration(
image: DecorationImage(
image: provider.imageProvider!,
fit: BoxFit.cover,
colorFilter: ColorFilter.mode(
Colors.white
.withOpacity(settings.isDark ? 0.55 : 0.75),
BlendMode.dstATop),
)),
),
),
)
: DecoratedBox(
@ -160,9 +161,7 @@ class PlayerScreenBackground extends StatelessWidget {
final hasBackground = enabled &&
(settings.blurPlayerBackground || settings.colorGradientBackground);
if (!hasBackground) return null;
final color = hasBackground
? Colors.transparent
: Theme.of(context).scaffoldBackgroundColor;
final color = Colors.transparent;
final brightness = hasBackground
? Brightness.light
: (ThemeData.estimateBrightnessForColor(color) == Brightness.light
@ -224,23 +223,7 @@ class _PlayerScreenHorizontalState extends State<PlayerScreenHorizontal> {
flex: 4,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: AspectRatio(
aspectRatio: 1.0,
child: settings.playerAlbumArtDropShadow
? Consumer<BackgroundProvider>(
builder: (context, background, child) => DecoratedBox(
decoration: BoxDecoration(boxShadow: [
BoxShadow(
color: background.dominantColor ??
Colors.transparent,
spreadRadius: 0.0,
blurRadius: 100.0)
]),
child: child),
child: BigAlbumArt(),
)
: BigAlbumArt(),
),
child: BigAlbumArt(),
),
),
const SizedBox(width: 56.0),
@ -303,23 +286,7 @@ class _PlayerScreenVerticalState extends State<PlayerScreenVertical> {
),
child: PlayerScreenTopRow(),
),
AspectRatio(
aspectRatio: 1.0,
child: settings.playerAlbumArtDropShadow
? Consumer<BackgroundProvider>(
builder: (context, background, child) => DecoratedBox(
decoration: BoxDecoration(boxShadow: [
BoxShadow(
color: background.dominantColor ??
Colors.transparent,
spreadRadius: 0.0,
blurRadius: 100.0)
]),
child: child),
child: BigAlbumArt(),
)
: BigAlbumArt(),
),
BigAlbumArt(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: PlayerTextSubtext(textSize: 64.sp),
@ -354,16 +321,17 @@ class PlayerTextSubtext extends StatelessWidget {
width: double.infinity,
child: currentMediaItem.displayTitle!.length >= 26
? Marquee(
key: Key(currentMediaItem.displayTitle!),
text: currentMediaItem.displayTitle!,
style: TextStyle(
fontSize: textSize, fontWeight: FontWeight.bold),
blankSpace: 32.0,
startPadding: 0.0,
accelerationDuration: Duration(seconds: 1),
pauseAfterRound: Duration(seconds: 2),
accelerationDuration: const Duration(seconds: 1),
pauseAfterRound: const Duration(seconds: 2),
crossAxisAlignment: CrossAxisAlignment.start,
showFadingOnlyWhenScrolling: false,
fadingEdgeEndFraction: 0.05,
fadingEdgeStartFraction: 0.05,
)
: Text(
currentMediaItem.displayTitle!,
@ -399,9 +367,9 @@ class QualityInfoWidget extends StatelessWidget {
String _getQualityStringFromInfo(StreamQualityInfo info) {
if (audioHandler.mediaItem.value == null) return '';
int bitrate = info.quality == Quality.MP3_128
int bitrate = info.quality == AudioQuality.MP3_128
? 128
: info.quality == Quality.MP3_320
: info.quality == AudioQuality.MP3_320
? 320
: info.calculateBitrate(audioHandler.mediaItem.value!.duration!);
@ -597,12 +565,12 @@ class ForwardReplay30Button extends StatelessWidget {
if (forward)
return IconButton(
onPressed: () => _seek(audioHandler.playbackState.value.position +
Duration(seconds: 30)),
const Duration(seconds: 30)),
icon: const Icon(Icons.forward_30));
return IconButton(
onPressed: () => _seek(
audioHandler.playbackState.value.position - Duration(seconds: 30)),
onPressed: () => _seek(audioHandler.playbackState.value.position -
const Duration(seconds: 30)),
icon: const Icon(Icons.replay_30));
}
}
@ -664,7 +632,7 @@ class _BigAlbumArtState extends State<BigAlbumArt> {
viewportFraction: 1.0,
);
StreamSubscription? _currentItemSub;
bool _animationLock = false;
bool _userScroll = false;
bool _initiatedByUser = false;
@override
@ -677,10 +645,9 @@ class _BigAlbumArtState extends State<BigAlbumArt> {
if (!_pageController.hasClients) return;
if (_pageController.page?.toInt() == playerHelper.queueIndex) return;
print('animating controller to page');
_animationLock = true;
await _pageController.animateToPage(playerHelper.queueIndex,
duration: Duration(milliseconds: 300), curve: Curves.easeInOut);
_animationLock = false;
});
super.initState();
}
@ -693,12 +660,12 @@ class _BigAlbumArtState extends State<BigAlbumArt> {
@override
Widget build(BuildContext context) {
return GestureDetector(
onVerticalDragUpdate: (DragUpdateDetails details) {
if (details.delta.dy > 16) {
Navigator.of(context).pop();
}
},
final child = GestureDetector(
// onVerticalDragUpdate: (DragUpdateDetails details) {
// if (details.delta.dy > 16) {
// Navigator.of(context).pop();
// }
// },
onTap: () => Navigator.push(
context,
PageRouteBuilder(
@ -718,33 +685,64 @@ class _BigAlbumArtState extends State<BigAlbumArt> {
BoxDecoration(color: Color.fromARGB(0x90, 0, 0, 0))),
);
})),
child: StreamBuilder<List<MediaItem>>(
stream: audioHandler.queue,
initialData: audioHandler.queue.valueOrNull,
builder: (context, snapshot) {
if (!snapshot.hasData)
return const Center(child: CircularProgressIndicator());
final queue = snapshot.data!;
return PageView.builder(
controller: _pageController,
onPageChanged: (int index) {
print("PAGE CHANGED!!!!");
if (pageViewLock || _animationLock) return;
_initiatedByUser = true;
audioHandler.skipToQueueItem(index);
},
itemCount: queue.length,
itemBuilder: (context, i) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Hero(
tag: queue[i].id,
child: CachedImage(
url: queue[i].artUri.toString(),
fullThumb: true,
child: NotificationListener(
onNotification: (notification) {
if (notification is ScrollStartNotification &&
notification.dragDetails != null) _userScroll = true;
if (notification is UserScrollNotification) _userScroll = true;
if (notification is ScrollEndNotification) _userScroll = false;
return false;
},
child: StreamBuilder<List<MediaItem>>(
stream: audioHandler.queue,
initialData: audioHandler.queue.valueOrNull,
builder: (context, snapshot) {
if (!snapshot.hasData)
return const Center(child: CircularProgressIndicator());
final queue = snapshot.data!;
return PageView.builder(
controller: _pageController,
onPageChanged: (int index) {
if (!_userScroll) return;
print("PAGE CHANGED!!!!");
Logger('BigAlbumArt').info('skipping to media item');
if (queue[index].id == audioHandler.mediaItem.value?.id)
return;
_initiatedByUser = true;
audioHandler.skipToQueueItem(index);
},
itemCount: queue.length,
itemBuilder: (context, i) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Hero(
tag: queue[i].id,
child: CachedImage(
url: queue[i].artUri.toString(),
fullThumb: true,
),
),
),
));
}),
));
}),
),
);
return AspectRatio(
aspectRatio: 1.0,
child: settings.playerAlbumArtDropShadow
? Consumer<BackgroundProvider>(
builder: (context, background, child) => AnimatedContainer(
duration: const Duration(seconds: 1),
decoration: BoxDecoration(boxShadow: [
BoxShadow(
color: background.dominantColor ?? Colors.transparent,
spreadRadius: 0.0,
blurRadius: 100.0)
]),
child: child),
child: child,
)
: child,
);
}
}
@ -765,19 +763,21 @@ class PlayerScreenTopRow extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: Text(
(short ?? false)
? (playerHelper.queueSource!.text ?? '')
: 'Playing from:'.i18n +
' ' +
(playerHelper.queueSource?.text ?? ''),
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.left,
style: TextStyle(fontSize: this.textSize ?? ScreenUtil().setSp(38)),
if (playerHelper.queueSource != null)
Expanded(
child: Text(
(short ?? false)
? (playerHelper.queueSource!.text ?? '')
: 'Playing from:'.i18n +
' ' +
(playerHelper.queueSource?.text ?? ''),
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.left,
style:
TextStyle(fontSize: this.textSize ?? ScreenUtil().setSp(38)),
),
),
),
IconButton(
icon: Icon(
Icons.menu,

View File

@ -6,7 +6,6 @@ import 'package:freezer/api/definitions.dart';
import 'package:freezer/api/player.dart';
import 'package:freezer/translations.i18n.dart';
import 'package:freezer/ui/menu.dart';
import 'package:freezer/ui/player_screen.dart';
import 'package:freezer/ui/tiles.dart';
class QueueScreen extends StatefulWidget {
@ -147,10 +146,8 @@ class _QueueScreenState extends State<QueueScreen> {
trailing: ReorderableDragStartListener(
child: const Icon(Icons.drag_handle), index: index),
onTap: () {
pageViewLock = true;
audioHandler.skipToQueueItem(index).then((value) {
Navigator.of(context).pop();
pageViewLock = false;
});
},
onHold: () => MenuSheet(context).defaultTrackMenu(track),

View File

@ -5,7 +5,6 @@ import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:fluttericon/font_awesome5_icons.dart';
import 'package:fluttericon/typicons_icons.dart';
import 'package:freezer/api/cache.dart';
import 'package:freezer/api/download.dart';
@ -13,7 +12,6 @@ import 'package:freezer/api/player.dart';
import 'package:freezer/notifiers/list_notifier.dart';
import 'package:freezer/ui/details_screens.dart';
import 'package:freezer/ui/elements.dart';
import 'package:freezer/ui/home_screen.dart';
import 'package:freezer/ui/menu.dart';
import 'package:freezer/translations.i18n.dart';

View File

@ -4,7 +4,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:flutter_material_color_picker/flutter_material_color_picker.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:fluttericon/font_awesome5_icons.dart';
import 'package:fluttericon/web_symbols_icons.dart';
import 'package:fluttertoast/fluttertoast.dart';
@ -27,6 +26,8 @@ import 'package:freezer/settings.dart';
import 'package:freezer/main.dart';
class SettingsScreen extends StatefulWidget {
const SettingsScreen();
@override
_SettingsScreenState createState() => _SettingsScreenState();
}
@ -594,7 +595,9 @@ class _QualityPickerState extends State<QualityPicker> {
}
//Update quality in settings
void _updateQuality(AudioQuality q) async {
void _updateQuality(AudioQuality? q) async {
if (q == null) return;
setState(() {
_quality = q;
});
@ -626,26 +629,26 @@ class _QualityPickerState extends State<QualityPicker> {
title: Text('MP3 128kbps'),
groupValue: _quality,
value: AudioQuality.MP3_128,
onChanged: (dynamic q) => _updateQuality(q),
onChanged: (q) => _updateQuality(q),
),
RadioListTile(
title: Text('MP3 320kbps'),
groupValue: _quality,
value: AudioQuality.MP3_320,
onChanged: (dynamic q) => _updateQuality(q),
onChanged: (q) => _updateQuality(q),
),
RadioListTile(
title: Text('FLAC'),
groupValue: _quality,
value: AudioQuality.FLAC,
onChanged: (dynamic q) => _updateQuality(q),
onChanged: (q) => _updateQuality(q),
),
if (widget.field == 'download')
RadioListTile(
title: Text('Ask before downloading'.i18n),
groupValue: _quality,
value: AudioQuality.ASK,
onChanged: (dynamic q) => _updateQuality(q),
onChanged: (q) => _updateQuality(q),
)
],
);

View File

@ -515,7 +515,7 @@ class AlbumCard extends StatelessWidget {
CachedImage(
width: 128.0,
height: 128.0,
url: album!.art!.thumb,
url: album.art!.thumb,
rounded: true),
Positioned(
child: PlayItemButton(
@ -533,7 +533,7 @@ class AlbumCard extends StatelessWidget {
SizedBox(
width: 144.0,
child: Text(
album!.title!,
album.title!,
maxLines: 1,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
@ -544,7 +544,7 @@ class AlbumCard extends StatelessWidget {
SizedBox(
width: 144.0,
child: Text(
album!.artistString,
album.artistString,
maxLines: 1,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,

1
linux/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
flutter/ephemeral

139
linux/CMakeLists.txt Normal file
View File

@ -0,0 +1,139 @@
# Project-level configuration.
cmake_minimum_required(VERSION 3.10)
project(runner LANGUAGES CXX)
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
set(BINARY_NAME "freezer")
# The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
set(APPLICATION_ID "f.f.freezer")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.
cmake_policy(SET CMP0063 NEW)
# Load bundled libraries from the lib/ directory relative to the binary.
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
# Root filesystem for cross-building.
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
endif()
# Define build configuration options.
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
# Compilation settings that should be applied to most targets.
#
# Be cautious about adding new options here, as plugins use this function by
# default. In most cases, you should add new options to specific targets instead
# of modifying this function.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_14)
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
endfunction()
# Flutter library and tool build rules.
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
add_subdirectory(${FLUTTER_MANAGED_DIR})
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
# Define the application target. To change its name, change BINARY_NAME above,
# not the value here, or `flutter run` will no longer work.
#
# Any new source files that you add to the application should be added here.
add_executable(${BINARY_NAME}
"main.cc"
"my_application.cc"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
)
# Apply the standard set of build settings. This can be removed for applications
# that need different build settings.
apply_standard_settings(${BINARY_NAME})
# Add dependency libraries. Add any application-specific dependencies here.
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
# Run the Flutter tool portions of the build. This must not be removed.
add_dependencies(${BINARY_NAME} flutter_assemble)
# Only the install-generated bundle's copy of the executable will launch
# correctly, since the resources must in the right relative locations. To avoid
# people trying to run the unbundled copy, put it in a subdirectory instead of
# the default top-level location.
set_target_properties(${BINARY_NAME}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
)
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# By default, "installing" just makes a relocatable bundle in the build
# directory.
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
# Start with a clean build bundle directory every time.
install(CODE "
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
" COMPONENT Runtime)
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
install(FILES "${bundled_library}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endforeach(bundled_library)
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()

View File

@ -0,0 +1,88 @@
# This file controls Flutter-level build steps. It should not be edited.
cmake_minimum_required(VERSION 3.10)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146.
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
# which isn't available in 3.10.
function(list_prepend LIST_NAME PREFIX)
set(NEW_LIST "")
foreach(element ${${LIST_NAME}})
list(APPEND NEW_LIST "${PREFIX}${element}")
endforeach(element)
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
endfunction()
# === Flutter Library ===
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"fl_basic_message_channel.h"
"fl_binary_codec.h"
"fl_binary_messenger.h"
"fl_dart_project.h"
"fl_engine.h"
"fl_json_message_codec.h"
"fl_json_method_codec.h"
"fl_message_codec.h"
"fl_method_call.h"
"fl_method_channel.h"
"fl_method_codec.h"
"fl_method_response.h"
"fl_plugin_registrar.h"
"fl_plugin_registry.h"
"fl_standard_message_codec.h"
"fl_standard_method_codec.h"
"fl_string_codec.h"
"fl_value.h"
"fl_view.h"
"flutter_linux.h"
)
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
target_link_libraries(flutter INTERFACE
PkgConfig::GTK
PkgConfig::GLIB
PkgConfig::GIO
)
add_dependencies(flutter flutter_assemble)
# === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time,
# since currently there's no way to get a full input/output list from the
# flutter tool.
add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/_phony_
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
VERBATIM
)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
)

View File

@ -0,0 +1,19 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
#include <dynamic_color/dynamic_color_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) dynamic_color_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "DynamicColorPlugin");
dynamic_color_plugin_register_with_registrar(dynamic_color_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
}

View File

@ -0,0 +1,15 @@
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter_linux/flutter_linux.h>
// Registers Flutter plugins.
void fl_register_plugins(FlPluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_

View File

@ -0,0 +1,25 @@
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
dynamic_color
url_launcher_linux
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)

6
linux/main.cc Normal file
View File

@ -0,0 +1,6 @@
#include "my_application.h"
int main(int argc, char** argv) {
g_autoptr(MyApplication) app = my_application_new();
return g_application_run(G_APPLICATION(app), argc, argv);
}

104
linux/my_application.cc Normal file
View File

@ -0,0 +1,104 @@
#include "my_application.h"
#include <flutter_linux/flutter_linux.h>
#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
#endif
#include "flutter/generated_plugin_registrant.h"
struct _MyApplication {
GtkApplication parent_instance;
char** dart_entrypoint_arguments;
};
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
// Implements GApplication::activate.
static void my_application_activate(GApplication* application) {
MyApplication* self = MY_APPLICATION(application);
GtkWindow* window =
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
// Use a header bar when running in GNOME as this is the common style used
// by applications and is the setup most users will be using (e.g. Ubuntu
// desktop).
// If running on X and not using GNOME then just use a traditional title bar
// in case the window manager does more exotic layout, e.g. tiling.
// If running on Wayland assume the header bar will work (may need changing
// if future cases occur).
gboolean use_header_bar = TRUE;
#ifdef GDK_WINDOWING_X11
GdkScreen* screen = gtk_window_get_screen(window);
if (GDK_IS_X11_SCREEN(screen)) {
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
use_header_bar = FALSE;
}
}
#endif
if (use_header_bar) {
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_widget_show(GTK_WIDGET(header_bar));
gtk_header_bar_set_title(header_bar, "freezer");
gtk_header_bar_set_show_close_button(header_bar, TRUE);
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
} else {
gtk_window_set_title(window, "freezer");
}
gtk_window_set_default_size(window, 1280, 720);
gtk_widget_show(GTK_WIDGET(window));
g_autoptr(FlDartProject) project = fl_dart_project_new();
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
FlView* view = fl_view_new(project);
gtk_widget_show(GTK_WIDGET(view));
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
gtk_widget_grab_focus(GTK_WIDGET(view));
}
// Implements GApplication::local_command_line.
static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
MyApplication* self = MY_APPLICATION(application);
// Strip out the first argument as it is the binary name.
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
g_autoptr(GError) error = nullptr;
if (!g_application_register(application, nullptr, &error)) {
g_warning("Failed to register: %s", error->message);
*exit_status = 1;
return TRUE;
}
g_application_activate(application);
*exit_status = 0;
return TRUE;
}
// Implements GObject::dispose.
static void my_application_dispose(GObject* object) {
MyApplication* self = MY_APPLICATION(object);
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
}
static void my_application_class_init(MyApplicationClass* klass) {
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
}
static void my_application_init(MyApplication* self) {}
MyApplication* my_application_new() {
return MY_APPLICATION(g_object_new(my_application_get_type(),
"application-id", APPLICATION_ID,
"flags", G_APPLICATION_NON_UNIQUE,
nullptr));
}

18
linux/my_application.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef FLUTTER_MY_APPLICATION_H_
#define FLUTTER_MY_APPLICATION_H_
#include <gtk/gtk.h>
G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
GtkApplication)
/**
* my_application_new:
*
* Creates a new Flutter-based application.
*
* Returns: a new #MyApplication.
*/
MyApplication* my_application_new();
#endif // FLUTTER_MY_APPLICATION_H_

7
macos/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
# Flutter-related
**/Flutter/ephemeral/
**/Pods/
# Xcode-related
**/dgph
**/xcuserdata/

View File

@ -0,0 +1 @@
#include "ephemeral/Flutter-Generated.xcconfig"

View File

@ -0,0 +1 @@
#include "ephemeral/Flutter-Generated.xcconfig"

View File

@ -0,0 +1,34 @@
//
// Generated file. Do not edit.
//
import FlutterMacOS
import Foundation
import audio_service
import audio_session
import connectivity_plus
import dynamic_color
import flutter_local_notifications
import just_audio
import package_info_plus
import path_provider_foundation
import share_plus
import sqflite
import url_launcher_macos
import wakelock_plus
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin"))
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin"))
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))
}

View File

@ -0,0 +1,695 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXAggregateTarget section */
33CC111A2044C6BA0003C045 /* Flutter Assemble */ = {
isa = PBXAggregateTarget;
buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */;
buildPhases = (
33CC111E2044C6BF0003C045 /* ShellScript */,
);
dependencies = (
);
name = "Flutter Assemble";
productName = FLX;
};
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; };
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 33CC10EC2044A3C60003C045;
remoteInfo = Runner;
};
33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 33CC111A2044C6BA0003C045;
remoteInfo = FLX;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
33CC110E2044A8840003C045 /* Bundle Framework */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Bundle Framework";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
33CC10ED2044A3C60003C045 /* freezer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "freezer.app"; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = "<group>"; };
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = "<group>"; };
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = "<group>"; };
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = "<group>"; };
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = "<group>"; };
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
331C80D2294CF70F00263BE5 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
33CC10EA2044A3C60003C045 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C80D6294CF71000263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C80D7294CF71000263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
33BA886A226E78AF003329D5 /* Configs */ = {
isa = PBXGroup;
children = (
33E5194F232828860026EE4D /* AppInfo.xcconfig */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
333000ED22D3DE5D00554162 /* Warnings.xcconfig */,
);
path = Configs;
sourceTree = "<group>";
};
33CC10E42044A3C60003C045 = {
isa = PBXGroup;
children = (
33FAB671232836740065AC1E /* Runner */,
33CEB47122A05771004F2AC0 /* Flutter */,
331C80D6294CF71000263BE5 /* RunnerTests */,
33CC10EE2044A3C60003C045 /* Products */,
D73912EC22F37F3D000D13A0 /* Frameworks */,
);
sourceTree = "<group>";
};
33CC10EE2044A3C60003C045 /* Products */ = {
isa = PBXGroup;
children = (
33CC10ED2044A3C60003C045 /* freezer.app */,
331C80D5294CF71000263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
33CC11242044D66E0003C045 /* Resources */ = {
isa = PBXGroup;
children = (
33CC10F22044A3C60003C045 /* Assets.xcassets */,
33CC10F42044A3C60003C045 /* MainMenu.xib */,
33CC10F72044A3C60003C045 /* Info.plist */,
);
name = Resources;
path = ..;
sourceTree = "<group>";
};
33CEB47122A05771004F2AC0 /* Flutter */ = {
isa = PBXGroup;
children = (
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,
);
path = Flutter;
sourceTree = "<group>";
};
33FAB671232836740065AC1E /* Runner */ = {
isa = PBXGroup;
children = (
33CC10F02044A3C60003C045 /* AppDelegate.swift */,
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
33E51913231747F40026EE4D /* DebugProfile.entitlements */,
33E51914231749380026EE4D /* Release.entitlements */,
33CC11242044D66E0003C045 /* Resources */,
33BA886A226E78AF003329D5 /* Configs */,
);
path = Runner;
sourceTree = "<group>";
};
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
isa = PBXGroup;
children = (
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C80D4294CF70F00263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
331C80D1294CF70F00263BE5 /* Sources */,
331C80D2294CF70F00263BE5 /* Frameworks */,
331C80D3294CF70F00263BE5 /* Resources */,
);
buildRules = (
);
dependencies = (
331C80DA294CF71000263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
33CC10EC2044A3C60003C045 /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
33CC10E92044A3C60003C045 /* Sources */,
33CC10EA2044A3C60003C045 /* Frameworks */,
33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */,
);
buildRules = (
);
dependencies = (
33CC11202044C79F0003C045 /* PBXTargetDependency */,
);
name = Runner;
productName = Runner;
productReference = 33CC10ED2044A3C60003C045 /* freezer.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
33CC10E52044A3C60003C045 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C80D4294CF70F00263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 33CC10EC2044A3C60003C045;
};
33CC10EC2044A3C60003C045 = {
CreatedOnToolsVersion = 9.2;
LastSwiftMigration = 1100;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.Sandbox = {
enabled = 1;
};
};
};
33CC111A2044C6BA0003C045 = {
CreatedOnToolsVersion = 9.2;
ProvisioningStyle = Manual;
};
};
};
buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 33CC10E42044A3C60003C045;
productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
33CC10EC2044A3C60003C045 /* Runner */,
331C80D4294CF70F00263BE5 /* RunnerTests */,
33CC111A2044C6BA0003C045 /* Flutter Assemble */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C80D3294CF70F00263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
33CC10EB2044A3C60003C045 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3399D490228B24CF009A79C7 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
};
33CC111E2044C6BF0003C045 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
Flutter/ephemeral/FlutterInputs.xcfilelist,
);
inputPaths = (
Flutter/ephemeral/tripwire,
);
outputFileListPaths = (
Flutter/ephemeral/FlutterOutputs.xcfilelist,
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C80D1294CF70F00263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
33CC10E92044A3C60003C045 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */,
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */,
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C80DA294CF71000263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 33CC10EC2044A3C60003C045 /* Runner */;
targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */;
};
33CC11202044C79F0003C045 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
33CC10F42044A3C60003C045 /* MainMenu.xib */ = {
isa = PBXVariantGroup;
children = (
33CC10F52044A3C60003C045 /* Base */,
);
name = MainMenu.xib;
path = Runner;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
331C80DB294CF71000263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = f.f.freezer.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/freezer.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/freezer";
};
name = Debug;
};
331C80DC294CF71000263BE5 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = f.f.freezer.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/freezer.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/freezer";
};
name = Release;
};
331C80DD294CF71000263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = f.f.freezer.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/freezer.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/freezer";
};
name = Profile;
};
338D0CE9231458BD00FA5F75 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Profile;
};
338D0CEA231458BD00FA5F75 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
name = Profile;
};
338D0CEB231458BD00FA5F75 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Manual;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Profile;
};
33CC10F92044A3C60003C045 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
33CC10FA2044A3C60003C045 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Release;
};
33CC10FC2044A3C60003C045 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
33CC10FD2044A3C60003C045 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
name = Release;
};
33CC111C2044C6BA0003C045 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Manual;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
33CC111D2044C6BA0003C045 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C80DB294CF71000263BE5 /* Debug */,
331C80DC294CF71000263BE5 /* Release */,
331C80DD294CF71000263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
33CC10F92044A3C60003C045 /* Debug */,
33CC10FA2044A3C60003C045 /* Release */,
338D0CE9231458BD00FA5F75 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
33CC10FC2044A3C60003C045 /* Debug */,
33CC10FD2044A3C60003C045 /* Release */,
338D0CEA231458BD00FA5F75 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = {
isa = XCConfigurationList;
buildConfigurations = (
33CC111C2044C6BA0003C045 /* Debug */,
33CC111D2044C6BA0003C045 /* Release */,
338D0CEB231458BD00FA5F75 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 33CC10E52044A3C60003C045 /* Project object */;
}

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "freezer.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "freezer.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C80D4294CF70F00263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "freezer.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "freezer.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,9 @@
import Cocoa
import FlutterMacOS
@NSApplicationMain
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
}

View File

@ -0,0 +1,68 @@
{
"images" : [
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "app_icon_16.png",
"scale" : "1x"
},
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "app_icon_32.png",
"scale" : "2x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "app_icon_32.png",
"scale" : "1x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "app_icon_64.png",
"scale" : "2x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "app_icon_128.png",
"scale" : "1x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "app_icon_256.png",
"scale" : "2x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "app_icon_256.png",
"scale" : "1x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "app_icon_512.png",
"scale" : "2x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "app_icon_512.png",
"scale" : "1x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "app_icon_1024.png",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Some files were not shown because too many files have changed in this diff Show More