first commit

This commit is contained in:
stonegate 2020-02-09 20:29:09 +08:00
parent 7b8d1eef69
commit f3f8312c58
163 changed files with 7568 additions and 0 deletions

37
.gitignore vendored Normal file
View File

@ -0,0 +1,37 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Web related
lib/generated_plugin_registrant.dart
# Exceptions to above rules.
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages

10
.metadata Normal file
View File

@ -0,0 +1,10 @@
# 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: 659dc8129d4edb9166e9a0d600439d135740933f
channel: beta
project_type: app

13
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,13 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Flutter",
"request": "launch",
"type": "dart"
}
]
}

7
android/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java

17
android/.project Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>android</name>
<comment>Project android created by Buildship.</comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures>
</projectDescription>

View File

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

6
android/app/.classpath Normal file
View File

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

23
android/app/.project Normal file
View File

@ -0,0 +1,23 @@
<?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>
</projectDescription>

View File

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

80
android/app/build.gradle Normal file
View File

@ -0,0 +1,80 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
compileSdkVersion 28
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.stonegate.tsacdop"
minSdkVersion 16
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}

View File

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.stonegate.tsacdop">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -0,0 +1,33 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.stonegate.tsacdop">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.INTERNET"/>
<application android:name="io.flutter.app.FlutterApplication" android:label="Tsacdop" android:icon="@mipmap/ic_launcher" android:networkSecurityConfig="@xml/network_security_config">
<activity android:name=".MainActivity" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name="com.google.flutter.plugins.audiofileplayer.AudiofileplayerService">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</service>
<receiver android:name="androidx.media.session.MediaButtonReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data android:name="flutterEmbedding" android:value="2" />
</application>
</manifest>

View File

@ -0,0 +1,12 @@
package com.stonegate.tsacdop
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.GeneratedPluginRegistrant
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 847 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 955 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 989 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
</resources>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">lizhi.fm</domain>
<domain includeSubdomains="true">xmcdn.com</domain>
</domain-config>
</network-security-config>

View File

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.stonegate.tsacdop">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

31
android/build.gradle Normal file
View File

@ -0,0 +1,31 @@
buildscript {
ext.kotlin_version = '1.3.50'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
jcenter()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@ -0,0 +1,4 @@
org.gradle.jvmargs=-Xmx1536M
android.enableR8=false
android.useAndroidX=true
android.enableJetifier=true

View File

@ -0,0 +1,6 @@
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip

15
android/settings.gradle Normal file
View File

@ -0,0 +1,15 @@
include ':app'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":$name"
project(":$name").projectDir = pluginDirectory
}

View File

@ -0,0 +1 @@
include ':app'

BIN
assets/listennote.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

32
ios/.gitignore vendored Normal file
View File

@ -0,0 +1,32 @@
*.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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,518 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B80C3931E831B6300D905FE /* App.framework */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEBA1CF902C7004384FC /* Flutter.framework */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
97C146F11CF9000F007C117D /* Supporting Files */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
97C146F11CF9000F007C117D /* Supporting Files */ = {
isa = PBXGroup;
children = (
);
name = "Supporting Files";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1020;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
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_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
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_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.tsacdopPlayer;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
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_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
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_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
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_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
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_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
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_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.tsacdopPlayer;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.tsacdopPlayer;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

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,91 @@
<?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>

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,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)
}
}

View File

@ -0,0 +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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View File

@ -0,0 +1,5 @@
# 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

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

View File

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

45
ios/Runner/Info.plist Normal file
View File

@ -0,0 +1,45 @@
<?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>tsacdop_player</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>

View File

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

29
lib/about.dart Normal file
View File

@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
class AboutApp extends StatelessWidget {
TextSpan buildTextSpan() {
return TextSpan(children: [
TextSpan(text: 'About Dopcast Player\n',style: TextStyle(fontSize: 20)),
TextSpan(
text:
'Dopcast Player is a podcast client developed by flutter, is a simple, easy-use player.\n'),
TextSpan(
text:
'Github https://github.com/stonga .\n'),
]);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.grey[100],
title: Text('About'),
),
body: Container(
padding: EdgeInsets.all(20),
alignment: Alignment.topLeft,
child: Text.rich(buildTextSpan()),
));
}
}

265
lib/addpodcast.dart Normal file
View File

@ -0,0 +1,265 @@
import 'package:flutter/material.dart';
import 'package:color_thief_flutter/color_thief_flutter.dart';
import 'class/importompl.dart';
import 'package:dio/dio.dart';
import 'package:provider/provider.dart';
import 'dart:convert';
import 'dart:async';
import 'class/searchpodcast.dart';
import 'class/podcastlocal.dart';
import 'class/sqflite_localpodcast.dart';
import 'home.dart';
import 'popupmenu.dart';
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final _MyHomePageDelegate _delegate = _MyHomePageDelegate();
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => ImportOmpl(),
child: Scaffold(
key: _scaffoldKey,
appBar: AppBar(
elevation: 0,
centerTitle: true,
backgroundColor: Colors.grey[100],
leading: IconButton(
tooltip: 'Add',
icon: const Icon(Icons.add_circle_outline),
onPressed: () async {
await showSearch<int>(
context: context,
delegate: _delegate,
);
},
),
title: Text('🎙TsacDop', style: TextStyle(color: Colors.blue[600])),
actions: <Widget>[
PopupMenu(),
],
),
body: Home(),
),
);
}
}
class _MyHomePageDelegate extends SearchDelegate<int> {
static Future<List> getList(String searchText) async {
String url =
"https://listennotes.p.mashape.com/api/v1/search?only_in=title&q=" +
searchText +
"&sort_by_date=0&type=podcast";
Response response = await Dio().get(url,
options: Options(headers: {
'X-Mashape-Key': "UtSwKG4afSmshZfglwsXylLKJZHgp1aZHi2jsnSYK5mZi0A32T",
'Accept': "application/json"
}));
Map searchResultMap = jsonDecode(response.toString());
var searchResult = SearchPodcast.fromJson(searchResultMap);
return searchResult.results;
}
@override
Widget buildLeading(BuildContext context) {
return IconButton(
tooltip: 'Back',
icon: AnimatedIcon(
icon: AnimatedIcons.menu_arrow,
progress: transitionAnimation,
),
onPressed: () {
close(context, 1);
},
);
}
@override
Widget buildSuggestions(BuildContext context) {
if (query.isEmpty)
return Center(
child: Container(
padding: EdgeInsets.only(top: 400),
child: Image(
image: AssetImage('assets/listennote.png'),
width: 300,
),
));
return FutureBuilder(
future: getList(query),
builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
if (!snapshot.hasData && query != null)
return Container(
padding: EdgeInsets.only(top: 200),
alignment: Alignment.topCenter,
child: CircularProgressIndicator(),
);
List content = snapshot.data;
return ListView.builder(
scrollDirection: Axis.vertical,
itemCount: content.length,
itemBuilder: (BuildContext context, int index) {
return SearchResult(
onlinePodcast: content[index],
);
},
);
},
);
}
@override
List<Widget> buildActions(BuildContext context) {
return <Widget>[
if (query.isEmpty)
IconButton(
tooltip: 'Voice Search',
icon: const Icon(Icons.mic),
onPressed: () {
query = 'TODO: implement voice input';
},
)
else
IconButton(
tooltip: 'Clear',
icon: const Icon(Icons.clear),
onPressed: () {
query = '';
},
),
];
}
@override
Widget buildResults(BuildContext context) {
if (query.isEmpty)
return Container(
height: 10,
width: 10,
margin: EdgeInsets.only(top: 400),
child: Image.asset(
'assets/listennote.png',
fit: BoxFit.fill,
),
);
return FutureBuilder(
future: getList(query),
builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
if (!snapshot.hasData && query != null)
return Container(
padding: EdgeInsets.only(top: 200),
alignment: Alignment.topCenter,
child: CircularProgressIndicator(),
);
List content = snapshot.data;
return ListView.builder(
scrollDirection: Axis.vertical,
itemCount: content.length,
itemBuilder: (BuildContext context, int index) {
return SearchResult(
onlinePodcast: content[index],
);
},
);
},
);
}
}
class SearchResult extends StatefulWidget {
final OnlinePodcast onlinePodcast;
SearchResult({this.onlinePodcast, Key key}) : super(key: key);
@override
_SearchResultState createState() => _SearchResultState();
}
class _SearchResultState extends State<SearchResult> {
bool _issubscribe;
bool _adding;
Future _subscribe(OnlinePodcast t) async {
if (mounted)
setState(() {
_adding = true;
});
String _primaryColor;
await getColorFromUrl(t.image).then((color) {
print(color.toString());
_primaryColor = color.toString();
});
var dbHelper = DBHelper();
final PodcastLocal _pdt =
PodcastLocal(t.title, t.image, t.rss, _primaryColor, t.publisher);
_pdt.description = t.description;
print(t.title + t.rss);
await dbHelper.savePodcastLocal(_pdt);
final response = await Dio().get(t.rss);
int result = await dbHelper.savePodcastRss(response.data);
if (result == 0 && mounted) setState(() => _issubscribe = true);
}
bool isXimalaya(String input) {
RegExp ximalaya = RegExp(r"ximalaya");
return ximalaya.hasMatch(input);
}
@override
void initState() {
super.initState();
_issubscribe = false;
_adding = false;
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 12.0),
child: ListTile(
leading: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(20.0)),
child: Image.network(
widget.onlinePodcast.image,
height: 40.0,
width: 40.0,
fit: BoxFit.fitWidth,
alignment: Alignment.center,
),
),
title: Text(widget.onlinePodcast.title),
subtitle: Text(widget.onlinePodcast.publisher),
trailing: isXimalaya(widget.onlinePodcast.rss)
? OutlineButton(child: Text('Not Support'), onPressed: null)
: !_issubscribe
? !_adding
? OutlineButton(
child: Text('Subscribe',
style: TextStyle(color: Colors.blue)),
onPressed: () {
_subscribe(widget.onlinePodcast);
})
: OutlineButton(
child: SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation(Colors.blue),
)),
onPressed: () {},
)
: OutlineButton(child: Text('Subscribe'), onPressed: null),
),
);
}
}

