✨ update theme
This commit is contained in:
parent
d713807b00
commit
3cff5afc2a
|
@ -1,11 +1,11 @@
|
|||
arguments=
|
||||
auto.sync=false
|
||||
build.scans.enabled=false
|
||||
connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(7.1.1))
|
||||
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
|
||||
connection.project.dir=
|
||||
eclipse.preferences.version=1
|
||||
gradle.user.home=
|
||||
java.home=/usr/lib/jvm/java-17-openjdk-17.0.2.0.8-7.fc36.x86_64
|
||||
java.home=/usr/lib/jvm/java-17-openjdk-17.0.3.0.7-1.fc36.x86_64
|
||||
jvm.arguments=
|
||||
offline.mode=false
|
||||
override.workspace.settings=true
|
||||
|
|
|
@ -32,9 +32,9 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
|||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
compileSdkVersion 31
|
||||
|
||||
ndkVersion "21.4.7075529"
|
||||
ndkVersion "22.1.7171670"
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<application android:name="${applicationName}" android:label="Tsacdop" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:networkSecurityConfig="@xml/network_security_config">
|
||||
<application android:name=".MainApplication" android:label="Tsacdop" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:networkSecurityConfig="@xml/network_security_config">
|
||||
<activity android:name="com.ryanheise.audioservice.AudioServiceActivity" 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">
|
||||
<meta-data android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/LaunchTheme" />
|
||||
<meta-data android:name="io.flutter.embedding.android.SplashScreenDrawable" android:resource="@drawable/normal_background" />
|
||||
|
|
|
@ -2,6 +2,7 @@ package io.flutter.plugins;
|
|||
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.annotation.NonNull;
|
||||
import io.flutter.Log;
|
||||
|
||||
import io.flutter.embedding.engine.FlutterEngine;
|
||||
import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistry;
|
||||
|
@ -11,29 +12,34 @@ import com.tekartik.sqflite.SqflitePlugin;
|
|||
import io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin;
|
||||
import vn.hunghd.flutterdownloader.FlutterDownloaderPlugin;
|
||||
|
||||
/**
|
||||
* Generated file. Do not edit. This file is generated by the Flutter tool based
|
||||
* on the plugins that support the Android platform.
|
||||
*/
|
||||
|
||||
@Keep
|
||||
public final class IsolatePluginRegistrant {
|
||||
public static void registerWith(PluginRegistry registry) {
|
||||
if (alreadyRegisteredWith(registry)) {
|
||||
return;
|
||||
private static final String TAG = "CustomPluginRegistrant";
|
||||
public static void registerWith(@NonNull FlutterEngine flutterEngine) {
|
||||
try {
|
||||
flutterEngine.getPlugins().add(new vn.hunghd.flutterdownloader.FlutterDownloaderPlugin());
|
||||
} catch(Exception e) {
|
||||
Log.e(TAG, "Error registering plugin flutter_downloader, vn.hunghd.flutterdownloader.FlutterDownloaderPlugin", e);
|
||||
}
|
||||
PathProviderPlugin.registerWith(registry.registrarFor("io.flutter.plugins.pathprovider.PathProviderPlugin"));
|
||||
SqflitePlugin.registerWith(registry.registrarFor("com.tekartik.sqflite.SqflitePlugin"));
|
||||
SharedPreferencesPlugin.registerWith(registry.registrarFor("io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin"));
|
||||
FlutterDownloaderPlugin.registerWith(registry.registrarFor("vn.hunghd.flutterdownloader.FlutterDownloaderPlugin"));
|
||||
}
|
||||
|
||||
private static boolean alreadyRegisteredWith(PluginRegistry registry) {
|
||||
final String key = IsolatePluginRegistrant.class.getCanonicalName();
|
||||
if (registry.hasPlugin(key)) {
|
||||
return true;
|
||||
try {
|
||||
flutterEngine.getPlugins().add(new com.rmawatson.flutterisolate.FlutterIsolatePlugin());
|
||||
} catch(Exception e) {
|
||||
Log.e(TAG, "Error registering plugin flutter_isolate, com.rmawatson.flutterisolate.FlutterIsolatePlugin", e);
|
||||
}
|
||||
try {
|
||||
flutterEngine.getPlugins().add(new io.flutter.plugins.pathprovider.PathProviderPlugin());
|
||||
} catch(Exception e) {
|
||||
Log.e(TAG, "Error registering plugin path_provider_android, io.flutter.plugins.pathprovider.PathProviderPlugin", e);
|
||||
}
|
||||
try {
|
||||
flutterEngine.getPlugins().add(new io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin());
|
||||
} catch(Exception e) {
|
||||
Log.e(TAG, "Error registering plugin shared_preferences, io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin", e);
|
||||
}
|
||||
try {
|
||||
flutterEngine.getPlugins().add(new com.tekartik.sqflite.SqflitePlugin());
|
||||
} catch(Exception e) {
|
||||
Log.e(TAG, "Error registering plugin sqflite, com.tekartik.sqflite.SqflitePlugin", e);
|
||||
}
|
||||
registry.registrarFor(key);
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -17,7 +17,6 @@ import com.rmawatson.flutterisolate.FlutterIsolatePlugin
|
|||
class MainActivity: FlutterActivity() {
|
||||
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
||||
GeneratedPluginRegistrant.registerWith(flutterEngine);
|
||||
FlutterIsolatePlugin.setCustomIsolateRegistrant(IsolatePluginRegistrant::class.java);
|
||||
MethodChannel(flutterEngine.dartExecutor, "android_app_retain").apply {
|
||||
setMethodCallHandler { method, result ->
|
||||
if (method.method == "sendToBackground") {
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package com.stonegate.tsacdop
|
||||
import com.rmawatson.flutterisolate.FlutterIsolatePlugin
|
||||
import io.flutter.app.FlutterApplication
|
||||
import io.flutter.plugins.IsolatePluginRegistrant
|
||||
|
||||
public class MainApplication: FlutterApplication() {
|
||||
public fun MainApplication() {
|
||||
FlutterIsolatePlugin.setCustomIsolateRegistrant(IsolatePluginRegistrant::class.java);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
buildscript {
|
||||
ext.kotlin_version = '1.3.70'
|
||||
ext.kotlin_version = '1.6.21'
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
|
|
|
@ -44,7 +44,7 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
|||
String? path;
|
||||
|
||||
Future<PlayHistory> _getPosition(EpisodeBrief episode) async {
|
||||
var dbHelper = DBHelper();
|
||||
final dbHelper = DBHelper();
|
||||
return await dbHelper.getPosition(episode);
|
||||
}
|
||||
|
||||
|
@ -86,204 +86,205 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SystemChrome.setSystemUIOverlayStyle(
|
||||
SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
systemNavigationBarColor: Colors.transparent,
|
||||
systemNavigationBarContrastEnforced: false,
|
||||
systemNavigationBarIconBrightness: context.iconBrightness,
|
||||
statusBarBrightness: context.brightness,
|
||||
statusBarIconBrightness: context.iconBrightness),
|
||||
);
|
||||
final s = context.s!;
|
||||
final audio = context.watch<AudioPlayerNotifier>();
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
|
||||
systemNavigationBarColor: Theme.of(context).primaryColor,
|
||||
systemNavigationBarIconBrightness:
|
||||
Theme.of(context).accentColorBrightness,
|
||||
),
|
||||
child: WillPopScope(
|
||||
onWillPop: () async {
|
||||
if (_playerKey.currentState != null &&
|
||||
_playerKey.currentState!.initSize! > 100) {
|
||||
_playerKey.currentState!.backToMini();
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
body: Stack(
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
if (_playerKey.currentState != null &&
|
||||
_playerKey.currentState!.initSize! > 100) {
|
||||
_playerKey.currentState!.backToMini();
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
body: SafeArea(
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
SafeArea(
|
||||
child: ScrollConfiguration(
|
||||
behavior: NoGrowBehavior(),
|
||||
child: NestedScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
controller: _controller,
|
||||
headerSliverBuilder: (context, innerBoxScrolled) {
|
||||
return <Widget>[
|
||||
SliverAppBar(
|
||||
floating: true,
|
||||
pinned: true,
|
||||
title: _showTitle
|
||||
? Text(
|
||||
widget.episodeItem!.title!,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)
|
||||
: Text(
|
||||
widget.episodeItem!.feedTitle!,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
color:
|
||||
context.textColor!.withOpacity(0.7)),
|
||||
),
|
||||
leading: CustomBackButton(),
|
||||
elevation: _showTitle ? 1 : 0,
|
||||
),
|
||||
];
|
||||
},
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20.0),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
ScrollConfiguration(
|
||||
behavior: NoGrowBehavior(),
|
||||
child: NestedScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
controller: _controller,
|
||||
headerSliverBuilder: (context, innerBoxScrolled) {
|
||||
return <Widget>[
|
||||
SliverAppBar(
|
||||
floating: true,
|
||||
pinned: true,
|
||||
title: _showTitle
|
||||
? Text(
|
||||
widget.episodeItem!.title!,
|
||||
textAlign: TextAlign.left,
|
||||
style: Theme.of(context).textTheme.headline5,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)
|
||||
: Text(
|
||||
widget.episodeItem!.feedTitle!,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
color:
|
||||
context.textColor!.withOpacity(0.7)),
|
||||
),
|
||||
leading: CustomBackButton(),
|
||||
elevation: _showTitle ? 1 : 0,
|
||||
),
|
||||
];
|
||||
},
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20.0),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
widget.episodeItem!.title!,
|
||||
textAlign: TextAlign.left,
|
||||
style: Theme.of(context).textTheme.headline5,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(20, 10, 20, 10),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
s.published(DateFormat.yMMMd().format(
|
||||
DateTime.fromMillisecondsSinceEpoch(
|
||||
widget.episodeItem!.pubDate!))),
|
||||
style:
|
||||
TextStyle(color: context.accentColor)),
|
||||
SizedBox(width: 10),
|
||||
if (widget.episodeItem!.explicit == 1)
|
||||
Text('E',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.red)),
|
||||
Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(20, 10, 20, 10),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
s.published(DateFormat.yMMMd().format(
|
||||
DateTime.fromMillisecondsSinceEpoch(
|
||||
widget.episodeItem!.pubDate!))),
|
||||
style:
|
||||
TextStyle(color: context.accentColor)),
|
||||
SizedBox(width: 10),
|
||||
if (widget.episodeItem!.explicit == 1)
|
||||
Text('E',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.red)),
|
||||
Spacer(),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 20, vertical: 5),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
if (widget.episodeItem!.duration != 0)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.cyan[300],
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(16.0))),
|
||||
height: 28.0,
|
||||
margin: EdgeInsets.only(right: 10.0),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 10.0),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
s.minsCount(
|
||||
widget.episodeItem!.duration! ~/ 60,
|
||||
),
|
||||
style: TextStyle(color: Colors.black),
|
||||
)),
|
||||
if (widget.episodeItem!.enclosureLength !=
|
||||
null &&
|
||||
widget.episodeItem!.enclosureLength != 0)
|
||||
Container(
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 20, vertical: 5),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
if (widget.episodeItem!.duration != 0)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.lightBlue[300],
|
||||
color: Colors.cyan[300],
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(16.0))),
|
||||
height: 28.0,
|
||||
margin: EdgeInsets.only(right: 10.0),
|
||||
padding:
|
||||
EdgeInsets.symmetric(horizontal: 10.0),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 10.0),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
'${widget.episodeItem!.enclosureLength! ~/ 1000000}MB',
|
||||
s.minsCount(
|
||||
widget.episodeItem!.duration! ~/ 60,
|
||||
),
|
||||
style: TextStyle(color: Colors.black),
|
||||
),
|
||||
)),
|
||||
if (widget.episodeItem!.enclosureLength !=
|
||||
null &&
|
||||
widget.episodeItem!.enclosureLength != 0)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.lightBlue[300],
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(16.0))),
|
||||
height: 28.0,
|
||||
margin: EdgeInsets.only(right: 10.0),
|
||||
padding:
|
||||
EdgeInsets.symmetric(horizontal: 10.0),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
'${widget.episodeItem!.enclosureLength! ~/ 1000000}MB',
|
||||
style: TextStyle(color: Colors.black),
|
||||
),
|
||||
FutureBuilder<PlayHistory>(
|
||||
future: _getPosition(widget.episodeItem!),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) {
|
||||
developer.log(snapshot.error as String);
|
||||
}
|
||||
if (snapshot.hasData &&
|
||||
snapshot.data!.seekValue! < 0.9 &&
|
||||
snapshot.data!.seconds! > 10) {
|
||||
return ButtonTheme(
|
||||
height: 28,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 0),
|
||||
child: OutlineButton(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(
|
||||
100.0),
|
||||
side: BorderSide(
|
||||
color:
|
||||
context.accentColor)),
|
||||
highlightedBorderColor:
|
||||
Colors.green[700],
|
||||
onPressed: () => audio.episodeLoad(
|
||||
widget.episodeItem,
|
||||
startPosition:
|
||||
(snapshot.data!.seconds! *
|
||||
1000)
|
||||
.toInt()),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CustomPaint(
|
||||
painter: ListenedPainter(
|
||||
context.textColor,
|
||||
stroke: 2.0),
|
||||
),
|
||||
),
|
||||
FutureBuilder<PlayHistory>(
|
||||
future: _getPosition(widget.episodeItem!),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) {
|
||||
developer.log(snapshot.error as String);
|
||||
}
|
||||
if (snapshot.hasData &&
|
||||
snapshot.data!.seekValue! < 0.9 &&
|
||||
snapshot.data!.seconds! > 10) {
|
||||
return ButtonTheme(
|
||||
height: 28,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 0),
|
||||
child: OutlineButton(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(
|
||||
100.0),
|
||||
side: BorderSide(
|
||||
color:
|
||||
context.accentColor)),
|
||||
highlightedBorderColor:
|
||||
Colors.green[700],
|
||||
onPressed: () => audio.episodeLoad(
|
||||
widget.episodeItem,
|
||||
startPosition:
|
||||
(snapshot.data!.seconds! *
|
||||
1000)
|
||||
.toInt()),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CustomPaint(
|
||||
painter: ListenedPainter(
|
||||
context.textColor,
|
||||
stroke: 2.0),
|
||||
),
|
||||
SizedBox(width: 5),
|
||||
Text(
|
||||
snapshot
|
||||
.data!.seconds!.toTime,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(width: 5),
|
||||
Text(
|
||||
snapshot
|
||||
.data!.seconds!.toTime,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Center();
|
||||
}
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Center();
|
||||
}
|
||||
}),
|
||||
],
|
||||
),
|
||||
ShowNote(episode: widget.episodeItem),
|
||||
Selector<AudioPlayerNotifier,
|
||||
Tuple2<bool, PlayerHeight?>>(
|
||||
selector: (_, audio) => Tuple2(
|
||||
audio.playerRunning, audio.playerHeight),
|
||||
builder: (_, data, __) {
|
||||
var height =
|
||||
kMinPlayerHeight[data.item2!.index];
|
||||
return SizedBox(
|
||||
height: data.item1 ? height : 0,
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
ShowNote(episode: widget.episodeItem),
|
||||
Selector<AudioPlayerNotifier,
|
||||
Tuple2<bool, PlayerHeight?>>(
|
||||
selector: (_, audio) => Tuple2(
|
||||
audio.playerRunning, audio.playerHeight),
|
||||
builder: (_, data, __) {
|
||||
var height =
|
||||
kMinPlayerHeight[data.item2!.index];
|
||||
return SizedBox(
|
||||
height: data.item1 ? height : 0,
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -451,7 +452,7 @@ class __MenuBarState extends State<_MenuBar> {
|
|||
_overlayEntry = _createOverlayEntry();
|
||||
Overlay.of(context)!.insert(_overlayEntry);
|
||||
await Future.delayed(Duration(seconds: 2));
|
||||
_overlayEntry?.remove();
|
||||
_overlayEntry.remove();
|
||||
})
|
||||
: _buttonOnMenu(
|
||||
child: Icon(
|
||||
|
|
|
@ -163,7 +163,6 @@ class _DownloadButtonState extends State<DownloadButton> {
|
|||
),
|
||||
),
|
||||
() => _requestDownload(task.episode));
|
||||
break;
|
||||
case 2:
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
|
@ -193,7 +192,6 @@ class _DownloadButtonState extends State<DownloadButton> {
|
|||
),
|
||||
),
|
||||
);
|
||||
break;
|
||||
case 6:
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
|
|
|
@ -299,7 +299,7 @@ class _AboutAppState extends State<AboutApp> {
|
|||
_overlayEntry = _createOverlayEntry(detail);
|
||||
Overlay.of(context)!.insert(_overlayEntry);
|
||||
await Future.delayed(Duration(seconds: 2));
|
||||
_overlayEntry?.remove();
|
||||
_overlayEntry.remove();
|
||||
},
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
|
|
|
@ -120,8 +120,7 @@ class PlayerWidget extends StatelessWidget {
|
|||
TextStyle(color: context.accentColor),
|
||||
)
|
||||
: Text(
|
||||
s!.timeLeft(
|
||||
(data.item2).toInt().toTime ?? ''),
|
||||
s!.timeLeft((data.item2).toInt().toTime),
|
||||
maxLines: 2,
|
||||
),
|
||||
);
|
||||
|
@ -227,7 +226,7 @@ class PlayerWidget extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<AudioPlayerNotifier, Tuple2<bool, PlayerHeight?>>(
|
||||
selector: (_, audio) => Tuple2(audio.playerRunning, audio?.playerHeight),
|
||||
selector: (_, audio) => Tuple2(audio.playerRunning, audio.playerHeight),
|
||||
builder: (_, data, __) {
|
||||
if (!data.item1) {
|
||||
return Center();
|
||||
|
@ -358,7 +357,8 @@ class LastPosition extends StatelessWidget {
|
|||
color: context.accentColor)),
|
||||
highlightedBorderColor: Colors.green[700],
|
||||
onPressed: () => audio.seekTo(
|
||||
(snapshot.data!.seconds! * 1000).toInt()),
|
||||
(snapshot.data!.seconds! * 1000)
|
||||
.toInt()),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
|
@ -1311,8 +1311,7 @@ class _ControlPanelState extends State<ControlPanel>
|
|||
child: Row(
|
||||
children: <Widget>[
|
||||
Text(
|
||||
(data.backgroundAudioPosition! ~/ 1000).toTime ??
|
||||
'',
|
||||
(data.backgroundAudioPosition! ~/ 1000).toTime,
|
||||
style: TextStyle(fontSize: 10),
|
||||
),
|
||||
Expanded(
|
||||
|
@ -1336,8 +1335,7 @@ class _ControlPanelState extends State<ControlPanel>
|
|||
),
|
||||
),
|
||||
Text(
|
||||
(data.backgroundAudioDuration ~/ 1000).toTime ??
|
||||
'',
|
||||
(data.backgroundAudioDuration ~/ 1000).toTime,
|
||||
style: TextStyle(fontSize: 10),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -241,7 +241,7 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
|
|||
),
|
||||
Selector<AudioPlayerNotifier, bool>(
|
||||
selector: (_, audio) =>
|
||||
audio?.playerRunning ?? false,
|
||||
audio.playerRunning,
|
||||
builder: (_, data, __) {
|
||||
return Padding(
|
||||
padding:
|
||||
|
@ -540,7 +540,7 @@ class _RecentUpdateState extends State<_RecentUpdate>
|
|||
var storage = KeyValueStorage(recentLayoutKey);
|
||||
var hideListenedStorage = KeyValueStorage(hideListenedKey);
|
||||
var index = await storage.getInt(defaultValue: 1);
|
||||
if (_layout == null) _layout = Layout.values[index!];
|
||||
if (_layout == null) _layout = Layout.values[index];
|
||||
if (_hideListened == null) {
|
||||
_hideListened = await hideListenedStorage.getBool(defaultValue: false);
|
||||
}
|
||||
|
@ -909,7 +909,7 @@ class _MyFavoriteState extends State<_MyFavorite>
|
|||
var storage = KeyValueStorage(favLayoutKey);
|
||||
var index = await storage.getInt(defaultValue: 1);
|
||||
var hideListenedStorage = KeyValueStorage(hideListenedKey);
|
||||
if (_layout == null) _layout = Layout.values[index!];
|
||||
if (_layout == null) _layout = Layout.values[index];
|
||||
if (_hideListened == null) {
|
||||
_hideListened = await hideListenedStorage.getBool(defaultValue: false);
|
||||
}
|
||||
|
@ -1189,7 +1189,7 @@ class _MyDownloadState extends State<_MyDownload>
|
|||
var storage = KeyValueStorage(downloadLayoutKey);
|
||||
var index = await storage.getInt(defaultValue: 1);
|
||||
var hideListenedStorage = KeyValueStorage(hideListenedKey);
|
||||
if (_layout == null) _layout = Layout.values[index!];
|
||||
if (_layout == null) _layout = Layout.values[index];
|
||||
if (_hideListened == null) {
|
||||
_hideListened = await hideListenedStorage.getBool(defaultValue: false);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ import 'dart:async';
|
|||
import 'dart:math' as math;
|
||||
|
||||
import 'package:connectivity/connectivity.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:focused_menu/focused_menu.dart';
|
||||
|
@ -712,7 +711,7 @@ class ShowEpisode extends StatelessWidget {
|
|||
return Selector<AudioPlayerNotifier,
|
||||
tuple.Tuple3<EpisodeBrief?, List<String>, bool>>(
|
||||
selector: (_, audio) => tuple.Tuple3(
|
||||
audio?.episode,
|
||||
audio.episode,
|
||||
audio.queue.episodes
|
||||
.map((e) => e!.enclosureUrl)
|
||||
.toList(),
|
||||
|
|
|
@ -34,7 +34,7 @@ class _PopupMenuState extends State<PopupMenu> {
|
|||
} else {
|
||||
refreshDate = i;
|
||||
}
|
||||
return refreshDate!.toDate(context);
|
||||
return refreshDate.toDate(context);
|
||||
}
|
||||
|
||||
void _saveOmpl(String path) async {
|
||||
|
|
|
@ -206,10 +206,10 @@ class _RssResultState extends State<RssResult> {
|
|||
_loadItems = 10;
|
||||
_onlinePodcast = OnlinePodcast(
|
||||
rss: widget.url,
|
||||
title: p?.title ?? widget.url,
|
||||
publisher: p?.author ?? "",
|
||||
description: p?.description ?? "No description for this podcast",
|
||||
image: p?.itunes?.image?.href ?? p?.image?.url ?? "",
|
||||
title: p.title ?? widget.url,
|
||||
publisher: p.author ?? "",
|
||||
description: p.description ?? "No description for this podcast",
|
||||
image: p.itunes?.image?.href ?? p.image?.url ?? "",
|
||||
count: p.items!.length);
|
||||
super.initState();
|
||||
}
|
||||
|
@ -325,7 +325,7 @@ class _RssResultState extends State<RssResult> {
|
|||
],
|
||||
),
|
||||
ListView.builder(
|
||||
itemCount: math.min(_loadItems! + 1, items.length),
|
||||
itemCount: math.min(_loadItems + 1, items.length),
|
||||
itemBuilder: (context, index) {
|
||||
if (index == _loadItems) {
|
||||
return Container(
|
||||
|
@ -379,7 +379,7 @@ class __SearchPopupMenuState extends State<_SearchPopupMenu> {
|
|||
Future<void> _getSearchEngine() async {
|
||||
final storage = KeyValueStorage(searchEngineKey);
|
||||
final index = await storage.getInt();
|
||||
setState(() => _searchEngine = SearchEngine.values[index!]);
|
||||
setState(() => _searchEngine = SearchEngine.values[index]);
|
||||
widget.onSelected!(_searchEngine);
|
||||
}
|
||||
|
||||
|
|
|
@ -66,28 +66,26 @@ class KeyValueStorage {
|
|||
final String key;
|
||||
KeyValueStorage(this.key);
|
||||
Future<List<GroupEntity>?> getGroups() async {
|
||||
var prefs = await SharedPreferences.getInstance();
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
if (prefs.getString(key) == null) {
|
||||
var home = PodcastGroup('Home');
|
||||
final home = PodcastGroup('Home');
|
||||
await prefs.setString(
|
||||
key,
|
||||
json.encode({
|
||||
'groups': [home.toEntity().toJson()]
|
||||
}));
|
||||
}
|
||||
return json
|
||||
.decode(prefs.getString(key)!)['groups']
|
||||
.cast<Map<String, Object>>()
|
||||
.map<GroupEntity>(GroupEntity.fromJson)
|
||||
.toList(growable: false);
|
||||
final groups = json.decode(prefs.getString(key)!)['groups'];
|
||||
return [for (final g in groups) GroupEntity.fromJson(g)];
|
||||
}
|
||||
|
||||
Future<bool> saveGroup(List<GroupEntity> groupList) async {
|
||||
var prefs = await SharedPreferences.getInstance();
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
return prefs.setString(
|
||||
key,
|
||||
json.encode(
|
||||
{'groups': groupList.map((group) => group.toJson()).toList()}));
|
||||
json.encode({
|
||||
'groups': [for (var g in groupList) g.toJson()]
|
||||
}));
|
||||
}
|
||||
|
||||
Future<List<PlaylistEntity>?> getPlaylists() async {
|
||||
|
@ -102,11 +100,8 @@ class KeyValueStorage {
|
|||
}));
|
||||
}
|
||||
print(prefs.getString(key));
|
||||
return json
|
||||
.decode(prefs.getString(key)!)['playlists']
|
||||
.cast<Map<String, Object>>()
|
||||
.map<PlaylistEntity>(PlaylistEntity.fromJson)
|
||||
.toList(growable: false);
|
||||
final playlist = json.decode(prefs.getString(key)!)['playlists'];
|
||||
return [for (final p in playlist) PlaylistEntity.fromJson(p)];
|
||||
}
|
||||
|
||||
Future<bool> savePlaylists(List<PlaylistEntity> playlists) async {
|
||||
|
@ -114,7 +109,7 @@ class KeyValueStorage {
|
|||
return prefs.setString(
|
||||
key,
|
||||
json.encode({
|
||||
'playlists': playlists.map((playlist) => playlist.toJson()).toList()
|
||||
'playlists': [for (var p in playlists) p.toJson()]
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -124,13 +119,13 @@ class KeyValueStorage {
|
|||
return prefs.setStringList(key, [playlist, episode, position.toString()]);
|
||||
}
|
||||
|
||||
Future<List<String>?> getPlayerState() async {
|
||||
Future<List<String>> getPlayerState() async {
|
||||
var prefs = await SharedPreferences.getInstance();
|
||||
if (prefs.getStringList(key) == null) {
|
||||
final position = prefs.getInt(audioPositionKey) ?? 0;
|
||||
await savePlayerState('', '', position);
|
||||
}
|
||||
return prefs.getStringList(key);
|
||||
return prefs.getStringList(key)!;
|
||||
}
|
||||
|
||||
Future<bool> saveInt(int setting) async {
|
||||
|
@ -240,7 +235,7 @@ class KeyValueStorage {
|
|||
|
||||
Future<void> addList(List<String?> addList) async {
|
||||
final list = await getStringList();
|
||||
await saveStringList([...list ?? [], ...addList]);
|
||||
await saveStringList([...list, ...addList]);
|
||||
}
|
||||
|
||||
Future<void> clearList() async {
|
||||
|
|
|
@ -18,11 +18,11 @@ enum Filter { downloaded, liked, search, all }
|
|||
const localFolderId = "46e48103-06c7-4fe1-a0b1-68aa7205b7f0";
|
||||
|
||||
class DBHelper {
|
||||
static Database? _db;
|
||||
Future<Database?> get database async {
|
||||
if (_db != null) return _db;
|
||||
Database? _db;
|
||||
Future<Database> get database async {
|
||||
if (_db != null) return _db!;
|
||||
_db = await initDb();
|
||||
return _db;
|
||||
return _db!;
|
||||
}
|
||||
|
||||
initDb() async {
|
||||
|
@ -143,12 +143,12 @@ class DBHelper {
|
|||
for (var s in podcasts) {
|
||||
List<Map> list;
|
||||
if (updateOnly) {
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT id, title, imageUrl, rssUrl, primaryColor, author, imagePath , provider,
|
||||
link ,update_count, episode_count, funding FROM PodcastLocal WHERE id = ? AND
|
||||
never_update = 0""", [s]);
|
||||
} else {
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT id, title, imageUrl, rssUrl, primaryColor, author, imagePath , provider,
|
||||
link ,update_count, episode_count, funding FROM PodcastLocal WHERE id = ?""",
|
||||
[s]);
|
||||
|
@ -178,12 +178,12 @@ class DBHelper {
|
|||
|
||||
List<Map> list;
|
||||
if (updateOnly) {
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT id, title, imageUrl, rssUrl, primaryColor, author, imagePath,
|
||||
provider, link, funding FROM PodcastLocal WHERE never_update = 0 ORDER BY
|
||||
add_date DESC""");
|
||||
} else {
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT id, title, imageUrl, rssUrl, primaryColor, author, imagePath,
|
||||
provider, link, funding FROM PodcastLocal ORDER BY add_date DESC""");
|
||||
}
|
||||
|
@ -210,7 +210,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<PodcastLocal?> getPodcastWithUrl(String? url) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"""SELECT P.id, P.title, P.imageUrl, P.rssUrl, P.primaryColor, P.author, P.imagePath,
|
||||
P.provider, P.link ,P.update_count, P.episode_count, P.funding FROM PodcastLocal P INNER JOIN
|
||||
|
@ -234,7 +234,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<int?> getPodcastCounts(String? id) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient
|
||||
.rawQuery('SELECT episode_count FROM PodcastLocal WHERE id = ?', [id]);
|
||||
if (list.isNotEmpty) return list.first['episode_count'];
|
||||
|
@ -242,7 +242,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<void> removePodcastNewMark(String? id) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
await dbClient.transaction((txn) async {
|
||||
await txn.rawUpdate(
|
||||
"UPDATE Episodes SET is_new = 0 WHERE feed_id = ? AND is_new = 1",
|
||||
|
@ -251,7 +251,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<bool> getNeverUpdate(String? id) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient
|
||||
.rawQuery('SELECT never_update FROM PodcastLocal WHERE id = ?', [id]);
|
||||
if (list.isNotEmpty) return list.first['never_update'] == 1;
|
||||
|
@ -259,14 +259,14 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<int> saveNeverUpdate(String? id, {required bool boo}) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
return await dbClient.rawUpdate(
|
||||
"UPDATE PodcastLocal SET never_update = ? WHERE id = ?",
|
||||
[boo ? 1 : 0, id]);
|
||||
}
|
||||
|
||||
Future<bool> getHideNewMark(String? id) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient
|
||||
.rawQuery('SELECT hide_new_mark FROM PodcastLocal WHERE id = ?', [id]);
|
||||
if (list.isNotEmpty) return list.first['hide_new_mark'] == 1;
|
||||
|
@ -274,14 +274,14 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<int> saveHideNewMark(String? id, {required bool boo}) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
return await dbClient.rawUpdate(
|
||||
"UPDATE PodcastLocal SET hide_new_mark = ? WHERE id = ?",
|
||||
[boo ? 1 : 0, id]);
|
||||
}
|
||||
|
||||
Future<int?> getPodcastUpdateCounts(String? id) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
'SELECt count(*) as count FROM Episodes WHERE feed_id = ? AND is_new = 1',
|
||||
[id]);
|
||||
|
@ -290,7 +290,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<int?> getSkipSecondsStart(String? id) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient
|
||||
.rawQuery('SELECT skip_seconds FROM PodcastLocal WHERE id = ?', [id]);
|
||||
if (list.isNotEmpty) return list.first['skip_seconds'];
|
||||
|
@ -298,13 +298,13 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<int> saveSkipSecondsStart(String? id, int? seconds) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
return await dbClient.rawUpdate(
|
||||
"UPDATE PodcastLocal SET skip_seconds = ? WHERE id = ?", [seconds, id]);
|
||||
}
|
||||
|
||||
Future<int?> getSkipSecondsEnd(String id) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
'SELECT skip_seconds_end FROM PodcastLocal WHERE id = ?', [id]);
|
||||
if (list.isNotEmpty) return list.first['skip_seconds_end'];
|
||||
|
@ -312,14 +312,14 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<int> saveSkipSecondsEnd(String? id, int seconds) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
return await dbClient.rawUpdate(
|
||||
"UPDATE PodcastLocal SET skip_seconds_end = ? WHERE id = ?",
|
||||
[seconds, id]);
|
||||
}
|
||||
|
||||
Future<bool> getAutoDownload(String? id) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient
|
||||
.rawQuery('SELECT auto_download FROM PodcastLocal WHERE id = ?', [id]);
|
||||
if (list.isNotEmpty) return list.first['auto_download'] == 1;
|
||||
|
@ -327,14 +327,14 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<int> saveAutoDownload(String? id, {required bool boo}) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
return await dbClient.rawUpdate(
|
||||
"UPDATE PodcastLocal SET auto_download = ? WHERE id = ?",
|
||||
[boo ? 1 : 0, id]);
|
||||
}
|
||||
|
||||
Future<String?> checkPodcast(String? url) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient
|
||||
.rawQuery('SELECT id FROM PodcastLocal WHERE rssUrl = ?', [url]);
|
||||
if (list.isEmpty) return '';
|
||||
|
@ -343,7 +343,7 @@ class DBHelper {
|
|||
|
||||
Future savePodcastLocal(PodcastLocal podcastLocal) async {
|
||||
var milliseconds = DateTime.now().millisecondsSinceEpoch;
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
await dbClient.transaction((txn) async {
|
||||
await txn.rawInsert(
|
||||
"""INSERT OR IGNORE INTO PodcastLocal (id, title, imageUrl, rssUrl,
|
||||
|
@ -377,13 +377,13 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<int> updatePodcastImage({String? id, String? filePath}) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
return await dbClient.rawUpdate(
|
||||
"UPDATE PodcastLocal SET imagePath= ? WHERE id = ?", [filePath, id]);
|
||||
}
|
||||
|
||||
Future<int> saveFiresideData(List<String?> list) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
var result = await dbClient.rawUpdate(
|
||||
'UPDATE PodcastLocal SET background_image = ? , hosts = ? WHERE id = ?',
|
||||
[list[1], list[2], list[0]]);
|
||||
|
@ -391,7 +391,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<List<String?>> getFiresideData(String? id) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
'SELECT background_image, hosts FROM PodcastLocal WHERE id = ?', [id]);
|
||||
if (list.isNotEmpty) {
|
||||
|
@ -402,7 +402,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<void> delPodcastLocal(String? id) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
await dbClient.rawDelete('DELETE FROM PodcastLocal WHERE id =?', [id]);
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"""SELECT downloaded FROM Episodes WHERE downloaded != 'ND' AND feed_id = ?""",
|
||||
|
@ -422,7 +422,7 @@ class DBHelper {
|
|||
|
||||
Future<void> saveHistory(PlayHistory history) async {
|
||||
if (history.url!.substring(0, 7) != 'file://') {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
final milliseconds = DateTime.now().millisecondsSinceEpoch;
|
||||
var recent = await getPlayHistory(1);
|
||||
if (recent.isNotEmpty && recent.first.title == history.title) {
|
||||
|
@ -446,7 +446,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<List<PlayHistory>> getPlayHistory(int top) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"""SELECT title, enclosure_url, seconds, seek_value, add_date FROM PlayHistory
|
||||
ORDER BY add_date DESC LIMIT ?
|
||||
|
@ -462,7 +462,7 @@ class DBHelper {
|
|||
|
||||
/// History list in playlist page, not include marked episdoes.
|
||||
Future<List<PlayHistory>> getPlayRecords(int? top) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"""SELECT title, enclosure_url, seconds, seek_value, add_date FROM PlayHistory
|
||||
WHERE seconds != 0 ORDER BY add_date DESC LIMIT ?
|
||||
|
@ -477,7 +477,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<int> isListened(String url) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
int? i = 0;
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"SELECT SUM(listen_time) FROM PlayHistory WHERE enclosure_url = ?",
|
||||
|
@ -490,7 +490,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<int?> markNotListened(String url) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
int? count;
|
||||
await dbClient.transaction((txn) async {
|
||||
count = await txn.rawUpdate(
|
||||
|
@ -505,7 +505,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<List<SubHistory>> getSubHistory() async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"""SELECT title, rss_url, add_date, remove_date, status FROM SubscribeHistory
|
||||
ORDER BY add_date DESC""");
|
||||
|
@ -521,7 +521,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<double> listenMins(int day) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
var now = DateTime.now();
|
||||
var start = DateTime(now.year, now.month, now.day)
|
||||
.subtract(Duration(days: day))
|
||||
|
@ -544,7 +544,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<PlayHistory> getPosition(EpisodeBrief episodeBrief) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"""SELECT title, enclosure_url, seconds, seek_value, add_date FROM PlayHistory
|
||||
WHERE enclosure_url = ? ORDER BY add_date DESC LIMIT 1""",
|
||||
|
@ -559,7 +559,7 @@ class DBHelper {
|
|||
|
||||
/// Check if episode was marked listend.
|
||||
Future<bool> checkMarked(EpisodeBrief episodeBrief) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"""SELECT title, enclosure_url, seconds, seek_value, add_date FROM PlayHistory
|
||||
WHERE enclosure_url = ? AND seek_value = 1 ORDER BY add_date DESC LIMIT 1""",
|
||||
|
@ -656,12 +656,14 @@ class DBHelper {
|
|||
Future<int> savePodcastRss(RssFeed feed, String id) async {
|
||||
feed.items!.removeWhere((item) => item == null);
|
||||
var result = feed.items!.length;
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
String? description, url;
|
||||
for (var i = 0; i < result; i++) {
|
||||
developer.log(feed.items![i].title!);
|
||||
description = _getDescription(feed.items![i]?.content?.value ?? '',
|
||||
feed.items![i].description ?? '', feed.items![i].itunes!.summary ?? '');
|
||||
description = _getDescription(
|
||||
feed.items![i].content?.value ?? '',
|
||||
feed.items![i].description ?? '',
|
||||
feed.items![i].itunes!.summary ?? '');
|
||||
if (feed.items![i].enclosure != null) {
|
||||
_isXimalaya(feed.items![i].enclosure!.url!)
|
||||
? url = feed.items![i].enclosure!.url!.split('=').last
|
||||
|
@ -669,7 +671,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
final title = feed.items![i].itunes!.title ?? feed.items![i].title;
|
||||
final length = feed.items![i]?.enclosure?.length;
|
||||
final length = feed.items![i].enclosure?.length;
|
||||
final pubDate = feed.items![i].pubDate;
|
||||
final date = _parsePubDate(pubDate);
|
||||
final milliseconds = date.millisecondsSinceEpoch;
|
||||
|
@ -723,7 +725,7 @@ class DBHelper {
|
|||
String? url, description;
|
||||
feed.items!.removeWhere((item) => item == null);
|
||||
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
var count = Sqflite.firstIntValue(await dbClient.rawQuery(
|
||||
'SELECT COUNT(*) FROM Episodes WHERE feed_id = ?',
|
||||
[podcastLocal.id]))!;
|
||||
|
@ -734,7 +736,7 @@ class DBHelper {
|
|||
}
|
||||
for (var item in feed.items!) {
|
||||
developer.log(item.title!);
|
||||
description = _getDescription(item.content!.value ?? '',
|
||||
description = _getDescription(item.content!.value,
|
||||
item.description ?? '', item.itunes!.summary ?? '');
|
||||
|
||||
if (item.enclosure?.url != null) {
|
||||
|
@ -744,7 +746,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
final title = item.itunes!.title ?? item.title;
|
||||
final length = item?.enclosure?.length ?? 0;
|
||||
final length = item.enclosure?.length ?? 0;
|
||||
final pubDate = item.pubDate;
|
||||
final date = _parsePubDate(pubDate);
|
||||
final milliseconds = date.millisecondsSinceEpoch;
|
||||
|
@ -794,7 +796,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<void> saveLocalEpisode(EpisodeBrief episode) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
await dbClient.transaction((txn) async {
|
||||
await txn.rawInsert(
|
||||
"""INSERT OR REPLACE INTO Episodes(title, enclosure_url, enclosure_length, pubDate,
|
||||
|
@ -817,7 +819,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<void> deleteLocalEpisodes(List<String> files) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
var s = files.map<String>((e) => "'$e'").toList();
|
||||
await dbClient.rawDelete(
|
||||
'DELETE FROM Episodes WHERE enclosure_url in (${s.join(',')})');
|
||||
|
@ -836,7 +838,7 @@ class DBHelper {
|
|||
if (reverse!) {
|
||||
switch (filter) {
|
||||
case Filter.all:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -845,14 +847,14 @@ class DBHelper {
|
|||
OR SUM(H.listen_time) = 0 ORDER BY E.milliseconds ASC""", [id]);
|
||||
break;
|
||||
case Filter.liked:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
WHERE P.id = ? AND E.liked = 1 ORDER BY E.milliseconds ASC""", [id]);
|
||||
break;
|
||||
case Filter.downloaded:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -860,7 +862,7 @@ class DBHelper {
|
|||
[id]);
|
||||
break;
|
||||
case Filter.search:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -872,7 +874,7 @@ class DBHelper {
|
|||
} else {
|
||||
switch (filter) {
|
||||
case Filter.all:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -881,14 +883,14 @@ class DBHelper {
|
|||
OR SUM(H.listen_time) = 0 ORDER BY E.milliseconds DESC""", [id]);
|
||||
break;
|
||||
case Filter.liked:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
WHERE P.id = ? AND E.liked = 1 ORDER BY E.milliseconds DESC""", [id]);
|
||||
break;
|
||||
case Filter.downloaded:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -896,7 +898,7 @@ class DBHelper {
|
|||
[id]);
|
||||
break;
|
||||
case Filter.search:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -909,7 +911,7 @@ class DBHelper {
|
|||
} else if (reverse!) {
|
||||
switch (filter) {
|
||||
case Filter.all:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -919,7 +921,7 @@ class DBHelper {
|
|||
[id, count]);
|
||||
break;
|
||||
case Filter.liked:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -929,7 +931,7 @@ class DBHelper {
|
|||
[id, count]);
|
||||
break;
|
||||
case Filter.downloaded:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -940,7 +942,7 @@ class DBHelper {
|
|||
[id, count]);
|
||||
break;
|
||||
case Filter.search:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -954,7 +956,7 @@ class DBHelper {
|
|||
} else {
|
||||
switch (filter) {
|
||||
case Filter.all:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -964,7 +966,7 @@ class DBHelper {
|
|||
[id, count]);
|
||||
break;
|
||||
case Filter.liked:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -974,7 +976,7 @@ class DBHelper {
|
|||
[id, count]);
|
||||
break;
|
||||
case Filter.downloaded:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -984,7 +986,7 @@ class DBHelper {
|
|||
[id, count]);
|
||||
break;
|
||||
case Filter.search:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -1001,21 +1003,21 @@ class DBHelper {
|
|||
if (reverse!) {
|
||||
switch (filter) {
|
||||
case Filter.all:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
WHERE P.id = ? ORDER BY E.milliseconds ASC""", [id]);
|
||||
break;
|
||||
case Filter.liked:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
WHERE P.id = ? AND E.liked = 1 ORDER BY E.milliseconds ASC""", [id]);
|
||||
break;
|
||||
case Filter.downloaded:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -1023,7 +1025,7 @@ class DBHelper {
|
|||
[id]);
|
||||
break;
|
||||
case Filter.search:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -1035,21 +1037,21 @@ class DBHelper {
|
|||
} else {
|
||||
switch (filter) {
|
||||
case Filter.all:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
WHERE P.id = ? ORDER BY E.milliseconds DESC""", [id]);
|
||||
break;
|
||||
case Filter.liked:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
WHERE P.id = ? AND E.liked = 1 ORDER BY E.milliseconds DESC""", [id]);
|
||||
break;
|
||||
case Filter.downloaded:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -1057,7 +1059,7 @@ class DBHelper {
|
|||
[id]);
|
||||
break;
|
||||
case Filter.search:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -1070,14 +1072,14 @@ class DBHelper {
|
|||
} else if (reverse!) {
|
||||
switch (filter) {
|
||||
case Filter.all:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
WHERE P.id = ? ORDER BY E.milliseconds ASC LIMIT ?""", [id, count]);
|
||||
break;
|
||||
case Filter.liked:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -1085,7 +1087,7 @@ class DBHelper {
|
|||
[id, count]);
|
||||
break;
|
||||
case Filter.downloaded:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -1093,7 +1095,7 @@ class DBHelper {
|
|||
[id, count]);
|
||||
break;
|
||||
case Filter.search:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -1105,14 +1107,14 @@ class DBHelper {
|
|||
} else {
|
||||
switch (filter) {
|
||||
case Filter.all:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
WHERE P.id = ? ORDER BY E.milliseconds DESC LIMIT ?""", [id, count]);
|
||||
break;
|
||||
case Filter.liked:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -1120,7 +1122,7 @@ class DBHelper {
|
|||
[id, count]);
|
||||
break;
|
||||
case Filter.downloaded:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -1128,7 +1130,7 @@ class DBHelper {
|
|||
[id, count]);
|
||||
break;
|
||||
case Filter.search:
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -1163,14 +1165,14 @@ class DBHelper {
|
|||
var episodes = <EpisodeBrief>[];
|
||||
List<Map> list;
|
||||
if (id == 'all') {
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
WHERE E.is_new = 1 AND E.downloaded = 'ND' AND P.auto_download = 1 ORDER BY E.milliseconds ASC""",
|
||||
);
|
||||
} else {
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new,
|
||||
E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit,
|
||||
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -1196,7 +1198,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<List<EpisodeBrief>> getRssItemTop(String? id) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
var episodes = <EpisodeBrief>[];
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new,
|
||||
|
@ -1225,7 +1227,7 @@ class DBHelper {
|
|||
var episodes = <EpisodeBrief>[];
|
||||
var list = <Map>[];
|
||||
if (hideListened) {
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new,
|
||||
E.milliseconds, P.title as feed_title, E.duration, E.explicit,
|
||||
P.imagePath, P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -1234,7 +1236,7 @@ class DBHelper {
|
|||
OR SUM(H.listen_time) = 0 ORDER BY E.milliseconds DESC LIMIT ? """,
|
||||
[localFolderId, top]);
|
||||
} else {
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new,
|
||||
E.milliseconds, P.title as feed_title, E.duration, E.explicit,
|
||||
P.imagePath, P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -1265,7 +1267,7 @@ class DBHelper {
|
|||
var episodes = <EpisodeBrief>[];
|
||||
var list = <Map>[];
|
||||
if (hideListened) {
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new,
|
||||
E.milliseconds, P.title as feed_title, E.duration, E.explicit,
|
||||
P.imagePath, P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -1274,7 +1276,7 @@ class DBHelper {
|
|||
OR SUM(H.listen_time) = 0 ORDER BY RANDOM() LIMIT ? """,
|
||||
[localFolderId, random]);
|
||||
} else {
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new,
|
||||
E.milliseconds, P.title as feed_title, E.duration, E.explicit,
|
||||
P.imagePath, P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -1307,7 +1309,7 @@ class DBHelper {
|
|||
var s = group.map<String>((e) => "'$e'").toList();
|
||||
var list = <Map>[];
|
||||
if (hideListened!) {
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new,
|
||||
E.milliseconds, P.title as feed_title, E.duration, E.explicit,
|
||||
P.imagePath, P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -1316,7 +1318,7 @@ class DBHelper {
|
|||
OR SUM(H.listen_time) = 0 ORDER BY E.milliseconds DESC LIMIT ? """,
|
||||
[top]);
|
||||
} else {
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new,
|
||||
E.milliseconds, P.title as feed_title, E.duration, E.explicit,
|
||||
P.imagePath, P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -1343,7 +1345,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<List<EpisodeBrief>> getRecentNewRssItem() async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
var episodes = <EpisodeBrief>[];
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new, E.media_id,
|
||||
|
@ -1370,7 +1372,7 @@ class DBHelper {
|
|||
|
||||
Future<List<EpisodeBrief>> getOutdatedEpisode(int deadline,
|
||||
{required bool deletePlayed}) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
var episodes = <EpisodeBrief>[];
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new,
|
||||
|
@ -1428,7 +1430,7 @@ class DBHelper {
|
|||
late List<Map> list;
|
||||
if (hideListened) {
|
||||
if (mode == 0) {
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.download_date, E.is_new,
|
||||
E.milliseconds, P.title as feed_title, E.duration, E.explicit,
|
||||
P.imagePath, P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -1437,7 +1439,7 @@ class DBHelper {
|
|||
OR SUM(H.listen_time) = 0 ORDER BY E.download_date DESC""",
|
||||
);
|
||||
} else if (mode == 1) {
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.download_date,E.is_new,
|
||||
E.milliseconds, P.title as feed_title, E.duration, E.explicit,
|
||||
P.imagePath, P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -1446,7 +1448,7 @@ class DBHelper {
|
|||
OR SUM(H.listen_time) = 0 ORDER BY E.download_date ASC""",
|
||||
);
|
||||
} else if (mode == 2) {
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.download_date,E.is_new,
|
||||
E.milliseconds, P.title as feed_title, E.duration, E.explicit,
|
||||
P.imagePath, P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -1458,7 +1460,7 @@ class DBHelper {
|
|||
} else //Ordered by date
|
||||
{
|
||||
if (mode == 0) {
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.download_date, E.is_new,
|
||||
E.milliseconds, P.title as feed_title, E.duration, E.explicit,
|
||||
P.imagePath, P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -1466,7 +1468,7 @@ class DBHelper {
|
|||
ORDER BY E.download_date DESC""",
|
||||
);
|
||||
} else if (mode == 1) {
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.download_date,E.is_new,
|
||||
E.milliseconds, P.title as feed_title, E.duration, E.explicit,
|
||||
P.imagePath, P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -1474,7 +1476,7 @@ class DBHelper {
|
|||
ORDER BY E.download_date ASC""",
|
||||
);
|
||||
} else if (mode == 2) {
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.download_date,E.is_new,
|
||||
E.milliseconds, P.title as feed_title, E.duration, E.explicit,
|
||||
P.imagePath, P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -1503,7 +1505,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<void> removeAllNewMark() async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
await dbClient.transaction((txn) async {
|
||||
await txn.rawUpdate("UPDATE Episodes SET is_new = 0 ");
|
||||
});
|
||||
|
@ -1514,7 +1516,7 @@ class DBHelper {
|
|||
var episodes = <EpisodeBrief>[];
|
||||
if (group.length > 0) {
|
||||
var s = group.map<String>((e) => "'$e'").toList();
|
||||
List<Map> list = await dbClient!.rawQuery(
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new, E.media_id,
|
||||
E.milliseconds, P.title as feed_title, E.duration, E.explicit,
|
||||
P.imagePath, P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -1543,7 +1545,7 @@ class DBHelper {
|
|||
var dbClient = await database;
|
||||
if (group.isNotEmpty) {
|
||||
var s = group.map<String>((e) => "'$e'").toList();
|
||||
await dbClient!.transaction((txn) async {
|
||||
await dbClient.transaction((txn) async {
|
||||
await txn.rawUpdate(
|
||||
"UPDATE Episodes SET is_new = 0 WHERE feed_id in (${s.join(',')})");
|
||||
});
|
||||
|
@ -1551,7 +1553,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<void> removeEpisodeNewMark(String? url) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
await dbClient.transaction((txn) async {
|
||||
await txn.rawUpdate(
|
||||
"UPDATE Episodes SET is_new = 0 WHERE enclosure_url = ?", [url]);
|
||||
|
@ -1566,7 +1568,7 @@ class DBHelper {
|
|||
var list = <Map>[];
|
||||
if (hideListened) {
|
||||
if (sortBy == 0) {
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath,
|
||||
P.title as feed_title, E.duration, E.explicit, P.primaryColor, E.is_new
|
||||
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -1574,7 +1576,7 @@ class DBHelper {
|
|||
WHERE E.liked = 1 GROUP BY E.enclosure_url HAVING SUM(H.listen_time) is null
|
||||
OR SUM(H.listen_time) = 0 ORDER BY E.milliseconds DESC LIMIT ?""", [i]);
|
||||
} else {
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath,
|
||||
P.title as feed_title, E.duration, E.explicit, P.primaryColor, E.is_new
|
||||
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -1584,13 +1586,13 @@ class DBHelper {
|
|||
}
|
||||
} else {
|
||||
if (sortBy == 0) {
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath,
|
||||
P.title as feed_title, E.duration, E.explicit, P.primaryColor, E.is_new
|
||||
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
WHERE E.liked = 1 ORDER BY E.milliseconds DESC LIMIT ?""", [i]);
|
||||
} else {
|
||||
list = await dbClient!.rawQuery(
|
||||
list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath,
|
||||
P.title as feed_title, E.duration, E.explicit, P.primaryColor, E.is_new
|
||||
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
|
@ -1617,7 +1619,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future setLiked(String url) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
var milliseconds = DateTime.now().millisecondsSinceEpoch;
|
||||
await dbClient.transaction((txn) async {
|
||||
await txn.rawUpdate(
|
||||
|
@ -1627,7 +1629,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future setUniked(String url) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
await dbClient.transaction((txn) async {
|
||||
await txn.rawUpdate(
|
||||
"UPDATE Episodes SET liked = 0 WHERE enclosure_url = ?", [url]);
|
||||
|
@ -1635,7 +1637,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<bool> isLiked(String url) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
var list = <Map>[];
|
||||
list = await dbClient
|
||||
.rawQuery("SELECT liked FROM Episodes WHERE enclosure_url = ?", [url]);
|
||||
|
@ -1646,7 +1648,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<bool> isDownloaded(String url) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"SELECT id FROM Episodes WHERE enclosure_url = ? AND enclosure_url != media_id",
|
||||
[url]);
|
||||
|
@ -1654,7 +1656,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<int?> saveDownloaded(String url, String? id) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
var milliseconds = DateTime.now().millisecondsSinceEpoch;
|
||||
int? count;
|
||||
await dbClient.transaction((txn) async {
|
||||
|
@ -1665,8 +1667,9 @@ class DBHelper {
|
|||
return count;
|
||||
}
|
||||
|
||||
Future<int?> saveMediaId(String url, String path, String? id, int size) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
Future<int?> saveMediaId(
|
||||
String url, String path, String? id, int size) async {
|
||||
var dbClient = await database;
|
||||
var milliseconds = DateTime.now().millisecondsSinceEpoch;
|
||||
int? count;
|
||||
await dbClient.transaction((txn) async {
|
||||
|
@ -1678,7 +1681,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<int?> delDownloaded(String url) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
int? count;
|
||||
await dbClient.transaction((txn) async {
|
||||
count = await txn.rawUpdate(
|
||||
|
@ -1690,7 +1693,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<String?> getDescription(String url) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
'SELECT description FROM Episodes WHERE enclosure_url = ?', [url]);
|
||||
String? description = list[0]['description'];
|
||||
|
@ -1698,7 +1701,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future saveEpisodeDes(String url, {String? description}) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
await dbClient.transaction((txn) async {
|
||||
await txn.rawUpdate(
|
||||
"UPDATE Episodes SET description = ? WHERE enclosure_url = ?",
|
||||
|
@ -1707,7 +1710,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<String?> getFeedDescription(String? id) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient
|
||||
.rawQuery('SELECT description FROM PodcastLocal WHERE id = ?', [id]);
|
||||
String? description = list[0]['description'];
|
||||
|
@ -1715,7 +1718,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<String?> getChapter(String url) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
'SELECT chapter_link FROM Episodes WHERE enclosure_url = ?', [url]);
|
||||
String? chapter = list[0]['chapter_link'];
|
||||
|
@ -1723,7 +1726,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<String?> getEpisodeImage(String url) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
'SELECT episode_image FROM Episodes WHERE enclosure_url = ?', [url]);
|
||||
String? image = list[0]['episode_image'];
|
||||
|
@ -1731,7 +1734,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<EpisodeBrief?> getRssItemWithUrl(String? url) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
EpisodeBrief episode;
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath,
|
||||
|
@ -1763,7 +1766,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<EpisodeBrief?> getRssItemWithMediaId(String id) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
EpisodeBrief episode;
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath,
|
||||
|
@ -1795,7 +1798,7 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<String?> getImageUrl(String url) async {
|
||||
var dbClient = await (database as FutureOr<Database>);
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"""SELECT P.imageUrl FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
WHERE E.enclosure_url = ?""", [url]);
|
||||
|
|
|
@ -225,7 +225,8 @@ class _PlaylistHomeState extends State<PlaylistHome> {
|
|||
if (!audio.playerRunning &&
|
||||
audio.episode!.duration != 0) {
|
||||
return (audio.lastPosition ~/
|
||||
(audio.episode!.duration! * 10));
|
||||
(audio.episode!.duration! *
|
||||
10));
|
||||
} else if (audio.playerRunning &&
|
||||
audio.backgroundAudioDuration !=
|
||||
0) {
|
||||
|
@ -323,11 +324,12 @@ class _Queue extends StatefulWidget {
|
|||
class __QueueState extends State<_Queue> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<AudioPlayerNotifier, Tuple3<Playlist?, bool, EpisodeBrief?>>(
|
||||
return Selector<AudioPlayerNotifier,
|
||||
Tuple3<Playlist?, bool, EpisodeBrief?>>(
|
||||
selector: (_, audio) =>
|
||||
Tuple3(audio.playlist, audio.playerRunning, audio.episode),
|
||||
builder: (_, data, __) {
|
||||
var episodes = data.item1?.episodes?.toSet()?.toList();
|
||||
var episodes = data.item1?.episodes.toSet().toList();
|
||||
var queue = data.item1;
|
||||
var running = data.item2;
|
||||
return queue == null
|
||||
|
@ -367,10 +369,9 @@ class __QueueState extends State<_Queue> {
|
|||
))
|
||||
.toList())
|
||||
: ListView.builder(
|
||||
itemCount: queue?.length,
|
||||
itemCount: queue.length,
|
||||
itemBuilder: (context, index) {
|
||||
final episode =
|
||||
queue != null ? queue.episodes[index] : null;
|
||||
final episode = queue.episodes[index];
|
||||
final isPlaying =
|
||||
data.item3 != null && data.item3 == episode;
|
||||
return episode == null
|
||||
|
@ -452,7 +453,9 @@ class __HistoryState extends State<_History> {
|
|||
}
|
||||
|
||||
Widget _timeTag(BuildContext context,
|
||||
{EpisodeBrief? episode, required int seconds, required double seekValue}) {
|
||||
{EpisodeBrief? episode,
|
||||
required int seconds,
|
||||
required double seekValue}) {
|
||||
final audio = context.watch<AudioPlayerNotifier>();
|
||||
final textWidth = _getMaskStop(seekValue, seconds).width;
|
||||
final stop = seekValue - 20 / textWidth + 40 * seekValue / textWidth;
|
||||
|
|
|
@ -108,7 +108,7 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller!.dispose();
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
@ -162,7 +162,7 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
final layoutStorage = KeyValueStorage(podcastLayoutKey);
|
||||
final hideListenedStorage = KeyValueStorage(hideListenedKey);
|
||||
final index = await layoutStorage.getInt(defaultValue: 1);
|
||||
if (_layout == null) _layout = Layout.values[index!];
|
||||
if (_layout == null) _layout = Layout.values[index];
|
||||
if (_hideListened == null) {
|
||||
_hideListened = await hideListenedStorage.getBool(defaultValue: false);
|
||||
}
|
||||
|
@ -709,8 +709,8 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
child: CustomScrollView(
|
||||
controller: _controller
|
||||
..addListener(() async {
|
||||
if (_controller!.offset ==
|
||||
_controller!.position.maxScrollExtent &&
|
||||
if (_controller.offset ==
|
||||
_controller.position.maxScrollExtent &&
|
||||
_dataCount == _top) {
|
||||
if (mounted) {
|
||||
setState(() => _loadMore = true);
|
||||
|
@ -723,7 +723,7 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
});
|
||||
}
|
||||
}
|
||||
if (_controller!.offset > 0 &&
|
||||
if (_controller.offset > 0 &&
|
||||
mounted &&
|
||||
!_scroll) {
|
||||
setState(() => _scroll = true);
|
||||
|
|
|
@ -1091,9 +1091,9 @@ class __GpodderInfoState extends State<_GpodderInfo> {
|
|||
initialData: [],
|
||||
builder: (context, snapshot) {
|
||||
final deviceId =
|
||||
snapshot.data!.isNotEmpty ? snapshot.data![1]! : '';
|
||||
snapshot.data!.isNotEmpty ? snapshot.data![1] : '';
|
||||
final deviceName =
|
||||
snapshot.data!.isNotEmpty ? snapshot.data![3]! : '';
|
||||
snapshot.data!.isNotEmpty ? snapshot.data![3] : '';
|
||||
return Column(
|
||||
children: [
|
||||
ListTile(
|
||||
|
|
|
@ -52,7 +52,7 @@ class _LayoutSettingState extends State<LayoutSetting> {
|
|||
Future<SearchEngine> _getSearchEngine() async {
|
||||
final storage = KeyValueStorage(searchEngineKey);
|
||||
final index = await storage.getInt();
|
||||
return SearchEngine.values[index!];
|
||||
return SearchEngine.values[index];
|
||||
}
|
||||
|
||||
Future<void> _saveSearchEngine(SearchEngine engine) async {
|
||||
|
|
|
@ -30,7 +30,7 @@ class _StorageSettingState extends State<StorageSetting>
|
|||
await cacheStorage.saveInt((200 * 1024 * 1024).toInt());
|
||||
cache = 200 * 1024 * 1024;
|
||||
}
|
||||
var value = cache! ~/ (1024 * 1024);
|
||||
var value = cache ~/ (1024 * 1024);
|
||||
if (value > 100) {
|
||||
_controller = AnimationController(
|
||||
vsync: this, duration: Duration(milliseconds: value * 2));
|
||||
|
|
|
@ -85,7 +85,7 @@ class SyncingSetting extends StatelessWidget {
|
|||
await settings.cancelWork();
|
||||
settings.setWorkManager(value);
|
||||
}
|
||||
: null,
|
||||
: (int i) {},
|
||||
items: <int>[1, 2, 4, 8, 24, 48]
|
||||
.map<DropdownMenuItem<int>>((e) {
|
||||
return DropdownMenuItem<int>(
|
||||
|
|
|
@ -186,7 +186,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
final cacheMax =
|
||||
await cacheStorage.getInt(defaultValue: (1024 * 1024 * 200).toInt());
|
||||
_audioHandler = await AudioService.init(
|
||||
builder: () => CustomAudioHandler(cacheMax!), config: _config);
|
||||
builder: () => CustomAudioHandler(cacheMax), config: _config);
|
||||
super.addListener(listener);
|
||||
}
|
||||
|
||||
|
@ -259,7 +259,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<void> _initAudioData() async {
|
||||
var index = await (_playerHeightStorage.getInt(defaultValue: 0) as FutureOr<int>);
|
||||
var index = await _playerHeightStorage.getInt(defaultValue: 0);
|
||||
_playerHeight = PlayerHeight.values[index];
|
||||
_currentSpeed = await _speedStorage.getDouble(defaultValue: 1.0);
|
||||
_skipSilence = await _skipSilenceStorage.getBool(defaultValue: false);
|
||||
|
@ -286,16 +286,16 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
|
||||
Future<void> initPlaylist() async {
|
||||
if (_playlists.isEmpty) {
|
||||
var playlistEntities = await (_playlistsStorgae.getPlaylists() as FutureOr<List<PlaylistEntity>>);
|
||||
var playlistEntities = await _playlistsStorgae.getPlaylists();
|
||||
_playlists = [
|
||||
for (var entity in playlistEntities) Playlist.fromEntity(entity)
|
||||
for (var entity in playlistEntities!) Playlist.fromEntity(entity)
|
||||
];
|
||||
await _playlists.first.getPlaylist();
|
||||
await _getAutoPlay();
|
||||
|
||||
///Get playerstate saved in storage.
|
||||
var state = await (_playerStateStorage.getPlayerState() as FutureOr<List<String>>);
|
||||
var idList = [for (var p in _playlists) p.id];
|
||||
final state = await _playerStateStorage.getPlayerState();
|
||||
final idList = [for (var p in _playlists) p.id];
|
||||
if (idList.contains(state[0])) {
|
||||
_playlist = _playlists.firstWhere(
|
||||
(p) => p.id == state[0],
|
||||
|
@ -311,7 +311,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
_queue.isNotEmpty &&
|
||||
_queue.episodes.first!.title == episode.title))) {
|
||||
_episode = episode;
|
||||
_lastPosition = int.parse(state[2] ?? '0');
|
||||
_lastPosition = int.parse(state[2]);
|
||||
if (_lastPosition > 0) {
|
||||
{
|
||||
final duration = episode.duration! * 1000;
|
||||
|
@ -332,7 +332,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
}
|
||||
} else {
|
||||
_playlist = _playlists.first;
|
||||
_episode = _playlist!.isNotEmpty ? _playlist!.episodes?.first : null;
|
||||
_episode = _playlist!.isNotEmpty ? _playlist!.episodes.first : null;
|
||||
_lastPosition = 0;
|
||||
}
|
||||
notifyListeners();
|
||||
|
@ -354,7 +354,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
_playerRunning = true;
|
||||
notifyListeners();
|
||||
_startAudioService(_playlist!,
|
||||
position: _lastPosition ?? 0,
|
||||
position: _lastPosition,
|
||||
index: _playlist!.episodes.indexOf(_episode));
|
||||
} else {
|
||||
log('Playlist is empty');
|
||||
|
@ -477,9 +477,10 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
//Check auto sleep timer setting
|
||||
await _getAutoSleepTimer();
|
||||
if (_autoSleepTimer!) {
|
||||
var startTime =
|
||||
await (_autoSleepTimerStartStorage.getInt(defaultValue: 1380) as FutureOr<int>);
|
||||
var endTime = await (_autoSleepTimerEndStorage.getInt(defaultValue: 360) as FutureOr<int>);
|
||||
var startTime = await (_autoSleepTimerStartStorage.getInt(
|
||||
defaultValue: 1380) as FutureOr<int>);
|
||||
var endTime = await (_autoSleepTimerEndStorage.getInt(defaultValue: 360)
|
||||
as FutureOr<int>);
|
||||
var currentTime = DateTime.now().hour * 60 + DateTime.now().minute;
|
||||
if ((startTime > endTime &&
|
||||
(currentTime > startTime || currentTime < endTime)) ||
|
||||
|
@ -540,13 +541,13 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
},
|
||||
);
|
||||
|
||||
_playbackStateSubscription = _audioHandler.playbackState
|
||||
.listen((event) async {
|
||||
_playbackStateSubscription =
|
||||
_audioHandler.playbackState.listen((event) async {
|
||||
_current = DateTime.now();
|
||||
_audioState = event.processingState;
|
||||
_playing = event.playing;
|
||||
_currentSpeed = event.speed;
|
||||
_currentPosition = event.updatePosition.inMilliseconds ?? 0;
|
||||
_currentPosition = event.updatePosition.inMilliseconds;
|
||||
if (_audioState == AudioProcessingState.completed) {
|
||||
if (_switchValue > 0) _switchValue = 0;
|
||||
}
|
||||
|
@ -606,11 +607,9 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
}
|
||||
if (event is Map && event['position'] != null) {
|
||||
_backgroundAudioPosition = event['position'].inMilliseconds;
|
||||
if (_backgroundAudioDuration != null &&
|
||||
_backgroundAudioDuration != 0 &&
|
||||
_backgroundAudioPosition != null) {
|
||||
if (_backgroundAudioDuration != 0 && _backgroundAudioPosition != null) {
|
||||
_seekSliderValue =
|
||||
_backgroundAudioPosition! / _backgroundAudioDuration ?? 0;
|
||||
_backgroundAudioPosition! / _backgroundAudioDuration;
|
||||
} else {
|
||||
_seekSliderValue = 0;
|
||||
}
|
||||
|
@ -621,7 +620,8 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
|
||||
/// Queue management
|
||||
Future<void> addToPlaylist(EpisodeBrief episode) async {
|
||||
var episodeNew = await (_dbHelper.getRssItemWithUrl(episode.enclosureUrl) as FutureOr<EpisodeBrief>);
|
||||
var episodeNew = await (_dbHelper.getRssItemWithUrl(episode.enclosureUrl)
|
||||
as FutureOr<EpisodeBrief>);
|
||||
if (episodeNew.isNew == 1) {
|
||||
await _dbHelper.removeEpisodeNewMark(episodeNew.enclosureUrl);
|
||||
}
|
||||
|
@ -636,7 +636,8 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<void> addToPlaylistAt(EpisodeBrief episode, int index) async {
|
||||
var episodeNew = await (_dbHelper.getRssItemWithUrl(episode.enclosureUrl) as FutureOr<EpisodeBrief>);
|
||||
var episodeNew = await (_dbHelper.getRssItemWithUrl(episode.enclosureUrl)
|
||||
as FutureOr<EpisodeBrief>);
|
||||
if (episodeNew.isNew == 1) {
|
||||
await _dbHelper.removeEpisodeNewMark(episodeNew.enclosureUrl);
|
||||
}
|
||||
|
@ -753,7 +754,8 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
void addEpisodesToPlaylist(Playlist playlist, {required List<EpisodeBrief> episodes}) {
|
||||
void addEpisodesToPlaylist(Playlist playlist,
|
||||
{required List<EpisodeBrief> episodes}) {
|
||||
for (var e in episodes) {
|
||||
playlist.addToPlayList(e);
|
||||
if (playerRunning && playlist == _playlist) {
|
||||
|
@ -1039,7 +1041,7 @@ class CustomAudioHandler extends BaseAudioHandler
|
|||
}[_player.processingState]!,
|
||||
playing: _player.playing,
|
||||
updatePosition: _player.position,
|
||||
queueIndex: _player.currentIndex!,
|
||||
queueIndex: _player.currentIndex ?? 0,
|
||||
bufferedPosition: _player.bufferedPosition,
|
||||
speed: _player.speed,
|
||||
));
|
||||
|
|
|
@ -5,7 +5,6 @@ import 'dart:isolate';
|
|||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_downloader/flutter_downloader.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
@ -70,19 +69,20 @@ class AutoDownloader {
|
|||
|
||||
void _unbindBackgroundIsolate() {
|
||||
IsolateNameServer.removePortNameMapping('auto_downloader_send_port');
|
||||
_completer?.complete();
|
||||
_completer.complete();
|
||||
}
|
||||
|
||||
Future<Directory> _getDownloadDirectory() async {
|
||||
final storage = KeyValueStorage(downloadPositionKey);
|
||||
final index = await (storage.getInt() as FutureOr<int>);
|
||||
final externalDirs = await (getExternalStorageDirectories() as FutureOr<List<Directory>>);
|
||||
return externalDirs[index];
|
||||
final index = await storage.getInt();
|
||||
final externalDirs = await getExternalStorageDirectories();
|
||||
return externalDirs![index];
|
||||
}
|
||||
|
||||
Future _saveMediaId(EpisodeTask episodeTask) async {
|
||||
final completeTask = await (FlutterDownloader.loadTasksWithRawQuery(
|
||||
query: "SELECT * FROM task WHERE task_id = '${episodeTask.taskId}'") as FutureOr<List<DownloadTask>>);
|
||||
query: "SELECT * FROM task WHERE task_id = '${episodeTask.taskId}'")
|
||||
as FutureOr<List<DownloadTask>>);
|
||||
var filePath =
|
||||
'file://${path.join(completeTask.first.savedDir, Uri.encodeComponent(completeTask.first.filename!))}';
|
||||
var fileStat = await File(
|
||||
|
@ -152,8 +152,8 @@ class DownloadState extends ChangeNotifier {
|
|||
Future<void> _loadTasks() async {
|
||||
_episodeTasks = [];
|
||||
var dbHelper = DBHelper();
|
||||
var tasks = await (FlutterDownloader.loadTasks() as FutureOr<List<DownloadTask>>);
|
||||
if (tasks.isNotEmpty) {
|
||||
var tasks = await FlutterDownloader.loadTasks();
|
||||
if (tasks != null && tasks.isNotEmpty) {
|
||||
for (var task in tasks) {
|
||||
var episode = await dbHelper.getRssItemWithUrl(task.url);
|
||||
if (episode == null) {
|
||||
|
@ -192,7 +192,8 @@ class DownloadState extends ChangeNotifier {
|
|||
Future<Directory> _getDownloadDirectory() async {
|
||||
final storage = KeyValueStorage(downloadPositionKey);
|
||||
final index = await (storage.getInt() as FutureOr<int>);
|
||||
final externalDirs = await (getExternalStorageDirectories() as FutureOr<List<Directory>>);
|
||||
final externalDirs =
|
||||
await (getExternalStorageDirectories() as FutureOr<List<Directory>>);
|
||||
return externalDirs[index];
|
||||
}
|
||||
|
||||
|
@ -230,7 +231,8 @@ class DownloadState extends ChangeNotifier {
|
|||
Future _saveMediaId(EpisodeTask episodeTask) async {
|
||||
episodeTask.status = DownloadTaskStatus.complete;
|
||||
final completeTask = await (FlutterDownloader.loadTasksWithRawQuery(
|
||||
query: "SELECT * FROM task WHERE task_id = '${episodeTask.taskId}'") as FutureOr<List<DownloadTask>>);
|
||||
query: "SELECT * FROM task WHERE task_id = '${episodeTask.taskId}'")
|
||||
as FutureOr<List<DownloadTask>>);
|
||||
var filePath =
|
||||
'file://${path.join(completeTask.first.savedDir, Uri.encodeComponent(completeTask.first.filename!))}';
|
||||
var fileStat = await File(
|
||||
|
@ -250,9 +252,9 @@ class DownloadState extends ChangeNotifier {
|
|||
}
|
||||
|
||||
EpisodeTask episodeToTask(EpisodeBrief? episode) {
|
||||
return _episodeTasks
|
||||
.firstWhere((task) => task.episode!.enclosureUrl == episode!.enclosureUrl,
|
||||
orElse: () {
|
||||
return _episodeTasks.firstWhere(
|
||||
(task) => task.episode!.enclosureUrl == episode!.enclosureUrl,
|
||||
orElse: () {
|
||||
return EpisodeTask(
|
||||
episode,
|
||||
'',
|
||||
|
@ -362,7 +364,7 @@ class DownloadState extends ChangeNotifier {
|
|||
final deletePlayed = await deletePlayedStorage.getBool(defaultValue: false);
|
||||
if (autoDelete == 0) {
|
||||
await autoDeleteStorage.saveInt(30);
|
||||
} else if (autoDelete! > 0) {
|
||||
} else if (autoDelete > 0) {
|
||||
var deadline = DateTime.now()
|
||||
.subtract(Duration(days: autoDelete))
|
||||
.millisecondsSinceEpoch;
|
||||
|
@ -373,10 +375,10 @@ class DownloadState extends ChangeNotifier {
|
|||
await delTask(episode);
|
||||
}
|
||||
}
|
||||
final tasks = await (FlutterDownloader.loadTasksWithRawQuery(
|
||||
final tasks = await FlutterDownloader.loadTasksWithRawQuery(
|
||||
query:
|
||||
'SELECT * FROM task WHERE time_created < $deadline AND status = 3') as FutureOr<List<DownloadTask>>);
|
||||
for (var task in tasks) {
|
||||
'SELECT * FROM task WHERE time_created < $deadline AND status = 3');
|
||||
for (var task in tasks ?? []) {
|
||||
FlutterDownloader.remove(
|
||||
taskId: task.taskId, shouldDeleteContent: true);
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_isolate/flutter_isolate.dart';
|
||||
import 'package:image/image.dart' as img;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:path_provider_android/path_provider_android.dart';
|
||||
import 'package:shared_preferences_android/shared_preferences_android.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'package:webfeed/webfeed.dart';
|
||||
import 'package:workmanager/workmanager.dart';
|
||||
|
@ -48,7 +50,7 @@ class GroupEntity {
|
|||
return {'name': name, 'id': id, 'color': color, 'podcastList': podcastList};
|
||||
}
|
||||
|
||||
static GroupEntity fromJson(Map<String, Object> json) {
|
||||
static GroupEntity fromJson(Map<String, dynamic> json) {
|
||||
var list = List<String>.from(json['podcastList'] as Iterable<dynamic>);
|
||||
return GroupEntity(json['name'] as String?, json['id'] as String?,
|
||||
json['color'] as String?, list);
|
||||
|
@ -537,7 +539,7 @@ class GroupList extends ChangeNotifier {
|
|||
) async {
|
||||
_syncRemove(podcast.rssUrl);
|
||||
await _unsubscribe(podcast);
|
||||
await File(podcast.imagePath!)?.delete();
|
||||
await File(podcast.imagePath!).delete();
|
||||
}
|
||||
|
||||
Future<void> saveOrder(PodcastGroup? group) async {
|
||||
|
@ -553,6 +555,8 @@ class GroupList extends ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<void> subIsolateEntryPoint(SendPort sendPort) async {
|
||||
if (Platform.isAndroid) SharedPreferencesAndroid.registerWith();
|
||||
if (Platform.isAndroid) PathProviderAndroid.registerWith();
|
||||
var items = <SubscribeItem>[];
|
||||
var _running = false;
|
||||
final listColor = <String>[
|
||||
|
@ -598,13 +602,13 @@ Future<void> subIsolateEntryPoint(SendPort sendPort) async {
|
|||
sendPort.send("done");
|
||||
}
|
||||
}
|
||||
developer.log('get dir');
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
|
||||
var dir = await getApplicationDocumentsDirectory();
|
||||
|
||||
var realUrl =
|
||||
final realUrl =
|
||||
response.redirects.isEmpty ? rss : response.realUri.toString();
|
||||
|
||||
var checkUrl = await dbHelper.checkPodcast(realUrl);
|
||||
final checkUrl = await dbHelper.checkPodcast(realUrl);
|
||||
|
||||
/// If url not existe in database.
|
||||
if (checkUrl == '') {
|
||||
|
|
|
@ -137,9 +137,11 @@ class SettingState extends ChangeNotifier {
|
|||
ThemeMode? _theme;
|
||||
ThemeMode? get theme => _theme;
|
||||
|
||||
ThemeData get lightTheme => ThemeData(
|
||||
colorScheme: ColorScheme.fromSwatch()
|
||||
ThemeData get lightTheme => ThemeData().copyWith(
|
||||
colorScheme: ThemeData()
|
||||
.colorScheme
|
||||
.copyWith(brightness: Brightness.light, secondary: _accentSetColor),
|
||||
brightness: Brightness.dark,
|
||||
primaryColor: Colors.grey[100],
|
||||
primaryColorLight: Colors.white,
|
||||
primaryColorDark: Colors.grey[300],
|
||||
|
@ -171,15 +173,20 @@ class SettingState extends ChangeNotifier {
|
|||
);
|
||||
|
||||
ThemeData get darkTheme => ThemeData.dark().copyWith(
|
||||
colorScheme: ColorScheme.fromSwatch()
|
||||
colorScheme: ThemeData.dark()
|
||||
.colorScheme
|
||||
.copyWith(brightness: Brightness.dark, secondary: _accentSetColor),
|
||||
brightness: Brightness.light,
|
||||
primaryColorDark: Colors.grey[800],
|
||||
scaffoldBackgroundColor: _realDark! ? Colors.black87 : Color(0XFF212121),
|
||||
scaffoldBackgroundColor:
|
||||
_realDark! ? Colors.black87 : Color(0XFF212121),
|
||||
primaryColor: _realDark! ? Colors.black : Color(0XFF1B1B1B),
|
||||
popupMenuTheme: PopupMenuThemeData()
|
||||
.copyWith(color: _realDark! ? Colors.grey[900] : null),
|
||||
appBarTheme: AppBarTheme(
|
||||
elevation: 0, systemOverlayStyle: SystemUiOverlayStyle.light),
|
||||
color: Colors.grey[900],
|
||||
elevation: 0,
|
||||
systemOverlayStyle: SystemUiOverlayStyle.light),
|
||||
buttonTheme: ButtonThemeData(height: 32),
|
||||
dialogBackgroundColor: _realDark! ? Colors.grey[900] : null,
|
||||
);
|
||||
|
@ -353,7 +360,7 @@ class SettingState extends ChangeNotifier {
|
|||
|
||||
Future _getTheme() async {
|
||||
var mode = await _themeStorage.getInt();
|
||||
_theme = ThemeMode.values[mode!];
|
||||
_theme = ThemeMode.values[mode];
|
||||
}
|
||||
|
||||
Future _getAccentSetColor() async {
|
||||
|
@ -439,7 +446,7 @@ class SettingState extends ChangeNotifier {
|
|||
}
|
||||
_locale = Locale(systemLanCode);
|
||||
} else {
|
||||
_locale = Locale(localeString.first!, localeString[1]);
|
||||
_locale = Locale(localeString.first, localeString[1]);
|
||||
}
|
||||
await S.load(_locale!);
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ class PlaylistEntity {
|
|||
};
|
||||
}
|
||||
|
||||
static PlaylistEntity fromJson(Map<String, Object> json) {
|
||||
static PlaylistEntity fromJson(Map<String, dynamic> json) {
|
||||
var list = List<String>.from(json['episodeList'] as Iterable<dynamic>);
|
||||
return PlaylistEntity(json['name'] as String?, json['id'] as String?,
|
||||
json['isLocal'] == null ? false : json['isLocal'] as bool?, list);
|
||||
|
|
|
@ -15,6 +15,7 @@ extension ContextExtension on BuildContext {
|
|||
Color? get textColor => Theme.of(this).textTheme.bodyText1!.color;
|
||||
Color get dialogBackgroundColor => Theme.of(this).dialogBackgroundColor;
|
||||
Brightness get brightness => Theme.of(this).brightness;
|
||||
Brightness get iconBrightness => Theme.of(this).colorScheme.brightness;
|
||||
double get width => MediaQuery.of(this).size.width;
|
||||
double get height => MediaQuery.of(this).size.height;
|
||||
double get paddingTop => MediaQuery.of(this).padding.top;
|
||||
|
@ -24,7 +25,6 @@ extension ContextExtension on BuildContext {
|
|||
|
||||
extension IntExtension on int {
|
||||
String toDate(BuildContext context) {
|
||||
if (this == null) return '';
|
||||
final s = context.s;
|
||||
var date = DateTime.fromMillisecondsSinceEpoch(this, isUtc: true);
|
||||
var difference = DateTime.now().toUtc().difference(date);
|
||||
|
@ -46,7 +46,7 @@ extension IntExtension on int {
|
|||
'${(this ~/ 60).toString().padLeft(2, '0')}:${(truncate() % 60).toString().padLeft(2, '0')}';
|
||||
|
||||
String toInterval(BuildContext context) {
|
||||
if (this == null || isNegative) return '';
|
||||
if (isNegative) return '';
|
||||
final s = context.s;
|
||||
var interval = Duration(milliseconds: this);
|
||||
if (interval.inHours <= 48) {
|
||||
|
@ -78,7 +78,8 @@ extension StringExtension on String {
|
|||
Color c;
|
||||
var color = json.decode(this);
|
||||
if (color[0] > 200 && color[1] > 200 && color[2] > 200) {
|
||||
c = Color.fromRGBO(255 - color[0] as int, 255 - color[1] as int, 255 - color[2] as int, 1.0);
|
||||
c = Color.fromRGBO(255 - color[0] as int, 255 - color[1] as int,
|
||||
255 - color[2] as int, 1.0);
|
||||
} else {
|
||||
c = Color.fromRGBO(color[0], color[1] > 200 ? 190 : color[1],
|
||||
color[2] > 200 ? 190 : color[2], 1);
|
||||
|
@ -90,7 +91,8 @@ extension StringExtension on String {
|
|||
Color c;
|
||||
var color = json.decode(this);
|
||||
if (color[0] < 50 && color[1] < 50 && color[2] < 50) {
|
||||
c = Color.fromRGBO(255 - color[0] as int, 255 - color[1] as int, 255 - color[2] as int, 1.0);
|
||||
c = Color.fromRGBO(255 - color[0] as int, 255 - color[1] as int,
|
||||
255 - color[2] as int, 1.0);
|
||||
} else {
|
||||
c = Color.fromRGBO(color[0] < 50 ? 100 : color[0],
|
||||
color[1] < 50 ? 100 : color[1], color[2] < 50 ? 100 : color[2], 1.0);
|
||||
|
|
|
@ -399,8 +399,7 @@ class _DropdownRoute<T> extends PopupRoute<_DropdownRouteResult<T>> {
|
|||
this.itemHeight,
|
||||
this.dropdownColor,
|
||||
this.displayItemCount,
|
||||
}) : assert(style != null),
|
||||
itemHeights = List<double>.filled(
|
||||
}) : itemHeights = List<double>.filled(
|
||||
items.length, itemHeight ?? kMinInteractiveDimension);
|
||||
|
||||
final List<_MenuItem<T>?> items;
|
||||
|
@ -456,7 +455,7 @@ class _DropdownRoute<T> extends PopupRoute<_DropdownRouteResult<T>> {
|
|||
double getItemOffset(int? index) {
|
||||
var offset = kMaterialListPadding.top;
|
||||
if (items.isNotEmpty && index! > 0) {
|
||||
assert(items.length == itemHeights?.length);
|
||||
assert(items.length == itemHeights.length);
|
||||
if (displayItemCount == null) {
|
||||
offset += itemHeights
|
||||
.sublist(0, index)
|
||||
|
@ -719,8 +718,7 @@ class DropdownButtonHideUnderline extends InheritedWidget {
|
|||
const DropdownButtonHideUnderline({
|
||||
Key? key,
|
||||
required Widget child,
|
||||
}) : assert(child != null),
|
||||
super(key: key, child: child);
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// Returns whether the underline of [DropdownButton] widgets should
|
||||
/// be hidden.
|
||||
|
@ -850,8 +848,7 @@ class MyDropdownButton<T> extends StatefulWidget {
|
|||
this.dropdownColor,
|
||||
this.displayItemCount,
|
||||
}) : assert(
|
||||
items == null ||
|
||||
items.isEmpty ||
|
||||
items.isEmpty ||
|
||||
value == null ||
|
||||
items.where((item) {
|
||||
return item.value == value;
|
||||
|
@ -862,12 +859,7 @@ class MyDropdownButton<T> extends StatefulWidget {
|
|||
'Either zero or 2 or more [DropdownMenuItem]s were detected '
|
||||
'with the same value',
|
||||
),
|
||||
assert(elevation != null),
|
||||
assert(iconSize != null),
|
||||
assert(isDense != null),
|
||||
assert(isExpanded != null),
|
||||
assert(autofocus != null),
|
||||
assert(itemHeight == null || itemHeight >= kMinInteractiveDimension),
|
||||
assert(itemHeight >= kMinInteractiveDimension),
|
||||
assert(displayItemCount == null || displayItemCount > 0),
|
||||
super(key: key);
|
||||
|
||||
|
@ -910,7 +902,7 @@ class MyDropdownButton<T> extends StatefulWidget {
|
|||
/// [disabledHint] is also null but [hint] is non-null, [hint] will instead
|
||||
/// be displayed.
|
||||
/// {@endtemplate}
|
||||
final ValueChanged<T>? onChanged;
|
||||
final ValueChanged<T> onChanged;
|
||||
|
||||
/// Called when the dropdown button is tapped.
|
||||
///
|
||||
|
@ -1253,7 +1245,7 @@ class _MyDropdownButtonState<T> extends State<MyDropdownButton<T>>
|
|||
Navigator.push(context, _dropdownRoute!).then<void>((newValue) {
|
||||
_removeDropdownRoute();
|
||||
if (!mounted || newValue == null) return;
|
||||
if (widget.onChanged != null) widget.onChanged!(newValue.result);
|
||||
widget.onChanged(newValue.result);
|
||||
});
|
||||
|
||||
if (widget.onTap != null) {
|
||||
|
@ -1306,15 +1298,15 @@ class _MyDropdownButtonState<T> extends State<MyDropdownButton<T>>
|
|||
bool get _enabled => widget.items.isNotEmpty && widget.onChanged != null;
|
||||
|
||||
Orientation _getOrientation(BuildContext context) {
|
||||
var result = MediaQuery.of(context)?.orientation;
|
||||
if (result == null) {
|
||||
// If there's no MediaQuery, then use the window aspect to determine
|
||||
// orientation.
|
||||
final size = window.physicalSize;
|
||||
result = size.width > size.height
|
||||
? Orientation.landscape
|
||||
: Orientation.portrait;
|
||||
}
|
||||
var result = MediaQuery.of(context).orientation;
|
||||
// if (result == null) {
|
||||
// // If there's no MediaQuery, then use the window aspect to determine
|
||||
// // orientation.
|
||||
// final size = window.physicalSize;
|
||||
// result = size.width > size.height
|
||||
// ? Orientation.landscape
|
||||
// : Orientation.portrait;
|
||||
// }
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -1501,8 +1493,7 @@ class DropdownButtonFormField<T> extends FormField<T?> {
|
|||
bool isExpanded = false,
|
||||
double? itemHeight,
|
||||
}) : assert(
|
||||
items == null ||
|
||||
items.isEmpty ||
|
||||
items.isEmpty ||
|
||||
value == null ||
|
||||
items.where((item) {
|
||||
return item.value == value;
|
||||
|
@ -1513,11 +1504,6 @@ class DropdownButtonFormField<T> extends FormField<T?> {
|
|||
'Either zero or 2 or more [DropdownMenuItem]s were detected '
|
||||
'with the same value',
|
||||
),
|
||||
assert(decoration != null),
|
||||
assert(elevation != null),
|
||||
assert(iconSize != null),
|
||||
assert(isDense != null),
|
||||
assert(isExpanded != null),
|
||||
assert(itemHeight == null || itemHeight > 0),
|
||||
super(
|
||||
key: key,
|
||||
|
@ -1582,7 +1568,6 @@ class _DropdownButtonFormFieldState<T> extends FormFieldState<T?> {
|
|||
@override
|
||||
void didChange(T? value) {
|
||||
super.didChange(value);
|
||||
assert(widget.onChanged != null);
|
||||
widget.onChanged(value);
|
||||
}
|
||||
|
||||
|
|
|
@ -341,8 +341,7 @@ Future<T?> _showMenu<T>({
|
|||
case TargetPlatform.fuchsia:
|
||||
case TargetPlatform.linux:
|
||||
case TargetPlatform.windows:
|
||||
label =
|
||||
semanticLabel ?? MaterialLocalizations.of(context)?.popupMenuLabel;
|
||||
label = semanticLabel ?? MaterialLocalizations.of(context).popupMenuLabel;
|
||||
}
|
||||
|
||||
return Navigator.of(context, rootNavigator: useRootNavigator)
|
||||
|
@ -505,9 +504,7 @@ class MyPopupMenuItem<int> extends PopupMenuEntry<int> {
|
|||
this.height = kMinInteractiveDimension,
|
||||
this.textStyle,
|
||||
required this.child,
|
||||
}) : assert(enabled != null),
|
||||
assert(height != null),
|
||||
super(key: key);
|
||||
}) : super(key: key);
|
||||
|
||||
final int? value;
|
||||
|
||||
|
|
|
@ -1,114 +1,22 @@
|
|||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Shows a full screen search page and returns the search result selected by
|
||||
/// the user when the page is closed.
|
||||
///
|
||||
/// The search page consists of an app bar with a search field and a body which
|
||||
/// can either show suggested search queries or the search results.
|
||||
///
|
||||
/// The appearance of the search page is determined by the provided
|
||||
/// `delegate`. The initial query string is given by `query`, which defaults
|
||||
/// to the empty string. When `query` is set to null, `delegate.query` will
|
||||
/// be used as the initial query.
|
||||
///
|
||||
/// This method returns the selected search result, which can be set in the
|
||||
/// [SearchDelegate.close] call. If the search page is closed with the system
|
||||
/// back button, it returns null.
|
||||
///
|
||||
/// A given [SearchDelegate] can only be associated with one active [showSearch]
|
||||
/// call. Call [SearchDelegate.close] before re-using the same delegate instance
|
||||
/// for another [showSearch] call.
|
||||
///
|
||||
/// The transition to the search page triggered by this method looks best if the
|
||||
/// screen triggering the transition contains an [AppBar] at the top and the
|
||||
/// transition is called from an [IconButton] that's part of [AppBar.actions].
|
||||
/// The animation provided by [SearchDelegate.transitionAnimation] can be used
|
||||
/// to trigger additional animations in the underlying page while the search
|
||||
/// page fades in or out. This is commonly used to animate an [AnimatedIcon] in
|
||||
/// the [AppBar.leading] position e.g. from the hamburger menu to the back arrow
|
||||
/// used to exit the search page.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [SearchDelegate] to define the content of the search page.
|
||||
Future<T?> showSearch<T>({
|
||||
required BuildContext context,
|
||||
required SearchDelegate<T> delegate,
|
||||
String query = '',
|
||||
}) {
|
||||
assert(delegate != null);
|
||||
assert(context != null);
|
||||
delegate.query = query ?? delegate.query;
|
||||
delegate.query = query;
|
||||
delegate._currentBody = _SearchBody.suggestions;
|
||||
return Navigator.of(context).push(_SearchPageRoute<T>(
|
||||
delegate: delegate,
|
||||
));
|
||||
}
|
||||
|
||||
/// Delegate for [showSearch] to define the content of the search page.
|
||||
///
|
||||
/// The search page always shows an [AppBar] at the top where users can
|
||||
/// enter their search queries. The buttons shown before and after the search
|
||||
/// query text field can be customized via [SearchDelegate.buildLeading] and
|
||||
/// [SearchDelegate.buildActions].
|
||||
///
|
||||
/// The body below the [AppBar] can either show suggested queries (returned by
|
||||
/// [SearchDelegate.buildSuggestions]) or - once the user submits a search - the
|
||||
/// results of the search as returned by [SearchDelegate.buildResults].
|
||||
///
|
||||
/// [SearchDelegate.query] always contains the current query entered by the user
|
||||
/// and should be used to build the suggestions and results.
|
||||
///
|
||||
/// The results can be brought on screen by calling [SearchDelegate.showResults]
|
||||
/// and you can go back to showing the suggestions by calling
|
||||
/// [SearchDelegate.showSuggestions].
|
||||
///
|
||||
/// Once the user has selected a search result, [SearchDelegate.close] should be
|
||||
/// called to remove the search page from the top of the navigation stack and
|
||||
/// to notify the caller of [showSearch] about the selected search result.
|
||||
///
|
||||
/// A given [SearchDelegate] can only be associated with one active [showSearch]
|
||||
/// call. Call [SearchDelegate.close] before re-using the same delegate instance
|
||||
/// for another [showSearch] call.
|
||||
abstract class SearchDelegate<T> {
|
||||
/// Constructor to be called by subclasses which may specify [searchFieldLabel], [keyboardType] and/or
|
||||
/// [textInputAction].
|
||||
///
|
||||
/// {@tool snippet}
|
||||
/// ```dart
|
||||
/// class CustomSearchHintDelegate extends SearchDelegate {
|
||||
/// CustomSearchHintDelegate({
|
||||
/// String hintText,
|
||||
/// }) : super(
|
||||
/// searchFieldLabel: hintText,
|
||||
/// keyboardType: TextInputType.text,
|
||||
/// textInputAction: TextInputAction.search,
|
||||
/// );
|
||||
///
|
||||
/// @override
|
||||
/// Widget buildLeading(BuildContext context) => Text("leading");
|
||||
///
|
||||
/// @override
|
||||
/// Widget buildSuggestions(BuildContext context) => Text("suggestions");
|
||||
///
|
||||
/// @override
|
||||
/// Widget buildResults(BuildContext context) => Text('results');
|
||||
///
|
||||
/// @override
|
||||
/// List<Widget> buildActions(BuildContext context) => [];
|
||||
/// }
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
SearchDelegate({
|
||||
this.searchFieldLabel,
|
||||
this.searchFieldStyle,
|
||||
|
@ -116,76 +24,15 @@ abstract class SearchDelegate<T> {
|
|||
this.textInputAction = TextInputAction.search,
|
||||
});
|
||||
|
||||
/// Suggestions shown in the body of the search page while the user types a
|
||||
/// query into the search field.
|
||||
///
|
||||
/// The delegate method is called whenever the content of [query] changes.
|
||||
/// The suggestions should be based on the current [query] string. If the query
|
||||
/// string is empty, it is good practice to show suggested queries based on
|
||||
/// past queries or the current context.
|
||||
///
|
||||
/// Usually, this method will return a [ListView] with one [ListTile] per
|
||||
/// suggestion. When [ListTile.onTap] is called, [query] should be updated
|
||||
/// with the corresponding suggestion and the results page should be shown
|
||||
/// by calling [showResults].
|
||||
Widget buildSuggestions(BuildContext context);
|
||||
|
||||
/// The results shown after the user submits a search from the search page.
|
||||
///
|
||||
/// The current value of [query] can be used to determine what the user
|
||||
/// searched for.
|
||||
///
|
||||
/// This method might be applied more than once to the same query.
|
||||
/// If your [buildResults] method is computationally expensive, you may want
|
||||
/// to cache the search results for one or more queries.
|
||||
///
|
||||
/// Typically, this method returns a [ListView] with the search results.
|
||||
/// When the user taps on a particular search result, [close] should be called
|
||||
/// with the selected result as argument. This will close the search page and
|
||||
/// communicate the result back to the initial caller of [showSearch].
|
||||
Widget buildResults(BuildContext context);
|
||||
|
||||
/// A widget to display before the current query in the [AppBar].
|
||||
///
|
||||
/// Typically an [IconButton] configured with a [BackButtonIcon] that exits
|
||||
/// the search with [close]. One can also use an [AnimatedIcon] driven by
|
||||
/// [transitionAnimation], which animates from e.g. a hamburger menu to the
|
||||
/// back button as the search overlay fades in.
|
||||
///
|
||||
/// Returns null if no widget should be shown.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [AppBar.leading], the intended use for the return value of this method.
|
||||
Widget buildLeading(BuildContext context);
|
||||
|
||||
/// Widgets to display after the search query in the [AppBar].
|
||||
///
|
||||
/// If the [query] is not empty, this should typically contain a button to
|
||||
/// clear the query and show the suggestions again (via [showSuggestions]) if
|
||||
/// the results are currently shown.
|
||||
///
|
||||
/// Returns null if no widget should be shown.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [AppBar.actions], the intended use for the return value of this method.
|
||||
List<Widget> buildActions(BuildContext context);
|
||||
|
||||
/// The theme used to style the [AppBar].
|
||||
///
|
||||
/// By default, a white theme is used.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [AppBar.backgroundColor], which is set to [ThemeData.primaryColor].
|
||||
/// * [AppBar.iconTheme], which is set to [ThemeData.primaryIconTheme].
|
||||
/// * [AppBar.textTheme], which is set to [ThemeData.primaryTextTheme].
|
||||
/// * [AppBar.brightness], which is set to [ThemeData.primaryColorBrightness].
|
||||
ThemeData appBarTheme(BuildContext context) {
|
||||
assert(context != null);
|
||||
final ThemeData theme = Theme.of(context);
|
||||
assert(theme != null);
|
||||
return theme.copyWith(
|
||||
primaryColor: Colors.white,
|
||||
primaryIconTheme: theme.primaryIconTheme.copyWith(color: Colors.grey),
|
||||
|
@ -194,46 +41,16 @@ abstract class SearchDelegate<T> {
|
|||
);
|
||||
}
|
||||
|
||||
/// The current query string shown in the [AppBar].
|
||||
///
|
||||
/// The user manipulates this string via the keyboard.
|
||||
///
|
||||
/// If the user taps on a suggestion provided by [buildSuggestions] this
|
||||
/// string should be updated to that suggestion via the setter.
|
||||
String get query => _queryTextController.text;
|
||||
set query(String value) {
|
||||
assert(query != null);
|
||||
_queryTextController.text = value;
|
||||
}
|
||||
|
||||
/// Transition from the suggestions returned by [buildSuggestions] to the
|
||||
/// [query] results returned by [buildResults].
|
||||
///
|
||||
/// If the user taps on a suggestion provided by [buildSuggestions] the
|
||||
/// screen should typically transition to the page showing the search
|
||||
/// results for the suggested query. This transition can be triggered
|
||||
/// by calling this method.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [showSuggestions] to show the search suggestions again.
|
||||
void showResults(BuildContext context) {
|
||||
_focusNode?.unfocus();
|
||||
_currentBody = _SearchBody.results;
|
||||
}
|
||||
|
||||
/// Transition from showing the results returned by [buildResults] to showing
|
||||
/// the suggestions returned by [buildSuggestions].
|
||||
///
|
||||
/// Calling this method will also put the input focus back into the search
|
||||
/// field of the [AppBar].
|
||||
///
|
||||
/// If the results are currently shown this method can be used to go back
|
||||
/// to showing the search suggestions.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [showResults] to show the search results.
|
||||
void showSuggestions(BuildContext context) {
|
||||
assert(_focusNode != null,
|
||||
'_focusNode must be set by route before showSuggestions is called.');
|
||||
|
@ -241,10 +58,6 @@ abstract class SearchDelegate<T> {
|
|||
_currentBody = _SearchBody.suggestions;
|
||||
}
|
||||
|
||||
/// Closes the search page and returns to the underlying route.
|
||||
///
|
||||
/// The value provided for `result` is used as the return value of the call
|
||||
/// to [showSearch] that launched the search initially.
|
||||
void close(BuildContext context, T result) {
|
||||
_currentBody = null;
|
||||
_focusNode?.unfocus();
|
||||
|
@ -253,39 +66,16 @@ abstract class SearchDelegate<T> {
|
|||
..pop(result);
|
||||
}
|
||||
|
||||
/// The hint text that is shown in the search field when it is empty.
|
||||
///
|
||||
/// If this value is set to null, the value of
|
||||
/// `MaterialLocalizations.of(context).searchFieldLabel` will be used instead.
|
||||
final String? searchFieldLabel;
|
||||
|
||||
/// The style of the [searchFieldLabel].
|
||||
///
|
||||
/// If this value is set to null, the value of the ambient [Theme]'s
|
||||
/// [InputDecorationTheme.hintStyle] will be used instead.
|
||||
final TextStyle? searchFieldStyle;
|
||||
|
||||
/// The type of action button to use for the keyboard.
|
||||
///
|
||||
/// Defaults to the default value specified in [TextField].
|
||||
final TextInputType? keyboardType;
|
||||
|
||||
/// The text input action configuring the soft keyboard to a particular action
|
||||
/// button.
|
||||
///
|
||||
/// Defaults to [TextInputAction.search].
|
||||
final TextInputAction textInputAction;
|
||||
|
||||
/// [Animation] triggered when the search pages fades in or out.
|
||||
///
|
||||
/// This animation is commonly used to animate [AnimatedIcon]s of
|
||||
/// [IconButton]s returned by [buildLeading] or [buildActions]. It can also be
|
||||
/// used to animate [IconButton]s contained within the route below the search
|
||||
/// page.
|
||||
Animation<double> get transitionAnimation => _proxyAnimation;
|
||||
|
||||
// The focus node to use for manipulating focus on the search page. This is
|
||||
// managed, owned, and set by the _SearchPageRoute using this delegate.
|
||||
FocusNode? _focusNode;
|
||||
|
||||
final TextEditingController _queryTextController = TextEditingController();
|
||||
|
@ -304,8 +94,6 @@ abstract class SearchDelegate<T> {
|
|||
_SearchPageRoute<T>? _route;
|
||||
}
|
||||
|
||||
/// Describes the body that is currently shown under the [AppBar] in the
|
||||
/// search page.
|
||||
enum _SearchBody {
|
||||
/// Suggested queries are shown in the body.
|
||||
///
|
||||
|
@ -321,7 +109,7 @@ enum _SearchBody {
|
|||
class _SearchPageRoute<T> extends PageRoute<T> {
|
||||
_SearchPageRoute({
|
||||
required this.delegate,
|
||||
}) : assert(delegate != null) {
|
||||
}) {
|
||||
assert(
|
||||
delegate._route == null,
|
||||
'The ${delegate.runtimeType} instance is currently used by another active '
|
||||
|
|
|
@ -916,9 +916,9 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
|
|||
duration: _kDialAnimateDuration,
|
||||
);
|
||||
_thetaTween = Tween<double>(begin: _getThetaForTime(widget.selectedTime));
|
||||
_theta = _thetaController!
|
||||
_theta = _thetaController
|
||||
.drive(CurveTween(curve: Curves.easeInSine))
|
||||
.drive(_thetaTween!)
|
||||
.drive(_thetaTween)
|
||||
..addListener(() => setState(() {/* _theta.value has changed */}));
|
||||
}
|
||||
|
||||
|
@ -948,7 +948,7 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
_thetaController!.dispose();
|
||||
_thetaController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
|
@ -548,7 +548,7 @@ class EpisodeGrid extends StatelessWidget {
|
|||
child: Selector<AudioPlayerNotifier,
|
||||
Tuple4<EpisodeBrief?, List<String>, bool, bool>>(
|
||||
selector: (_, audio) => Tuple4(
|
||||
audio?.episode,
|
||||
audio.episode,
|
||||
audio.queue.episodes.map((e) => e!.enclosureUrl).toList(),
|
||||
audio.episodeState,
|
||||
audio.playerRunning),
|
||||
|
|
|
@ -70,6 +70,8 @@ dependencies:
|
|||
path_provider: ^2.0.1
|
||||
http_parser: ^4.0.0
|
||||
collection: ^1.15.0-nullsafety.4
|
||||
shared_preferences_android: ^2.0.12
|
||||
path_provider_android: ^2.0.14
|
||||
|
||||
dependency_overrides:
|
||||
meta: 1.3.0
|
||||
|
|
Loading…
Reference in New Issue