update theme

This commit is contained in:
xijieyin 2022-05-16 00:18:19 +08:00
parent d713807b00
commit 3cff5afc2a
35 changed files with 500 additions and 699 deletions

View File

@ -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

View File

@ -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'

View File

@ -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" />

View File

@ -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;
}
}

View File

@ -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") {

View File

@ -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);
}
}

View File

@ -1,5 +1,5 @@
buildscript {
ext.kotlin_version = '1.3.70'
ext.kotlin_version = '1.6.21'
repositories {
google()
jcenter()

View File

@ -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(

View File

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

View File

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

View File

@ -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),
),
],

View File

@ -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);
}

View File

@ -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(),

View File

@ -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 {

View File

@ -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);
}

View File

@ -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 {

View File

@ -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]);

View File

@ -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;

View File

@ -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);

View File

@ -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(

View File

@ -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 {

View File

@ -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));

View File

@ -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>(

View File

@ -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,
));

View File

@ -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);
}

View File

@ -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 == '') {

View File

@ -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!);
}

View File

@ -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);

View File

@ -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);

View File

@ -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);
}

View File

@ -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;

View File

@ -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 '

View File

@ -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();
}

View File

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

View File

@ -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