504
lib/audio_player.dart Normal file
View File

@ -0,0 +1,504 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:provider/provider.dart';
import 'package:network_image_to_byte/network_image_to_byte.dart';
import 'package:audiofileplayer/audiofileplayer.dart';
import 'package:audiofileplayer/audio_system.dart';
import 'package:logging/logging.dart';
import 'package:flutter/material.dart';
import 'package:marquee/marquee.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'class/audiostate.dart';
final Logger _logger = Logger('audiofileplayer');
class PlayerWidget extends StatefulWidget {
@override
_PlayerWidgetState createState() => _PlayerWidgetState();
}
class _PlayerWidgetState extends State<PlayerWidget> {
static const String replay10ButtonId = 'replay10ButtonId';
static const String newReleasesButtonId = 'newReleasesButtonId';
static const String likeButtonId = 'likeButtonId';
static const String pausenowButtonId = 'pausenowButtonId';
static const String forwardButtonId = 'forwardButtonId';
Audio _backgroundAudio;
bool _backgroundAudioPlaying;
double _backgroundAudioDurationSeconds;
double _backgroundAudioPositionSeconds = 0;
bool _remoteAudioLoading;
String _remoteErrorMessage;
double _seekSliderValue = 0.0;
String url;
String _title;
String _feedtitle;
String _imgurl;
bool _isLoading;
@override
void initState() {
super.initState();
AudioSystem.instance.addMediaEventListener(_mediaEventListener);
_isLoading = false;
}
void _initbackgroundAudioPlayer(String url) {
_remoteErrorMessage = null;
_remoteAudioLoading = true;
Provider.of<Urlchange>(context, listen: false).audioState = AudioState.load;
if (_backgroundAudioPlaying == true) _backgroundAudio?.pause();
_backgroundAudio?.dispose();
_backgroundAudio = Audio.loadFromRemoteUrl(url,
onDuration: (double durationSeconds) {
setState(() {
_backgroundAudioDurationSeconds = durationSeconds;
_remoteAudioLoading = false;
Provider.of<Urlchange>(context, listen: false).audioState =
AudioState.play;
});
_setNotification();
},
onPosition: (double positionSeconds) {
setState(() {
if (_backgroundAudioPositionSeconds <
_backgroundAudioDurationSeconds) {
_seekSliderValue = _backgroundAudioPositionSeconds /
_backgroundAudioDurationSeconds;
_backgroundAudioPositionSeconds = positionSeconds;
} else {
_seekSliderValue = 1;
_backgroundAudioPositionSeconds = _backgroundAudioDurationSeconds;
}
});
},
onError: (String message) => setState(() {
_remoteErrorMessage = message;
_backgroundAudio.dispose();
_backgroundAudio = null;
_backgroundAudioPlaying = false;
_remoteAudioLoading = false;
Provider.of<Urlchange>(context, listen: false).audioState =
AudioState.error;
}),
onComplete: () => setState(() {
_backgroundAudioPlaying = false;
_remoteAudioLoading = false;
Provider.of<Urlchange>(context, listen: false).audioState =
AudioState.complete;
}),
looping: false,
playInBackground: true)
..play();
}
void _initbackgroundAudioPlayerLocal(String path) {
_remoteErrorMessage = null;
_remoteAudioLoading = true;
ByteData audio = getAudio(path);
Provider.of<Urlchange>(context, listen: false).audioState = AudioState.load;
if (_backgroundAudioPlaying == true) _backgroundAudio?.pause();
_backgroundAudio?.dispose();
_backgroundAudio = Audio.loadFromByteData(audio,
onDuration: (double durationSeconds) {
setState(() {
_backgroundAudioDurationSeconds = durationSeconds;
_remoteAudioLoading = false;
});
_setNotification();
Provider.of<Urlchange>(context, listen: false).audioState =
AudioState.play;
},
onPosition: (double positionSeconds) {
setState(() {
if (_backgroundAudioPositionSeconds <
_backgroundAudioDurationSeconds) {
_seekSliderValue = _backgroundAudioPositionSeconds /
_backgroundAudioDurationSeconds;
_backgroundAudioPositionSeconds = positionSeconds;
} else {
_seekSliderValue = 1;
_backgroundAudioPositionSeconds = _backgroundAudioDurationSeconds;
}
});
},
onError: (String message) => setState(() {
_remoteErrorMessage = message;
_backgroundAudio.dispose();
_backgroundAudio = null;
_backgroundAudioPlaying = false;
_remoteAudioLoading = false;
Provider.of<Urlchange>(context, listen: false).audioState =
AudioState.error;
}),
onComplete: () => setState(() {
_backgroundAudioPlaying = false;
_remoteAudioLoading = false;
Provider.of<Urlchange>(context, listen: false).audioState =
AudioState.complete;
}),
looping: false,
playInBackground: true)
..play();
}
Future<String> _getFile(String url) async {
final task = await FlutterDownloader.loadTasksWithRawQuery(
query: "SELECT * FROM task WHERE url = '$url' AND status = 3");
if (task.length != 0) {
String _filePath = task.first.savedDir + '/' + task.first.filename;
return _filePath;
}
return 'NotDownload';
}
ByteData getAudio(String path) {
File audioFile = File(path);
Uint8List audio = audioFile.readAsBytesSync();
return ByteData.view(audio.buffer);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
final url = Provider.of<Urlchange>(context).audiourl;
if (url != this.url) {
setState(() {
this.url = url;
_title = Provider.of<Urlchange>(context).title;
_feedtitle = Provider.of<Urlchange>(context).feedtitle;
_imgurl = Provider.of<Urlchange>(context).imageurl;
_backgroundAudioPlaying = true;
_isLoading = true;
_getFile(url).then((result) {
result == 'NotDownload'
? _initbackgroundAudioPlayer(url)
: _initbackgroundAudioPlayerLocal(result);
});
});
}
}
@override
void dispose() {
AudioSystem.instance.removeMediaEventListener(_mediaEventListener);
_backgroundAudio?.dispose();
super.dispose();
}
static String _stringForSeconds(double seconds) {
if (seconds == null) return null;
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
}
void _mediaEventListener(MediaEvent mediaEvent) {
_logger.info('App received media event of type: ${mediaEvent.type}');
final MediaActionType type = mediaEvent.type;
if (type == MediaActionType.play) {
_resumeBackgroundAudio();
} else if (type == MediaActionType.pause) {
_pauseBackgroundAudio();
} else if (type == MediaActionType.playPause) {
_backgroundAudioPlaying
? _pauseBackgroundAudio()
: _resumeBackgroundAudio();
} else if (type == MediaActionType.stop) {
_stopBackgroundAudio();
} else if (type == MediaActionType.seekTo) {
_backgroundAudio.seek(mediaEvent.seekToPositionSeconds);
AudioSystem.instance
.setPlaybackState(true, mediaEvent.seekToPositionSeconds);
} else if (type == MediaActionType.skipForward) {
final double skipIntervalSeconds = mediaEvent.skipIntervalSeconds;
_forwardBackgroundAudio(skipIntervalSeconds);
_logger.info(
'Skip-forward event had skipIntervalSeconds $skipIntervalSeconds.');
} else if (type == MediaActionType.skipBackward) {
final double skipIntervalSeconds = mediaEvent.skipIntervalSeconds;
_forwardBackgroundAudio(skipIntervalSeconds);
_logger.info(
'Skip-backward event had skipIntervalSeconds $skipIntervalSeconds.');
} else if (type == MediaActionType.custom) {
if (mediaEvent.customEventId == replay10ButtonId) {
_forwardBackgroundAudio(-10.0);
} else if (mediaEvent.customEventId == likeButtonId) {
_resumeBackgroundAudio();
} else if (mediaEvent.customEventId == forwardButtonId) {
_forwardBackgroundAudio(30.0);
} else if (mediaEvent.customEventId == pausenowButtonId) {
_pauseBackgroundAudio();
}
}
}
Future<Uint8List> _networkImageToByte(String url) async {
Uint8List byteImage = await networkImageToByte(url);
return byteImage;
}
final _pauseButton = AndroidCustomMediaButton(
'pausenow', pausenowButtonId, 'ic_stat_pause_circle_filled');
final _replay10Button = AndroidCustomMediaButton(
'replay10', replay10ButtonId, 'ic_stat_replay_10');
final _forwardButton = AndroidCustomMediaButton(
'forward', forwardButtonId, 'ic_stat_forward_30');
final _playnowButton = AndroidCustomMediaButton(
'playnow', likeButtonId, 'ic_stat_play_circle_filled');
Future<void> _setNotification() async {
final Uint8List imageBytes = await _networkImageToByte(_imgurl);
AudioSystem.instance.setMetadata(AudioMetadata(
title: _title,
artist: _feedtitle,
album: _feedtitle,
genre: "Podcast",
durationSeconds: _backgroundAudioDurationSeconds,
artBytes: imageBytes));
AudioSystem.instance
.setPlaybackState(true, _backgroundAudioPositionSeconds);
AudioSystem.instance.setAndroidNotificationButtons(<dynamic>[
AndroidMediaButtonType.pause,
_forwardButton,
AndroidMediaButtonType.stop,
], androidCompactIndices: <int>[
0,
1
]);
AudioSystem.instance.setSupportedMediaActions(<MediaActionType>{
MediaActionType.playPause,
MediaActionType.pause,
MediaActionType.next,
MediaActionType.previous,
MediaActionType.skipForward,
MediaActionType.skipBackward,
MediaActionType.seekTo,
MediaActionType.custom,
}, skipIntervalSeconds: 30);
}
Future<void> _resumeBackgroundAudio() async {
_backgroundAudio.resume();
setState(() {
_backgroundAudioPlaying = true;
Provider.of<Urlchange>(context, listen: false).audioState =
AudioState.play;
});
final Uint8List imageBytes = await _networkImageToByte(_imgurl);
AudioSystem.instance.setMetadata(AudioMetadata(
title: _title,
artist: _feedtitle,
album: _feedtitle,
genre: "Podcast",
durationSeconds: _backgroundAudioDurationSeconds,
artBytes: imageBytes));
AudioSystem.instance
.setPlaybackState(true, _backgroundAudioPositionSeconds);
AudioSystem.instance.setAndroidNotificationButtons(<dynamic>[
AndroidMediaButtonType.pause,
_forwardButton,
AndroidMediaButtonType.stop,
], androidCompactIndices: <int>[
0,
1
]);
AudioSystem.instance.setSupportedMediaActions(<MediaActionType>{
MediaActionType.playPause,
MediaActionType.pause,
MediaActionType.next,
MediaActionType.previous,
MediaActionType.skipForward,
MediaActionType.skipBackward,
MediaActionType.seekTo,
MediaActionType.custom,
}, skipIntervalSeconds: 30);
}
void _pauseBackgroundAudio() {
_backgroundAudio.pause();
setState(() {
_backgroundAudioPlaying = false;
Provider.of<Urlchange>(context, listen: false).audioState =
AudioState.pause;
});
AudioSystem.instance
.setPlaybackState(false, _backgroundAudioPositionSeconds);
AudioSystem.instance.setAndroidNotificationButtons(<dynamic>[
AndroidMediaButtonType.play,
_forwardButton,
AndroidMediaButtonType.stop,
], androidCompactIndices: <int>[
0,
1,
]);
AudioSystem.instance.setSupportedMediaActions(<MediaActionType>{
MediaActionType.playPause,
MediaActionType.play,
MediaActionType.next,
MediaActionType.previous,
});
}
void _stopBackgroundAudio() {
_backgroundAudio.pause();
setState(() => _backgroundAudioPlaying = false);
AudioSystem.instance.stopBackgroundDisplay();
}
void _forwardBackgroundAudio(double seconds) {
final double forwardposition = _backgroundAudioPositionSeconds + seconds;
_backgroundAudio.seek(forwardposition);
AudioSystem.instance.setPlaybackState(true, forwardposition);
}
@override
Widget build(BuildContext context) {
return !_isLoading
? Center()
: Container(
padding: EdgeInsets.symmetric(horizontal: 10.0),
color: Colors.grey[100],
height: 120.0,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
alignment: Alignment.centerLeft,
child: _remoteErrorMessage != null
? Text(_remoteErrorMessage,
style:
const TextStyle(color: const Color(0xFFFF0000)))
: Text(
_remoteAudioLoading ? 'Buffring...' : '',
style: TextStyle(color: Colors.blue),
),
),
SliderTheme(
data: SliderTheme.of(context).copyWith(
activeTrackColor: Colors.blue[100],
inactiveTrackColor: Colors.grey[300],
trackHeight: 2.0,
thumbColor: Colors.blue[400],
thumbShape:
RoundSliderThumbShape(enabledThumbRadius: 5.0),
overlayColor: Colors.blue.withAlpha(32),
overlayShape:
RoundSliderOverlayShape(overlayRadius: 14.0),
),
child: Slider(
value: _seekSliderValue,
onChanged: (double val) {
setState(() => _seekSliderValue = val);
final double positionSeconds =
val * _backgroundAudioDurationSeconds;
_backgroundAudio.seek(positionSeconds);
AudioSystem.instance
.setPlaybackState(true, positionSeconds);
}),
),
Container(
height: 20.0,
padding: EdgeInsets.symmetric(horizontal: 10.0),
child: Row(
children: <Widget>[
Text(
_stringForSeconds(_backgroundAudioPositionSeconds) ??
'',
style: TextStyle(fontSize: 10),
),
Expanded(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 30),
alignment: Alignment.center,
child: (_title.length > 50)
? Marquee(
text: _title,
style: TextStyle(fontWeight: FontWeight.bold),
scrollAxis: Axis.horizontal,
crossAxisAlignment: CrossAxisAlignment.start,
blankSpace: 30.0,
velocity: 50.0,
pauseAfterRound: Duration(seconds: 1),
startPadding: 30.0,
accelerationDuration: Duration(seconds: 1),
accelerationCurve: Curves.linear,
decelerationDuration:
Duration(milliseconds: 500),
decelerationCurve: Curves.easeOut,
)
: Text(
_title,
style: TextStyle(fontWeight: FontWeight.bold),
),
)),
Text(
_stringForSeconds(_backgroundAudioDurationSeconds) ??
'',
style: TextStyle(fontSize: 10),
),
],
),
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
padding: EdgeInsets.symmetric(horizontal: 30.0),
onPressed: _backgroundAudioPlaying
? () => _forwardBackgroundAudio(-10)
: null,
iconSize: 25.0,
icon: Icon(Icons.replay_10),
color: Colors.black),
_backgroundAudioPlaying
? IconButton(
padding: EdgeInsets.symmetric(horizontal: 30.0),
onPressed: _backgroundAudioPlaying
? () {
_pauseBackgroundAudio();
}
: null,
iconSize: 32.0,
icon: Icon(Icons.pause_circle_filled),
color: Colors.black)
: IconButton(
padding: EdgeInsets.symmetric(horizontal: 30.0),
onPressed: _backgroundAudioPlaying
? null
: () {
_resumeBackgroundAudio();
},
iconSize: 32.0,
icon: Icon(Icons.play_circle_filled),
color: Colors.black),
IconButton(
padding: EdgeInsets.symmetric(horizontal: 30.0),
onPressed: _backgroundAudioPlaying
? () => _forwardBackgroundAudio(30)
: null,
iconSize: 25.0,
icon: Icon(Icons.forward_30),
color: Colors.black),
/*IconButton(
onPressed: _isPlaying || _isPaused ? () => _stop() : null,
iconSize: 32.0,
icon: Icon(Icons.stop),
color: Colors.black), */
],
),
]),
);
}
}

