diff --git a/android/.settings/org.eclipse.buildship.core.prefs b/android/.settings/org.eclipse.buildship.core.prefs index a8686ad..1886311 100644 --- a/android/.settings/org.eclipse.buildship.core.prefs +++ b/android/.settings/org.eclipse.buildship.core.prefs @@ -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 diff --git a/android/app/build.gradle b/android/app/build.gradle index 4fa572b..f33bd52 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -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' diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 7d99abc..90de22b 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -7,7 +7,7 @@ - + diff --git a/android/app/src/main/java/io/flutter/plugins/IsolatePluginRegistrant.java b/android/app/src/main/java/io/flutter/plugins/IsolatePluginRegistrant.java index 25a9b42..99c729a 100644 --- a/android/app/src/main/java/io/flutter/plugins/IsolatePluginRegistrant.java +++ b/android/app/src/main/java/io/flutter/plugins/IsolatePluginRegistrant.java @@ -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; } } \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/stonegate/tsacdop/MainActivity.kt b/android/app/src/main/kotlin/com/stonegate/tsacdop/MainActivity.kt index 1f4cac6..678a0b2 100644 --- a/android/app/src/main/kotlin/com/stonegate/tsacdop/MainActivity.kt +++ b/android/app/src/main/kotlin/com/stonegate/tsacdop/MainActivity.kt @@ -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") { diff --git a/android/app/src/main/kotlin/com/stonegate/tsacdop/MainApplication.kt b/android/app/src/main/kotlin/com/stonegate/tsacdop/MainApplication.kt new file mode 100644 index 0000000..05e8eb8 --- /dev/null +++ b/android/app/src/main/kotlin/com/stonegate/tsacdop/MainApplication.kt @@ -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); + } +} \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index 94e89a3..f72f1f7 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.3.70' + ext.kotlin_version = '1.6.21' repositories { google() jcenter() diff --git a/lib/episodes/episode_detail.dart b/lib/episodes/episode_detail.dart index e96e7d2..35c6710 100644 --- a/lib/episodes/episode_detail.dart +++ b/lib/episodes/episode_detail.dart @@ -44,7 +44,7 @@ class _EpisodeDetailState extends State { String? path; Future _getPosition(EpisodeBrief episode) async { - var dbHelper = DBHelper(); + final dbHelper = DBHelper(); return await dbHelper.getPosition(episode); } @@ -86,204 +86,205 @@ class _EpisodeDetailState extends State { @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(); - return AnnotatedRegion( - 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: [ - SafeArea( - child: ScrollConfiguration( - behavior: NoGrowBehavior(), - child: NestedScrollView( - scrollDirection: Axis.vertical, - controller: _controller, - headerSliverBuilder: (context, innerBoxScrolled) { - return [ - 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 [ + 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: [ - 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: [ + 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( - 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( + 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>( - 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>( + 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( diff --git a/lib/episodes/episode_download.dart b/lib/episodes/episode_download.dart index 88af8a0..624a8c2 100644 --- a/lib/episodes/episode_download.dart +++ b/lib/episodes/episode_download.dart @@ -163,7 +163,6 @@ class _DownloadButtonState extends State { ), ), () => _requestDownload(task.episode)); - break; case 2: return Material( color: Colors.transparent, @@ -193,7 +192,6 @@ class _DownloadButtonState extends State { ), ), ); - break; case 6: return Material( color: Colors.transparent, diff --git a/lib/home/about.dart b/lib/home/about.dart index 76f6b7b..ef7daf5 100644 --- a/lib/home/about.dart +++ b/lib/home/about.dart @@ -299,7 +299,7 @@ class _AboutAppState extends State { _overlayEntry = _createOverlayEntry(detail); Overlay.of(context)!.insert(_overlayEntry); await Future.delayed(Duration(seconds: 2)); - _overlayEntry?.remove(); + _overlayEntry.remove(); }, child: Row( crossAxisAlignment: CrossAxisAlignment.center, diff --git a/lib/home/audioplayer.dart b/lib/home/audioplayer.dart index f2f1c75..7c97295 100644 --- a/lib/home/audioplayer.dart +++ b/lib/home/audioplayer.dart @@ -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>( - 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 child: Row( children: [ Text( - (data.backgroundAudioPosition! ~/ 1000).toTime ?? - '', + (data.backgroundAudioPosition! ~/ 1000).toTime, style: TextStyle(fontSize: 10), ), Expanded( @@ -1336,8 +1335,7 @@ class _ControlPanelState extends State ), ), Text( - (data.backgroundAudioDuration ~/ 1000).toTime ?? - '', + (data.backgroundAudioDuration ~/ 1000).toTime, style: TextStyle(fontSize: 10), ), ], diff --git a/lib/home/home.dart b/lib/home/home.dart index 211bb1a..f3eb7c5 100644 --- a/lib/home/home.dart +++ b/lib/home/home.dart @@ -241,7 +241,7 @@ class _HomeState extends State with SingleTickerProviderStateMixin { ), Selector( 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); } diff --git a/lib/home/home_groups.dart b/lib/home/home_groups.dart index e87c550..aa878c5 100644 --- a/lib/home/home_groups.dart +++ b/lib/home/home_groups.dart @@ -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, bool>>( selector: (_, audio) => tuple.Tuple3( - audio?.episode, + audio.episode, audio.queue.episodes .map((e) => e!.enclosureUrl) .toList(), diff --git a/lib/home/home_menu.dart b/lib/home/home_menu.dart index aec3370..a5f20c9 100644 --- a/lib/home/home_menu.dart +++ b/lib/home/home_menu.dart @@ -34,7 +34,7 @@ class _PopupMenuState extends State { } else { refreshDate = i; } - return refreshDate!.toDate(context); + return refreshDate.toDate(context); } void _saveOmpl(String path) async { diff --git a/lib/home/search_podcast.dart b/lib/home/search_podcast.dart index 87ef04a..ea0f942 100644 --- a/lib/home/search_podcast.dart +++ b/lib/home/search_podcast.dart @@ -206,10 +206,10 @@ class _RssResultState extends State { _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 { ], ), 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 _getSearchEngine() async { final storage = KeyValueStorage(searchEngineKey); final index = await storage.getInt(); - setState(() => _searchEngine = SearchEngine.values[index!]); + setState(() => _searchEngine = SearchEngine.values[index]); widget.onSelected!(_searchEngine); } diff --git a/lib/local_storage/key_value_storage.dart b/lib/local_storage/key_value_storage.dart index cbce010..073ca01 100644 --- a/lib/local_storage/key_value_storage.dart +++ b/lib/local_storage/key_value_storage.dart @@ -66,28 +66,26 @@ class KeyValueStorage { final String key; KeyValueStorage(this.key); Future?> 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(GroupEntity.fromJson) - .toList(growable: false); + final groups = json.decode(prefs.getString(key)!)['groups']; + return [for (final g in groups) GroupEntity.fromJson(g)]; } Future saveGroup(List 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?> getPlaylists() async { @@ -102,11 +100,8 @@ class KeyValueStorage { })); } print(prefs.getString(key)); - return json - .decode(prefs.getString(key)!)['playlists'] - .cast>() - .map(PlaylistEntity.fromJson) - .toList(growable: false); + final playlist = json.decode(prefs.getString(key)!)['playlists']; + return [for (final p in playlist) PlaylistEntity.fromJson(p)]; } Future savePlaylists(List 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?> getPlayerState() async { + Future> 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 saveInt(int setting) async { @@ -240,7 +235,7 @@ class KeyValueStorage { Future addList(List addList) async { final list = await getStringList(); - await saveStringList([...list ?? [], ...addList]); + await saveStringList([...list, ...addList]); } Future clearList() async { diff --git a/lib/local_storage/sqflite_localpodcast.dart b/lib/local_storage/sqflite_localpodcast.dart index dcf8295..a782248 100644 --- a/lib/local_storage/sqflite_localpodcast.dart +++ b/lib/local_storage/sqflite_localpodcast.dart @@ -18,11 +18,11 @@ enum Filter { downloaded, liked, search, all } const localFolderId = "46e48103-06c7-4fe1-a0b1-68aa7205b7f0"; class DBHelper { - static Database? _db; - Future get database async { - if (_db != null) return _db; + Database? _db; + Future 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 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 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 getPodcastWithUrl(String? url) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; List 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 getPodcastCounts(String? id) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; List 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 removePodcastNewMark(String? id) async { - var dbClient = await (database as FutureOr); + 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 getNeverUpdate(String? id) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; List 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 saveNeverUpdate(String? id, {required bool boo}) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; return await dbClient.rawUpdate( "UPDATE PodcastLocal SET never_update = ? WHERE id = ?", [boo ? 1 : 0, id]); } Future getHideNewMark(String? id) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; List 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 saveHideNewMark(String? id, {required bool boo}) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; return await dbClient.rawUpdate( "UPDATE PodcastLocal SET hide_new_mark = ? WHERE id = ?", [boo ? 1 : 0, id]); } Future getPodcastUpdateCounts(String? id) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; List 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 getSkipSecondsStart(String? id) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; List 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 saveSkipSecondsStart(String? id, int? seconds) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; return await dbClient.rawUpdate( "UPDATE PodcastLocal SET skip_seconds = ? WHERE id = ?", [seconds, id]); } Future getSkipSecondsEnd(String id) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; List 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 saveSkipSecondsEnd(String? id, int seconds) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; return await dbClient.rawUpdate( "UPDATE PodcastLocal SET skip_seconds_end = ? WHERE id = ?", [seconds, id]); } Future getAutoDownload(String? id) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; List 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 saveAutoDownload(String? id, {required bool boo}) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; return await dbClient.rawUpdate( "UPDATE PodcastLocal SET auto_download = ? WHERE id = ?", [boo ? 1 : 0, id]); } Future checkPodcast(String? url) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; List 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); + 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 updatePodcastImage({String? id, String? filePath}) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; return await dbClient.rawUpdate( "UPDATE PodcastLocal SET imagePath= ? WHERE id = ?", [filePath, id]); } Future saveFiresideData(List list) async { - var dbClient = await (database as FutureOr); + 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> getFiresideData(String? id) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; List list = await dbClient.rawQuery( 'SELECT background_image, hosts FROM PodcastLocal WHERE id = ?', [id]); if (list.isNotEmpty) { @@ -402,7 +402,7 @@ class DBHelper { } Future delPodcastLocal(String? id) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; await dbClient.rawDelete('DELETE FROM PodcastLocal WHERE id =?', [id]); List list = await dbClient.rawQuery( """SELECT downloaded FROM Episodes WHERE downloaded != 'ND' AND feed_id = ?""", @@ -422,7 +422,7 @@ class DBHelper { Future saveHistory(PlayHistory history) async { if (history.url!.substring(0, 7) != 'file://') { - var dbClient = await (database as FutureOr); + 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> getPlayHistory(int top) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; List 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> getPlayRecords(int? top) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; List 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 isListened(String url) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; int? i = 0; List list = await dbClient.rawQuery( "SELECT SUM(listen_time) FROM PlayHistory WHERE enclosure_url = ?", @@ -490,7 +490,7 @@ class DBHelper { } Future markNotListened(String url) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; int? count; await dbClient.transaction((txn) async { count = await txn.rawUpdate( @@ -505,7 +505,7 @@ class DBHelper { } Future> getSubHistory() async { - var dbClient = await (database as FutureOr); + var dbClient = await database; List 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 listenMins(int day) async { - var dbClient = await (database as FutureOr); + 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 getPosition(EpisodeBrief episodeBrief) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; List 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 checkMarked(EpisodeBrief episodeBrief) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; List 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 savePodcastRss(RssFeed feed, String id) async { feed.items!.removeWhere((item) => item == null); var result = feed.items!.length; - var dbClient = await (database as FutureOr); + 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); + 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 saveLocalEpisode(EpisodeBrief episode) async { - var dbClient = await (database as FutureOr); + 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 deleteLocalEpisodes(List files) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; var s = files.map((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 = []; List 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> getRssItemTop(String? id) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; var episodes = []; List list = await dbClient.rawQuery( """SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new, @@ -1225,7 +1227,7 @@ class DBHelper { var episodes = []; var list = []; 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 = []; var list = []; 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((e) => "'$e'").toList(); var list = []; 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> getRecentNewRssItem() async { - var dbClient = await (database as FutureOr); + var dbClient = await database; var episodes = []; List 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> getOutdatedEpisode(int deadline, {required bool deletePlayed}) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; var episodes = []; List list = await dbClient.rawQuery( """SELECT E.title, E.enclosure_url, E.enclosure_length, E.is_new, @@ -1428,7 +1430,7 @@ class DBHelper { late List 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 removeAllNewMark() async { - var dbClient = await (database as FutureOr); + 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 = []; if (group.length > 0) { var s = group.map((e) => "'$e'").toList(); - List list = await dbClient!.rawQuery( + List 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((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 removeEpisodeNewMark(String? url) async { - var dbClient = await (database as FutureOr); + 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 = []; 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); + 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); + 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 isLiked(String url) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; var list = []; list = await dbClient .rawQuery("SELECT liked FROM Episodes WHERE enclosure_url = ?", [url]); @@ -1646,7 +1648,7 @@ class DBHelper { } Future isDownloaded(String url) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; List list = await dbClient.rawQuery( "SELECT id FROM Episodes WHERE enclosure_url = ? AND enclosure_url != media_id", [url]); @@ -1654,7 +1656,7 @@ class DBHelper { } Future saveDownloaded(String url, String? id) async { - var dbClient = await (database as FutureOr); + 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 saveMediaId(String url, String path, String? id, int size) async { - var dbClient = await (database as FutureOr); + Future 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 delDownloaded(String url) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; int? count; await dbClient.transaction((txn) async { count = await txn.rawUpdate( @@ -1690,7 +1693,7 @@ class DBHelper { } Future getDescription(String url) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; List 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); + 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 getFeedDescription(String? id) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; List list = await dbClient .rawQuery('SELECT description FROM PodcastLocal WHERE id = ?', [id]); String? description = list[0]['description']; @@ -1715,7 +1718,7 @@ class DBHelper { } Future getChapter(String url) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; List 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 getEpisodeImage(String url) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; List 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 getRssItemWithUrl(String? url) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; EpisodeBrief episode; List list = await dbClient.rawQuery( """SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath, @@ -1763,7 +1766,7 @@ class DBHelper { } Future getRssItemWithMediaId(String id) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; EpisodeBrief episode; List list = await dbClient.rawQuery( """SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath, @@ -1795,7 +1798,7 @@ class DBHelper { } Future getImageUrl(String url) async { - var dbClient = await (database as FutureOr); + var dbClient = await database; List 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]); diff --git a/lib/playlists/playlist_home.dart b/lib/playlists/playlist_home.dart index 4495d2c..cca7f93 100644 --- a/lib/playlists/playlist_home.dart +++ b/lib/playlists/playlist_home.dart @@ -225,7 +225,8 @@ class _PlaylistHomeState extends State { 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>( + return Selector>( 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(); final textWidth = _getMaskStop(seekValue, seconds).width; final stop = seekValue - 20 / textWidth + 40 * seekValue / textWidth; diff --git a/lib/podcasts/podcast_detail.dart b/lib/podcasts/podcast_detail.dart index 2678eb0..56139b5 100644 --- a/lib/podcasts/podcast_detail.dart +++ b/lib/podcasts/podcast_detail.dart @@ -108,7 +108,7 @@ class _PodcastDetailState extends State { @override void dispose() { - _controller!.dispose(); + _controller.dispose(); super.dispose(); } @@ -162,7 +162,7 @@ class _PodcastDetailState extends State { 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 { 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 { }); } } - if (_controller!.offset > 0 && + if (_controller.offset > 0 && mounted && !_scroll) { setState(() => _scroll = true); diff --git a/lib/settings/data_backup.dart b/lib/settings/data_backup.dart index 7b6348a..630b451 100644 --- a/lib/settings/data_backup.dart +++ b/lib/settings/data_backup.dart @@ -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( diff --git a/lib/settings/layouts.dart b/lib/settings/layouts.dart index b9d57a7..059a448 100644 --- a/lib/settings/layouts.dart +++ b/lib/settings/layouts.dart @@ -52,7 +52,7 @@ class _LayoutSettingState extends State { Future _getSearchEngine() async { final storage = KeyValueStorage(searchEngineKey); final index = await storage.getInt(); - return SearchEngine.values[index!]; + return SearchEngine.values[index]; } Future _saveSearchEngine(SearchEngine engine) async { diff --git a/lib/settings/storage.dart b/lib/settings/storage.dart index ffca41a..7122aac 100644 --- a/lib/settings/storage.dart +++ b/lib/settings/storage.dart @@ -30,7 +30,7 @@ class _StorageSettingState extends State 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)); diff --git a/lib/settings/syncing.dart b/lib/settings/syncing.dart index b9a7ed0..395cd00 100644 --- a/lib/settings/syncing.dart +++ b/lib/settings/syncing.dart @@ -85,7 +85,7 @@ class SyncingSetting extends StatelessWidget { await settings.cancelWork(); settings.setWorkManager(value); } - : null, + : (int i) {}, items: [1, 2, 4, 8, 24, 48] .map>((e) { return DropdownMenuItem( diff --git a/lib/state/audio_state.dart b/lib/state/audio_state.dart index 8a0facb..e3bc719 100644 --- a/lib/state/audio_state.dart +++ b/lib/state/audio_state.dart @@ -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 _initAudioData() async { - var index = await (_playerHeightStorage.getInt(defaultValue: 0) as FutureOr); + 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 initPlaylist() async { if (_playlists.isEmpty) { - var playlistEntities = await (_playlistsStorgae.getPlaylists() as FutureOr>); + 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>); - 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); - var endTime = await (_autoSleepTimerEndStorage.getInt(defaultValue: 360) as FutureOr); + var startTime = await (_autoSleepTimerStartStorage.getInt( + defaultValue: 1380) as FutureOr); + var endTime = await (_autoSleepTimerEndStorage.getInt(defaultValue: 360) + as FutureOr); 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 addToPlaylist(EpisodeBrief episode) async { - var episodeNew = await (_dbHelper.getRssItemWithUrl(episode.enclosureUrl) as FutureOr); + var episodeNew = await (_dbHelper.getRssItemWithUrl(episode.enclosureUrl) + as FutureOr); if (episodeNew.isNew == 1) { await _dbHelper.removeEpisodeNewMark(episodeNew.enclosureUrl); } @@ -636,7 +636,8 @@ class AudioPlayerNotifier extends ChangeNotifier { } Future addToPlaylistAt(EpisodeBrief episode, int index) async { - var episodeNew = await (_dbHelper.getRssItemWithUrl(episode.enclosureUrl) as FutureOr); + var episodeNew = await (_dbHelper.getRssItemWithUrl(episode.enclosureUrl) + as FutureOr); if (episodeNew.isNew == 1) { await _dbHelper.removeEpisodeNewMark(episodeNew.enclosureUrl); } @@ -753,7 +754,8 @@ class AudioPlayerNotifier extends ChangeNotifier { } } - void addEpisodesToPlaylist(Playlist playlist, {required List episodes}) { + void addEpisodesToPlaylist(Playlist playlist, + {required List 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, )); diff --git a/lib/state/download_state.dart b/lib/state/download_state.dart index b12ccac..7f279d5 100644 --- a/lib/state/download_state.dart +++ b/lib/state/download_state.dart @@ -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 _getDownloadDirectory() async { final storage = KeyValueStorage(downloadPositionKey); - final index = await (storage.getInt() as FutureOr); - final externalDirs = await (getExternalStorageDirectories() as FutureOr>); - 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>); + query: "SELECT * FROM task WHERE task_id = '${episodeTask.taskId}'") + as FutureOr>); 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 _loadTasks() async { _episodeTasks = []; var dbHelper = DBHelper(); - var tasks = await (FlutterDownloader.loadTasks() as FutureOr>); - 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 _getDownloadDirectory() async { final storage = KeyValueStorage(downloadPositionKey); final index = await (storage.getInt() as FutureOr); - final externalDirs = await (getExternalStorageDirectories() as FutureOr>); + final externalDirs = + await (getExternalStorageDirectories() as FutureOr>); 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>); + query: "SELECT * FROM task WHERE task_id = '${episodeTask.taskId}'") + as FutureOr>); 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>); - 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); } diff --git a/lib/state/podcast_group.dart b/lib/state/podcast_group.dart index c4467e1..592caaf 100644 --- a/lib/state/podcast_group.dart +++ b/lib/state/podcast_group.dart @@ -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 json) { + static GroupEntity fromJson(Map json) { var list = List.from(json['podcastList'] as Iterable); 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 saveOrder(PodcastGroup? group) async { @@ -553,6 +555,8 @@ class GroupList extends ChangeNotifier { } Future subIsolateEntryPoint(SendPort sendPort) async { + if (Platform.isAndroid) SharedPreferencesAndroid.registerWith(); + if (Platform.isAndroid) PathProviderAndroid.registerWith(); var items = []; var _running = false; final listColor = [ @@ -598,13 +602,13 @@ Future 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 == '') { diff --git a/lib/state/setting_state.dart b/lib/state/setting_state.dart index 54e764f..36e8b3e 100644 --- a/lib/state/setting_state.dart +++ b/lib/state/setting_state.dart @@ -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!); } diff --git a/lib/type/playlist.dart b/lib/type/playlist.dart index 2225df2..89d7f47 100644 --- a/lib/type/playlist.dart +++ b/lib/type/playlist.dart @@ -21,7 +21,7 @@ class PlaylistEntity { }; } - static PlaylistEntity fromJson(Map json) { + static PlaylistEntity fromJson(Map json) { var list = List.from(json['episodeList'] as Iterable); return PlaylistEntity(json['name'] as String?, json['id'] as String?, json['isLocal'] == null ? false : json['isLocal'] as bool?, list); diff --git a/lib/util/extension_helper.dart b/lib/util/extension_helper.dart index 61bb420..125a417 100644 --- a/lib/util/extension_helper.dart +++ b/lib/util/extension_helper.dart @@ -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); diff --git a/lib/widgets/custom_dropdown.dart b/lib/widgets/custom_dropdown.dart index d7f0487..051e2a0 100644 --- a/lib/widgets/custom_dropdown.dart +++ b/lib/widgets/custom_dropdown.dart @@ -399,8 +399,7 @@ class _DropdownRoute extends PopupRoute<_DropdownRouteResult> { this.itemHeight, this.dropdownColor, this.displayItemCount, - }) : assert(style != null), - itemHeights = List.filled( + }) : itemHeights = List.filled( items.length, itemHeight ?? kMinInteractiveDimension); final List<_MenuItem?> items; @@ -456,7 +455,7 @@ class _DropdownRoute extends PopupRoute<_DropdownRouteResult> { 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 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 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 extends StatefulWidget { /// [disabledHint] is also null but [hint] is non-null, [hint] will instead /// be displayed. /// {@endtemplate} - final ValueChanged? onChanged; + final ValueChanged onChanged; /// Called when the dropdown button is tapped. /// @@ -1253,7 +1245,7 @@ class _MyDropdownButtonState extends State> Navigator.push(context, _dropdownRoute!).then((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 extends State> 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 extends FormField { 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 extends FormField { '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 extends FormFieldState { @override void didChange(T? value) { super.didChange(value); - assert(widget.onChanged != null); widget.onChanged(value); } diff --git a/lib/widgets/custom_popupmenu.dart b/lib/widgets/custom_popupmenu.dart index 3d682a4..f1705a7 100644 --- a/lib/widgets/custom_popupmenu.dart +++ b/lib/widgets/custom_popupmenu.dart @@ -341,8 +341,7 @@ Future _showMenu({ 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 extends PopupMenuEntry { this.height = kMinInteractiveDimension, this.textStyle, required this.child, - }) : assert(enabled != null), - assert(height != null), - super(key: key); + }) : super(key: key); final int? value; diff --git a/lib/widgets/custom_search_delegate.dart b/lib/widgets/custom_search_delegate.dart index ade2826..fa0c231 100644 --- a/lib/widgets/custom_search_delegate.dart +++ b/lib/widgets/custom_search_delegate.dart @@ -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 showSearch({ required BuildContext context, required SearchDelegate 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( 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 { - /// 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 buildActions(BuildContext context) => []; - /// } - /// ``` - /// {@end-tool} SearchDelegate({ this.searchFieldLabel, this.searchFieldStyle, @@ -116,76 +24,15 @@ abstract class SearchDelegate { 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 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 { ); } - /// 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 { _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 { ..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 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 { _SearchPageRoute? _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 extends PageRoute { _SearchPageRoute({ required this.delegate, - }) : assert(delegate != null) { + }) { assert( delegate._route == null, 'The ${delegate.runtimeType} instance is currently used by another active ' diff --git a/lib/widgets/custom_time_picker.dart b/lib/widgets/custom_time_picker.dart index 1aa42d9..d06dc59 100644 --- a/lib/widgets/custom_time_picker.dart +++ b/lib/widgets/custom_time_picker.dart @@ -916,9 +916,9 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin { duration: _kDialAnimateDuration, ); _thetaTween = Tween(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(); } diff --git a/lib/widgets/episodegrid.dart b/lib/widgets/episodegrid.dart index 3dd6305..4d0ab21 100644 --- a/lib/widgets/episodegrid.dart +++ b/lib/widgets/episodegrid.dart @@ -548,7 +548,7 @@ class EpisodeGrid extends StatelessWidget { child: Selector, bool, bool>>( selector: (_, audio) => Tuple4( - audio?.episode, + audio.episode, audio.queue.episodes.map((e) => e!.enclosureUrl).toList(), audio.episodeState, audio.playerRunning), diff --git a/pubspec.yaml b/pubspec.yaml index dec2249..9b83857 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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