Search ui changed a lot, add podcast detail panel.
Update audio service to latest version.
This commit is contained in:
parent
602cc67342
commit
b619be9a9b
|
@ -1,13 +1,12 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.stonegate.tsacdop"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.stonegate.tsacdop" xmlns:tools="http://schemas.android.com/tools">
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
|
||||||
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
|
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
|
||||||
calls FlutterMain.startInitialization(this); in its onCreate method.
|
calls FlutterMain.startInitialization(this); in its onCreate method.
|
||||||
In most cases you can leave this as-is, but you if you want to provide
|
In most cases you can leave this as-is, but you if you want to provide
|
||||||
additional functionality it is fine to subclass or reimplement
|
additional functionality it is fine to subclass or reimplement
|
||||||
FlutterApplication and put your custom class here. -->
|
FlutterApplication and put your custom class here. -->
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<application android:name="io.flutter.app.FlutterApplication" android:label="Tsacdop" android:icon="@mipmap/ic_launcher_icon" android:roundIcon="@mipmap/ic_launcher_round" android:networkSecurityConfig="@xml/network_security_config">
|
<application android:name="io.flutter.app.FlutterApplication" android:label="Tsacdop" android:icon="@mipmap/ic_launcher_icon" android:roundIcon="@mipmap/ic_launcher_round" android:networkSecurityConfig="@xml/network_security_config">
|
||||||
<activity android:name=".MainActivity" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
|
<activity android:name=".MainActivity" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
|
||||||
<meta-data android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/LaunchTheme" />
|
<meta-data android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/LaunchTheme" />
|
||||||
|
@ -22,11 +21,8 @@
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.media.browse.MediaBrowserService" />
|
<action android:name="android.media.browse.MediaBrowserService" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
|
||||||
</intent-filter>
|
|
||||||
</service>
|
</service>
|
||||||
<receiver android:name="androidx.media.session.MediaButtonReceiver">
|
<receiver android:name="com.ryanheise.audioservice.MediaButtonReceiver">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
|
@ -14,8 +14,9 @@ import 'package:google_fonts/google_fonts.dart';
|
||||||
|
|
||||||
import '../state/audio_state.dart';
|
import '../state/audio_state.dart';
|
||||||
import '../type/episodebrief.dart';
|
import '../type/episodebrief.dart';
|
||||||
|
import '../type/play_histroy.dart';
|
||||||
import '../local_storage/sqflite_localpodcast.dart';
|
import '../local_storage/sqflite_localpodcast.dart';
|
||||||
import '../util/context_extension.dart';
|
import '../util/extension_helper.dart';
|
||||||
import '../util/custompaint.dart';
|
import '../util/custompaint.dart';
|
||||||
import 'episode_download.dart';
|
import 'episode_download.dart';
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import 'package:tsacdop/util/custompaint.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:line_icons/line_icons.dart';
|
import 'package:line_icons/line_icons.dart';
|
||||||
|
|
||||||
import '../util/context_extension.dart';
|
import '../util/extension_helper.dart';
|
||||||
|
|
||||||
const String version = '0.4.7';
|
const String version = '0.4.7';
|
||||||
|
|
||||||
|
|
|
@ -9,17 +9,18 @@ import 'package:audio_service/audio_service.dart';
|
||||||
import 'package:line_icons/line_icons.dart';
|
import 'package:line_icons/line_icons.dart';
|
||||||
|
|
||||||
import '../type/episodebrief.dart';
|
import '../type/episodebrief.dart';
|
||||||
|
import '../type/play_histroy.dart';
|
||||||
import '../state/audio_state.dart';
|
import '../state/audio_state.dart';
|
||||||
import '../local_storage/sqflite_localpodcast.dart';
|
import '../local_storage/sqflite_localpodcast.dart';
|
||||||
import '../local_storage/key_value_storage.dart';
|
import '../local_storage/key_value_storage.dart';
|
||||||
import '../util/pageroute.dart';
|
import '../util/pageroute.dart';
|
||||||
import '../util/colorize.dart';
|
import '../util/colorize.dart';
|
||||||
import '../util/context_extension.dart';
|
import '../util/extension_helper.dart';
|
||||||
import '../util/custompaint.dart';
|
import '../util/custompaint.dart';
|
||||||
import '../util/custom_slider.dart';
|
import '../util/custom_slider.dart';
|
||||||
import '../episodes/episode_detail.dart';
|
import '../episodes/episode_detail.dart';
|
||||||
import 'playlist.dart';
|
import 'playlist.dart';
|
||||||
import 'audiopanel.dart';
|
import '../util/audiopanel.dart';
|
||||||
|
|
||||||
final List<BoxShadow> _customShadow = [
|
final List<BoxShadow> _customShadow = [
|
||||||
BoxShadow(blurRadius: 26, offset: Offset(-6, -6), color: Colors.white),
|
BoxShadow(blurRadius: 26, offset: Offset(-6, -6), color: Colors.white),
|
||||||
|
@ -438,9 +439,9 @@ class _PlayerWidgetState extends State<PlayerWidget> {
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: Selector<AudioPlayerNotifier,
|
child: Selector<AudioPlayerNotifier,
|
||||||
Tuple3<BasicPlaybackState, double, String>>(
|
Tuple3<bool, double, String>>(
|
||||||
selector: (_, audio) => Tuple3(
|
selector: (_, audio) => Tuple3(
|
||||||
audio.audioState,
|
audio.buffering,
|
||||||
(audio.backgroundAudioDuration -
|
(audio.backgroundAudioDuration -
|
||||||
audio.backgroundAudioPosition) /
|
audio.backgroundAudioPosition) /
|
||||||
1000,
|
1000,
|
||||||
|
@ -453,12 +454,7 @@ class _PlayerWidgetState extends State<PlayerWidget> {
|
||||||
? Text(data.item3,
|
? Text(data.item3,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: const Color(0xFFFF0000)))
|
color: const Color(0xFFFF0000)))
|
||||||
: data.item1 == BasicPlaybackState.buffering ||
|
: data.item1
|
||||||
data.item1 ==
|
|
||||||
BasicPlaybackState.connecting ||
|
|
||||||
data.item1 ==
|
|
||||||
BasicPlaybackState.skippingToNext ||
|
|
||||||
data.item1 == BasicPlaybackState.stopped
|
|
||||||
? Text(
|
? Text(
|
||||||
s.buffering,
|
s.buffering,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
@ -475,35 +471,18 @@ class _PlayerWidgetState extends State<PlayerWidget> {
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: Selector<AudioPlayerNotifier, BasicPlaybackState>(
|
child: Selector<AudioPlayerNotifier, Tuple2<bool, bool>>(
|
||||||
selector: (_, audio) => audio.audioState,
|
selector: (_, audio) =>
|
||||||
builder: (_, audioplay, __) {
|
Tuple2(audio.buffering, audio.playing),
|
||||||
|
builder: (_, data, __) {
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
//Spacer(),
|
//Spacer(),
|
||||||
audioplay == BasicPlaybackState.playing
|
data.item1
|
||||||
? InkWell(
|
? Stack(
|
||||||
onTap:
|
alignment: Alignment.center,
|
||||||
audioplay == BasicPlaybackState.playing
|
children: <Widget>[
|
||||||
? () {
|
|
||||||
audio.pauseAduio();
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
child: ImageRotate(
|
|
||||||
title: audio.episode?.title,
|
|
||||||
path: audio.episode?.imagePath),
|
|
||||||
)
|
|
||||||
: InkWell(
|
|
||||||
onTap:
|
|
||||||
audioplay == BasicPlaybackState.playing
|
|
||||||
? null
|
|
||||||
: () {
|
|
||||||
audio.resumeAudio();
|
|
||||||
},
|
|
||||||
child: Stack(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
children: <Widget>[
|
|
||||||
Container(
|
Container(
|
||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(
|
||||||
vertical: 10.0),
|
vertical: 10.0),
|
||||||
|
@ -521,13 +500,48 @@ class _PlayerWidgetState extends State<PlayerWidget> {
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
color: Colors.black),
|
color: Colors.black),
|
||||||
),
|
),
|
||||||
Icon(
|
])
|
||||||
Icons.play_arrow,
|
: data.item2
|
||||||
color: Colors.white,
|
? InkWell(
|
||||||
)
|
onTap: data.item2
|
||||||
],
|
? () => audio.pauseAduio()
|
||||||
),
|
: null,
|
||||||
),
|
child: ImageRotate(
|
||||||
|
title: audio.episode?.title,
|
||||||
|
path: audio.episode?.imagePath),
|
||||||
|
)
|
||||||
|
: InkWell(
|
||||||
|
onTap: data.item2
|
||||||
|
? null
|
||||||
|
: () => audio.resumeAudio(),
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
vertical: 10.0),
|
||||||
|
child: Container(
|
||||||
|
height: 30.0,
|
||||||
|
width: 30.0,
|
||||||
|
child: CircleAvatar(
|
||||||
|
backgroundImage: FileImage(File(
|
||||||
|
"${audio.episode.imagePath}")),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
height: 40.0,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: Colors.black),
|
||||||
|
),
|
||||||
|
if (!data.item1)
|
||||||
|
Icon(
|
||||||
|
Icons.play_arrow,
|
||||||
|
color: Colors.white,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
onPressed: () => audio.playNext(),
|
onPressed: () => audio.playNext(),
|
||||||
|
@ -1148,7 +1162,7 @@ class _ControlPanelState extends State<ControlPanel>
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
|
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
|
||||||
return Container(
|
return Container(
|
||||||
color: Theme.of(context).primaryColor,
|
color: context.primaryColor,
|
||||||
height: 300,
|
height: 300,
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10.0),
|
padding: EdgeInsets.symmetric(horizontal: 10.0),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
|
@ -1159,10 +1173,9 @@ class _ControlPanelState extends State<ControlPanel>
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Consumer<AudioPlayerNotifier>(
|
Consumer<AudioPlayerNotifier>(
|
||||||
builder: (_, data, __) {
|
builder: (_, data, __) {
|
||||||
Color _c =
|
Color _c = (context.brightness == Brightness.light)
|
||||||
(Theme.of(context).brightness == Brightness.light)
|
? data.episode.primaryColor.colorizedark()
|
||||||
? data.episode.primaryColor.colorizedark()
|
: data.episode.primaryColor.colorizeLight();
|
||||||
: data.episode.primaryColor.colorizeLight();
|
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
@ -1217,15 +1230,16 @@ class _ControlPanelState extends State<ControlPanel>
|
||||||
color: const Color(0xFFFF0000)))
|
color: const Color(0xFFFF0000)))
|
||||||
: Text(
|
: Text(
|
||||||
data.audioState ==
|
data.audioState ==
|
||||||
BasicPlaybackState
|
AudioProcessingState
|
||||||
.buffering ||
|
.buffering ||
|
||||||
data.audioState ==
|
data.audioState ==
|
||||||
BasicPlaybackState
|
AudioProcessingState
|
||||||
.connecting ||
|
.connecting ||
|
||||||
data.audioState ==
|
data.audioState ==
|
||||||
BasicPlaybackState.none ||
|
AudioProcessingState
|
||||||
|
.none ||
|
||||||
data.audioState ==
|
data.audioState ==
|
||||||
BasicPlaybackState
|
AudioProcessingState
|
||||||
.skippingToNext
|
.skippingToNext
|
||||||
? context.s.buffering
|
? context.s.buffering
|
||||||
: '',
|
: '',
|
||||||
|
@ -1250,9 +1264,9 @@ class _ControlPanelState extends State<ControlPanel>
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
height: 100,
|
height: 100,
|
||||||
child: Selector<AudioPlayerNotifier, BasicPlaybackState>(
|
child: Selector<AudioPlayerNotifier, bool>(
|
||||||
selector: (_, audio) => audio.audioState,
|
selector: (_, audio) => audio.playing,
|
||||||
builder: (_, backplay, __) {
|
builder: (_, playing, __) {
|
||||||
return Material(
|
return Material(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: Row(
|
child: Row(
|
||||||
|
@ -1261,10 +1275,9 @@ class _ControlPanelState extends State<ControlPanel>
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 25.0),
|
padding: EdgeInsets.symmetric(horizontal: 25.0),
|
||||||
onPressed:
|
onPressed: playing
|
||||||
backplay == BasicPlaybackState.playing
|
? () => audio.forwardAudio(-10)
|
||||||
? () => audio.forwardAudio(-10)
|
: null,
|
||||||
: null,
|
|
||||||
iconSize: 32.0,
|
iconSize: 32.0,
|
||||||
icon: Icon(Icons.replay_10),
|
icon: Icon(Icons.replay_10),
|
||||||
color: Colors.grey[500]),
|
color: Colors.grey[500]),
|
||||||
|
@ -1285,14 +1298,13 @@ class _ControlPanelState extends State<ControlPanel>
|
||||||
Brightness.dark
|
Brightness.dark
|
||||||
? _customShadowNight
|
? _customShadowNight
|
||||||
: _customShadow),
|
: _customShadow),
|
||||||
child: backplay == BasicPlaybackState.playing
|
child: playing
|
||||||
? Material(
|
? Material(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
borderRadius: BorderRadius.all(
|
borderRadius: BorderRadius.all(
|
||||||
Radius.circular(30)),
|
Radius.circular(30)),
|
||||||
onTap: backplay ==
|
onTap: playing
|
||||||
BasicPlaybackState.playing
|
|
||||||
? () {
|
? () {
|
||||||
audio.pauseAduio();
|
audio.pauseAduio();
|
||||||
}
|
}
|
||||||
|
@ -1312,8 +1324,7 @@ class _ControlPanelState extends State<ControlPanel>
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
borderRadius: BorderRadius.all(
|
borderRadius: BorderRadius.all(
|
||||||
Radius.circular(30)),
|
Radius.circular(30)),
|
||||||
onTap: backplay ==
|
onTap: playing
|
||||||
BasicPlaybackState.playing
|
|
||||||
? null
|
? null
|
||||||
: () {
|
: () {
|
||||||
audio.resumeAudio();
|
audio.resumeAudio();
|
||||||
|
@ -1333,10 +1344,9 @@ class _ControlPanelState extends State<ControlPanel>
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 25.0),
|
padding: EdgeInsets.symmetric(horizontal: 25.0),
|
||||||
onPressed:
|
onPressed: playing
|
||||||
backplay == BasicPlaybackState.playing
|
? () => audio.forwardAudio(30)
|
||||||
? () => audio.forwardAudio(30)
|
: null,
|
||||||
: null,
|
|
||||||
iconSize: 32.0,
|
iconSize: 32.0,
|
||||||
icon: Icon(Icons.forward_30),
|
icon: Icon(Icons.forward_30),
|
||||||
color: Colors.grey[500]),
|
color: Colors.grey[500]),
|
||||||
|
|
|
@ -11,12 +11,13 @@ import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:feature_discovery/feature_discovery.dart';
|
import 'package:feature_discovery/feature_discovery.dart';
|
||||||
|
|
||||||
import '../state/audio_state.dart';
|
import '../state/audio_state.dart';
|
||||||
|
import '../type/playlist.dart';
|
||||||
import '../type/episodebrief.dart';
|
import '../type/episodebrief.dart';
|
||||||
import '../local_storage/sqflite_localpodcast.dart';
|
import '../local_storage/sqflite_localpodcast.dart';
|
||||||
import '../local_storage/key_value_storage.dart';
|
import '../local_storage/key_value_storage.dart';
|
||||||
import '../util/episodegrid.dart';
|
import '../util/episodegrid.dart';
|
||||||
import '../util/mypopupmenu.dart';
|
import '../util/mypopupmenu.dart';
|
||||||
import '../util/context_extension.dart';
|
import '../util/extension_helper.dart';
|
||||||
import '../util/custompaint.dart';
|
import '../util/custompaint.dart';
|
||||||
import '../state/download_state.dart';
|
import '../state/download_state.dart';
|
||||||
import '../state/podcast_group.dart';
|
import '../state/podcast_group.dart';
|
||||||
|
|
|
@ -12,6 +12,7 @@ import 'package:tuple/tuple.dart';
|
||||||
import 'package:line_icons/line_icons.dart';
|
import 'package:line_icons/line_icons.dart';
|
||||||
|
|
||||||
import '../type/episodebrief.dart';
|
import '../type/episodebrief.dart';
|
||||||
|
import '../type/play_histroy.dart';
|
||||||
import '../state/podcast_group.dart';
|
import '../state/podcast_group.dart';
|
||||||
import '../state/download_state.dart';
|
import '../state/download_state.dart';
|
||||||
import '../type/podcastlocal.dart';
|
import '../type/podcastlocal.dart';
|
||||||
|
@ -19,7 +20,7 @@ import '../state/audio_state.dart';
|
||||||
import '../util/custompaint.dart';
|
import '../util/custompaint.dart';
|
||||||
import '../util/pageroute.dart';
|
import '../util/pageroute.dart';
|
||||||
import '../util/colorize.dart';
|
import '../util/colorize.dart';
|
||||||
import '../util/context_extension.dart';
|
import '../util/extension_helper.dart';
|
||||||
import '../local_storage/sqflite_localpodcast.dart';
|
import '../local_storage/sqflite_localpodcast.dart';
|
||||||
import '../local_storage/key_value_storage.dart';
|
import '../local_storage/key_value_storage.dart';
|
||||||
import '../episodes/episode_detail.dart';
|
import '../episodes/episode_detail.dart';
|
||||||
|
|
|
@ -15,7 +15,7 @@ import 'package:intl/intl.dart';
|
||||||
|
|
||||||
import '../settings/settting.dart';
|
import '../settings/settting.dart';
|
||||||
import '../state/refresh_podcast.dart';
|
import '../state/refresh_podcast.dart';
|
||||||
import '../util/context_extension.dart';
|
import '../util/extension_helper.dart';
|
||||||
import 'about.dart';
|
import 'about.dart';
|
||||||
|
|
||||||
class PopupMenu extends StatefulWidget {
|
class PopupMenu extends StatefulWidget {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import '../state/podcast_group.dart';
|
||||||
import '../state/download_state.dart';
|
import '../state/download_state.dart';
|
||||||
import '../state/refresh_podcast.dart';
|
import '../state/refresh_podcast.dart';
|
||||||
import '../type/episodebrief.dart';
|
import '../type/episodebrief.dart';
|
||||||
import '../util/context_extension.dart';
|
import '../util/extension_helper.dart';
|
||||||
|
|
||||||
class Import extends StatelessWidget {
|
class Import extends StatelessWidget {
|
||||||
Widget importColumn(String text, BuildContext context) {
|
Widget importColumn(String text, BuildContext context) {
|
||||||
|
|
|
@ -10,7 +10,8 @@ import 'package:line_icons/line_icons.dart';
|
||||||
|
|
||||||
import '../state/audio_state.dart';
|
import '../state/audio_state.dart';
|
||||||
import '../type/episodebrief.dart';
|
import '../type/episodebrief.dart';
|
||||||
import '../util/context_extension.dart';
|
import '../type/playlist.dart';
|
||||||
|
import '../util/extension_helper.dart';
|
||||||
import '../util/custompaint.dart';
|
import '../util/custompaint.dart';
|
||||||
import '../util/colorize.dart';
|
import '../util/colorize.dart';
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -4,7 +4,7 @@ import 'package:provider/provider.dart';
|
||||||
import '../state/setting_state.dart';
|
import '../state/setting_state.dart';
|
||||||
import '../home/home.dart';
|
import '../home/home.dart';
|
||||||
import '../util/pageroute.dart';
|
import '../util/pageroute.dart';
|
||||||
import '../util/context_extension.dart';
|
import '../util/extension_helper.dart';
|
||||||
import 'fourthpage.dart';
|
import 'fourthpage.dart';
|
||||||
import 'secondpage.dart';
|
import 'secondpage.dart';
|
||||||
import 'thirdpage.dart';
|
import 'thirdpage.dart';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flare_flutter/flare_actor.dart';
|
import 'package:flare_flutter/flare_actor.dart';
|
||||||
import '../util/context_extension.dart';
|
import '../util/extension_helper.dart';
|
||||||
|
|
||||||
class FirstPage extends StatefulWidget {
|
class FirstPage extends StatefulWidget {
|
||||||
FirstPage({Key key}) : super(key: key);
|
FirstPage({Key key}) : super(key: key);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flare_flutter/flare_actor.dart';
|
import 'package:flare_flutter/flare_actor.dart';
|
||||||
import '../util/context_extension.dart';
|
import '../util/extension_helper.dart';
|
||||||
|
|
||||||
class FourthPage extends StatefulWidget {
|
class FourthPage extends StatefulWidget {
|
||||||
FourthPage({Key key}) : super(key: key);
|
FourthPage({Key key}) : super(key: key);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flare_flutter/flare_actor.dart';
|
import 'package:flare_flutter/flare_actor.dart';
|
||||||
import '../util/context_extension.dart';
|
import '../util/extension_helper.dart';
|
||||||
|
|
||||||
class SecondPage extends StatefulWidget {
|
class SecondPage extends StatefulWidget {
|
||||||
SecondPage({Key key}) : super(key: key);
|
SecondPage({Key key}) : super(key: key);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flare_flutter/flare_actor.dart';
|
import 'package:flare_flutter/flare_actor.dart';
|
||||||
import '../util/context_extension.dart';
|
import '../util/extension_helper.dart';
|
||||||
|
|
||||||
class ThirdPage extends StatefulWidget {
|
class ThirdPage extends StatefulWidget {
|
||||||
ThirdPage({Key key}) : super(key: key);
|
ThirdPage({Key key}) : super(key: key);
|
||||||
|
|
|
@ -25,13 +25,14 @@ const String downloadLayoutKey = 'downloadLayoutKey';
|
||||||
const String autoDownloadNetworkKey = 'autoDownloadNetwork';
|
const String autoDownloadNetworkKey = 'autoDownloadNetwork';
|
||||||
const String episodePopupMenuKey = 'episodePopupMenuKey';
|
const String episodePopupMenuKey = 'episodePopupMenuKey';
|
||||||
const String autoDeleteKey = 'autoDeleteKey';
|
const String autoDeleteKey = 'autoDeleteKey';
|
||||||
//SleepTImer
|
|
||||||
const String autoSleepTimerKey = 'autoSleepTimerKey';
|
const String autoSleepTimerKey = 'autoSleepTimerKey';
|
||||||
const String autoSleepTimerStartKey = 'autoSleepTimerStartKey';
|
const String autoSleepTimerStartKey = 'autoSleepTimerStartKey';
|
||||||
const String autoSleepTimerEndKey = 'autoSleepTimerEndKey';
|
const String autoSleepTimerEndKey = 'autoSleepTimerEndKey';
|
||||||
const String defaultSleepTimerKey = 'defaultSleepTimerKey';
|
const String defaultSleepTimerKey = 'defaultSleepTimerKey';
|
||||||
const String autoSleepTimerModeKey = 'autoSleepTimerModeKey';
|
const String autoSleepTimerModeKey = 'autoSleepTimerModeKey';
|
||||||
const String tapToOpenPopupMenuKey = 'tapToOpenPopupMenuKey';
|
const String tapToOpenPopupMenuKey = 'tapToOpenPopupMenuKey';
|
||||||
|
const String fastForwardSecondsKey = 'fastForwardSecondsKey';
|
||||||
|
const String rewindSecondsKey = 'rewindSecondsKey';
|
||||||
|
|
||||||
class KeyValueStorage {
|
class KeyValueStorage {
|
||||||
final String key;
|
final String key;
|
||||||
|
|
|
@ -5,7 +5,7 @@ import 'package:intl/intl.dart';
|
||||||
import 'package:flutter_downloader/flutter_downloader.dart';
|
import 'package:flutter_downloader/flutter_downloader.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import '../type/podcastlocal.dart';
|
import '../type/podcastlocal.dart';
|
||||||
import '../state/audio_state.dart';
|
import '../type/play_histroy.dart';
|
||||||
import '../type/episodebrief.dart';
|
import '../type/episodebrief.dart';
|
||||||
import '../webfeed/webfeed.dart';
|
import '../webfeed/webfeed.dart';
|
||||||
import '../type/sub_history.dart';
|
import '../type/sub_history.dart';
|
||||||
|
|
|
@ -16,13 +16,14 @@ import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
|
||||||
import '../type/podcastlocal.dart';
|
import '../type/podcastlocal.dart';
|
||||||
import '../type/episodebrief.dart';
|
import '../type/episodebrief.dart';
|
||||||
|
import '../type/play_histroy.dart';
|
||||||
import '../local_storage/sqflite_localpodcast.dart';
|
import '../local_storage/sqflite_localpodcast.dart';
|
||||||
import '../local_storage/key_value_storage.dart';
|
import '../local_storage/key_value_storage.dart';
|
||||||
import '../util/episodegrid.dart';
|
import '../util/episodegrid.dart';
|
||||||
import '../home/audioplayer.dart';
|
import '../home/audioplayer.dart';
|
||||||
import '../type/fireside_data.dart';
|
import '../type/fireside_data.dart';
|
||||||
import '../util/colorize.dart';
|
import '../util/colorize.dart';
|
||||||
import '../util/context_extension.dart';
|
import '../util/extension_helper.dart';
|
||||||
import '../util/custompaint.dart';
|
import '../util/custompaint.dart';
|
||||||
import '../util/general_dialog.dart';
|
import '../util/general_dialog.dart';
|
||||||
import '../state/audio_state.dart';
|
import '../state/audio_state.dart';
|
||||||
|
|
|
@ -13,7 +13,7 @@ import '../type/podcastlocal.dart';
|
||||||
import '../local_storage/sqflite_localpodcast.dart';
|
import '../local_storage/sqflite_localpodcast.dart';
|
||||||
import '../util/colorize.dart';
|
import '../util/colorize.dart';
|
||||||
import '../util/duraiton_picker.dart';
|
import '../util/duraiton_picker.dart';
|
||||||
import '../util/context_extension.dart';
|
import '../util/extension_helper.dart';
|
||||||
import '../util/general_dialog.dart';
|
import '../util/general_dialog.dart';
|
||||||
|
|
||||||
class PodcastGroupList extends StatefulWidget {
|
class PodcastGroupList extends StatefulWidget {
|
||||||
|
|
|
@ -12,7 +12,7 @@ import '../state/podcast_group.dart';
|
||||||
import 'podcast_group.dart';
|
import 'podcast_group.dart';
|
||||||
import 'podcastlist.dart';
|
import 'podcastlist.dart';
|
||||||
import '../util/pageroute.dart';
|
import '../util/pageroute.dart';
|
||||||
import '../util/context_extension.dart';
|
import '../util/extension_helper.dart';
|
||||||
import '../util/general_dialog.dart';
|
import '../util/general_dialog.dart';
|
||||||
import 'custom_tabview.dart';
|
import 'custom_tabview.dart';
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ import '../type/podcastlocal.dart';
|
||||||
import '../local_storage/sqflite_localpodcast.dart';
|
import '../local_storage/sqflite_localpodcast.dart';
|
||||||
import 'podcast_detail.dart';
|
import 'podcast_detail.dart';
|
||||||
import '../util/pageroute.dart';
|
import '../util/pageroute.dart';
|
||||||
import '../util/context_extension.dart';
|
import '../util/extension_helper.dart';
|
||||||
|
|
||||||
class AboutPodcast extends StatefulWidget {
|
class AboutPodcast extends StatefulWidget {
|
||||||
final PodcastLocal podcastLocal;
|
final PodcastLocal podcastLocal;
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
|
||||||
|
import '../type/searchpodcast.dart';
|
||||||
|
import '../type/searchepisodes.dart';
|
||||||
|
import '../.env.dart';
|
||||||
|
|
||||||
|
class SearchEngine {
|
||||||
|
searchPodcasts({String searchText, int nextOffset}) async {
|
||||||
|
String apiKey = environment['apiKey'];
|
||||||
|
String url = "https://listen-api.listennotes.com/api/v2/search?q=" +
|
||||||
|
Uri.encodeComponent(searchText) +
|
||||||
|
"&sort_by_date=0&type=podcast&offset=$nextOffset";
|
||||||
|
Response response = await Dio().get(url,
|
||||||
|
options: Options(headers: {
|
||||||
|
'X-ListenAPI-Key': "$apiKey",
|
||||||
|
'Accept': "application/json"
|
||||||
|
}));
|
||||||
|
Map searchResultMap = jsonDecode(response.toString());
|
||||||
|
var searchResult = SearchPodcast.fromJson(searchResultMap);
|
||||||
|
return searchResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<SearchEpisodes<dynamic>> fetchEpisode(
|
||||||
|
{String id, int nextEpisodeDate}) async {
|
||||||
|
String apiKey = environment['apiKey'];
|
||||||
|
String url =
|
||||||
|
"https://listen-api.listennotes.com/api/v2/podcasts/$id?next_episode_pub_date=$nextEpisodeDate";
|
||||||
|
Response response = await Dio().get(url,
|
||||||
|
options: Options(headers: {
|
||||||
|
'X-ListenAPI-Key': "$apiKey",
|
||||||
|
'Accept': "application/json"
|
||||||
|
}));
|
||||||
|
Map searchResultMap = jsonDecode(response.toString());
|
||||||
|
var searchResult = SearchEpisodes.fromJson(searchResultMap);
|
||||||
|
return searchResult;
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,7 @@ import 'package:wc_flutter_share/wc_flutter_share.dart';
|
||||||
|
|
||||||
import '../state/podcast_group.dart';
|
import '../state/podcast_group.dart';
|
||||||
import '../state/setting_state.dart';
|
import '../state/setting_state.dart';
|
||||||
import '../util/context_extension.dart';
|
import '../util/extension_helper.dart';
|
||||||
import '../service/ompl_build.dart';
|
import '../service/ompl_build.dart';
|
||||||
|
|
||||||
class DataBackup extends StatefulWidget {
|
class DataBackup extends StatefulWidget {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import 'package:provider/provider.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
|
||||||
import '../type/episodebrief.dart';
|
import '../type/episodebrief.dart';
|
||||||
import '../util/context_extension.dart';
|
import '../util/extension_helper.dart';
|
||||||
import '../state/download_state.dart';
|
import '../state/download_state.dart';
|
||||||
import '../local_storage/sqflite_localpodcast.dart';
|
import '../local_storage/sqflite_localpodcast.dart';
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,8 @@ import 'package:tsacdop/state/podcast_group.dart';
|
||||||
import '../local_storage/sqflite_localpodcast.dart';
|
import '../local_storage/sqflite_localpodcast.dart';
|
||||||
import '../webfeed/webfeed.dart';
|
import '../webfeed/webfeed.dart';
|
||||||
import '../type/searchpodcast.dart';
|
import '../type/searchpodcast.dart';
|
||||||
import '../util/context_extension.dart';
|
import '../util/extension_helper.dart';
|
||||||
import '../state/audio_state.dart';
|
import '../type/play_histroy.dart';
|
||||||
import '../state/podcast_group.dart';
|
import '../state/podcast_group.dart';
|
||||||
import '../type/sub_history.dart';
|
import '../type/sub_history.dart';
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import 'package:intl/intl.dart';
|
||||||
import 'package:line_icons/line_icons.dart';
|
import 'package:line_icons/line_icons.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
import '../util/context_extension.dart';
|
import '../util/extension_helper.dart';
|
||||||
import '../generated/l10n.dart';
|
import '../generated/l10n.dart';
|
||||||
|
|
||||||
class LanguagesSetting extends StatefulWidget {
|
class LanguagesSetting extends StatefulWidget {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import '../util/context_extension.dart';
|
import '../util/extension_helper.dart';
|
||||||
import '../util/episodegrid.dart';
|
import '../util/episodegrid.dart';
|
||||||
import '../util/custompaint.dart';
|
import '../util/custompaint.dart';
|
||||||
import '../local_storage/key_value_storage.dart';
|
import '../local_storage/key_value_storage.dart';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import '../util/context_extension.dart';
|
import '../util/extension_helper.dart';
|
||||||
import 'licenses.dart';
|
import 'licenses.dart';
|
||||||
|
|
||||||
class Libries extends StatelessWidget {
|
class Libries extends StatelessWidget {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import 'package:flutter_time_picker_spinner/flutter_time_picker_spinner.dart';
|
||||||
import '../state/setting_state.dart';
|
import '../state/setting_state.dart';
|
||||||
import '../home/audioplayer.dart';
|
import '../home/audioplayer.dart';
|
||||||
import '../util/general_dialog.dart';
|
import '../util/general_dialog.dart';
|
||||||
import '../util/context_extension.dart';
|
import '../util/extension_helper.dart';
|
||||||
import '../util/custom_dropdown.dart';
|
import '../util/custom_dropdown.dart';
|
||||||
|
|
||||||
String stringForMins(int mins) {
|
String stringForMins(int mins) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'package:flutter/services.dart';
|
||||||
import 'package:line_icons/line_icons.dart';
|
import 'package:line_icons/line_icons.dart';
|
||||||
import 'package:flare_flutter/flare_actor.dart';
|
import 'package:flare_flutter/flare_actor.dart';
|
||||||
|
|
||||||
import '../util/context_extension.dart';
|
import '../util/extension_helper.dart';
|
||||||
import '../util/custompaint.dart';
|
import '../util/custompaint.dart';
|
||||||
import '../local_storage/key_value_storage.dart';
|
import '../local_storage/key_value_storage.dart';
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:feature_discovery/feature_discovery.dart';
|
import 'package:feature_discovery/feature_discovery.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
|
|
||||||
import '../util/context_extension.dart';
|
import '../util/extension_helper.dart';
|
||||||
import '../intro_slider/app_intro.dart';
|
import '../intro_slider/app_intro.dart';
|
||||||
import '../home/home.dart';
|
import '../home/home.dart';
|
||||||
import '../podcasts/podcast_manage.dart';
|
import '../podcasts/podcast_manage.dart';
|
||||||
|
|
|
@ -6,7 +6,7 @@ import 'package:google_fonts/google_fonts.dart';
|
||||||
import '../settings/downloads_manage.dart';
|
import '../settings/downloads_manage.dart';
|
||||||
import '../state/setting_state.dart';
|
import '../state/setting_state.dart';
|
||||||
import '../local_storage/key_value_storage.dart';
|
import '../local_storage/key_value_storage.dart';
|
||||||
import '../util/context_extension.dart';
|
import '../util/extension_helper.dart';
|
||||||
import '../util/custom_dropdown.dart';
|
import '../util/custom_dropdown.dart';
|
||||||
|
|
||||||
class StorageSetting extends StatefulWidget {
|
class StorageSetting extends StatefulWidget {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import 'package:provider/provider.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
import '../state/setting_state.dart';
|
import '../state/setting_state.dart';
|
||||||
import '../util/context_extension.dart';
|
import '../util/extension_helper.dart';
|
||||||
import '../util/custom_dropdown.dart';
|
import '../util/custom_dropdown.dart';
|
||||||
|
|
||||||
class SyncingSetting extends StatelessWidget {
|
class SyncingSetting extends StatelessWidget {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'package:flutter/services.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import '../state/setting_state.dart';
|
import '../state/setting_state.dart';
|
||||||
import '../util/context_extension.dart';
|
import '../util/extension_helper.dart';
|
||||||
import '../util/general_dialog.dart';
|
import '../util/general_dialog.dart';
|
||||||
|
|
||||||
class ThemeSetting extends StatelessWidget {
|
class ThemeSetting extends StatelessWidget {
|
||||||
|
|
|
@ -1,19 +1,15 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:math' as math;
|
|
||||||
|
|
||||||
import 'package:path/path.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:audio_service/audio_service.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
import 'package:just_audio/just_audio.dart';
|
import 'package:just_audio/just_audio.dart';
|
||||||
import 'package:rxdart/rxdart.dart';
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
|
|
||||||
import '../type/episodebrief.dart';
|
import '../type/episodebrief.dart';
|
||||||
|
import '../type/play_histroy.dart';
|
||||||
|
import '../type/playlist.dart';
|
||||||
import '../local_storage/key_value_storage.dart';
|
import '../local_storage/key_value_storage.dart';
|
||||||
import '../local_storage/sqflite_localpodcast.dart';
|
import '../local_storage/sqflite_localpodcast.dart';
|
||||||
import '../.env.dart';
|
|
||||||
|
|
||||||
MediaControl playControl = MediaControl(
|
MediaControl playControl = MediaControl(
|
||||||
androidIcon: 'drawable/ic_stat_play_circle_filled',
|
androidIcon: 'drawable/ic_stat_play_circle_filled',
|
||||||
|
@ -50,144 +46,113 @@ void _audioPlayerTaskEntrypoint() async {
|
||||||
AudioServiceBackground.run(() => AudioPlayerTask());
|
AudioServiceBackground.run(() => AudioPlayerTask());
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlayHistory {
|
/// Sleep timer mode.
|
||||||
DBHelper dbHelper = DBHelper();
|
|
||||||
String title;
|
|
||||||
String url;
|
|
||||||
double seconds;
|
|
||||||
double seekValue;
|
|
||||||
DateTime playdate;
|
|
||||||
PlayHistory(this.title, this.url, this.seconds, this.seekValue,
|
|
||||||
{this.playdate});
|
|
||||||
EpisodeBrief _episode;
|
|
||||||
EpisodeBrief get episode => _episode;
|
|
||||||
|
|
||||||
getEpisode() async {
|
|
||||||
_episode = await dbHelper.getRssItemWithUrl(url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Playlist {
|
|
||||||
String name;
|
|
||||||
DBHelper dbHelper = DBHelper();
|
|
||||||
|
|
||||||
List<EpisodeBrief> _playlist;
|
|
||||||
|
|
||||||
List<EpisodeBrief> get playlist => _playlist;
|
|
||||||
KeyValueStorage storage = KeyValueStorage('playlist');
|
|
||||||
|
|
||||||
getPlaylist() async {
|
|
||||||
List<String> urls = await storage.getStringList();
|
|
||||||
if (urls.length == 0) {
|
|
||||||
_playlist = [];
|
|
||||||
} else {
|
|
||||||
_playlist = [];
|
|
||||||
|
|
||||||
for (String url in urls) {
|
|
||||||
EpisodeBrief episode = await dbHelper.getRssItemWithUrl(url);
|
|
||||||
if (episode != null) _playlist.add(episode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
savePlaylist() async {
|
|
||||||
List<String> urls = [];
|
|
||||||
urls.addAll(_playlist.map((e) => e.enclosureUrl));
|
|
||||||
await storage.saveStringList(urls.toSet().toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
addToPlayList(EpisodeBrief episodeBrief) async {
|
|
||||||
if (!_playlist.contains(episodeBrief)) {
|
|
||||||
_playlist.add(episodeBrief);
|
|
||||||
await savePlaylist();
|
|
||||||
dbHelper.removeEpisodeNewMark(episodeBrief.enclosureUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addToPlayListAt(EpisodeBrief episodeBrief, int index) async {
|
|
||||||
if (!_playlist.contains(episodeBrief)) {
|
|
||||||
_playlist.insert(index, episodeBrief);
|
|
||||||
await savePlaylist();
|
|
||||||
dbHelper.removeEpisodeNewMark(episodeBrief.enclosureUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<int> delFromPlaylist(EpisodeBrief episodeBrief) async {
|
|
||||||
int index = _playlist.indexOf(episodeBrief);
|
|
||||||
_playlist.removeWhere(
|
|
||||||
(episode) => episode.enclosureUrl == episodeBrief.enclosureUrl);
|
|
||||||
print('delete' + episodeBrief.title);
|
|
||||||
await savePlaylist();
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum SleepTimerMode { endOfEpisode, timer, undefined }
|
enum SleepTimerMode { endOfEpisode, timer, undefined }
|
||||||
enum ShareStatus { generate, download, complete, undefined, error }
|
|
||||||
|
//enum ShareStatus { generate, download, complete, undefined, error }
|
||||||
|
|
||||||
class AudioPlayerNotifier extends ChangeNotifier {
|
class AudioPlayerNotifier extends ChangeNotifier {
|
||||||
DBHelper dbHelper = DBHelper();
|
DBHelper dbHelper = DBHelper();
|
||||||
KeyValueStorage positionStorage = KeyValueStorage(audioPositionKey);
|
var positionStorage = KeyValueStorage(audioPositionKey);
|
||||||
KeyValueStorage autoPlayStorage = KeyValueStorage(autoPlayKey);
|
var autoPlayStorage = KeyValueStorage(autoPlayKey);
|
||||||
KeyValueStorage autoSleepTimerStorage = KeyValueStorage(autoSleepTimerKey);
|
var autoSleepTimerStorage = KeyValueStorage(autoSleepTimerKey);
|
||||||
KeyValueStorage defaultSleepTimerStorage =
|
var defaultSleepTimerStorage = KeyValueStorage(defaultSleepTimerKey);
|
||||||
KeyValueStorage(defaultSleepTimerKey);
|
var autoSleepTimerModeStorage = KeyValueStorage(autoSleepTimerModeKey);
|
||||||
KeyValueStorage autoSleepTimerModeStorage =
|
var autoSleepTimerStartStorage = KeyValueStorage(autoSleepTimerStartKey);
|
||||||
KeyValueStorage(autoSleepTimerModeKey);
|
var autoSleepTimerEndStorage = KeyValueStorage(autoSleepTimerEndKey);
|
||||||
KeyValueStorage autoSleepTimerStartStorage =
|
var fastForwardSecondsStorage = KeyValueStorage(fastForwardSecondsKey);
|
||||||
KeyValueStorage(autoSleepTimerStartKey);
|
var rewindSecondsStorage = KeyValueStorage(rewindSecondsKey);
|
||||||
KeyValueStorage autoSleepTimerEndStorage =
|
|
||||||
KeyValueStorage(autoSleepTimerEndKey);
|
|
||||||
|
|
||||||
|
/// Current playing episdoe.
|
||||||
EpisodeBrief _episode;
|
EpisodeBrief _episode;
|
||||||
|
|
||||||
|
/// Current playlist.
|
||||||
Playlist _queue = Playlist();
|
Playlist _queue = Playlist();
|
||||||
|
|
||||||
|
/// Notifier for playlist change.
|
||||||
bool _queueUpdate = false;
|
bool _queueUpdate = false;
|
||||||
BasicPlaybackState _audioState = BasicPlaybackState.none;
|
|
||||||
bool _playerRunning = false;
|
/// Player state.
|
||||||
|
AudioProcessingState _audioState = AudioProcessingState.none;
|
||||||
|
|
||||||
|
/// Player playing.
|
||||||
|
bool _playing = false;
|
||||||
|
|
||||||
|
/// Fastforward second.
|
||||||
|
int _fastForwardSeconds;
|
||||||
|
|
||||||
|
/// Rewind seconds.
|
||||||
|
int _rewindSeconds;
|
||||||
|
|
||||||
|
/// No slide, set true if slide on seekbar.
|
||||||
bool _noSlide = true;
|
bool _noSlide = true;
|
||||||
|
|
||||||
|
/// Current episode duration.
|
||||||
int _backgroundAudioDuration = 0;
|
int _backgroundAudioDuration = 0;
|
||||||
|
|
||||||
|
/// Current episode positin.
|
||||||
int _backgroundAudioPosition = 0;
|
int _backgroundAudioPosition = 0;
|
||||||
|
|
||||||
|
/// Erroe maeesage.
|
||||||
String _remoteErrorMessage;
|
String _remoteErrorMessage;
|
||||||
|
|
||||||
|
/// Seekbar value, min 0, max 1.0.
|
||||||
double _seekSliderValue = 0.0;
|
double _seekSliderValue = 0.0;
|
||||||
|
|
||||||
|
/// Record plyaer position.
|
||||||
int _lastPostion = 0;
|
int _lastPostion = 0;
|
||||||
|
|
||||||
|
/// Set true if sleep timer mode is end of episode.
|
||||||
bool _stopOnComplete = false;
|
bool _stopOnComplete = false;
|
||||||
|
|
||||||
|
/// Sleep timer timer.
|
||||||
Timer _stopTimer;
|
Timer _stopTimer;
|
||||||
|
|
||||||
|
/// Sleep timer time left.
|
||||||
int _timeLeft = 0;
|
int _timeLeft = 0;
|
||||||
|
|
||||||
|
/// Start sleep timer.
|
||||||
bool _startSleepTimer = false;
|
bool _startSleepTimer = false;
|
||||||
|
|
||||||
|
/// Control sleep timer anamation.
|
||||||
double _switchValue = 0;
|
double _switchValue = 0;
|
||||||
|
|
||||||
|
/// Sleep timer mode.
|
||||||
SleepTimerMode _sleepTimerMode = SleepTimerMode.undefined;
|
SleepTimerMode _sleepTimerMode = SleepTimerMode.undefined;
|
||||||
|
|
||||||
//Auto stop at the end of episode when you start play at scheduled time.
|
//Auto stop at the end of episode when you start play at scheduled time.
|
||||||
bool _autoSleepTimer;
|
bool _autoSleepTimer;
|
||||||
//Default sleep timer time.
|
|
||||||
ShareStatus _shareStatus = ShareStatus.undefined;
|
|
||||||
String _shareFile = '';
|
|
||||||
//set autoplay episode in playlist
|
//set autoplay episode in playlist
|
||||||
bool _autoPlay;
|
bool _autoPlay;
|
||||||
|
|
||||||
|
/// Datetime now.
|
||||||
DateTime _current;
|
DateTime _current;
|
||||||
|
|
||||||
|
/// Current position.
|
||||||
int _currentPosition;
|
int _currentPosition;
|
||||||
|
|
||||||
|
/// Current speed.
|
||||||
double _currentSpeed = 1;
|
double _currentSpeed = 1;
|
||||||
BehaviorSubject<List<MediaItem>> queueSubject;
|
|
||||||
//Update episode card when setting changed
|
//Update episode card when setting changed
|
||||||
bool _episodeState = false;
|
bool _episodeState = false;
|
||||||
|
|
||||||
BasicPlaybackState get audioState => _audioState;
|
AudioProcessingState get audioState => _audioState;
|
||||||
|
|
||||||
int get backgroundAudioDuration => _backgroundAudioDuration;
|
int get backgroundAudioDuration => _backgroundAudioDuration;
|
||||||
int get backgroundAudioPosition => _backgroundAudioPosition;
|
int get backgroundAudioPosition => _backgroundAudioPosition;
|
||||||
double get seekSliderValue => _seekSliderValue;
|
double get seekSliderValue => _seekSliderValue;
|
||||||
String get remoteErrorMessage => _remoteErrorMessage;
|
String get remoteErrorMessage => _remoteErrorMessage;
|
||||||
bool get playerRunning => _playerRunning;
|
bool get playerRunning => _audioState != AudioProcessingState.none;
|
||||||
|
bool get buffering => _audioState != AudioProcessingState.ready;
|
||||||
int get lastPositin => _lastPostion;
|
int get lastPositin => _lastPostion;
|
||||||
Playlist get queue => _queue;
|
Playlist get queue => _queue;
|
||||||
|
bool get playing => _playing;
|
||||||
bool get queueUpdate => _queueUpdate;
|
bool get queueUpdate => _queueUpdate;
|
||||||
EpisodeBrief get episode => _episode;
|
EpisodeBrief get episode => _episode;
|
||||||
bool get stopOnComplete => _stopOnComplete;
|
bool get stopOnComplete => _stopOnComplete;
|
||||||
bool get startSleepTimer => _startSleepTimer;
|
bool get startSleepTimer => _startSleepTimer;
|
||||||
SleepTimerMode get sleepTimerMode => _sleepTimerMode;
|
SleepTimerMode get sleepTimerMode => _sleepTimerMode;
|
||||||
ShareStatus get shareStatus => _shareStatus;
|
|
||||||
String get shareFile => _shareFile;
|
|
||||||
//bool get autoPlay => _autoPlay;
|
|
||||||
int get timeLeft => _timeLeft;
|
int get timeLeft => _timeLeft;
|
||||||
double get switchValue => _switchValue;
|
double get switchValue => _switchValue;
|
||||||
double get currentSpeed => _currentSpeed;
|
double get currentSpeed => _currentSpeed;
|
||||||
|
@ -199,11 +164,6 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
set setShareStatue(ShareStatus status) {
|
|
||||||
_shareStatus = status;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
set setEpisodeState(bool boo) {
|
set setEpisodeState(bool boo) {
|
||||||
_episodeState = !_episodeState;
|
_episodeState = !_episodeState;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
@ -234,11 +194,9 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||||
if (running) {}
|
if (running) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
loadPlaylist() async {
|
Future<void> loadPlaylist() async {
|
||||||
await _queue.getPlaylist();
|
await _queue.getPlaylist();
|
||||||
await _getAutoPlay();
|
await _getAutoPlay();
|
||||||
// await _getAutoAdd();
|
|
||||||
// await addNewEpisode('all');
|
|
||||||
_lastPostion = await positionStorage.getInt();
|
_lastPostion = await positionStorage.getInt();
|
||||||
if (_lastPostion > 0 && _queue.playlist.length > 0) {
|
if (_lastPostion > 0 && _queue.playlist.length > 0) {
|
||||||
final EpisodeBrief episode = _queue.playlist.first;
|
final EpisodeBrief episode = _queue.playlist.first;
|
||||||
|
@ -252,12 +210,13 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||||
await lastWorkStorage.saveInt(0);
|
await lastWorkStorage.saveInt(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
episodeLoad(EpisodeBrief episode, {int startPosition = 0}) async {
|
Future<void> episodeLoad(EpisodeBrief episode,
|
||||||
|
{int startPosition = 0}) async {
|
||||||
print(episode.enclosureUrl);
|
print(episode.enclosureUrl);
|
||||||
final EpisodeBrief episodeNew =
|
final EpisodeBrief episodeNew =
|
||||||
await dbHelper.getRssItemWithUrl(episode.enclosureUrl);
|
await dbHelper.getRssItemWithUrl(episode.enclosureUrl);
|
||||||
//TODO load episode from last position when player running
|
//TODO load episode from last position when player running
|
||||||
if (_playerRunning) {
|
if (playerRunning) {
|
||||||
PlayHistory history = PlayHistory(_episode.title, _episode.enclosureUrl,
|
PlayHistory history = PlayHistory(_episode.title, _episode.enclosureUrl,
|
||||||
backgroundAudioPosition / 1000, seekSliderValue);
|
backgroundAudioPosition / 1000, seekSliderValue);
|
||||||
await dbHelper.saveHistory(history);
|
await dbHelper.saveHistory(history);
|
||||||
|
@ -275,8 +234,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||||
_backgroundAudioPosition = 0;
|
_backgroundAudioPosition = 0;
|
||||||
_seekSliderValue = 0;
|
_seekSliderValue = 0;
|
||||||
_episode = episodeNew;
|
_episode = episodeNew;
|
||||||
_playerRunning = true;
|
_audioState = AudioProcessingState.connecting;
|
||||||
_audioState = BasicPlaybackState.connecting;
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
//await _queue.savePlaylist();
|
//await _queue.savePlaylist();
|
||||||
_startAudioService(startPosition, episodeNew.enclosureUrl);
|
_startAudioService(startPosition, episodeNew.enclosureUrl);
|
||||||
|
@ -289,18 +247,29 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||||
_startAudioService(int position, String url) async {
|
_startAudioService(int position, String url) async {
|
||||||
_stopOnComplete = false;
|
_stopOnComplete = false;
|
||||||
_sleepTimerMode = SleepTimerMode.undefined;
|
_sleepTimerMode = SleepTimerMode.undefined;
|
||||||
|
|
||||||
|
/// Connect to audio service.
|
||||||
if (!AudioService.connected) {
|
if (!AudioService.connected) {
|
||||||
await AudioService.connect();
|
await AudioService.connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get fastword and rewind seconds.
|
||||||
|
_fastForwardSeconds =
|
||||||
|
await fastForwardSecondsStorage.getInt(defaultValue: 30);
|
||||||
|
_rewindSeconds = await rewindSecondsStorage.getInt(defaultValue: 10);
|
||||||
|
|
||||||
|
/// Start audio service.
|
||||||
await AudioService.start(
|
await AudioService.start(
|
||||||
backgroundTaskEntrypoint: _audioPlayerTaskEntrypoint,
|
backgroundTaskEntrypoint: _audioPlayerTaskEntrypoint,
|
||||||
androidNotificationChannelName: 'Tsacdop',
|
androidNotificationChannelName: 'Tsacdop',
|
||||||
notificationColor: 0xFF4d91be,
|
androidNotificationColor: 0xFF4d91be,
|
||||||
androidNotificationIcon: 'drawable/ic_notification',
|
androidNotificationIcon: 'drawable/ic_notification',
|
||||||
enableQueue: true,
|
androidEnableQueue: true,
|
||||||
androidStopOnRemoveTask: true,
|
androidStopForegroundOnPause: true,
|
||||||
androidStopForegroundOnPause: true);
|
fastForwardInterval: Duration(seconds: _fastForwardSeconds),
|
||||||
//Check autoplay setting
|
rewindInterval: Duration(seconds: _rewindSeconds));
|
||||||
|
|
||||||
|
//Check autoplay setting, if true only add one episode, else add playlist.
|
||||||
await _getAutoPlay();
|
await _getAutoPlay();
|
||||||
if (_autoPlay) {
|
if (_autoPlay) {
|
||||||
for (var episode in _queue.playlist)
|
for (var episode in _queue.playlist)
|
||||||
|
@ -315,7 +284,6 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||||
await autoSleepTimerStartStorage.getInt(defaultValue: 1380);
|
await autoSleepTimerStartStorage.getInt(defaultValue: 1380);
|
||||||
int endTime = await autoSleepTimerEndStorage.getInt(defaultValue: 360);
|
int endTime = await autoSleepTimerEndStorage.getInt(defaultValue: 360);
|
||||||
int currentTime = DateTime.now().hour * 60 + DateTime.now().minute;
|
int currentTime = DateTime.now().hour * 60 + DateTime.now().minute;
|
||||||
print('CurrentTime' + currentTime.toString());
|
|
||||||
if ((startTime > endTime &&
|
if ((startTime > endTime &&
|
||||||
(currentTime > startTime || currentTime < endTime)) ||
|
(currentTime > startTime || currentTime < endTime)) ||
|
||||||
((startTime < endTime) &&
|
((startTime < endTime) &&
|
||||||
|
@ -327,78 +295,106 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||||
sleepTimer(defaultTimer);
|
sleepTimer(defaultTimer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_playerRunning = true;
|
|
||||||
await AudioService.play();
|
await AudioService.play();
|
||||||
|
|
||||||
AudioService.currentMediaItemStream
|
AudioService.currentMediaItemStream
|
||||||
.where((event) => event != null)
|
.where((event) => event != null)
|
||||||
.listen((item) async {
|
.listen((item) async {
|
||||||
EpisodeBrief episode = await dbHelper.getRssItemWithMediaId(item.id);
|
EpisodeBrief episode = await dbHelper.getRssItemWithMediaId(item.id);
|
||||||
|
|
||||||
|
_backgroundAudioDuration = item.duration?.inMilliseconds ?? 0;
|
||||||
if (episode != null) {
|
if (episode != null) {
|
||||||
_episode = episode;
|
_episode = episode;
|
||||||
_backgroundAudioDuration = item?.duration ?? 0;
|
_backgroundAudioDuration = item.duration.inMilliseconds ?? 0;
|
||||||
if (position > 0 &&
|
if (position > 0 &&
|
||||||
_backgroundAudioDuration > 0 &&
|
_backgroundAudioDuration > 0 &&
|
||||||
_episode.enclosureUrl == url) {
|
_episode.enclosureUrl == url) {
|
||||||
AudioService.seekTo(position);
|
AudioService.seekTo(Duration(milliseconds: position));
|
||||||
position = 0;
|
position = 0;
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
} else {
|
} else {
|
||||||
_queue.playlist.removeAt(0);
|
// _queue.playlist.removeAt(0);
|
||||||
AudioService.skipToNext();
|
AudioService.skipToNext();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
queueSubject = BehaviorSubject<List<MediaItem>>();
|
// queueSubject = BehaviorSubject<List<MediaItem>>();
|
||||||
queueSubject.addStream(
|
// queueSubject.addStream(
|
||||||
AudioService.queueStream.distinct().where((event) => event != null));
|
// AudioService.queueStream.distinct().where((event) => event != null));
|
||||||
queueSubject.stream.listen((event) {
|
//queueSubject.stream.
|
||||||
if (event.length == _queue.playlist.length - 1 &&
|
AudioService.customEventStream.distinct().listen((event) async {
|
||||||
_audioState == BasicPlaybackState.skippingToNext) {
|
if (event is String && _episode.title == event) {
|
||||||
if (event.length == 0 || _stopOnComplete) {
|
print(event);
|
||||||
_queue.delFromPlaylist(_episode);
|
_queue.delFromPlaylist(_episode);
|
||||||
_lastPostion = 0;
|
_lastPostion = 0;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
positionStorage.saveInt(_lastPostion);
|
await positionStorage.saveInt(_lastPostion);
|
||||||
final PlayHistory history = PlayHistory(
|
final PlayHistory history = PlayHistory(
|
||||||
_episode.title,
|
_episode.title,
|
||||||
_episode.enclosureUrl,
|
_episode.enclosureUrl,
|
||||||
backgroundAudioPosition / 1000,
|
backgroundAudioPosition / 1000,
|
||||||
seekSliderValue);
|
seekSliderValue);
|
||||||
dbHelper.saveHistory(history);
|
dbHelper.saveHistory(history);
|
||||||
} else if (event.first.id != _episode.mediaId) {
|
|
||||||
_lastPostion = 0;
|
|
||||||
notifyListeners();
|
|
||||||
positionStorage.saveInt(_lastPostion);
|
|
||||||
_queue.delFromPlaylist(_episode);
|
|
||||||
final PlayHistory history = PlayHistory(
|
|
||||||
_episode.title,
|
|
||||||
_episode.enclosureUrl,
|
|
||||||
backgroundAudioPosition / 1000,
|
|
||||||
seekSliderValue);
|
|
||||||
dbHelper.saveHistory(history);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
AudioService.playbackStateStream.listen((event) async {
|
// AudioService.queueStream
|
||||||
|
// .distinct()
|
||||||
|
// .where((event) => event != null)
|
||||||
|
// .listen((event) {
|
||||||
|
// if (event.length == _queue.playlist.length - 1 &&
|
||||||
|
// _audioState == AudioProcessingState.skippingToNext) {
|
||||||
|
// if (event.length == 0 || _stopOnComplete) {
|
||||||
|
// _queue.delFromPlaylist(_episode);
|
||||||
|
// _lastPostion = 0;
|
||||||
|
// notifyListeners();
|
||||||
|
// positionStorage.saveInt(_lastPostion);
|
||||||
|
// final PlayHistory history = PlayHistory(
|
||||||
|
// _episode.title,
|
||||||
|
// _episode.enclosureUrl,
|
||||||
|
// backgroundAudioPosition / 1000,
|
||||||
|
// seekSliderValue);
|
||||||
|
// dbHelper.saveHistory(history);
|
||||||
|
// } else if (event.first.id != _episode.mediaId) {
|
||||||
|
// _lastPostion = 0;
|
||||||
|
// notifyListeners();
|
||||||
|
// positionStorage.saveInt(_lastPostion);
|
||||||
|
// _queue.delFromPlaylist(_episode);
|
||||||
|
// final PlayHistory history = PlayHistory(
|
||||||
|
// _episode.title,
|
||||||
|
// _episode.enclosureUrl,
|
||||||
|
// backgroundAudioPosition / 1000,
|
||||||
|
// seekSliderValue);
|
||||||
|
// dbHelper.saveHistory(history);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
AudioService.playbackStateStream
|
||||||
|
.distinct()
|
||||||
|
.where((event) => event != null)
|
||||||
|
.listen((event) async {
|
||||||
_current = DateTime.now();
|
_current = DateTime.now();
|
||||||
_audioState = event?.basicState;
|
_audioState = event?.processingState;
|
||||||
if (_audioState == BasicPlaybackState.stopped) {
|
_playing = event?.playing;
|
||||||
_playerRunning = false;
|
_currentSpeed = event.speed;
|
||||||
|
_currentPosition = event.currentPosition.inMilliseconds ?? 0;
|
||||||
|
|
||||||
|
if (_audioState == AudioProcessingState.stopped) {
|
||||||
if (_switchValue > 0) _switchValue = 0;
|
if (_switchValue > 0) _switchValue = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_audioState == BasicPlaybackState.error) {
|
/// Get error state.
|
||||||
|
if (_audioState == AudioProcessingState.error) {
|
||||||
_remoteErrorMessage = 'Network Error';
|
_remoteErrorMessage = 'Network Error';
|
||||||
}
|
}
|
||||||
if (_audioState != BasicPlaybackState.error &&
|
|
||||||
_audioState != BasicPlaybackState.paused) {
|
/// Reset error state.
|
||||||
|
if (_audioState != AudioProcessingState.error && _playing) {
|
||||||
_remoteErrorMessage = null;
|
_remoteErrorMessage = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_currentPosition = event?.currentPosition ?? 0;
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -407,7 +403,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||||
Timer.periodic(Duration(milliseconds: 500), (timer) {
|
Timer.periodic(Duration(milliseconds: 500), (timer) {
|
||||||
double s = _currentSpeed ?? 1.0;
|
double s = _currentSpeed ?? 1.0;
|
||||||
if (_noSlide) {
|
if (_noSlide) {
|
||||||
if (_audioState == BasicPlaybackState.playing) {
|
if (_playing) {
|
||||||
getPosition = _currentPosition +
|
getPosition = _currentPosition +
|
||||||
((DateTime.now().difference(_current).inMilliseconds) * s)
|
((DateTime.now().difference(_current).inMilliseconds) * s)
|
||||||
.toInt();
|
.toInt();
|
||||||
|
@ -432,7 +428,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
if (_audioState == BasicPlaybackState.stopped) {
|
if (_audioState == AudioProcessingState.stopped) {
|
||||||
timer.cancel();
|
timer.cancel();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -444,9 +440,8 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||||
_backgroundAudioPosition = 0;
|
_backgroundAudioPosition = 0;
|
||||||
_seekSliderValue = 0;
|
_seekSliderValue = 0;
|
||||||
_episode = _queue.playlist.first;
|
_episode = _queue.playlist.first;
|
||||||
_playerRunning = true;
|
|
||||||
_audioState = BasicPlaybackState.connecting;
|
|
||||||
_queueUpdate = !_queueUpdate;
|
_queueUpdate = !_queueUpdate;
|
||||||
|
_audioState = AudioProcessingState.connecting;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
_startAudioService(_lastPostion ?? 0, _queue.playlist.first.enclosureUrl);
|
_startAudioService(_lastPostion ?? 0, _queue.playlist.first.enclosureUrl);
|
||||||
}
|
}
|
||||||
|
@ -457,7 +452,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||||
|
|
||||||
addToPlaylist(EpisodeBrief episode) async {
|
addToPlaylist(EpisodeBrief episode) async {
|
||||||
if (!_queue.playlist.contains(episode)) {
|
if (!_queue.playlist.contains(episode)) {
|
||||||
if (_playerRunning) {
|
if (playerRunning) {
|
||||||
await AudioService.addQueueItem(episode.toMediaItem());
|
await AudioService.addQueueItem(episode.toMediaItem());
|
||||||
}
|
}
|
||||||
await _queue.addToPlayList(episode);
|
await _queue.addToPlayList(episode);
|
||||||
|
@ -466,7 +461,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
addToPlaylistAt(EpisodeBrief episode, int index) async {
|
addToPlaylistAt(EpisodeBrief episode, int index) async {
|
||||||
if (_playerRunning) {
|
if (playerRunning) {
|
||||||
await AudioService.addQueueItemAt(episode.toMediaItem(), index);
|
await AudioService.addQueueItemAt(episode.toMediaItem(), index);
|
||||||
}
|
}
|
||||||
await _queue.addToPlayListAt(episode, index);
|
await _queue.addToPlayListAt(episode, index);
|
||||||
|
@ -500,7 +495,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> delFromPlaylist(EpisodeBrief episode) async {
|
Future<int> delFromPlaylist(EpisodeBrief episode) async {
|
||||||
if (_playerRunning) {
|
if (playerRunning) {
|
||||||
await AudioService.removeQueueItem(episode.toMediaItem());
|
await AudioService.removeQueueItem(episode.toMediaItem());
|
||||||
}
|
}
|
||||||
int index = await _queue.delFromPlaylist(episode);
|
int index = await _queue.delFromPlaylist(episode);
|
||||||
|
@ -511,7 +506,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||||
|
|
||||||
moveToTop(EpisodeBrief episode) async {
|
moveToTop(EpisodeBrief episode) async {
|
||||||
await delFromPlaylist(episode);
|
await delFromPlaylist(episode);
|
||||||
if (_playerRunning) {
|
if (playerRunning) {
|
||||||
await addToPlaylistAt(episode, 1);
|
await addToPlaylistAt(episode, 1);
|
||||||
} else {
|
} else {
|
||||||
await addToPlaylistAt(episode, 0);
|
await addToPlaylistAt(episode, 0);
|
||||||
|
@ -526,29 +521,29 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
resumeAudio() async {
|
resumeAudio() async {
|
||||||
if (_audioState != BasicPlaybackState.connecting &&
|
if (_audioState != AudioProcessingState.connecting &&
|
||||||
_audioState != BasicPlaybackState.none) AudioService.play();
|
_audioState != AudioProcessingState.none) AudioService.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
forwardAudio(int s) {
|
forwardAudio(int s) {
|
||||||
int pos = _backgroundAudioPosition + s * 1000;
|
int pos = _backgroundAudioPosition + s * 1000;
|
||||||
AudioService.seekTo(pos);
|
AudioService.seekTo(Duration(milliseconds: pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
seekTo(int position) async {
|
seekTo(int position) async {
|
||||||
if (_audioState != BasicPlaybackState.connecting &&
|
if (_audioState != AudioProcessingState.connecting &&
|
||||||
_audioState != BasicPlaybackState.none)
|
_audioState != AudioProcessingState.none)
|
||||||
await AudioService.seekTo(position);
|
await AudioService.seekTo(Duration(milliseconds: position));
|
||||||
}
|
}
|
||||||
|
|
||||||
sliderSeek(double val) async {
|
sliderSeek(double val) async {
|
||||||
if (_audioState != BasicPlaybackState.connecting &&
|
if (_audioState != AudioProcessingState.connecting &&
|
||||||
_audioState != BasicPlaybackState.none) {
|
_audioState != AudioProcessingState.none) {
|
||||||
_noSlide = false;
|
_noSlide = false;
|
||||||
_seekSliderValue = val;
|
_seekSliderValue = val;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
_currentPosition = (val * _backgroundAudioDuration).toInt();
|
_currentPosition = (val * _backgroundAudioDuration).toInt();
|
||||||
await AudioService.seekTo(_currentPosition);
|
await AudioService.seekTo(Duration(milliseconds: _currentPosition));
|
||||||
_noSlide = true;
|
_noSlide = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -579,9 +574,9 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||||
_stopOnComplete = false;
|
_stopOnComplete = false;
|
||||||
_startSleepTimer = false;
|
_startSleepTimer = false;
|
||||||
_switchValue = 0;
|
_switchValue = 0;
|
||||||
_playerRunning = false;
|
//_playerRunning = false;
|
||||||
notifyListeners();
|
|
||||||
AudioService.stop();
|
AudioService.stop();
|
||||||
|
notifyListeners();
|
||||||
AudioService.disconnect();
|
AudioService.disconnect();
|
||||||
});
|
});
|
||||||
} else if (_sleepTimerMode == SleepTimerMode.endOfEpisode) {
|
} else if (_sleepTimerMode == SleepTimerMode.endOfEpisode) {
|
||||||
|
@ -610,51 +605,9 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shareClip(int start, int duration) async {
|
|
||||||
_shareStatus = ShareStatus.generate;
|
|
||||||
notifyListeners();
|
|
||||||
int length = math.min(duration, (_backgroundAudioDuration ~/ 1000 - start));
|
|
||||||
final BaseOptions options = BaseOptions(
|
|
||||||
connectTimeout: 60000,
|
|
||||||
receiveTimeout: 120000,
|
|
||||||
);
|
|
||||||
String imageUrl = await dbHelper.getImageUrl(_episode.enclosureUrl);
|
|
||||||
String url = "https://podcastapi.stonegate.me/clip?" +
|
|
||||||
"audio_link=${_episode.enclosureUrl}&image_link=$imageUrl&title=${_episode.feedTitle}" +
|
|
||||||
"&text=${_episode.title}&start=$start&length=$length";
|
|
||||||
String shareKey = environment['shareKey'];
|
|
||||||
try {
|
|
||||||
Response response = await Dio(options).get(url,
|
|
||||||
options: Options(headers: {
|
|
||||||
'X-Share-Key': "$shareKey",
|
|
||||||
}));
|
|
||||||
String shareLink = response.data;
|
|
||||||
print(shareLink);
|
|
||||||
String fileName = _episode.title + start.toString() + '.mp4';
|
|
||||||
_shareStatus = ShareStatus.download;
|
|
||||||
notifyListeners();
|
|
||||||
Directory dir = await getTemporaryDirectory();
|
|
||||||
String shareDir = join(dir.path, 'share', fileName);
|
|
||||||
try {
|
|
||||||
await Dio().download(shareLink, shareDir);
|
|
||||||
_shareFile = shareDir;
|
|
||||||
_shareStatus = ShareStatus.complete;
|
|
||||||
notifyListeners();
|
|
||||||
} on DioError catch (e) {
|
|
||||||
print(e);
|
|
||||||
_shareStatus = ShareStatus.error;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print(e);
|
|
||||||
_shareStatus = ShareStatus.error;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() async {
|
void dispose() async {
|
||||||
await AudioService.stop();
|
// await AudioService.stop();
|
||||||
await AudioService.disconnect();
|
await AudioService.disconnect();
|
||||||
//_playerRunning = false;
|
//_playerRunning = false;
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
@ -666,66 +619,57 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||||
|
|
||||||
List<MediaItem> _queue = [];
|
List<MediaItem> _queue = [];
|
||||||
AudioPlayer _audioPlayer = AudioPlayer();
|
AudioPlayer _audioPlayer = AudioPlayer();
|
||||||
Completer _completer = Completer();
|
AudioProcessingState _skipState;
|
||||||
BasicPlaybackState _skipState;
|
|
||||||
bool _lostFocus;
|
|
||||||
bool _playing;
|
bool _playing;
|
||||||
|
bool _interrupted = false;
|
||||||
bool _stopAtEnd;
|
bool _stopAtEnd;
|
||||||
int _cacheMax;
|
int _cacheMax;
|
||||||
bool get hasNext => _queue.length > 0;
|
bool get hasNext => _queue.length > 0;
|
||||||
|
|
||||||
MediaItem get mediaItem => _queue.length > 0 ? _queue.first : null;
|
MediaItem get mediaItem => _queue.length > 0 ? _queue.first : null;
|
||||||
|
|
||||||
BasicPlaybackState _stateToBasicState(AudioPlaybackState state) {
|
StreamSubscription<AudioPlaybackState> _playerStateSubscription;
|
||||||
switch (state) {
|
StreamSubscription<AudioPlaybackEvent> _eventSubscription;
|
||||||
case AudioPlaybackState.none:
|
|
||||||
return BasicPlaybackState.none;
|
|
||||||
case AudioPlaybackState.stopped:
|
|
||||||
return _skipState ?? BasicPlaybackState.stopped;
|
|
||||||
case AudioPlaybackState.paused:
|
|
||||||
return BasicPlaybackState.paused;
|
|
||||||
case AudioPlaybackState.playing:
|
|
||||||
return BasicPlaybackState.playing;
|
|
||||||
case AudioPlaybackState.connecting:
|
|
||||||
return _skipState ?? BasicPlaybackState.connecting;
|
|
||||||
case AudioPlaybackState.completed:
|
|
||||||
return BasicPlaybackState.stopped;
|
|
||||||
default:
|
|
||||||
throw Exception("Illegal state");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onStart() async {
|
Future<void> onStart(Map<String, dynamic> params) async {
|
||||||
_stopAtEnd = false;
|
_stopAtEnd = false;
|
||||||
_lostFocus = false;
|
_playerStateSubscription = _audioPlayer.playbackStateStream
|
||||||
|
|
||||||
var playerStateSubscription = _audioPlayer.playbackStateStream
|
|
||||||
.where((state) => state == AudioPlaybackState.completed)
|
.where((state) => state == AudioPlaybackState.completed)
|
||||||
.listen((state) {
|
.listen((state) {
|
||||||
_handlePlaybackCompleted();
|
_handlePlaybackCompleted();
|
||||||
});
|
});
|
||||||
var eventSubscription = _audioPlayer.playbackEventStream.listen((event) {
|
|
||||||
|
_eventSubscription = _audioPlayer.playbackEventStream.listen((event) {
|
||||||
if (event.playbackError != null) {
|
if (event.playbackError != null) {
|
||||||
_setState(state: _skipState ?? BasicPlaybackState.error);
|
_playing = false;
|
||||||
|
_setState(processingState: _skipState ?? AudioProcessingState.error);
|
||||||
}
|
}
|
||||||
BasicPlaybackState state;
|
final bufferingState =
|
||||||
if (event.buffering) {
|
event.buffering ? AudioProcessingState.buffering : null;
|
||||||
state = _skipState ?? BasicPlaybackState.buffering;
|
switch (event.state) {
|
||||||
} else {
|
case AudioPlaybackState.paused:
|
||||||
state = _stateToBasicState(event.state);
|
_setState(
|
||||||
}
|
processingState: bufferingState ?? AudioProcessingState.ready,
|
||||||
if (state != BasicPlaybackState.stopped) {
|
position: event.position,
|
||||||
_setState(
|
);
|
||||||
state: state,
|
break;
|
||||||
position: event.position.inMilliseconds,
|
case AudioPlaybackState.playing:
|
||||||
speed: event.speed,
|
_setState(
|
||||||
);
|
processingState: bufferingState ?? AudioProcessingState.ready,
|
||||||
|
position: event.position,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case AudioPlaybackState.connecting:
|
||||||
|
_setState(
|
||||||
|
processingState: _skipState ?? AudioProcessingState.connecting,
|
||||||
|
position: event.position,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await _completer.future;
|
|
||||||
playerStateSubscription.cancel();
|
|
||||||
eventSubscription.cancel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handlePlaybackCompleted() async {
|
void _handlePlaybackCompleted() async {
|
||||||
|
@ -740,7 +684,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||||
}
|
}
|
||||||
|
|
||||||
void playPause() {
|
void playPause() {
|
||||||
if (AudioServiceBackground.state.basicState == BasicPlaybackState.playing)
|
if (AudioServiceBackground.state.playing)
|
||||||
onPause();
|
onPause();
|
||||||
else
|
else
|
||||||
onPlay();
|
onPlay();
|
||||||
|
@ -748,24 +692,27 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onSkipToNext() async {
|
Future<void> onSkipToNext() async {
|
||||||
_skipState = BasicPlaybackState.skippingToNext;
|
_skipState = AudioProcessingState.skippingToNext;
|
||||||
|
_playing = false;
|
||||||
await _audioPlayer.stop();
|
await _audioPlayer.stop();
|
||||||
if (_queue.length > 0) _queue.removeAt(0);
|
if (_queue.length > 0) {
|
||||||
|
AudioServiceBackground.sendCustomEvent(_queue.first.title);
|
||||||
|
_queue.removeAt(0);
|
||||||
|
}
|
||||||
await AudioServiceBackground.setQueue(_queue);
|
await AudioServiceBackground.setQueue(_queue);
|
||||||
// }
|
// }
|
||||||
if (_queue.length == 0 || _stopAtEnd) {
|
if (_queue.length == 0 || _stopAtEnd) {
|
||||||
// await Future.delayed(Duration(milliseconds: 300));
|
|
||||||
_skipState = null;
|
_skipState = null;
|
||||||
onStop();
|
onStop();
|
||||||
} else {
|
} else {
|
||||||
await AudioServiceBackground.setQueue(_queue);
|
await AudioServiceBackground.setQueue(_queue);
|
||||||
await AudioServiceBackground.setMediaItem(mediaItem);
|
await AudioServiceBackground.setMediaItem(mediaItem);
|
||||||
await _audioPlayer.setUrl(mediaItem.id, _cacheMax);
|
await _audioPlayer.setUrl(mediaItem.id, cacheMax: _cacheMax);
|
||||||
print(mediaItem.title);
|
print(mediaItem.title);
|
||||||
Duration duration = await _audioPlayer.durationFuture;
|
Duration duration = await _audioPlayer.durationFuture;
|
||||||
if (duration != null)
|
if (duration != null)
|
||||||
await AudioServiceBackground.setMediaItem(
|
await AudioServiceBackground.setMediaItem(
|
||||||
mediaItem.copyWith(duration: duration.inMilliseconds));
|
mediaItem.copyWith(duration: duration));
|
||||||
_skipState = null;
|
_skipState = null;
|
||||||
// Resume playback if we were playing
|
// Resume playback if we were playing
|
||||||
// if (_playing) {
|
// if (_playing) {
|
||||||
|
@ -784,35 +731,22 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||||
_playing = true;
|
_playing = true;
|
||||||
_cacheMax = await cacheStorage.getInt(
|
_cacheMax = await cacheStorage.getInt(
|
||||||
defaultValue: (200 * 1024 * 1024).toInt());
|
defaultValue: (200 * 1024 * 1024).toInt());
|
||||||
// await AudioServiceBackground.setQueue(_queue);
|
|
||||||
if (_cacheMax == 0) {
|
if (_cacheMax == 0) {
|
||||||
await cacheStorage.saveInt((200 * 1024 * 1024).toInt());
|
await cacheStorage.saveInt((200 * 1024 * 1024).toInt());
|
||||||
_cacheMax = 200 * 1024 * 1024;
|
_cacheMax = 200 * 1024 * 1024;
|
||||||
}
|
}
|
||||||
await _audioPlayer.setUrl(mediaItem.id, _cacheMax);
|
await _audioPlayer.setUrl(mediaItem.id, cacheMax: _cacheMax);
|
||||||
var duration = await _audioPlayer.durationFuture;
|
var duration = await _audioPlayer.durationFuture;
|
||||||
if (duration != null)
|
if (duration != null)
|
||||||
await AudioServiceBackground.setMediaItem(
|
await AudioServiceBackground.setMediaItem(
|
||||||
mediaItem.copyWith(duration: duration.inMilliseconds));
|
mediaItem.copyWith(duration: duration));
|
||||||
playFromStart();
|
playFromStart();
|
||||||
}
|
} else {
|
||||||
// if (mediaItem.extras['skip'] > 0) {
|
|
||||||
// await _audioPlayer.setClip(
|
|
||||||
// start: Duration(seconds: 60));
|
|
||||||
// print(mediaItem.extras['skip']);
|
|
||||||
// print('set clip success');
|
|
||||||
// }
|
|
||||||
else {
|
|
||||||
_playing = true;
|
_playing = true;
|
||||||
if (_audioPlayer.playbackEvent.state != AudioPlaybackState.connecting ||
|
if (_audioPlayer.playbackEvent.state != AudioPlaybackState.connecting ||
|
||||||
_audioPlayer.playbackEvent.state != AudioPlaybackState.none)
|
_audioPlayer.playbackEvent.state != AudioPlaybackState.none)
|
||||||
_audioPlayer.play();
|
_audioPlayer.play();
|
||||||
}
|
}
|
||||||
// if (mediaItem.extras['skip'] >
|
|
||||||
// _audioPlayer.playbackEvent.position.inSeconds ??
|
|
||||||
// 0) {
|
|
||||||
// _audioPlayer.seek(Duration(seconds: mediaItem.extras['skip']));
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -823,7 +757,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||||
try {
|
try {
|
||||||
_audioPlayer.play();
|
_audioPlayer.play();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_setState(state: BasicPlaybackState.error);
|
_setState(processingState: AudioProcessingState.error);
|
||||||
}
|
}
|
||||||
if (mediaItem.extras['skip'] > 0) {
|
if (mediaItem.extras['skip'] > 0) {
|
||||||
_audioPlayer.seek(Duration(seconds: mediaItem.extras['skip']));
|
_audioPlayer.seek(Duration(seconds: mediaItem.extras['skip']));
|
||||||
|
@ -834,8 +768,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||||
void onPause() {
|
void onPause() {
|
||||||
if (_skipState == null) {
|
if (_skipState == null) {
|
||||||
if (_playing == null) {
|
if (_playing == null) {
|
||||||
} else if (_audioPlayer.playbackEvent.state ==
|
} else if (_playing) {
|
||||||
AudioPlaybackState.playing) {
|
|
||||||
_playing = false;
|
_playing = false;
|
||||||
_audioPlayer.pause();
|
_audioPlayer.pause();
|
||||||
}
|
}
|
||||||
|
@ -843,10 +776,10 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onSeekTo(int position) {
|
void onSeekTo(Duration position) {
|
||||||
if (_audioPlayer.playbackEvent.state != AudioPlaybackState.connecting ||
|
if (_audioPlayer.playbackEvent.state != AudioPlaybackState.connecting ||
|
||||||
_audioPlayer.playbackEvent.state != AudioPlaybackState.none)
|
_audioPlayer.playbackEvent.state != AudioPlaybackState.none)
|
||||||
_audioPlayer.seek(Duration(milliseconds: position));
|
_audioPlayer.seek(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -854,20 +787,26 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||||
if (button == MediaButton.media)
|
if (button == MediaButton.media)
|
||||||
playPause();
|
playPause();
|
||||||
else if (button == MediaButton.next)
|
else if (button == MediaButton.next)
|
||||||
_audioPlayer.seek(Duration(
|
_seekRelative(fastForwardInterval);
|
||||||
milliseconds: AudioServiceBackground.state.position + 30 * 1000));
|
else if (button == MediaButton.previous) _seekRelative(-rewindInterval);
|
||||||
else if (button == MediaButton.previous)
|
}
|
||||||
_audioPlayer.seek(Duration(
|
|
||||||
milliseconds: AudioServiceBackground.state.position - 10 * 1000));
|
Future<void> _seekRelative(Duration offset) async {
|
||||||
|
var newPosition = _audioPlayer.playbackEvent.position + offset;
|
||||||
|
if (newPosition < Duration.zero) newPosition = Duration.zero;
|
||||||
|
if (newPosition > mediaItem.duration) newPosition = mediaItem.duration;
|
||||||
|
await _audioPlayer.seek(newPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onStop() async {
|
Future<void> onStop() async {
|
||||||
await _audioPlayer.stop();
|
await _audioPlayer.stop();
|
||||||
await _audioPlayer.dispose();
|
await _audioPlayer.dispose();
|
||||||
_setState(state: BasicPlaybackState.stopped);
|
_playing = false;
|
||||||
await Future.delayed(Duration(milliseconds: 300));
|
_playerStateSubscription.cancel();
|
||||||
_completer?.complete();
|
_eventSubscription.cancel();
|
||||||
|
await _setState(processingState: AudioProcessingState.none);
|
||||||
|
await super.onStop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -890,10 +829,10 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||||
_queue.insert(0, mediaItem);
|
_queue.insert(0, mediaItem);
|
||||||
await AudioServiceBackground.setQueue(_queue);
|
await AudioServiceBackground.setQueue(_queue);
|
||||||
await AudioServiceBackground.setMediaItem(mediaItem);
|
await AudioServiceBackground.setMediaItem(mediaItem);
|
||||||
await _audioPlayer.setUrl(mediaItem.id, _cacheMax);
|
await _audioPlayer.setUrl(mediaItem.id, cacheMax: _cacheMax);
|
||||||
Duration duration = await _audioPlayer.durationFuture ?? Duration.zero;
|
Duration duration = await _audioPlayer.durationFuture ?? Duration.zero;
|
||||||
AudioServiceBackground.setMediaItem(
|
AudioServiceBackground.setMediaItem(
|
||||||
mediaItem.copyWith(duration: duration.inMilliseconds));
|
mediaItem.copyWith(duration: duration));
|
||||||
playFromStart();
|
playFromStart();
|
||||||
//onPlay();
|
//onPlay();
|
||||||
} else {
|
} else {
|
||||||
|
@ -904,26 +843,26 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onFastForward() {
|
void onFastForward() {
|
||||||
_audioPlayer.seek(Duration(
|
_seekRelative(fastForwardInterval);
|
||||||
milliseconds: AudioServiceBackground.state.position + 30 * 1000));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onRewind() {
|
void onRewind() {
|
||||||
_audioPlayer.seek(Duration(
|
_seekRelative(rewindInterval);
|
||||||
milliseconds: AudioServiceBackground.state.position - 10 * 1000));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onAudioFocusLost() {
|
void onAudioFocusLost(AudioInterruption interruption) {
|
||||||
if (_skipState == null) {
|
if (_playing) _interrupted = true;
|
||||||
if (_playing == null) {
|
switch (interruption) {
|
||||||
} else if (_audioPlayer.playbackEvent.state ==
|
case AudioInterruption.pause:
|
||||||
AudioPlaybackState.playing) {
|
case AudioInterruption.temporaryPause:
|
||||||
_playing = false;
|
case AudioInterruption.unknownPause:
|
||||||
_lostFocus = true;
|
onPause();
|
||||||
_audioPlayer.pause();
|
break;
|
||||||
}
|
case AudioInterruption.temporaryDuck:
|
||||||
|
_audioPlayer.setVolume(0.5);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -940,14 +879,18 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onAudioFocusGained() {
|
void onAudioFocusGained(AudioInterruption interruption) {
|
||||||
if (_skipState == null) {
|
switch (interruption) {
|
||||||
if (_lostFocus) {
|
case AudioInterruption.temporaryPause:
|
||||||
_lostFocus = false;
|
if (!_playing && _interrupted) onPlay();
|
||||||
_playing = true;
|
break;
|
||||||
_audioPlayer.play();
|
case AudioInterruption.temporaryDuck:
|
||||||
}
|
_audioPlayer.setVolume(1.0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
_interrupted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -965,24 +908,27 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setState(
|
Future<void> _setState({
|
||||||
{@required BasicPlaybackState state, int position, double speed}) {
|
AudioProcessingState processingState,
|
||||||
|
Duration position,
|
||||||
|
Duration bufferedPosition,
|
||||||
|
}) async {
|
||||||
if (position == null) {
|
if (position == null) {
|
||||||
position = _audioPlayer.playbackEvent.position.inMilliseconds;
|
position = _audioPlayer.playbackEvent.position;
|
||||||
}
|
}
|
||||||
if (speed == null) {
|
await AudioServiceBackground.setState(
|
||||||
speed = _audioPlayer.playbackEvent.speed;
|
controls: getControls(),
|
||||||
}
|
|
||||||
AudioServiceBackground.setState(
|
|
||||||
controls: getControls(state),
|
|
||||||
systemActions: [MediaAction.seekTo],
|
systemActions: [MediaAction.seekTo],
|
||||||
basicState: state,
|
processingState:
|
||||||
|
processingState ?? AudioServiceBackground.state.processingState,
|
||||||
|
playing: _playing,
|
||||||
position: position,
|
position: position,
|
||||||
speed: speed,
|
bufferedPosition: bufferedPosition ?? position,
|
||||||
|
speed: _audioPlayer.speed,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<MediaControl> getControls(BasicPlaybackState state) {
|
List<MediaControl> getControls() {
|
||||||
if (_playing) {
|
if (_playing) {
|
||||||
return [pauseControl, forward30, skipToNextControl, stopControl];
|
return [pauseControl, forward30, skipToNextControl, stopControl];
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -62,7 +62,7 @@ class EpisodeBrief {
|
||||||
title: title,
|
title: title,
|
||||||
artist: feedTitle,
|
artist: feedTitle,
|
||||||
album: feedTitle,
|
album: feedTitle,
|
||||||
// duration: 0,
|
duration: Duration.zero,
|
||||||
artUri: 'file://$imagePath',
|
artUri: 'file://$imagePath',
|
||||||
extras: {'skip': skipSeconds});
|
extras: {'skip': skipSeconds});
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
import '../local_storage/sqflite_localpodcast.dart';
|
||||||
|
import 'episodebrief.dart';
|
||||||
|
|
||||||
|
class PlayHistory {
|
||||||
|
DBHelper dbHelper = DBHelper();
|
||||||
|
String title;
|
||||||
|
String url;
|
||||||
|
double seconds;
|
||||||
|
double seekValue;
|
||||||
|
DateTime playdate;
|
||||||
|
PlayHistory(this.title, this.url, this.seconds, this.seekValue,
|
||||||
|
{this.playdate});
|
||||||
|
EpisodeBrief _episode;
|
||||||
|
EpisodeBrief get episode => _episode;
|
||||||
|
|
||||||
|
getEpisode() async {
|
||||||
|
_episode = await dbHelper.getRssItemWithUrl(url);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
import '../local_storage/sqflite_localpodcast.dart';
|
||||||
|
import '../local_storage/key_value_storage.dart';
|
||||||
|
import 'episodebrief.dart';
|
||||||
|
|
||||||
|
class Playlist {
|
||||||
|
String name;
|
||||||
|
DBHelper dbHelper = DBHelper();
|
||||||
|
|
||||||
|
List<EpisodeBrief> _playlist;
|
||||||
|
|
||||||
|
List<EpisodeBrief> get playlist => _playlist;
|
||||||
|
KeyValueStorage storage = KeyValueStorage('playlist');
|
||||||
|
|
||||||
|
getPlaylist() async {
|
||||||
|
List<String> urls = await storage.getStringList();
|
||||||
|
if (urls.length == 0) {
|
||||||
|
_playlist = [];
|
||||||
|
} else {
|
||||||
|
_playlist = [];
|
||||||
|
|
||||||
|
for (String url in urls) {
|
||||||
|
EpisodeBrief episode = await dbHelper.getRssItemWithUrl(url);
|
||||||
|
if (episode != null) _playlist.add(episode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
savePlaylist() async {
|
||||||
|
List<String> urls = [];
|
||||||
|
urls.addAll(_playlist.map((e) => e.enclosureUrl));
|
||||||
|
await storage.saveStringList(urls.toSet().toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
addToPlayList(EpisodeBrief episodeBrief) async {
|
||||||
|
if (!_playlist.contains(episodeBrief)) {
|
||||||
|
_playlist.add(episodeBrief);
|
||||||
|
await savePlaylist();
|
||||||
|
dbHelper.removeEpisodeNewMark(episodeBrief.enclosureUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addToPlayListAt(EpisodeBrief episodeBrief, int index) async {
|
||||||
|
if (!_playlist.contains(episodeBrief)) {
|
||||||
|
_playlist.insert(index, episodeBrief);
|
||||||
|
await savePlaylist();
|
||||||
|
dbHelper.removeEpisodeNewMark(episodeBrief.enclosureUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> delFromPlaylist(EpisodeBrief episodeBrief) async {
|
||||||
|
int index = _playlist.indexOf(episodeBrief);
|
||||||
|
_playlist.removeWhere(
|
||||||
|
(episode) => episode.enclosureUrl == episodeBrief.enclosureUrl);
|
||||||
|
print('delete' + episodeBrief.title);
|
||||||
|
await savePlaylist();
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
import 'package:tsacdop/type/searchpodcast.dart';
|
||||||
|
part 'searchepisodes.g.dart';
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class SearchEpisodes<E> {
|
||||||
|
@_ConvertE()
|
||||||
|
final List<E> episodes;
|
||||||
|
@JsonKey(name: 'next_episode_pub_date')
|
||||||
|
final int nextEpisodeDate;
|
||||||
|
SearchEpisodes({this.episodes, this.nextEpisodeDate});
|
||||||
|
factory SearchEpisodes.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SearchEpisodesFromJson<E>(json);
|
||||||
|
Map<String, dynamic> toJson() => _$SearchEpisodesToJson(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ConvertE<E> implements JsonConverter<E, Object> {
|
||||||
|
const _ConvertE();
|
||||||
|
@override
|
||||||
|
E fromJson(Object json) {
|
||||||
|
return OnlineEpisode.fromJson(json) as E;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object toJson(E object) {
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class OnlineEpisode {
|
||||||
|
final String title;
|
||||||
|
@JsonKey(name: 'pub_date_ms')
|
||||||
|
final int pubDate;
|
||||||
|
@JsonKey(name: 'audio_length_sec')
|
||||||
|
final int length;
|
||||||
|
OnlineEpisode({this.title, this.pubDate, this.length});
|
||||||
|
factory OnlineEpisode.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$OnlineEpisodeFromJson(json);
|
||||||
|
Map<String, dynamic> toJson() => _$OnlineEpisodeToJson(this);
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'searchepisodes.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
SearchEpisodes<E> _$SearchEpisodesFromJson<E>(Map<String, dynamic> json) {
|
||||||
|
return SearchEpisodes<E>(
|
||||||
|
episodes:
|
||||||
|
(json['episodes'] as List)?.map(_ConvertE<E>().fromJson)?.toList(),
|
||||||
|
nextEpisodeDate: json['next_episode_pub_date'] as int,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> _$SearchEpisodesToJson<E>(SearchEpisodes<E> instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'episodes': instance.episodes?.map(_ConvertE<E>().toJson)?.toList(),
|
||||||
|
'next_episode_pub_date': instance.nextEpisodeDate,
|
||||||
|
};
|
||||||
|
|
||||||
|
OnlineEpisode _$OnlineEpisodeFromJson(Map<String, dynamic> json) {
|
||||||
|
return OnlineEpisode(
|
||||||
|
title: json['title'] as String,
|
||||||
|
pubDate: json['pub_date_ms'] as int,
|
||||||
|
length: json['audio_length_sec'] as int,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> _$OnlineEpisodeToJson(OnlineEpisode instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'title': instance.title,
|
||||||
|
'pub_date_ms': instance.pubDate,
|
||||||
|
'audio_length_sec': instance.length,
|
||||||
|
};
|
|
@ -1,43 +1,44 @@
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
part 'searchpodcast.g.dart';
|
part 'searchpodcast.g.dart';
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class SearchPodcast<P>{
|
class SearchPodcast<P> {
|
||||||
@_ConvertP()
|
@_ConvertP()
|
||||||
final List<P> results;
|
final List<P> results;
|
||||||
@JsonKey(name: 'next_offset')
|
@JsonKey(name: 'next_offset')
|
||||||
final int nextOffset;
|
final int nextOffset;
|
||||||
final int total;
|
final int total;
|
||||||
final int count;
|
final int count;
|
||||||
SearchPodcast(
|
SearchPodcast({this.results, this.nextOffset, this.total, this.count});
|
||||||
{this.results, this.nextOffset, this.total, this.count}
|
factory SearchPodcast.fromJson(Map<String, dynamic> json) =>
|
||||||
);
|
_$SearchPodcastFromJson<P>(json);
|
||||||
factory SearchPodcast.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$SearchPodcastFromJson<P>(json);
|
|
||||||
Map<String, dynamic> toJson() => _$SearchPodcastToJson(this);
|
Map<String, dynamic> toJson() => _$SearchPodcastToJson(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ConvertP<P> implements JsonConverter<P, Object>{
|
class _ConvertP<P> implements JsonConverter<P, Object> {
|
||||||
const _ConvertP();
|
const _ConvertP();
|
||||||
@override
|
@override
|
||||||
P fromJson(Object json){
|
P fromJson(Object json) {
|
||||||
return OnlinePodcast.fromJson(json) as P;
|
return OnlinePodcast.fromJson(json) as P;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Object toJson(P object){
|
Object toJson(P object) {
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class OnlinePodcast{
|
class OnlinePodcast {
|
||||||
@JsonKey(name: 'earliest_pub_date_ms')
|
@JsonKey(name: 'earliest_pub_date_ms')
|
||||||
final int earliestPubDate;
|
final int earliestPubDate;
|
||||||
@JsonKey(name: 'title_original')
|
@JsonKey(name: 'title_original')
|
||||||
final String title;
|
final String title;
|
||||||
final String rss;
|
final String rss;
|
||||||
@JsonKey(name: 'lastest_pub_date_ms')
|
@JsonKey(name: 'latest_pub_date_ms')
|
||||||
final int lastestPubDate;
|
final int latestPubDate;
|
||||||
@JsonKey(name: 'description_original')
|
@JsonKey(name: 'description_original')
|
||||||
final String description;
|
final String description;
|
||||||
@JsonKey(name: 'total_episodes')
|
@JsonKey(name: 'total_episodes')
|
||||||
|
@ -45,10 +46,30 @@ class OnlinePodcast{
|
||||||
final String image;
|
final String image;
|
||||||
@JsonKey(name: 'publisher_original')
|
@JsonKey(name: 'publisher_original')
|
||||||
final String publisher;
|
final String publisher;
|
||||||
|
final String id;
|
||||||
OnlinePodcast(
|
OnlinePodcast(
|
||||||
{this.earliestPubDate, this.title, this.count, this.description, this.image, this.lastestPubDate, this.rss, this.publisher}
|
{this.earliestPubDate,
|
||||||
);
|
this.title,
|
||||||
|
this.count,
|
||||||
|
this.description,
|
||||||
|
this.image,
|
||||||
|
this.latestPubDate,
|
||||||
|
this.rss,
|
||||||
|
this.publisher,
|
||||||
|
this.id});
|
||||||
factory OnlinePodcast.fromJson(Map<String, dynamic> json) =>
|
factory OnlinePodcast.fromJson(Map<String, dynamic> json) =>
|
||||||
_$OnlinePodcastFromJson(json);
|
_$OnlinePodcastFromJson(json);
|
||||||
Map<String, dynamic> toJson() => _$OnlinePodcastToJson(this);
|
Map<String, dynamic> toJson() => _$OnlinePodcastToJson(this);
|
||||||
}
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object onlinePodcast) =>
|
||||||
|
onlinePodcast is OnlinePodcast && onlinePodcast.id == id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => hashValues(id, title);
|
||||||
|
|
||||||
|
int get interval {
|
||||||
|
if (count < 1) return null;
|
||||||
|
return (latestPubDate - earliestPubDate) ~/ count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,34 +8,33 @@ part of 'searchpodcast.dart';
|
||||||
|
|
||||||
SearchPodcast<P> _$SearchPodcastFromJson<P>(Map<String, dynamic> json) {
|
SearchPodcast<P> _$SearchPodcastFromJson<P>(Map<String, dynamic> json) {
|
||||||
return SearchPodcast<P>(
|
return SearchPodcast<P>(
|
||||||
results: (json['results'] as List)
|
results: (json['results'] as List)?.map(_ConvertP<P>().fromJson)?.toList(),
|
||||||
?.map((e) => e == null ? null : _ConvertP<P>().fromJson(e))
|
nextOffset: json['next_offset'] as int,
|
||||||
?.toList(),
|
total: json['total'] as int,
|
||||||
nextOffset: json['next_offset'] as int,
|
count: json['count'] as int,
|
||||||
total: json['total'] as int,
|
);
|
||||||
count: json['count'] as int);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> _$SearchPodcastToJson<P>(SearchPodcast<P> instance) =>
|
Map<String, dynamic> _$SearchPodcastToJson<P>(SearchPodcast<P> instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'results': instance.results
|
'results': instance.results?.map(_ConvertP<P>().toJson)?.toList(),
|
||||||
?.map((e) => e == null ? null : _ConvertP<P>().toJson(e))
|
|
||||||
?.toList(),
|
|
||||||
'next_offset': instance.nextOffset,
|
'next_offset': instance.nextOffset,
|
||||||
'total': instance.total,
|
'total': instance.total,
|
||||||
'count': instance.count
|
'count': instance.count,
|
||||||
};
|
};
|
||||||
|
|
||||||
OnlinePodcast _$OnlinePodcastFromJson(Map<String, dynamic> json) {
|
OnlinePodcast _$OnlinePodcastFromJson(Map<String, dynamic> json) {
|
||||||
return OnlinePodcast(
|
return OnlinePodcast(
|
||||||
earliestPubDate: json['earliest_pub_date_ms'] as int,
|
earliestPubDate: json['earliest_pub_date_ms'] as int,
|
||||||
title: json['title_original'] as String,
|
title: json['title_original'] as String,
|
||||||
count: json['total_episodes'] as int,
|
count: json['total_episodes'] as int,
|
||||||
description: json['description_original'] as String,
|
description: json['description_original'] as String,
|
||||||
image: json['image'] as String,
|
image: json['image'] as String,
|
||||||
lastestPubDate: json['lastest_pub_date_ms'] as int,
|
latestPubDate: json['latest_pub_date_ms'] as int,
|
||||||
rss: json['rss'] as String,
|
rss: json['rss'] as String,
|
||||||
publisher: json['publisher_original'] as String);
|
publisher: json['publisher_original'] as String,
|
||||||
|
id: json['id'] as String,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> _$OnlinePodcastToJson(OnlinePodcast instance) =>
|
Map<String, dynamic> _$OnlinePodcastToJson(OnlinePodcast instance) =>
|
||||||
|
@ -43,9 +42,10 @@ Map<String, dynamic> _$OnlinePodcastToJson(OnlinePodcast instance) =>
|
||||||
'earliest_pub_date_ms': instance.earliestPubDate,
|
'earliest_pub_date_ms': instance.earliestPubDate,
|
||||||
'title_original': instance.title,
|
'title_original': instance.title,
|
||||||
'rss': instance.rss,
|
'rss': instance.rss,
|
||||||
'lastest_pub_date_ms': instance.lastestPubDate,
|
'latest_pub_date_ms': instance.latestPubDate,
|
||||||
'description_original': instance.description,
|
'description_original': instance.description,
|
||||||
'total_episodes': instance.count,
|
'total_episodes': instance.count,
|
||||||
'image': instance.image,
|
'image': instance.image,
|
||||||
'publisher_original': instance.publisher
|
'publisher_original': instance.publisher,
|
||||||
|
'id': instance.id,
|
||||||
};
|
};
|
||||||
|
|
|
@ -34,12 +34,12 @@ class _AudioPanelState extends State<AudioPanel> with TickerProviderStateMixin {
|
||||||
_controller =
|
_controller =
|
||||||
AnimationController(vsync: this, duration: Duration(milliseconds: 50))
|
AnimationController(vsync: this, duration: Duration(milliseconds: 50))
|
||||||
..addListener(() {
|
..addListener(() {
|
||||||
setState(() {});
|
if (mounted) setState(() {});
|
||||||
});
|
});
|
||||||
_slowController =
|
_slowController =
|
||||||
AnimationController(vsync: this, duration: Duration(milliseconds: 200))
|
AnimationController(vsync: this, duration: Duration(milliseconds: 200))
|
||||||
..addListener(() {
|
..addListener(() {
|
||||||
setState(() {});
|
if (mounted) setState(() {});
|
||||||
});
|
});
|
||||||
_animation =
|
_animation =
|
||||||
Tween<double>(begin: initSize, end: initSize).animate(_controller);
|
Tween<double>(begin: initSize, end: initSize).animate(_controller);
|
|
@ -1,15 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import '../generated/l10n.dart';
|
|
||||||
|
|
||||||
extension ContextExtension on BuildContext {
|
|
||||||
Color get primaryColor => Theme.of(this).primaryColor;
|
|
||||||
Color get accentColor => Theme.of(this).accentColor;
|
|
||||||
Color get scaffoldBackgroundColor => Theme.of(this).scaffoldBackgroundColor;
|
|
||||||
Color get primaryColorDark => Theme.of(this).primaryColorDark;
|
|
||||||
Color get textColor => Theme.of(this).textTheme.bodyText1.color;
|
|
||||||
Brightness get brightness => Theme.of(this).brightness;
|
|
||||||
double get width => MediaQuery.of(this).size.width;
|
|
||||||
double get height => MediaQuery.of(this).size.width;
|
|
||||||
TextTheme get textTheme => Theme.of(this).textTheme;
|
|
||||||
S get s => S.of(this);
|
|
||||||
}
|
|
|
@ -16,11 +16,12 @@ import 'open_container.dart';
|
||||||
import '../state/audio_state.dart';
|
import '../state/audio_state.dart';
|
||||||
import '../state/download_state.dart';
|
import '../state/download_state.dart';
|
||||||
import '../type/episodebrief.dart';
|
import '../type/episodebrief.dart';
|
||||||
|
import '../type/play_histroy.dart';
|
||||||
import '../episodes/episode_detail.dart';
|
import '../episodes/episode_detail.dart';
|
||||||
import '../local_storage/sqflite_localpodcast.dart';
|
import '../local_storage/sqflite_localpodcast.dart';
|
||||||
import '../local_storage/key_value_storage.dart';
|
import '../local_storage/key_value_storage.dart';
|
||||||
import 'colorize.dart';
|
import 'colorize.dart';
|
||||||
import 'context_extension.dart';
|
import 'extension_helper.dart';
|
||||||
import 'custompaint.dart';
|
import 'custompaint.dart';
|
||||||
|
|
||||||
enum Layout { three, two, one }
|
enum Layout { three, two, one }
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import '../generated/l10n.dart';
|
||||||
|
|
||||||
|
extension ContextExtension on BuildContext {
|
||||||
|
Color get primaryColor => Theme.of(this).primaryColor;
|
||||||
|
Color get accentColor => Theme.of(this).accentColor;
|
||||||
|
Color get scaffoldBackgroundColor => Theme.of(this).scaffoldBackgroundColor;
|
||||||
|
Color get primaryColorDark => Theme.of(this).primaryColorDark;
|
||||||
|
Color get textColor => Theme.of(this).textTheme.bodyText1.color;
|
||||||
|
Brightness get brightness => Theme.of(this).brightness;
|
||||||
|
double get width => MediaQuery.of(this).size.width;
|
||||||
|
double get height => MediaQuery.of(this).size.height;
|
||||||
|
TextTheme get textTheme => Theme.of(this).textTheme;
|
||||||
|
S get s => S.of(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
extension IntExtension on int {
|
||||||
|
String toDate(BuildContext context) {
|
||||||
|
if (this == null) return '';
|
||||||
|
final s = context.s;
|
||||||
|
DateTime date = DateTime.fromMillisecondsSinceEpoch(this, isUtc: true);
|
||||||
|
var difference = DateTime.now().toUtc().difference(date);
|
||||||
|
if (difference.inHours < 24) {
|
||||||
|
return s.hoursAgo(difference.inHours);
|
||||||
|
} else if (difference.inDays < 7) {
|
||||||
|
return s.daysAgo(difference.inDays);
|
||||||
|
} else {
|
||||||
|
return DateFormat.yMMMd().format(
|
||||||
|
DateTime.fromMillisecondsSinceEpoch(this, isUtc: true).toLocal());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String get toTime =>
|
||||||
|
'${(this ~/ 60)}:${(this.truncate() % 60).toString().padLeft(2, '0')}';
|
||||||
|
|
||||||
|
String toInterval(BuildContext context) {
|
||||||
|
if (this == null || this.isNegative) return '';
|
||||||
|
final s = context.s;
|
||||||
|
var interval = Duration(milliseconds: this);
|
||||||
|
if (interval.inHours <= 48)
|
||||||
|
return 'Published daily';
|
||||||
|
else if (interval.inDays > 2 && interval.inDays <= 14)
|
||||||
|
return 'Published weekly';
|
||||||
|
else if (interval.inDays > 14 && interval.inDays < 60)
|
||||||
|
return 'Published monthly';
|
||||||
|
else
|
||||||
|
return 'Published yearly';
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'context_extension.dart';
|
import 'extension_helper.dart';
|
||||||
|
|
||||||
generalDialog(BuildContext context,
|
generalDialog(BuildContext context,
|
||||||
{Widget title, Widget content, List<Widget> actions}) =>
|
{Widget title, Widget content, List<Widget> actions}) =>
|
||||||
|
@ -26,7 +26,7 @@ generalDialog(BuildContext context,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(10.0))),
|
borderRadius: BorderRadius.all(Radius.circular(10.0))),
|
||||||
titlePadding: EdgeInsets.all(20),
|
titlePadding: EdgeInsets.all(20),
|
||||||
title: SizedBox(width: context.width-160, child: title),
|
title: SizedBox(width: context.width - 160, child: title),
|
||||||
content: content,
|
content: content,
|
||||||
actionsPadding: EdgeInsets.all(10),
|
actionsPadding: EdgeInsets.all(10),
|
||||||
actions: actions),
|
actions: actions),
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'context_extension.dart';
|
import 'extension_helper.dart';
|
||||||
|
|
||||||
/// Signature for a function that creates a [Widget] to be used within an
|
/// Signature for a function that creates a [Widget] to be used within an
|
||||||
/// [OpenContainer].
|
/// [OpenContainer].
|
||||||
|
|
68
pubspec.yaml
68
pubspec.yaml
|
@ -11,41 +11,43 @@ dependencies:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
cupertino_icons: ^0.1.3
|
|
||||||
json_annotation: ^3.0.1
|
|
||||||
sqflite: ^1.3.0
|
|
||||||
flutter_html: ^0.11.1
|
|
||||||
path_provider: ^1.6.8
|
|
||||||
color_thief_flutter: ^1.0.2
|
|
||||||
provider: ^4.1.2
|
|
||||||
google_fonts: ^1.1.0
|
|
||||||
dio: ^3.0.9
|
|
||||||
file_picker: ^1.9.0+1
|
|
||||||
marquee: ^1.3.1
|
|
||||||
flutter_downloader: ^1.4.4
|
|
||||||
permission_handler: ^5.0.0+hotfix.3
|
|
||||||
fluttertoast: ^4.0.1
|
|
||||||
intl: ^0.16.1
|
|
||||||
url_launcher: ^5.4.10
|
|
||||||
image: ^2.1.12
|
|
||||||
shared_preferences: ^0.5.7
|
|
||||||
uuid: ^2.2.0
|
|
||||||
tuple: ^1.0.3
|
|
||||||
cached_network_image: ^2.2.0+1
|
|
||||||
workmanager: ^0.2.3
|
|
||||||
fl_chart: ^0.10.1
|
|
||||||
audio_service: ^0.8.0
|
|
||||||
flutter_file_dialog: ^0.0.5
|
|
||||||
flutter_linkify: ^3.1.3
|
|
||||||
extended_nested_scroll_view: ^0.4.0
|
|
||||||
connectivity: ^0.4.8+2
|
|
||||||
flare_flutter: ^2.0.5
|
|
||||||
rxdart: ^0.24.0
|
|
||||||
wc_flutter_share: ^0.2.1
|
|
||||||
auto_animated: ^2.1.0
|
auto_animated: ^2.1.0
|
||||||
|
audio_service: ^0.11.2
|
||||||
|
cached_network_image: ^2.2.0+1
|
||||||
|
color_thief_flutter: ^1.0.2
|
||||||
|
cupertino_icons: ^0.1.3
|
||||||
|
connectivity: ^0.4.9
|
||||||
|
dio: ^3.0.9
|
||||||
|
extended_nested_scroll_view: ^1.0.1
|
||||||
feature_discovery: ^0.10.0
|
feature_discovery: ^0.10.0
|
||||||
|
file_picker: ^1.12.0
|
||||||
|
flutter_html: ^0.11.1
|
||||||
|
flutter_downloader: ^1.4.4
|
||||||
|
fluttertoast: ^4.0.0
|
||||||
flutter_isolate: ^1.0.0+14
|
flutter_isolate: ^1.0.0+14
|
||||||
flutter_time_picker_spinner: ^1.0.6+1
|
flutter_time_picker_spinner: ^1.0.6+1
|
||||||
|
flutter_linkify: ^3.1.3
|
||||||
|
flutter_file_dialog: ^0.0.5
|
||||||
|
flare_flutter: ^2.0.5
|
||||||
|
fl_chart: ^0.10.1
|
||||||
|
marquee: ^1.3.1
|
||||||
|
google_fonts: ^1.1.0
|
||||||
|
image: ^2.1.14
|
||||||
|
intl: ^0.16.1
|
||||||
|
json_serializable: ^3.3.0
|
||||||
|
json_annotation: ^3.0.1
|
||||||
|
path_provider: ^1.6.11
|
||||||
|
permission_handler: ^5.0.1
|
||||||
|
provider: ^4.3.1
|
||||||
|
rxdart: ^0.24.1
|
||||||
|
sqflite: ^1.3.1
|
||||||
|
shared_preferences: ^0.5.8
|
||||||
|
tuple: ^1.0.3
|
||||||
|
url_launcher: ^5.5.0
|
||||||
|
uuid: ^2.2.0
|
||||||
|
xml: ^4.2.0
|
||||||
|
workmanager: ^0.2.3
|
||||||
|
wc_flutter_share: ^0.2.2
|
||||||
just_audio:
|
just_audio:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/stonega/just_audio.git
|
url: https://github.com/stonega/just_audio.git
|
||||||
|
@ -59,9 +61,7 @@ dependencies:
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
build_runner: ^1.10.0
|
||||||
dependency_overrides:
|
|
||||||
xml: "4.2.0"
|
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
assets:
|
assets:
|
||||||
|
|
Loading…
Reference in New Issue