37
lib/class/audiostate.dart Normal file
View File

@ -0,0 +1,37 @@
import 'package:flutter/foundation.dart';
enum AudioState {load, play, pause, complete, error}
class Urlchange with ChangeNotifier {
String _audiourl;
String get audiourl => _audiourl;
set audioUrl(String playing) {
_audiourl = playing;
notifyListeners();
}
String _title;
String get title => _title;
set rssTitle(String title){
_title = title;
}
String _feedTitle;
String get feedtitle => _feedTitle;
set feedTitle(String feed){
_feedTitle = feed;
}
String _imageurl;
String get imageurl => _imageurl;
set imageUrl(String image){
_imageurl = image;
}
AudioState _audioState;
AudioState get audioState => _audioState;
set audioState(AudioState state){
_audioState = state;
notifyListeners();
}
}

View File

@ -0,0 +1,18 @@
import 'package:flutter/foundation.dart';
enum DownloadState { stop, load, donwload, complete, error }
class EpisodeDownload with ChangeNotifier {
String _title;
String get title => _title;
set title(String t) {
_title = t;
notifyListeners();
}
DownloadState _downloadState = DownloadState.stop;
DownloadState get downloadState => _downloadState;
set downloadState(DownloadState state){
_downloadState = state;
notifyListeners();
}
}

View File

@ -0,0 +1,27 @@
class EpisodeBrief {
final String title;
String description;
final String pubDate;
final int enclosureLength;
final String enclosureUrl;
final String feedTitle;
final String imageUrl;
final String primaryColor;
final int liked;
final String downloaded;
final int duration;
final int explicit;
EpisodeBrief(
this.title,
this.enclosureUrl,
this.enclosureLength,
this.pubDate,
this.feedTitle,
this.imageUrl,
this.primaryColor,
this.liked,
this.downloaded,
this.duration,
this.explicit
);
}

18
lib/class/importompl.dart Normal file
View File

@ -0,0 +1,18 @@
import 'package:flutter/foundation.dart';
enum ImportState{start, import, complete, stop, error}
class ImportOmpl extends ChangeNotifier{
ImportState _importState = ImportState.stop;
String _rssTitle;
String get rsstitle => _rssTitle;
set rssTitle(String title){
_rssTitle = title;
notifyListeners();
}
ImportState get importState => _importState;
set importState(ImportState state){
_importState = state;
notifyListeners();
}
}

View File

@ -0,0 +1,10 @@
class PodcastLocal {
final String title;
final String imageUrl;
final String rssUrl;
final String author;
String description;
final String primaryColor;
PodcastLocal(this.title, this.imageUrl, this.rssUrl, this.primaryColor, this.author);
}

83
lib/class/podcastrss.dart Normal file
View File

@ -0,0 +1,83 @@
import 'package:json_annotation/json_annotation.dart';
part 'podcastrss.g.dart';
@JsonSerializable()
class Podcastrss<R>{
@_ConvertR()
final R rss;
Podcastrss({this.rss});
factory Podcastrss.fromJson(Map<String, dynamic> json) =>
_$PodcastrssFromJson(json);
Map<String, dynamic> toJson() => _$PodcastrssToJson(this);
}
class _ConvertR<R> implements JsonConverter<R, Object>{
const _ConvertR();
@override
R fromJson(Object json){
return Rss.fromJson(json) as R;
}
@override
Object toJson(R object){
return object;
}
}
@JsonSerializable()
class Rss<C>{
@_ConvertC()
final C channel;
Rss({this.channel});
factory Rss.fromJson(Map<String, dynamic> json) =>
_$RssFromJson(json);
Map<String, dynamic> toJson() => _$RssToJson(this);
}
class _ConvertC<C> implements JsonConverter<C, Object>{
const _ConvertC();
@override
C fromJson(Object json){
return Channel.fromJson(json) as C;
}
@override
Object toJson(C object){
return object;
}
}
@JsonSerializable()
class Channel<E> {
final String title;
final String link;
final String description;
@_ConvertE()
final List<E> item;
Channel({this.title, this.link, this.description, this.item});
factory Channel.fromJson(Map<String, dynamic> json) =>
_$ChannelFromJson(json);
Map<String, dynamic> toJson() => _$ChannelToJson(this);
}
class _ConvertE<E> implements JsonConverter<E, Object>{
const _ConvertE();
@override
E fromJson(Object json){
return EpisodeItem.fromJson(json) as E;
}
@override
Object toJson(E object){
return object;
}
}
@JsonSerializable()
class EpisodeItem{
final String title;
final String link;
final String pubDate;
final String description;
EpisodeItem({this.title, this.link, this.pubDate, this.description}
);
factory EpisodeItem.fromJson(Map<String, dynamic> json) =>
_$EpisodeItemFromJson(json);
Map<String, dynamic> toJson() => _$EpisodeItemToJson(this);
}

View File

@ -0,0 +1,66 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'podcastrss.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Podcastrss<R> _$PodcastrssFromJson<R>(Map<String, dynamic> json) {
return Podcastrss<R>(
rss: json['rss'] == null ? null : _ConvertR<R>().fromJson(json['rss']));
}
Map<String, dynamic> _$PodcastrssToJson<R>(Podcastrss<R> instance) =>
<String, dynamic>{
'rss': instance.rss == null ? null : _ConvertR<R>().toJson(instance.rss)
};
Rss<C> _$RssFromJson<C>(Map<String, dynamic> json) {
return Rss<C>(
channel: json['channel'] == null
? null
: _ConvertC<C>().fromJson(json['channel']));
}
Map<String, dynamic> _$RssToJson<C>(Rss<C> instance) => <String, dynamic>{
'channel': instance.channel == null
? null
: _ConvertC<C>().toJson(instance.channel)
};
Channel<E> _$ChannelFromJson<E>(Map<String, dynamic> json) {
return Channel<E>(
title: json['title'] as String,
link: json['link'] as String,
description: json['description'] as String,
item: (json['item'] as List)
?.map((e) => e == null ? null : _ConvertE<E>().fromJson(e))
?.toList());
}
Map<String, dynamic> _$ChannelToJson<E>(Channel<E> instance) =>
<String, dynamic>{
'title': instance.title,
'link': instance.link,
'description': instance.description,
'item': instance.item
?.map((e) => e == null ? null : _ConvertE<E>().toJson(e))
?.toList()
};
EpisodeItem _$EpisodeItemFromJson(Map<String, dynamic> json) {
return EpisodeItem(
title: json['title'] as String,
link: json['link'] as String,
pubDate: json['pubDate'] as String,
description: json['description'] as String);
}
Map<String, dynamic> _$EpisodeItemToJson(EpisodeItem instance) =>
<String, dynamic>{
'title': instance.title,
'link': instance.link,
'pubDate': instance.pubDate,
'description': instance.description
};

112
lib/class/podcasts.dart Normal file
View File

@ -0,0 +1,112 @@
import 'package:json_annotation/json_annotation.dart';
part 'podcasts.g.dart';
@JsonSerializable()
class Podcast<E, F>{
final String version;
final String title;
@JsonKey(name: 'homepage_url')
final String homepageUrl;
@JsonKey(name: 'feed_url')
final String feedUrl;
final String description;
@JsonKey(name: '_fireside')
@_ConvertF()
final F fireSide;
@JsonKey(name: 'items')
@_ConvertE()
final List<E> items;
Podcast(
{this.version, this.title, this.homepageUrl, this.feedUrl, this.description, this.fireSide, this.items}
);
factory Podcast.fromJson(Map<String, dynamic> json) =>
_$PodcastFromJson<E, F>(json);
Map<String, dynamic> toJson() => _$PodcastToJson(this);
}
class _ConvertE<E> implements JsonConverter<E, Object>{
const _ConvertE();
@override
E fromJson(Object json){
return EpisodeItem.fromJson(json) as E;
}
@override
Object toJson(E object){
return object;
}
}
class _ConvertF<F> implements JsonConverter<F, Object>{
const _ConvertF();
@override
F fromJson(Object json){
return FireSide.fromJson(json) as F;
}
@override
Object toJson(F object){
return object;
}
}
@JsonSerializable()
class FireSide{
final String pubdate;
final bool explicit;
final String copyright;
final String owner;
final String image;
FireSide({this.pubdate, this.explicit, this.copyright, this.owner, this.image});
factory FireSide.fromJson(Map<String, dynamic> json) =>
_$FireSideFromJson(json);
Map<String, dynamic> toJson() => _$FireSideToJson(this);
}
@JsonSerializable()
class EpisodeItem<A>{
final String id;
final String title;
final String url;
@JsonKey(name: 'content_text')
final String contentText;
@JsonKey(name: 'content_html')
final String contentHtml;
final String summary;
@JsonKey(name: 'date_published')
final String datePublished;
@_ConvertA()
final List<A> attachments;
EpisodeItem({this.id, this.title, this.url, this.contentText, this.contentHtml, this.summary, this.datePublished, this.attachments}
);
factory EpisodeItem.fromJson(Map<String, dynamic> json) =>
_$EpisodeItemFromJson<A>(json);
Map<String, dynamic> toJson() => _$EpisodeItemToJson(this);
}
class _ConvertA<A> implements JsonConverter<A, Object> {
const _ConvertA();
@override
A fromJson(Object json){
return Attachment.fromJson(json) as A;
}
@override
Object toJson(A object){
return object;
}
}
@JsonSerializable()
class Attachment{
final String url;
@JsonKey(name: 'mime_type')
final String mimeType;
@JsonKey(name: 'size_in_bytes')
final int sizeInBytes;
@JsonKey(name: 'duration_in_seconds')
final int durationInSeconds;
Attachment(
{this.url, this.mimeType, this.sizeInBytes, this.durationInSeconds}
);
factory Attachment.fromJson(Map<String, dynamic> json) =>
_$AttachmentFromJson(json);
Map<String, dynamic> toJson() => _$AttachmentToJson(this);
}

98
lib/class/podcasts.g.dart Normal file
View File

@ -0,0 +1,98 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'podcasts.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Podcast<E, F> _$PodcastFromJson<E, F>(Map<String, dynamic> json) {
return Podcast<E, F>(
version: json['version'] as String,
title: json['title'] as String,
homepageUrl: json['homepage_url'] as String,
feedUrl: json['feed_url'] as String,
description: json['description'] as String,
fireSide: json['_fireside'] == null
? null
: _ConvertF<F>().fromJson(json['_fireside']),
items: (json['items'] as List)
?.map((e) => e == null ? null : _ConvertE<E>().fromJson(e))
?.toList());
}
Map<String, dynamic> _$PodcastToJson<E, F>(Podcast<E, F> instance) =>
<String, dynamic>{
'version': instance.version,
'title': instance.title,
'homepage_url': instance.homepageUrl,
'feed_url': instance.feedUrl,
'description': instance.description,
'_fireside': instance.fireSide == null
? null
: _ConvertF<F>().toJson(instance.fireSide),
'items': instance.items
?.map((e) => e == null ? null : _ConvertE<E>().toJson(e))
?.toList()
};
FireSide _$FireSideFromJson(Map<String, dynamic> json) {
return FireSide(
pubdate: json['pubdate'] as String,
explicit: json['explicit'] as bool,
copyright: json['copyright'] as String,
owner: json['owner'] as String,
image: json['image'] as String);
}
Map<String, dynamic> _$FireSideToJson(FireSide instance) => <String, dynamic>{
'pubdate': instance.pubdate,
'explicit': instance.explicit,
'copyright': instance.copyright,
'owner': instance.owner,
'image': instance.image
};
EpisodeItem<A> _$EpisodeItemFromJson<A>(Map<String, dynamic> json) {
return EpisodeItem<A>(
id: json['id'] as String,
title: json['title'] as String,
url: json['url'] as String,
contentText: json['content_text'] as String,
contentHtml: json['content_html'] as String,
summary: json['summary'] as String,
datePublished: json['date_published'] as String,
attachments: (json['attachments'] as List)
?.map((e) => e == null ? null : _ConvertA<A>().fromJson(e))
?.toList());
}
Map<String, dynamic> _$EpisodeItemToJson<A>(EpisodeItem<A> instance) =>
<String, dynamic>{
'id': instance.id,
'title': instance.title,
'url': instance.url,
'content_text': instance.contentText,
'content_html': instance.contentHtml,
'summary': instance.summary,
'date_published': instance.datePublished,
'attachments': instance.attachments
?.map((e) => e == null ? null : _ConvertA<A>().toJson(e))
?.toList()
};
Attachment _$AttachmentFromJson(Map<String, dynamic> json) {
return Attachment(
url: json['url'] as String,
mimeType: json['mime_type'] as String,
sizeInBytes: json['size_in_bytes'] as int,
durationInSeconds: json['duration_in_seconds'] as int);
}
Map<String, dynamic> _$AttachmentToJson(Attachment instance) =>
<String, dynamic>{
'url': instance.url,
'mime_type': instance.mimeType,
'size_in_bytes': instance.sizeInBytes,
'duration_in_seconds': instance.durationInSeconds
};

View File

@ -0,0 +1,54 @@
import 'package:json_annotation/json_annotation.dart';
part 'searchpodcast.g.dart';
@JsonSerializable()
class SearchPodcast<P>{
@_ConvertP()
final List<P> results;
@JsonKey(name: 'next_offset')
final int nextOffset;
final int total;
final int count;
SearchPodcast(
{this.results, this.nextOffset, this.total, this.count}
);
factory SearchPodcast.fromJson(Map<String, dynamic> json) =>
_$SearchPodcastFromJson<P>(json);
Map<String, dynamic> toJson() => _$SearchPodcastToJson(this);
}
class _ConvertP<P> implements JsonConverter<P, Object>{
const _ConvertP();
@override
P fromJson(Object json){
return OnlinePodcast.fromJson(json) as P;
}
@override
Object toJson(P object){
return object;
}
}
@JsonSerializable()
class OnlinePodcast{
@JsonKey(name: 'earliest_pub_date_ms')
final int earliestPubDate;
@JsonKey(name: 'title_original')
final String title;
final String rss;
@JsonKey(name: 'lastest_pub_date_ms')
final int lastestPubDate;
@JsonKey(name: 'description_original')
final String description;
@JsonKey(name: 'total_episodes')
final int count;
final String image;
@JsonKey(name: 'publisher_original')
final String publisher;
OnlinePodcast(
{this.earliestPubDate, this.title, this.count, this.description, this.image, this.lastestPubDate, this.rss, this.publisher}
);
factory OnlinePodcast.fromJson(Map<String, dynamic> json) =>
_$OnlinePodcastFromJson(json);
Map<String, dynamic> toJson() => _$OnlinePodcastToJson(this);
}

View File

@ -0,0 +1,51 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'searchpodcast.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
SearchPodcast<P> _$SearchPodcastFromJson<P>(Map<String, dynamic> json) {
return SearchPodcast<P>(
results: (json['results'] as List)
?.map((e) => e == null ? null : _ConvertP<P>().fromJson(e))
?.toList(),
nextOffset: json['next_offset'] as int,
total: json['total'] as int,
count: json['count'] as int);
}
Map<String, dynamic> _$SearchPodcastToJson<P>(SearchPodcast<P> instance) =>
<String, dynamic>{
'results': instance.results
?.map((e) => e == null ? null : _ConvertP<P>().toJson(e))
?.toList(),
'next_offset': instance.nextOffset,
'total': instance.total,
'count': instance.count
};
OnlinePodcast _$OnlinePodcastFromJson(Map<String, dynamic> json) {
return OnlinePodcast(
earliestPubDate: json['earliest_pub_date_ms'] as int,
title: json['title_original'] as String,
count: json['total_episodes'] as int,
description: json['description_original'] as String,
image: json['image'] as String,
lastestPubDate: json['lastest_pub_date_ms'] as int,
rss: json['rss'] as String,
publisher: json['publisher_original'] as String);
}
Map<String, dynamic> _$OnlinePodcastToJson(OnlinePodcast instance) =>
<String, dynamic>{
'earliest_pub_date_ms': instance.earliestPubDate,
'title_original': instance.title,
'rss': instance.rss,
'lastest_pub_date_ms': instance.lastestPubDate,
'description_original': instance.description,
'total_episodes': instance.count,
'image': instance.image,
'publisher_original': instance.publisher
};

View File

@ -0,0 +1,451 @@
import 'package:sqflite/sqflite.dart';
import 'dart:async';
import 'dart:io' as io;
import 'package:path/path.dart';
import 'package:intl/intl.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:path_provider/path_provider.dart';
import 'podcastlocal.dart';
import 'episodebrief.dart';
import '../webfeed/webfeed.dart';
class DBHelper {
static Database _db;
Future<Database> get database async {
if (_db != null) return _db;
_db = await initDb();
return _db;
}
initDb() async {
io.Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, "podcasts.db");
Database theDb = await openDatabase(path, version: 1, onCreate: _onCreate);
return theDb;
}
void _onCreate(Database db, int version) async {
await db.execute(
"""CREATE TABLE PodcastLocal(id INTEGER PRIMARY KEY,title TEXT,
imageUrl TEXT,rssUrl TEXT UNIQUE,primaryColor TEXT,author TEXT, description TEXT, add_date INTEGER)""");
await db
.execute("""CREATE TABLE Episodes(id INTEGER PRIMARY KEY,title TEXT,
enclosure_url TEXT UNIQUE, enclosure_length INTEGER, pubDate TEXT,
description TEXT, feed_title TEXT, feed_link TEXT, milliseconds INTEGER,
duration INTEGER DEFAULT 0, explicit INTEGER DEFAULT 0, liked INTEGER DEFAULT 0,
downloaded TEXT DEFAULT 'ND', download_date INTEGER DEFAULT 0)""");
}
Future<List<PodcastLocal>> getPodcastLocal() async {
var dbClient = await database;
List<Map> list = await dbClient.rawQuery(
'SELECT title, imageUrl, rssUrl, primaryColor, author FROM PodcastLocal ORDER BY add_date DESC');
List<PodcastLocal> podcastLocal = List();
for (int i = 0; i < list.length; i++) {
podcastLocal.add(PodcastLocal(
list[i]['title'],
list[i]['imageUrl'],
list[i]['rssUrl'],
list[i]['primaryColor'],
list[i]['author'],
));
}
print(podcastLocal.length);
return podcastLocal;
}
Future savePodcastLocal(PodcastLocal podcastLocal) async {
print('save');
int _milliseconds = DateTime.now().millisecondsSinceEpoch;
var dbClient = await database;
await dbClient.transaction((txn) async {
return await txn.rawInsert(
"""INSERT OR IGNORE INTO PodcastLocal (title, imageUrl, rssUrl,
primaryColor, author, description, add_date) VALUES(?, ?, ?, ?, ?, ?, ?)""",
[
podcastLocal.title,
podcastLocal.imageUrl,
podcastLocal.rssUrl,
podcastLocal.primaryColor,
podcastLocal.author,
podcastLocal.description,
_milliseconds
]);
});
}
Future delPodcastLocal(String title) async {
print('deleted');
var dbClient = await database;
await dbClient
.rawDelete('DELETE FROM PodcastLocal WHERE title =?', [title]);
List<Map> list = await dbClient.rawQuery(
"""SELECT downloaded FROM Episodes WHERE downloaded != 'ND' AND feed_title = ?""",
[title]);
for(int i=0; i < list.length; i++){
if(list[i] != null)
FlutterDownloader.remove(taskId: list[i]['downloaded'], shouldDeleteContent: true);
print('Removed all download task');
}
await dbClient
.rawDelete('DELETE FROM Episodes WHERE feed_title=?', [title]);
}
Future getImageUrl(String title) async {
var dbClient = await database;
List<Map> list = await dbClient
.rawQuery('SELECT imageUrl FROM PodcastLocal WHERE title = ?', [title]);
String url = list[0]['imageUrl'];
return url;
}
int stringToDate(String s) {
var months = {
'Jan': 1,
'Feb': 2,
'Mar': 3,
'Apr': 4,
'May': 5,
'Jun': 6,
'Jul': 7,
'Aug': 8,
'Sep': 9,
'Oct': 10,
'Nov': 11,
'Dec': 12
};
int y;
int m;
int d;
int h;
int min;
int sec;
int result;
try {
y = int.parse(s.substring(12, 16));
} catch (e) {
y = 0;
}
try {
m = months[s.substring(8, 11)];
} catch (e) {
m = 0;
}
try {
d = int.parse(s.substring(5, 7));
} catch (e) {
d = 0;
}
try {
h = int.parse(s.substring(17, 19));
} catch (e) {
h = 0;
}
try {
min = int.parse(s.substring(20, 22));
} catch (e) {
min = 0;
}
try {
sec = int.parse(s.substring(23, 25));
} catch (e) {
sec = 0;
}
try {
result = DateTime(y, m, d, h, min, sec).millisecondsSinceEpoch;
} catch (e) {
result = 0;
}
return result;
}
static _parsePubDate(String pubDate) {
if (pubDate == null) return null;
return DateFormat('EEE, dd MMM yyyy HH:mm:ss Z', 'en_US').parse(pubDate);
}
int getExplicit(bool b) {
int result;
if (b == true) {
result = 1;
return result;
} else {
result = 0;
return result;
}
}
bool isXimalaya(String input) {
RegExp ximalaya = RegExp(r"ximalaya.com");
return ximalaya.hasMatch(input);
}
Future<int> savePodcastRss(String rss) async {
String _title;
String _url;
String _description;
var _p = RssFeed.parse(rss);
int _result = _p.items.length;
var dbClient = await database;
int _count = Sqflite.firstIntValue(await dbClient.rawQuery(
'SELECT COUNT(*) FROM Episodes WHERE feed_title = ?', [_p.title]));
print(_count);
if (_count == _result) {
_result = 0;
return _result;
} else {
for (int i = 0; i < (_result - _count); i++) {
print(_p.items[i].title);
_p.items[i].itunes.title != null
? _title = _p.items[i].itunes.title
: _title = _p.items[i].title;
_p.items[i].itunes.summary != null
? _description = _p.items[i].itunes.summary
: _description = _p.items[i].description;
isXimalaya(_p.items[i].enclosure.url)
? _url = _p.items[i].enclosure.url.split('=').last
: _url = _p.items[i].enclosure.url;
final _length = _p.items[i].enclosure.length;
final _pubDate = _p.items[i].pubDate;
final DateTime _date = _parsePubDate(_pubDate);
final _milliseconds = _date.millisecondsSinceEpoch;
final _duration = _p.items[i].itunes.duration.inMinutes;
final _explicit = getExplicit(_p.items[i].itunes.explicit);
if (_p.items[i].enclosure.url != null) {
await dbClient.transaction((txn) {
return txn.rawInsert(
"""INSERT OR IGNORE INTO Episodes(title, enclosure_url, enclosure_length, pubDate,
description, feed_title, milliseconds, duration, explicit) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)""",
[
_title,
_url,
_length,
_pubDate,
_description,
_p.title,
_milliseconds,
_duration,
_explicit,
]);
});
}
}
_result = 0;
return _result;
}
}
Future<List<EpisodeBrief>> getRssItem(String title) async {
var dbClient = await database;
List<EpisodeBrief> episodes = List();
List<Map> list = await dbClient
.rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length,
E.pubDate, E.feed_title, E.duration, E.explicit, E.liked,
E.downloaded, P.imageUrl, P.primaryColor
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_title = P.title
where E.feed_title = ? ORDER BY E.milliseconds DESC""", [title]);
for (int x = 0; x < list.length; x++) {
episodes.add(EpisodeBrief(
list[x]['title'],
list[x]['enclosure_url'],
list[x]['enclosure_length'],
list[x]['pubDate'],
list[x]['feed_title'],
list[x]['imageUrl'],
list[x]['primaryColor'],
list[x]['liked'],
list[x]['downloaded'],
list[x]['duration'],
list[x]['explicit']));
}
print(episodes.length);
print(title);
return episodes;
}
Future<List<EpisodeBrief>> getRssItemTop(String title) async {
var dbClient = await database;
List<EpisodeBrief> episodes = List();
List<Map> list = await dbClient.rawQuery(
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
E.pubDate, E.feed_title, E.duration, E.explicit, E.liked,
E.downloaded, P.imageUrl, P.primaryColor
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_title = P.title
where E.feed_title = ? ORDER BY E.milliseconds DESC LIMIT 3""",
[title]);
for (int x = 0; x < list.length; x++) {
episodes.add(EpisodeBrief(
list[x]['title'],
list[x]['enclosure_url'],
list[x]['enclosure_length'],
list[x]['pubDate'],
list[x]['feed_title'],
list[x]['imageUrl'],
list[x]['primaryColor'],
list[x]['liked'],
list[x]['downloaded'],
list[x]['duration'],
list[x]['explicit']));
}
print(episodes.length);
print(title);
return episodes;
}
Future<EpisodeBrief> getRssItemDownload(String url) async {
var dbClient = await database;
EpisodeBrief episode;
List<Map> list = await dbClient.rawQuery(
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
E.pubDate, E.feed_title, E.duration, E.explicit, E.liked,
E.downloaded, P.imageUrl, P.primaryColor
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_title = P.title
where E.enclosure_url = ? ORDER BY E.milliseconds DESC LIMIT 3""",
[url]);
if (list != null)
episode = EpisodeBrief(
list.first['title'],
list.first['enclosure_url'],
list.first['enclosure_length'],
list.first['pubDate'],
list.first['feed_title'],
list.first['imageUrl'],
list.first['primaryColor'],
list.first['liked'],
list.first['downloaded'],
list.first['duration'],
list.first['explicit']);
return episode;
}
Future<List<EpisodeBrief>> getRecentRssItem() async {
var dbClient = await database;
List<EpisodeBrief> episodes = List();
List<Map> list = await dbClient
.rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length,
E.pubDate, E.feed_title, E.duration, E.explicit, E.liked,
E.downloaded, P.imageUrl, P.primaryColor
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_title = P.title
ORDER BY E.milliseconds DESC LIMIT 99""");
for (int x = 0; x < list.length; x++) {
episodes.add(EpisodeBrief(
list[x]['title'],
list[x]['enclosure_url'],
list[x]['enclosure_length'],
list[x]['pubDate'],
list[x]['feed_title'],
list[x]['imageUrl'],
list[x]['primaryColor'],
list[x]['liked'],
list[x]['doanloaded'],
list[x]['duration'],
list[x]['explicit']));
}
print(episodes.length);
return episodes;
}
Future<List<EpisodeBrief>> getLikedRssItem() async {
var dbClient = await database;
List<EpisodeBrief> episodes = List();
List<Map> list = await dbClient.rawQuery(
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.pubDate,
E.feed_title, E.duration, E.explicit, E.liked, E.downloaded, P.imageUrl,
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_title = P.title
WHERE E.liked = 1 ORDER BY E.milliseconds DESC LIMIT 99""");
for (int x = 0; x < list.length; x++) {
episodes.add(EpisodeBrief(
list[x]['title'],
list[x]['enclosure_url'],
list[x]['enclosure_length'],
list[x]['pubDate'],
list[x]['feed_title'],
list[x]['imageUrl'],
list[x]['primaryColor'],
list[x]['liked'],
list[x]['downloaded'],
list[x]['duration'],
list[x]['explicit']));
}
print(episodes.length);
return episodes;
}
Future<int> setLiked(String title) async {
var dbClient = await database;
int count = await dbClient
.rawUpdate("UPDATE Episodes SET liked = 1 WHERE title = ?", [title]);
print('liked');
return count;
}
Future<int> setUniked(String title) async {
var dbClient = await database;
int count = await dbClient
.rawUpdate("UPDATE Episodes SET liked = 0 WHERE title = ?", [title]);
print('unliked');
return count;
}
Future<int> saveDownloaded(String url, String id) async {
var dbClient = await database;
int _milliseconds = DateTime.now().millisecondsSinceEpoch;
int count = await dbClient.rawUpdate(
"UPDATE Episodes SET downloaded = ?, download_date = ? WHERE enclosure_url = ?",
[id, _milliseconds, url]);
print('Downloaded ' + url);
return count;
}
Future<int> delDownloaded(String url) async {
var dbClient = await database;
int count = await dbClient.rawUpdate(
"UPDATE Episodes SET downloaded = 'ND' WHERE enclosure_url = ?", [url]);
print('Deleted ' + url);
return count;
}
Future<List<EpisodeBrief>> getDownloadedRssItem() async {
var dbClient = await database;
List<EpisodeBrief> episodes = List();
List<Map> list = await dbClient.rawQuery(
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.pubDate,
E.feed_title, E.duration, E.explicit, E.liked, E.downloaded, P.imageUrl,
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_title = P.title
WHERE E.downloaded != 'ND' ORDER BY E.download_date DESC LIMIT 99""");
for (int x = 0; x < list.length; x++) {
episodes.add(EpisodeBrief(
list[x]['title'],
list[x]['enclosure_url'],
list[x]['enclosure_length'],
list[x]['pubDate'],
list[x]['feed_title'],
list[x]['imageUrl'],
list[x]['primaryColor'],
list[x]['liked'],
list[x]['downloaded'],
list[x]['duration'],
list[x]['explicit']));
}
print(episodes.length);
return episodes;
}
Future<String> getDescription(String title) async {
var dbClient = await database;
List<Map> list = await dbClient
.rawQuery('SELECT description FROM Episodes WHERE title = ?', [title]);
String description = list[0]['description'];
return description;
}
Future<String> getFeedDescription(String title) async {
var dbClient = await database;
List<Map> list = await dbClient.rawQuery(
'SELECT description FROM PodcastLocal WHERE title = ?', [title]);
String description = list[0]['description'];
return description;
}
}

533
lib/episodedetail.dart Normal file
View File

@ -0,0 +1,533 @@
import 'dart:convert';
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:url_launcher/url_launcher.dart';
import 'class/audiostate.dart';
import 'class/episodebrief.dart';
import 'class/sqflite_localpodcast.dart';
import 'episodedownload.dart';
enum DownloadState { stop, load, donwload, complete, error }
class EpisodeDetail extends StatefulWidget {
final EpisodeBrief episodeItem;
final String heroTag;
EpisodeDetail({this.episodeItem, this.heroTag, Key key}) : super(key: key);
@override
_EpisodeDetailState createState() => _EpisodeDetailState();
}
class _EpisodeDetailState extends State<EpisodeDetail> {
final textstyle = TextStyle(fontSize: 15.0, color: Colors.black);
double downloadProgress;
Color _c;
bool _loaddes;
Future getSDescription(String title) async {
var dbHelper = DBHelper();
widget.episodeItem.description = await dbHelper.getDescription(title);
if (mounted)
setState(() {
_loaddes = true;
});
}
_launchUrl(String url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
@override
void initState() {
super.initState();
_loaddes = false;
getSDescription(widget.episodeItem.title);
}
@override
Widget build(BuildContext context) {
var color = json.decode(widget.episodeItem.primaryColor);
(color[0] > 200 && color[1] > 200 && color[2] > 200)
? _c = Color.fromRGBO(
(255 - color[0]), 255 - color[1], 255 - color[2], 1.0)
: _c = Color.fromRGBO(color[0], color[1], color[2], 0.8);
return Scaffold(
backgroundColor: Colors.grey[100],
appBar: AppBar(
title: Text(widget.episodeItem.feedTitle),
elevation: 0.0,
centerTitle: true,
backgroundColor: Colors.grey[100],
),
body: Container(
color: Colors.grey[100],
padding: EdgeInsets.all(12.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
padding: EdgeInsets.symmetric(horizontal: 12.0),
margin: EdgeInsets.only(bottom: 10.0),
alignment: Alignment.topLeft,
child: Text(
widget.episodeItem.title,
style: Theme.of(context).textTheme.title,
),
),
Container(
padding: EdgeInsets.all(12.0),
height: 50,
child: Row(
children: <Widget>[
(widget.episodeItem.explicit == 1)
? Container(
decoration: BoxDecoration(
color: Colors.red[800],
shape: BoxShape.circle),
height: 25.0,
margin: EdgeInsets.only(right: 10.0),
padding: EdgeInsets.symmetric(horizontal: 10.0),
alignment: Alignment.center,
child: Text('E',
style: TextStyle(color: Colors.white)))
: Center(),
Container(
decoration: BoxDecoration(
color: Colors.cyan[300],
borderRadius:
BorderRadius.all(Radius.circular(15.0))),
height: 30.0,
margin: EdgeInsets.only(right: 10.0),
padding: EdgeInsets.symmetric(horizontal: 10.0),
alignment: Alignment.center,
child: Text(
(widget.episodeItem.duration).toString() + 'mins',
style: textstyle),
),
Container(
decoration: BoxDecoration(
color: Colors.lightBlue[300],
borderRadius:
BorderRadius.all(Radius.circular(15.0))),
height: 30.0,
margin: EdgeInsets.only(right: 10.0),
padding: EdgeInsets.symmetric(horizontal: 10.0),
alignment: Alignment.center,
child: Text(
((widget.episodeItem.enclosureLength) ~/ 1000000)
.toString() +
'MB',
style: textstyle),
),
Container(
decoration: BoxDecoration(
color: Colors.lightGreen[300],
borderRadius:
BorderRadius.all(Radius.circular(15.0))),
height: 30.0,
alignment: Alignment.center,
margin: EdgeInsets.only(right: 10.0),
padding: EdgeInsets.symmetric(horizontal: 10.0),
child: Text(
widget.episodeItem.pubDate.substring(0, 16),
style: textstyle),
),
],
),
),
],
),
),
Expanded(
child: Container(
padding: EdgeInsets.only(left: 12.0, right: 12.0, top: 5.0),
child: SingleChildScrollView(
child: (widget.episodeItem.description != null && _loaddes)
? Html(data: widget.episodeItem.description,
onLinkTap: (url){
_launchUrl(url);
},
useRichText: true,
)
: Center(),
),
),
),
MenuBar(
episodeItem: widget.episodeItem,
heroTag: widget.heroTag,
),
],
),
),
);
}
}
class MenuBar extends StatefulWidget {
final EpisodeBrief episodeItem;
final String heroTag;
MenuBar({this.episodeItem, this.heroTag, Key key}) : super(key: key);
@override
_MenuBarState createState() => _MenuBarState();
}
class _MenuBarState extends State<MenuBar> {
bool _liked;
int _like;
Future<int> saveLiked(String title) async {
var dbHelper = DBHelper();
int result = await dbHelper.setLiked(title);
if (result == 1 && mounted) setState(() => _liked = true);
return result;
}
Future<int> setUnliked(String title) async {
var dbHelper = DBHelper();
int result = await dbHelper.setUniked(title);
if (result == 1 && mounted)
setState(() {
_liked = false;
_like = 0;
});
return result;
}
@override
void initState() {
super.initState();
_liked = false;
_like = widget.episodeItem.liked;
}
@override
Widget build(BuildContext context) {
final urlChange = Provider.of<Urlchange>(context);
return Consumer<Urlchange>(
builder: (context, urlchange, _) => Container(
height: 50.0,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(10.0)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
(widget.episodeItem.title == urlChange.title &&
urlChange.audioState == AudioState.play)
? ImageRotate(
url: widget.episodeItem.imageUrl,
)
: Hero(
tag: widget.episodeItem.enclosureUrl + widget.heroTag,
child: Container(
padding: EdgeInsets.all(10.0),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(15.0)),
child: Container(
height: 30.0,
width: 30.0,
color: Colors.white,
child: CachedNetworkImage(
imageUrl: widget.episodeItem.imageUrl,
),
),
),
),
),
(_like == 0 && !_liked)
? IconButton(
icon: Icon(
Icons.favorite_border,
color: Colors.grey[700],
),
onPressed: () {
saveLiked(widget.episodeItem.title);
},
)
: IconButton(
icon: Icon(
Icons.favorite,
color: Colors.red,
),
onPressed: () {
setUnliked(widget.episodeItem.title);
},
),
DownloadButton(episodeBrief: widget.episodeItem),
IconButton(
icon: Icon(Icons.playlist_add, color: Colors.grey[700]),
onPressed: () {/*TODO*/},
),
Spacer(),
(widget.episodeItem.title != urlchange.title)
? IconButton(
icon: Icon(
Icons.play_arrow,
color: Colors.grey[700],
),
onPressed: () {
urlChange.audioUrl = widget.episodeItem.enclosureUrl;
urlChange.rssTitle = widget.episodeItem.title;
urlChange.feedTitle = widget.episodeItem.feedTitle;
urlChange.imageUrl = widget.episodeItem.imageUrl;
},
)
: (widget.episodeItem.title == urlchange.title &&
urlchange.audioState == AudioState.play)
? Container(
padding: EdgeInsets.only(right: 15),
child: SizedBox(
width: 15, height: 15, child: WaveLoader()))
: Container(
padding: EdgeInsets.only(right: 15),
child: SizedBox(
width: 15,
height: 15,
child: LineLoader(),
),
),
],
),
),
);
}
}
class LinePainter extends CustomPainter {
double _fraction;
Paint _paint;
LinePainter(this._fraction) {
_paint = Paint()
..color = Colors.blue
..strokeWidth = 2.0
..strokeCap = StrokeCap.round;
}
@override
void paint(Canvas canvas, Size size) {
canvas.drawLine(Offset(0, size.height / 2.0),
Offset(size.width * _fraction, size.height / 2.0), _paint);
}
@override
bool shouldRepaint(LinePainter oldDelegate) {
return oldDelegate._fraction != _fraction;
}
}
class LineLoader extends StatefulWidget {
@override
_LineLoaderState createState() => _LineLoaderState();
}
class _LineLoaderState extends State<LineLoader>
with SingleTickerProviderStateMixin {
double _fraction = 0.0;
Animation animation;
AnimationController controller;
@override
void initState() {
super.initState();
controller = AnimationController(
vsync: this, duration: Duration(milliseconds: 1000));
animation = Tween(begin: 0.0, end: 1.0).animate(controller)
..addListener(() {
if (mounted)
setState(() {
_fraction = animation.value;
});
});
controller.forward();
controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.reset();
} else if (status == AnimationStatus.dismissed) {
controller.forward();
}
});
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return CustomPaint(painter: LinePainter(_fraction));
}
}
class WavePainter extends CustomPainter {
double _fraction;
double _value;
WavePainter(this._fraction);
@override
void paint(Canvas canvas, Size size) {
if (_fraction < 0.5) {
_value = _fraction;
} else {
_value = 1 - _fraction;
}
Path _path = Path();
Paint _paint = Paint()
..color = Colors.blue
..strokeWidth = 2.0
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke;
_path.moveTo(0, size.height / 2);
_path.lineTo(0, size.height / 2 + size.height * _value * 0.2);
_path.moveTo(0, size.height / 2);
_path.lineTo(0, size.height / 2 - size.height * _value * 0.2);
_path.moveTo(size.width / 4, size.height / 2);
_path.lineTo(size.width / 4, size.height / 2 + size.height * _value * 0.8);
_path.moveTo(size.width / 4, size.height / 2);
_path.lineTo(size.width / 4, size.height / 2 - size.height * _value * 0.8);
_path.moveTo(size.width / 2, size.height / 2);
_path.lineTo(size.width / 2, size.height / 2 + size.height * _value * 0.5);
_path.moveTo(size.width / 2, size.height / 2);
_path.lineTo(size.width / 2, size.height / 2 - size.height * _value * 0.5);
_path.moveTo(size.width * 3 / 4, size.height / 2);
_path.lineTo(
size.width * 3 / 4, size.height / 2 + size.height * _value * 0.6);
_path.moveTo(size.width * 3 / 4, size.height / 2);
_path.lineTo(
size.width * 3 / 4, size.height / 2 - size.height * _value * 0.6);
_path.moveTo(size.width, size.height / 2);
_path.lineTo(size.width, size.height / 2 + size.height * _value * 0.2);
_path.moveTo(size.width, size.height / 2);
_path.lineTo(size.width, size.height / 2 - size.height * _value * 0.2);
canvas.drawPath(_path, _paint);
}
@override
bool shouldRepaint(WavePainter oldDelegate) {
return oldDelegate._fraction != _fraction;
}
}
class WaveLoader extends StatefulWidget {
@override
_WaveLoaderState createState() => _WaveLoaderState();
}
class _WaveLoaderState extends State<WaveLoader>
with SingleTickerProviderStateMixin {
double _fraction = 0.0;
Animation animation;
AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this, duration: Duration(milliseconds: 1000));
animation = Tween(begin: 0.0, end: 1.0).animate(_controller)
..addListener(() {
if (mounted)
setState(() {
_fraction = animation.value;
});
});
_controller.forward();
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reset();
} else if (status == AnimationStatus.dismissed) {
_controller.forward();
}
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return CustomPaint(painter: WavePainter(_fraction));
}
}
class ImageRotate extends StatefulWidget {
final String url;
ImageRotate({this.url, Key key}) : super(key: key);
@override
_ImageRotateState createState() => _ImageRotateState();
}
class _ImageRotateState extends State<ImageRotate>
with SingleTickerProviderStateMixin {
Animation _animation;
AnimationController _controller;
double _value;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 2000),
);
_animation = Tween(begin: 0.0, end: 1.0).animate(_controller)
..addListener(() {
if (mounted)
setState(() {
_value = _animation.value;
});
});
_controller.forward();
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reset();
} else if (status == AnimationStatus.dismissed) {
_controller.forward();
}
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Transform.rotate(
angle: 2 * math.pi * _value,
child: Container(
padding: EdgeInsets.all(10.0),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(15.0)),
child: Container(
height: 30.0,
width: 30.0,
color: Colors.white,
child: CachedNetworkImage(
imageUrl: widget.url,
),
),
),
),
);
}
}

282
lib/episodedownload.dart Normal file
View File

@ -0,0 +1,282 @@
import 'dart:isolate';
import 'dart:ui';
import 'dart:io';
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:path_provider/path_provider.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'class/episodebrief.dart';
import 'class/sqflite_localpodcast.dart';
class DownloadButton extends StatefulWidget {
final EpisodeBrief episodeBrief;
DownloadButton({this.episodeBrief, Key key}) : super(key: key);
@override
_DownloadButtonState createState() => _DownloadButtonState();
}
class _DownloadButtonState extends State<DownloadButton> {
_TaskInfo _task;
bool _isLoading;
bool _permissionReady;
String _localPath;
ReceivePort _port = ReceivePort();
Future<String> _getPath() async {
final dir = await getExternalStorageDirectory();
return dir.path;
}
@override
void initState() {
super.initState();
_bindBackgroundIsolate();
FlutterDownloader.registerCallback(downloadCallback);
_isLoading = true;
_permissionReady = false;
_prepare();
}
@override
void dispose() {
_unbindBackgroundIsolate();
super.dispose();
}
void _bindBackgroundIsolate() {
bool isSuccess = IsolateNameServer.registerPortWithName(
_port.sendPort, 'downloader_send_port');
if (!isSuccess) {
_unbindBackgroundIsolate();
_bindBackgroundIsolate();
return;
}
_port.listen((dynamic data) {
print('UI isolate callback: $data');
String id = data[0];
DownloadTaskStatus status = data[1];
int progress = data[2];
if (_task.taskId == id) {
print(_task.progress);
setState(() {
_task.status = status;
_task.progress = progress;
});
}
});
}
void _unbindBackgroundIsolate() {
IsolateNameServer.removePortNameMapping('downloader_send_port');
}
static void downloadCallback(
String id, DownloadTaskStatus status, int progress) {
print('Background callback task in $id status ($status) $progress');
final SendPort send =
IsolateNameServer.lookupPortByName('downloader_send_port');
send.send([id, status, progress]);
}
void _requestDownload(_TaskInfo task) async {
_permissionReady = await _checkPermmison();
if (_permissionReady)
task.taskId = await FlutterDownloader.enqueue(
url: task.link,
savedDir: _localPath,
showNotification: true,
openFileFromNotification: false,
);
var dbHelper = DBHelper();
await dbHelper.saveDownloaded(task.link, task.taskId);
Fluttertoast.showToast(
msg: 'Downloading',
gravity: ToastGravity.BOTTOM,
);
}
void _deleteDownload(_TaskInfo task) async {
await FlutterDownloader.remove(
taskId: task.taskId, shouldDeleteContent: true);
var dbHelper = DBHelper();
await dbHelper.delDownloaded(task.link);
await _prepare();
setState(() {});
Fluttertoast.showToast(
msg: 'Download removed',
gravity: ToastGravity.BOTTOM,
);
}
void _pauseDownload(_TaskInfo task) async {
await FlutterDownloader.pause(taskId: task.taskId);
Fluttertoast.showToast(
msg: 'Download paused',
gravity: ToastGravity.BOTTOM,
);
}
void _resumeDownload(_TaskInfo task) async {
String newTaskId = await FlutterDownloader.resume(taskId: task.taskId);
task.taskId = newTaskId;
var dbHelper = DBHelper();
await dbHelper.saveDownloaded(task.taskId, task.link);
Fluttertoast.showToast(
msg: 'Download resumed',
gravity: ToastGravity.BOTTOM,
);
}
void _retryDownload(_TaskInfo task) async {
String newTaskId = await FlutterDownloader.retry(taskId: task.taskId);
task.taskId = newTaskId;
var dbHelper = DBHelper();
await dbHelper.saveDownloaded(task.taskId, task.link);
Fluttertoast.showToast(
msg: 'Download again',
gravity: ToastGravity.BOTTOM,
);
}
Future<Null> _prepare() async {
final tasks = await FlutterDownloader.loadTasks();
_task = _TaskInfo(
name: widget.episodeBrief.title,
link: widget.episodeBrief.enclosureUrl);
tasks?.forEach((task) {
if (_task.link == task.url) {
_task.taskId = task.taskId;
_task.status = task.status;
_task.progress = task.progress;
}
});
_localPath = (await _getPath()) + '/' + widget.episodeBrief.feedTitle;
print(_localPath);
final saveDir = Directory(_localPath);
bool hasExisted = await saveDir.exists();
if (!hasExisted) {
saveDir.create();
}
setState(() {
_isLoading = false;
});
}
Future<bool> _checkPermmison() async {
PermissionStatus permission = await PermissionHandler()
.checkPermissionStatus(PermissionGroup.storage);
if (permission != PermissionStatus.granted) {
Map<PermissionGroup, PermissionStatus> permissions =
await PermissionHandler()
.requestPermissions([PermissionGroup.storage]);
if (permissions[PermissionGroup.storage] == PermissionStatus.granted) {
return true;
} else {
return false;
}
} else {
return true;
}
}
@override
Widget build(BuildContext context) {
return _downloadButton(_task);
}
Widget _downloadButton(_TaskInfo task) {
if (_isLoading)
return Center();
else if (task.status == DownloadTaskStatus.undefined) {
return IconButton(
onPressed: () {
_requestDownload(task);
},
icon: Icon(
Icons.arrow_downward,
color: Colors.grey[700],
),
);
} else if (task.status == DownloadTaskStatus.running) {
return InkWell(
onTap: () {
_pauseDownload(task);
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 18),
child: SizedBox(
height: 18,
width: 18,
child: CircularProgressIndicator(
backgroundColor: Colors.grey[200],
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
value: task.progress / 100,
),
),
),
);
} else if (task.status == DownloadTaskStatus.paused) {
return InkWell(
onTap: () {
_resumeDownload(task);
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 18),
child: SizedBox(
height: 18,
width: 18,
child: CircularProgressIndicator(
backgroundColor: Colors.grey[200],
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.red),
value: task.progress / 100,
),
),
),
);
} else if (task.status == DownloadTaskStatus.complete) {
return IconButton(
icon: Icon(
Icons.done_all,
color: Colors.blue,
),
onPressed: () {
_deleteDownload(task);
},
);
} else if (task.status == DownloadTaskStatus.failed) {
return IconButton(
icon: Icon(Icons.refresh, color: Colors.red),
onPressed: () {
_retryDownload(task);
},
);
}
return Center();
}
}
class _TaskInfo {
final String name;
final String link;
String taskId;
int progress = 0;
DownloadTaskStatus status = DownloadTaskStatus.undefined;
_TaskInfo({this.name, this.link});
}

323
lib/episodegrid.dart Normal file
View File

@ -0,0 +1,323 @@
import 'dart:convert';
import 'dart:isolate';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:google_fonts/google_fonts.dart';
import 'class/episodebrief.dart';
import 'episodedetail.dart';
import 'pageroute.dart';
class EpisodeGrid extends StatelessWidget {
final List<EpisodeBrief> podcast;
final bool showFavorite;
final bool showDownload;
final bool showNumber;
final String heroTag;
EpisodeGrid(
{Key key,
this.podcast,
this.showDownload,
this.showFavorite,
this.showNumber,
this.heroTag})
: super(key: key);
@override
Widget build(BuildContext context) {
return CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
primary: false,
slivers: <Widget>[
SliverPadding(
padding: const EdgeInsets.all(5.0),
sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: 1.0,
crossAxisCount: 3,
mainAxisSpacing: 6.0,
crossAxisSpacing: 6.0,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
Color _c;
var color = json.decode(podcast[index].primaryColor);
(color[0] > 200 && color[1] > 200 && color[2] > 200)
? _c = Color.fromRGBO(
(255 - color[0]), 255 - color[1], 255 - color[2], 1.0)
: _c = Color.fromRGBO(color[0], color[1], color[2], 1.0);
return InkWell(
onTap: () {
Navigator.push(
context,
ScaleRoute(
page: EpisodeDetail(
episodeItem: podcast[index],
heroTag: heroTag
)),
);
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(5.0)),
color: Theme.of(context).scaffoldBackgroundColor,
border: Border.all(
color: Colors.grey[100],
width: 3.0,
),
boxShadow: [
BoxShadow(
color: Colors.grey[100],
blurRadius: 1.0,
spreadRadius: 0.5,
),
]),
alignment: Alignment.center,
padding: EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
flex: 2,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Hero(
tag: podcast[index].enclosureUrl + heroTag,
child: Container(
child: ClipRRect(
borderRadius:
BorderRadius.all(Radius.circular(15.0)),
child: Container(
height: 30.0,
width: 30.0,
child: CachedNetworkImage(
imageUrl: podcast[index].imageUrl,
),
),
),
),
),
Spacer(),
showNumber
? Container(
alignment: Alignment.topRight,
child: Text(
(podcast.length - index).toString(),
style: GoogleFonts.teko(
textStyle: TextStyle(
fontSize: 20.0,
color: _c,
),
),
),
)
: Center(),
],
),
),
Expanded(
flex: 5,
child: Container(
padding: EdgeInsets.only(top: 2.0),
child: Text(
podcast[index].title,
style: TextStyle(
fontSize: 15.0,
),
maxLines: 4,
),
),
),
Expanded(
flex: 1,
child: Row(
children: <Widget>[
Align(
alignment: Alignment.bottomLeft,
child: Text(
podcast[index].pubDate.substring(4, 16),
style: TextStyle(
color: _c, fontStyle: FontStyle.italic),
),
),
Spacer(),
showDownload
? DownloadIcon(episodeBrief: podcast[index])
: Center(),
Padding(
padding: EdgeInsets.all(1),
),
showFavorite
? Container(
alignment: Alignment.bottomRight,
child: (podcast[index].liked == 0)
? Center()
: IconTheme(
data: IconThemeData(size: 15),
child: Icon(
Icons.favorite,
color: Colors.red,
),
),
)
: Center(),
],
),
),
],
),
),
);
},
childCount: podcast.length,
),
),
),
],
);
}
}
class DownloadIcon extends StatefulWidget {
final EpisodeBrief episodeBrief;
DownloadIcon({this.episodeBrief, Key key}) : super(key: key);
@override
_DownloadIconState createState() => _DownloadIconState();
}
class _DownloadIconState extends State<DownloadIcon> {
_TaskInfo _task;
bool _isLoading;
ReceivePort _port = ReceivePort();
@override
void initState() {
super.initState();
_bindBackgroundIsolate();
FlutterDownloader.registerCallback(downloadCallback);
_isLoading = true;
_prepare();
}
@override
void dispose() {
_unbindBackgroundIsolate();
super.dispose();
}
void _bindBackgroundIsolate() {
bool isSuccess = IsolateNameServer.registerPortWithName(
_port.sendPort, 'downloader_send_port');
if (!isSuccess) {
_unbindBackgroundIsolate();
_bindBackgroundIsolate();
return;
}
_port.listen((dynamic data) {
print('UI isolate callback: $data');
String id = data[0];
DownloadTaskStatus status = data[1];
int progress = data[2];
if (_task.taskId == id) {
setState(() {
_task.status = status;
_task.progress = progress;
});
}
});
}
void _unbindBackgroundIsolate() {
IsolateNameServer.removePortNameMapping('downloader_send_port');
}
static void downloadCallback(
String id, DownloadTaskStatus status, int progress) {
print('Background callback task in $id status ($status) $progress');
final SendPort send =
IsolateNameServer.lookupPortByName('downloader_send_port');
send.send([id, status, progress]);
}
Future<Null> _prepare() async {
final tasks = await FlutterDownloader.loadTasks();
_task = _TaskInfo(
name: widget.episodeBrief.title,
link: widget.episodeBrief.enclosureUrl);
tasks?.forEach((task) {
if (_task.link == task.url) {
_task.taskId = task.taskId;
_task.status = task.status;
_task.progress = task.progress;
}
});
setState(() {
_isLoading = false;
});
}
@override
Widget build(BuildContext context) {
return _downloadButton(_task);
}
Widget _downloadButton(_TaskInfo task) {
if (_isLoading)
return Center();
else if (task.status == DownloadTaskStatus.running) {
return SizedBox(
height: 12,
width: 12,
child: CircularProgressIndicator(
backgroundColor: Colors.grey[200],
strokeWidth: 1,
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
value: task.progress / 100,
),
);
} else if (task.status == DownloadTaskStatus.paused) {
return SizedBox(
height: 12,
width: 12,
child: CircularProgressIndicator(
backgroundColor: Colors.grey[200],
strokeWidth: 1,
valueColor: AlwaysStoppedAnimation<Color>(Colors.red),
value: task.progress / 100,
),
);
} else if (task.status == DownloadTaskStatus.complete) {
return IconTheme(
data: IconThemeData(size: 15),
child: Icon(
Icons.done_all,
color: Colors.blue,
),
);
} else if (task.status == DownloadTaskStatus.failed) {
return IconTheme(
data: IconThemeData(size: 15),
child: Icon(Icons.refresh, color: Colors.red),
);
}
return Center();
}
}
class _TaskInfo {
final String name;
final String link;
String taskId;
int progress = 0;
DownloadTaskStatus status = DownloadTaskStatus.undefined;
_TaskInfo({this.name, this.link});
}

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