diff --git a/.circleci/config.yml b/.circleci/config.yml
index 965bb38..6d2077b 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -13,9 +13,7 @@ jobs:
- run:
name: Run Flutter doctor
command: flutter doctor
- - run:
- name: flutter pub get
- command: flutter pub get
+
-run:
name: flutter run
command: flutter run
diff --git a/README.md b/README.md
index 306d295..ab035e0 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,21 @@
# Tsacdop
[![CircleCI](https://circleci.com/gh/stonega/tsacdop.svg?style=svg)](https://circleci.com/gh/stonega/workflows/tsacdop/)
## About
-![logo](https://raw.githubusercontent.com/stonega/tsacdop/master/android/app/src/main/res/mipmap-hdpi/ic_launcher.png)
-
-![tsacdop](https://raw.githubusercontent.com/stonega/tsacdop/master/android/app/src/main/res/mipmap-hdpi/text.png)
+
+
+
+
Enjoy podcasts with Tsacdop.
-Tsacdop is a podcasts player developed with flutter, only for Android right now.
+Tsacdop is a podcast player developed with flutter, only for Android right now.
The development is still on early stage.
-Credit to flutter team and involved plugin developers, especially [webfeed](https://github.com/witochandra/webfeed) and [audiofileplayer](https://github.com/google/flutter.plugins/tree/master/packages/audiofileplayer/).
+Credit to flutter team and involved plugin developers, especially [webfeed](https://github.com/witochandra/webfeed) and [Just_Audio](https://pub.dev/packages/just_audio).
The podcasts search engine is powered by [ListenNotes](https://listennotes.com).
-
+## Preview
+![homepage](https://raw.githubusercontent.com/stonega/tsacdop/master/preview/Screenshot_homepage.png) ![podcast](https://raw.githubusercontent.com/stonega/tsacdop/master/preview/Screenshot_podcast.png) ![episode](https://raw.githubusercontent.com/stonega/tsacdop/master/preview/Screenshot_episode.png) ![data](https://raw.githubusercontent.com/stonega/tsacdop/master/preview/Screenshot_data.png)
## License
Tsacdop is licensed under the [MIT](https://github.com/stonega/tsacdop/blob/master/LICENSE) license.
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 810589b..5a8e443 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -67,6 +67,7 @@ android {
buildTypes {
release {
signingConfig signingConfigs.release
+ shrinkResources false
}
}
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index e2e874c..54d65c5 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -6,15 +6,16 @@
FlutterApplication and put your custom class here. -->
+
-
+
-
+
diff --git a/android/app/src/main/res/drawable-hdpi/baseline_close_white_24.png b/android/app/src/main/res/drawable-hdpi/baseline_close_white_24.png
new file mode 100644
index 0000000..434cc0b
Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/baseline_close_white_24.png differ
diff --git a/android/app/src/main/res/drawable-hdpi/baseline_skip_next_white_24.png b/android/app/src/main/res/drawable-hdpi/baseline_skip_next_white_24.png
new file mode 100644
index 0000000..af8c8c9
Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/baseline_skip_next_white_24.png differ
diff --git a/android/app/src/main/res/drawable-hdpi/ic_action_pause.png b/android/app/src/main/res/drawable-hdpi/ic_action_pause.png
new file mode 100644
index 0000000..55f33b2
Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/ic_action_pause.png differ
diff --git a/android/app/src/main/res/drawable-hdpi/ic_action_play_arrow.png b/android/app/src/main/res/drawable-hdpi/ic_action_play_arrow.png
new file mode 100644
index 0000000..87d5743
Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/ic_action_play_arrow.png differ
diff --git a/android/app/src/main/res/drawable-hdpi/ic_action_skip_next.png b/android/app/src/main/res/drawable-hdpi/ic_action_skip_next.png
new file mode 100644
index 0000000..28a76a3
Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/ic_action_skip_next.png differ
diff --git a/android/app/src/main/res/drawable-hdpi/ic_action_skip_previous.png b/android/app/src/main/res/drawable-hdpi/ic_action_skip_previous.png
new file mode 100644
index 0000000..d60ff08
Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/ic_action_skip_previous.png differ
diff --git a/android/app/src/main/res/drawable-hdpi/ic_action_stop.png b/android/app/src/main/res/drawable-hdpi/ic_action_stop.png
new file mode 100644
index 0000000..5435114
Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/ic_action_stop.png differ
diff --git a/android/app/src/main/res/drawable-mdpi/baseline_close_white_24.png b/android/app/src/main/res/drawable-mdpi/baseline_close_white_24.png
new file mode 100644
index 0000000..296d24d
Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/baseline_close_white_24.png differ
diff --git a/android/app/src/main/res/drawable-mdpi/baseline_skip_next_white_24.png b/android/app/src/main/res/drawable-mdpi/baseline_skip_next_white_24.png
new file mode 100644
index 0000000..6d8b565
Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/baseline_skip_next_white_24.png differ
diff --git a/android/app/src/main/res/drawable-mdpi/ic_action_pause.png b/android/app/src/main/res/drawable-mdpi/ic_action_pause.png
new file mode 100644
index 0000000..e8ff072
Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_action_pause.png differ
diff --git a/android/app/src/main/res/drawable-mdpi/ic_action_play_arrow.png b/android/app/src/main/res/drawable-mdpi/ic_action_play_arrow.png
new file mode 100644
index 0000000..71fff1d
Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_action_play_arrow.png differ
diff --git a/android/app/src/main/res/drawable-mdpi/ic_action_skip_next.png b/android/app/src/main/res/drawable-mdpi/ic_action_skip_next.png
new file mode 100644
index 0000000..632e4ab
Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_action_skip_next.png differ
diff --git a/android/app/src/main/res/drawable-mdpi/ic_action_skip_previous.png b/android/app/src/main/res/drawable-mdpi/ic_action_skip_previous.png
new file mode 100644
index 0000000..cf9b5f7
Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_action_skip_previous.png differ
diff --git a/android/app/src/main/res/drawable-mdpi/ic_action_stop.png b/android/app/src/main/res/drawable-mdpi/ic_action_stop.png
new file mode 100644
index 0000000..95e837d
Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_action_stop.png differ
diff --git a/android/app/src/main/res/drawable-xhdpi/baseline_close_white_24.png b/android/app/src/main/res/drawable-xhdpi/baseline_close_white_24.png
new file mode 100644
index 0000000..dccd2c2
Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/baseline_close_white_24.png differ
diff --git a/android/app/src/main/res/drawable-xhdpi/baseline_skip_next_white_24.png b/android/app/src/main/res/drawable-xhdpi/baseline_skip_next_white_24.png
new file mode 100644
index 0000000..f515023
Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/baseline_skip_next_white_24.png differ
diff --git a/android/app/src/main/res/drawable-xhdpi/ic_action_pause.png b/android/app/src/main/res/drawable-xhdpi/ic_action_pause.png
new file mode 100644
index 0000000..fbdee83
Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/ic_action_pause.png differ
diff --git a/android/app/src/main/res/drawable-xhdpi/ic_action_play_arrow.png b/android/app/src/main/res/drawable-xhdpi/ic_action_play_arrow.png
new file mode 100644
index 0000000..62d2067
Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/ic_action_play_arrow.png differ
diff --git a/android/app/src/main/res/drawable-xhdpi/ic_action_skip_next.png b/android/app/src/main/res/drawable-xhdpi/ic_action_skip_next.png
new file mode 100644
index 0000000..164718d
Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/ic_action_skip_next.png differ
diff --git a/android/app/src/main/res/drawable-xhdpi/ic_action_skip_previous.png b/android/app/src/main/res/drawable-xhdpi/ic_action_skip_previous.png
new file mode 100644
index 0000000..b33307e
Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/ic_action_skip_previous.png differ
diff --git a/android/app/src/main/res/drawable-xhdpi/ic_action_stop.png b/android/app/src/main/res/drawable-xhdpi/ic_action_stop.png
new file mode 100644
index 0000000..3f7f54d
Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/ic_action_stop.png differ
diff --git a/android/app/src/main/res/drawable-xxhdpi/baseline_close_white_24.png b/android/app/src/main/res/drawable-xxhdpi/baseline_close_white_24.png
new file mode 100644
index 0000000..574b3d9
Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/baseline_close_white_24.png differ
diff --git a/android/app/src/main/res/drawable-xxhdpi/baseline_skip_next_white_24.png b/android/app/src/main/res/drawable-xxhdpi/baseline_skip_next_white_24.png
new file mode 100644
index 0000000..459ace5
Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/baseline_skip_next_white_24.png differ
diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_action_pause.png b/android/app/src/main/res/drawable-xxhdpi/ic_action_pause.png
new file mode 100644
index 0000000..8ac598d
Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/ic_action_pause.png differ
diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_action_play_arrow.png b/android/app/src/main/res/drawable-xxhdpi/ic_action_play_arrow.png
new file mode 100644
index 0000000..47ce73a
Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/ic_action_play_arrow.png differ
diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_action_skip_next.png b/android/app/src/main/res/drawable-xxhdpi/ic_action_skip_next.png
new file mode 100644
index 0000000..0f3b6a1
Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/ic_action_skip_next.png differ
diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_action_skip_previous.png b/android/app/src/main/res/drawable-xxhdpi/ic_action_skip_previous.png
new file mode 100644
index 0000000..2e09dfa
Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/ic_action_skip_previous.png differ
diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_action_stop.png b/android/app/src/main/res/drawable-xxhdpi/ic_action_stop.png
new file mode 100644
index 0000000..17da4a3
Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/ic_action_stop.png differ
diff --git a/android/app/src/main/res/drawable-xxxhdpi/baseline_close_white_36.png b/android/app/src/main/res/drawable-xxxhdpi/baseline_close_white_36.png
new file mode 100644
index 0000000..de87814
Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/baseline_close_white_36.png differ
diff --git a/android/app/src/main/res/drawable-xxxhdpi/baseline_skip_next_white_24.png b/android/app/src/main/res/drawable-xxxhdpi/baseline_skip_next_white_24.png
new file mode 100644
index 0000000..caaf796
Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/baseline_skip_next_white_24.png differ
diff --git a/android/app/src/main/res/drawable-xxxhdpi/ic_action_pause.png b/android/app/src/main/res/drawable-xxxhdpi/ic_action_pause.png
new file mode 100644
index 0000000..4343502
Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/ic_action_pause.png differ
diff --git a/android/app/src/main/res/drawable-xxxhdpi/ic_action_play_arrow.png b/android/app/src/main/res/drawable-xxxhdpi/ic_action_play_arrow.png
new file mode 100644
index 0000000..e9f9281
Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/ic_action_play_arrow.png differ
diff --git a/android/app/src/main/res/drawable-xxxhdpi/ic_action_skip_next.png b/android/app/src/main/res/drawable-xxxhdpi/ic_action_skip_next.png
new file mode 100644
index 0000000..ea1a197
Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/ic_action_skip_next.png differ
diff --git a/android/app/src/main/res/drawable-xxxhdpi/ic_action_skip_previous.png b/android/app/src/main/res/drawable-xxxhdpi/ic_action_skip_previous.png
new file mode 100644
index 0000000..3dcbcd4
Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/ic_action_skip_previous.png differ
diff --git a/android/app/src/main/res/drawable-xxxhdpi/ic_action_stop.png b/android/app/src/main/res/drawable-xxxhdpi/ic_action_stop.png
new file mode 100644
index 0000000..20ee1b7
Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/ic_action_stop.png differ
diff --git a/android/app/src/main/res/mipmap-hdpi/text_light.png b/android/app/src/main/res/mipmap-hdpi/text_light.png
new file mode 100644
index 0000000..738e4de
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/text_light.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/text_light.png b/android/app/src/main/res/mipmap-mdpi/text_light.png
new file mode 100644
index 0000000..1f9e204
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/text_light.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/text_light.png b/android/app/src/main/res/mipmap-xhdpi/text_light.png
new file mode 100644
index 0000000..f9a0f77
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/text_light.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/text_light.png b/android/app/src/main/res/mipmap-xxhdpi/text_light.png
new file mode 100644
index 0000000..64c52e1
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/text_light.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/text_light.png b/android/app/src/main/res/mipmap-xxxhdpi/text_light.png
new file mode 100644
index 0000000..e35a80e
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/text_light.png differ
diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..81e5bcf
--- /dev/null
+++ b/android/app/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+
+ #121212
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/xml/network_security_config.xml b/android/app/src/main/res/xml/network_security_config.xml
index caf1945..f18e1f0 100644
--- a/android/app/src/main/res/xml/network_security_config.xml
+++ b/android/app/src/main/res/xml/network_security_config.xml
@@ -1,7 +1,8 @@
-
- lizhi.fm
- xmcdn.com
-
+
+
+
+
+
\ No newline at end of file
diff --git a/assets/listennotes_light.png b/assets/listennotes_light.png
new file mode 100644
index 0000000..8fbee9d
Binary files /dev/null and b/assets/listennotes_light.png differ
diff --git a/assets/text_light.png b/assets/text_light.png
new file mode 100644
index 0000000..e35a80e
Binary files /dev/null and b/assets/text_light.png differ
diff --git a/lib/class/audiostate.dart b/lib/class/audiostate.dart
index da0cc46..9893dab 100644
--- a/lib/class/audiostate.dart
+++ b/lib/class/audiostate.dart
@@ -1,17 +1,46 @@
-import 'dart:typed_data';
import 'dart:async';
-import 'dart:io';
import 'package:flutter/foundation.dart';
+import 'package:audio_service/audio_service.dart';
+import 'package:just_audio/just_audio.dart';
import 'package:tsacdop/class/episodebrief.dart';
-import 'package:audiofileplayer/audiofileplayer.dart';
-import 'package:flutter_downloader/flutter_downloader.dart';
-import 'package:logging/logging.dart';
-import 'package:audiofileplayer/audio_system.dart';
import 'package:tsacdop/local_storage/key_value_storage.dart';
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
-//enum AudioState { load, play, pause, complete, error, stop }
+MediaControl playControl = MediaControl(
+ androidIcon: 'drawable/ic_stat_play_circle_filled',
+ label: 'Play',
+ action: MediaAction.play,
+);
+MediaControl pauseControl = MediaControl(
+ androidIcon: 'drawable/ic_stat_pause_circle_filled',
+ label: 'Pause',
+ action: MediaAction.pause,
+);
+MediaControl skipToNextControl = MediaControl(
+ androidIcon: 'drawable/baseline_skip_next_white_24',
+ label: 'Next',
+ action: MediaAction.skipToNext,
+);
+MediaControl skipToPreviousControl = MediaControl(
+ androidIcon: 'drawable/ic_action_skip_previous',
+ label: 'Previous',
+ action: MediaAction.skipToPrevious,
+);
+MediaControl stopControl = MediaControl(
+ androidIcon: 'drawable/baseline_close_white_24',
+ label: 'Stop',
+ action: MediaAction.stop,
+);
+MediaControl forward30 = MediaControl(
+ androidIcon: 'drawable/ic_stat_forward_30',
+ label: 'forward30',
+ action: MediaAction.fastForward,
+);
+
+void _audioPlayerTaskEntrypoint() async {
+ AudioServiceBackground.run(() => AudioPlayerTask());
+}
class PlayHistory {
DBHelper dbHelper = DBHelper();
@@ -19,7 +48,9 @@ class PlayHistory {
String url;
double seconds;
double seekValue;
- PlayHistory(this.title, this.url, this.seconds, this.seekValue);
+ DateTime playdate;
+ PlayHistory(this.title, this.url, this.seconds, this.seekValue,
+ {this.playdate});
EpisodeBrief _episode;
EpisodeBrief get episode => _episode;
@@ -31,32 +62,36 @@ class PlayHistory {
class Playlist {
String name;
DBHelper dbHelper = DBHelper();
- List urls;
+ // list of urls
+ //List _urls;
+ //list of episodes
List _playlist;
+ //list of miediaitem
+
List get playlist => _playlist;
KeyValueStorage storage = KeyValueStorage('playlist');
- Playlist(this.name, {List urls}) : urls = urls ?? [];
getPlaylist() async {
- List _urls = await storage.getStringList();
- if (_urls.length == 0) {
+ List urls = await storage.getStringList();
+ print(urls);
+ if (urls.length == 0) {
_playlist = [];
} else {
_playlist = [];
- await Future.forEach(_urls, (url) async {
+ await Future.forEach(urls, (url) async {
EpisodeBrief episode = await dbHelper.getRssItemWithUrl(url);
print(episode.title);
_playlist.add(episode);
});
}
- print(_playlist.length);
+ print('Playlist: ' + _playlist.length.toString());
}
savePlaylist() async {
- urls = [];
+ List urls = [];
urls.addAll(_playlist.map((e) => e.enclosureUrl));
print(urls);
- await storage.saveStringlist(urls);
+ await storage.saveStringList(urls);
}
addToPlayList(EpisodeBrief episodeBrief) async {
@@ -64,6 +99,11 @@ class Playlist {
await savePlaylist();
}
+ addToPlayListAt(EpisodeBrief episodeBrief, int index) async {
+ _playlist.insert(index, episodeBrief);
+ await savePlaylist();
+ }
+
delFromPlaylist(EpisodeBrief episodeBrief) async {
_playlist
.removeWhere((item) => item.enclosureUrl == episodeBrief.enclosureUrl);
@@ -71,39 +111,32 @@ class Playlist {
}
}
-class AudioPlayer extends ChangeNotifier {
- static const String replay10ButtonId = 'replay10ButtonId';
- static const String newReleasesButtonId = 'newReleasesButtonId';
- static const String likeButtonId = 'likeButtonId';
- static const String pausenowButtonId = 'pausenowButtonId';
- static const String forwardButtonId = 'forwardButtonId';
-
+class AudioPlayerNotifier extends ChangeNotifier {
DBHelper dbHelper = DBHelper();
KeyValueStorage storage = KeyValueStorage('audioposition');
EpisodeBrief _episode;
- Playlist _queue = Playlist('now');
+ Playlist _queue = Playlist();
+ BasicPlaybackState _audioState = BasicPlaybackState.none;
bool _playerRunning = false;
- Audio _backgroundAudio;
- bool _backgroundAudioPlaying = false;
- double _backgroundAudioDurationSeconds = 0;
- double _backgroundAudioPositionSeconds = 0;
- bool _remoteAudioLoading = false;
+ bool _noSlide = true;
+ int _backgroundAudioDuration = 0;
+ int _backgroundAudioPosition = 0;
String _remoteErrorMessage;
+
double _seekSliderValue = 0.0;
- int _lastPostion;
- bool _skip = false;
+ int _lastPostion = 0;
bool _stopOnComplete = false;
Timer _stopTimer;
//Show stopwatch after user setting timer.
- bool _showStopWatch = false;
-
-
- final Logger _logger = Logger('audiofileplayer');
+ bool _showStopWatch = false;
+ bool _autoPlay = true;
+ DateTime _current;
+ int _currentPosition;
- bool get backgroundAudioPlaying => _backgroundAudioPlaying;
- bool get remoteAudioLoading => _remoteAudioLoading;
- double get backgroundAudioDuration => _backgroundAudioDurationSeconds;
- double get backgroundAudioPosition => _backgroundAudioPositionSeconds;
+ BasicPlaybackState get audioState => _audioState;
+
+ int get backgroundAudioDuration => _backgroundAudioDuration;
+ int get backgroundAudioPosition => _backgroundAudioPosition;
double get seekSliderValue => _seekSliderValue;
String get remoteErrorMessage => _remoteErrorMessage;
bool get playerRunning => _playerRunning;
@@ -112,385 +145,470 @@ class AudioPlayer extends ChangeNotifier {
EpisodeBrief get episode => _episode;
bool get stopOnComplete => _stopOnComplete;
bool get showStopWatch => _showStopWatch;
-
+ bool get autoPlay => _autoPlay;
set setStopOnComplete(bool boo) {
_stopOnComplete = boo;
}
+ set autoPlaySwitch(bool boo) {
+ _autoPlay = boo;
+ notifyListeners();
+ }
+
+ @override
+ void addListener(VoidCallback listener) async {
+ super.addListener(listener);
+ await AudioService.connect();
+ }
+
loadPlaylist() async {
await _queue.getPlaylist();
_lastPostion = await storage.getInt();
}
episodeLoad(EpisodeBrief episode) async {
- if (_playerRunning && _episode != null) {
+ if (_playerRunning) {
PlayHistory history = PlayHistory(_episode.title, _episode.enclosureUrl,
- backgroundAudioDuration, seekSliderValue);
+ backgroundAudioPosition / 1000, seekSliderValue);
await dbHelper.saveHistory(history);
+ AudioService.addQueueItemAt(episode.toMediaItem(), 0);
+ _queue.playlist
+ .removeWhere((item) => item.enclosureUrl == episode.enclosureUrl);
+ _queue.playlist.insert(0, episode);
+ notifyListeners();
+ await _queue.savePlaylist();
+ } else {
+ await _queue.getPlaylist();
+ _queue.playlist
+ .removeWhere((item) => item.enclosureUrl == episode.enclosureUrl);
+ _queue.playlist.insert(0, episode);
+ _queue.savePlaylist();
+ _backgroundAudioDuration = 0;
+ _backgroundAudioPosition = 0;
+ _seekSliderValue = 0;
+ _episode = episode;
+ _playerRunning = true;
+ notifyListeners();
+ await _queue.savePlaylist();
+ _startAudioService(0);
}
- AudioSystem.instance.addMediaEventListener(_mediaEventListener);
- _backgroundAudioPlaying = false;
- _episode = episode;
- await _queue.getPlaylist();
- _queue.playlist
- .removeWhere((item) => item.enclosureUrl == _episode.enclosureUrl);
- _queue.playlist.insert(0, _episode);
- await _queue.savePlaylist();
- await _play(_episode);
+ }
+
+ _startAudioService(int position) async {
+ if (!AudioService.connected) {
+ await AudioService.connect();
+ }
+ await AudioService.start(
+ backgroundTaskEntrypoint: _audioPlayerTaskEntrypoint,
+ androidNotificationChannelName: 'Tsacdop',
+ notificationColor: 0xFF2196f3,
+ androidNotificationIcon: 'mipmap/ic_launcher',
+ enableQueue: true,
+ androidStopOnRemoveTask: true,
+ );
_playerRunning = true;
- notifyListeners();
+ if (autoPlay) {
+ await Future.forEach(_queue.playlist, (episode) async {
+ await AudioService.addQueueItem(episode.toMediaItem());
+ });
+ } else {
+ await AudioService.addQueueItem(_queue.playlist.first.toMediaItem());
+ }
+ await AudioService.play();
+ AudioService.currentMediaItemStream.listen((item) async {
+ print(position);
+ print(_backgroundAudioDuration);
+ if (item != null) {
+ _episode = await dbHelper.getRssItemWithMediaId(item.id);
+ _backgroundAudioDuration = item?.duration ?? 0;
+ if (position > 0 && _backgroundAudioDuration > 0) {
+ AudioService.seekTo(position);
+ position = 0;
+ }
+ // _playerRunning = true;
+ }
+ notifyListeners();
+ });
+ AudioService.playbackStateStream.listen((event) async {
+ _current = DateTime.now();
+ _audioState = event?.basicState;
+ if (_audioState == BasicPlaybackState.skippingToNext &&
+ _episode != null) {
+ _queue.delFromPlaylist(_episode);
+ }
+ if (_audioState == BasicPlaybackState.paused ||
+ _audioState == BasicPlaybackState.skippingToNext &&
+ _episode != null) {
+ PlayHistory history = PlayHistory(_episode.title, _episode.enclosureUrl,
+ backgroundAudioPosition / 1000, seekSliderValue);
+ await dbHelper.saveHistory(history);
+ }
+ if (_audioState == BasicPlaybackState.stopped) {
+ _playerRunning = false;
+ }
+ _currentPosition = event?.currentPosition ?? 0;
+ notifyListeners();
+ });
+ Timer.periodic(Duration(milliseconds: 500), (timer) {
+ if (_noSlide) {
+ _audioState == BasicPlaybackState.playing
+ ? (_backgroundAudioPosition < _backgroundAudioDuration)
+ ? _backgroundAudioPosition = _currentPosition +
+ DateTime.now().difference(_current).inMilliseconds
+ : _backgroundAudioPosition = _backgroundAudioDuration
+ : _backgroundAudioPosition = _currentPosition;
+
+ if (_backgroundAudioDuration != null &&
+ _backgroundAudioDuration != 0 &&
+ _backgroundAudioPosition != null) {
+ _seekSliderValue =
+ _backgroundAudioPosition / _backgroundAudioDuration ?? 0;
+ }
+ if (_backgroundAudioPosition > 0) {
+ _lastPostion = _backgroundAudioPosition;
+ storage.saveInt(_lastPostion);
+ }
+ notifyListeners();
+ }
+ if (_audioState == BasicPlaybackState.stopped) {
+ timer.cancel();
+ }
+ });
}
playlistLoad() async {
- _backgroundAudioPlaying = false;
await _queue.getPlaylist();
+ _backgroundAudioDuration = 0;
+ _backgroundAudioPosition = 0;
+ _seekSliderValue = 0;
_episode = _queue.playlist.first;
- _skip = true;
- await _play(_episode);
_playerRunning = true;
notifyListeners();
+ _startAudioService(_lastPostion ?? 0);
}
playNext() async {
- storage.saveInt(0);
- _lastPostion = 0;
- PlayHistory history = PlayHistory(_episode.title, _episode.enclosureUrl,
- backgroundAudioDuration, seekSliderValue);
- await dbHelper.saveHistory(history);
- await _queue.delFromPlaylist(_episode);
- if (_queue.playlist.length > 0 && !_stopOnComplete) {
- playlistLoad();
- } else {
- _stopOnComplete = false;
- _backgroundAudioPlaying = false;
- _remoteAudioLoading = false;
- _playerRunning = false;
- _disposeAudio();
- notifyListeners();
- }
+ AudioService.skipToNext();
}
addToPlaylist(EpisodeBrief episode) async {
- _queue.addToPlayList(episode);
- await _queue.getPlaylist();
+ if (_playerRunning) {
+ await AudioService.addQueueItem(episode.toMediaItem());
+ }
+ print('add to playlist when not rnnning');
+ await _queue.addToPlayList(episode);
notifyListeners();
}
+ addToPlaylistAt(EpisodeBrief episode, int index) async {
+ if (_playerRunning) {
+ await AudioService.addQueueItemAt(episode.toMediaItem(), index);
+ }
+ print('add to playlist when not rnnning');
+ await _queue.addToPlayListAt(episode, index);
+ notifyListeners();
+ }
+
+ updateMediaItem(EpisodeBrief episode) async {
+ int index = _queue.playlist
+ .indexWhere((item) => item.enclosureUrl == episode.enclosureUrl);
+ if (index > 0) {
+ await delFromPlaylist(episode);
+ await addToPlaylistAt(episode, index);
+ }
+ }
+
delFromPlaylist(EpisodeBrief episode) async {
- _queue.delFromPlaylist(episode);
- await _queue.getPlaylist();
+ if (_playerRunning) {
+ await AudioService.removeQueueItem(episode.toMediaItem());
+ }
+ await _queue.delFromPlaylist(episode);
notifyListeners();
}
pauseAduio() async {
- _pauseBackgroundAudio();
- notifyListeners();
- PlayHistory history = PlayHistory(_episode.title, _episode.enclosureUrl,
- backgroundAudioPosition, seekSliderValue);
- await dbHelper.saveHistory(history);
+ AudioService.pause();
}
- resumeAudio() {
- _resumeBackgroundAudio();
- notifyListeners();
+ resumeAudio() async {
+ AudioService.play();
}
- forwardAudio(double s) {
- _forwardBackgroundAudio(s);
- notifyListeners();
+ forwardAudio(int s) {
+ int pos = _backgroundAudioPosition + s * 1000;
+ AudioService.seekTo(pos);
}
- sliderSeek(double val) {
+ sliderSeek(double val) async {
+ print(val.toString());
+ _noSlide = false;
_seekSliderValue = val;
notifyListeners();
- final double positionSeconds = val * _backgroundAudioDurationSeconds;
- _backgroundAudio.seek(positionSeconds);
- AudioSystem.instance.setPlaybackState(true, positionSeconds);
+ _currentPosition = (val * _backgroundAudioDuration).toInt();
+ await AudioService.seekTo(_currentPosition);
+ _noSlide = true;
}
- //Set sleep time
+
+ //Set sleep time
sleepTimer(int mins) {
_showStopWatch = true;
notifyListeners();
- _stopTimer = Timer(Duration(minutes: mins),(){
+ _stopTimer = Timer(Duration(minutes: mins), () {
_stopOnComplete = false;
- _backgroundAudioPlaying = false;
- _remoteAudioLoading = false;
- _playerRunning = false;
_showStopWatch = false;
- _disposeAudio();
+ AudioService.stop();
notifyListeners();
});
}
+
//Cancel sleep timer
- cancelTimer(){
+ cancelTimer() {
_stopTimer.cancel();
_showStopWatch = false;
notifyListeners();
}
- _disposeAudio() {
- pauseAduio();
- AudioSystem.instance?.stopBackgroundDisplay();
- AudioSystem.instance?.removeMediaEventListener(_mediaEventListener);
- _backgroundAudio?.dispose();
+ @override
+ void dispose() async {
+ await AudioService.stop();
+ await AudioService.disconnect();
+ super.dispose();
+ }
+}
+
+class AudioPlayerTask extends BackgroundAudioTask {
+ List _queue = [];
+ AudioPlayer _audioPlayer = AudioPlayer();
+ Completer _completer = Completer();
+ BasicPlaybackState _skipState;
+ bool _playing;
+
+ bool get hasNext => _queue.length > 0;
+
+ MediaItem get mediaItem => _queue.first;
+
+ BasicPlaybackState _stateToBasicState(AudioPlaybackState state) {
+ switch (state) {
+ case AudioPlaybackState.none:
+ return BasicPlaybackState.none;
+ case AudioPlaybackState.stopped:
+ return 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
- dispose() {
- _disposeAudio();
- super.dispose();
- }
-
- _play(EpisodeBrief episodeBrief) async {
- AudioSystem.instance.addMediaEventListener(_mediaEventListener);
- String url = _queue.playlist.first.enclosureUrl;
- _getFile(url).then((result) {
- result == 'NotDownload'
- ? _initbackgroundAudioPlayer(url)
- : _initbackgroundAudioPlayerLocal(result);
+ Future onStart() async {
+ print('start background task');
+ var playerStateSubscription = _audioPlayer.playbackStateStream
+ .where((state) => state == AudioPlaybackState.completed)
+ .listen((state) {
+ _handlePlaybackCompleted();
});
- }
-
- Future _getFile(String url) async {
- final task = await FlutterDownloader.loadTasksWithRawQuery(
- query: "SELECT * FROM task WHERE url = '$url' AND status = 3");
- if (task.length != 0) {
- String _filePath = task.first.savedDir + '/' + task.first.filename;
- return _filePath;
- }
- return 'NotDownload';
- }
-
- ByteData _getAudio(String path) {
- File audioFile = File(path);
- Uint8List audio = audioFile.readAsBytesSync();
- return ByteData.view(audio.buffer);
- }
-
- onDuration(double durationSeconds) {
- _backgroundAudioDurationSeconds = durationSeconds;
- _remoteAudioLoading = false;
- _backgroundAudioPlaying = true;
- if (_skip) {
- _forwardBackgroundAudio(_lastPostion.toDouble());
- _backgroundAudioPositionSeconds = _lastPostion.toDouble();
- }
- _skip = false;
- _setNotification(true);
- notifyListeners();
- }
-
- onPosition(double positionSeconds) {
- if (_backgroundAudioPositionSeconds < _backgroundAudioDurationSeconds) {
- _seekSliderValue =
- _backgroundAudioPositionSeconds / _backgroundAudioDurationSeconds;
- _backgroundAudioPositionSeconds = positionSeconds;
- notifyListeners();
- } else {
- _seekSliderValue = 1;
- _backgroundAudioPositionSeconds = _backgroundAudioDurationSeconds;
- notifyListeners();
- }
- _lastPostion = positionSeconds.toInt();
- storage.saveInt(_lastPostion);
- }
-
- onError(String message) {
- _remoteErrorMessage = message;
- _backgroundAudio.dispose();
- _backgroundAudio = null;
- _backgroundAudioPlaying = false;
- _remoteAudioLoading = false;
- }
-
- void _initbackgroundAudioPlayerLocal(String path) {
- _remoteErrorMessage = null;
- _remoteAudioLoading = true;
- ByteData audio = _getAudio(path);
- _backgroundAudio?.pause();
- _backgroundAudioPositionSeconds = 0;
- _setNotification(false);
- _backgroundAudio = Audio.loadFromByteData(audio,
- onDuration: (double durationSeconds) => onDuration(durationSeconds),
- onPosition: (double positionSeconds) => onPosition(positionSeconds),
- onError: (String message) => onError(message),
- onComplete: () => playNext(),
- looping: false,
- playInBackground: true)
- ..play();
- }
-
- void _initbackgroundAudioPlayer(String url) {
- _remoteErrorMessage = null;
- _remoteAudioLoading = true;
- notifyListeners();
- _backgroundAudio?.pause();
- _backgroundAudioPositionSeconds = 0;
- _setNotification(false);
- _backgroundAudio = Audio.loadFromRemoteUrl(url,
- onDuration: (double durationSeconds) => onDuration(durationSeconds),
- onPosition: (double positionSeconds) => onPosition(positionSeconds),
- onError: (String message) => onError(message),
- onComplete: () => playNext(),
- looping: false,
- playInBackground: true)
- ..resume();
- }
-
- void _mediaEventListener(MediaEvent mediaEvent) {
- _logger.info('App received media event of type: ${mediaEvent.type}');
- final MediaActionType type = mediaEvent.type;
- if (type == MediaActionType.play) {
- _resumeBackgroundAudio();
- } else if (type == MediaActionType.pause) {
- _pauseBackgroundAudio();
- } else if (type == MediaActionType.playPause) {
- _backgroundAudioPlaying
- ? _pauseBackgroundAudio()
- : _resumeBackgroundAudio();
- } else if (type == MediaActionType.stop) {
- _stopBackgroundAudio();
- } else if (type == MediaActionType.seekTo) {
- _backgroundAudio.seek(mediaEvent.seekToPositionSeconds);
- AudioSystem.instance
- .setPlaybackState(true, mediaEvent.seekToPositionSeconds);
- } else if (type == MediaActionType.skipForward) {
- final double skipIntervalSeconds = mediaEvent.skipIntervalSeconds;
- _forwardBackgroundAudio(skipIntervalSeconds);
- _logger.info(
- 'Skip-forward event had skipIntervalSeconds $skipIntervalSeconds.');
- } else if (type == MediaActionType.skipBackward) {
- final double skipIntervalSeconds = mediaEvent.skipIntervalSeconds;
- _forwardBackgroundAudio(skipIntervalSeconds);
- _logger.info(
- 'Skip-backward event had skipIntervalSeconds $skipIntervalSeconds.');
- } else if (type == MediaActionType.custom) {
- if (mediaEvent.customEventId == replay10ButtonId) {
- _forwardBackgroundAudio(-10.0);
- } else if (mediaEvent.customEventId == likeButtonId) {
- _resumeBackgroundAudio();
- } else if (mediaEvent.customEventId == forwardButtonId) {
- _forwardBackgroundAudio(30.0);
- } else if (mediaEvent.customEventId == pausenowButtonId) {
- _pauseBackgroundAudio();
+ var eventSubscription = _audioPlayer.playbackEventStream.listen((event) {
+ BasicPlaybackState state;
+ if (event.buffering) {
+ state = BasicPlaybackState.buffering;
+ } else {
+ state = _stateToBasicState(event.state);
}
+ if (state != BasicPlaybackState.stopped) {
+ _setState(
+ state: state,
+ position: event.position.inMilliseconds,
+ );
+ }
+ });
+ await _completer.future;
+ playerStateSubscription.cancel();
+ eventSubscription.cancel();
+ }
+
+ void _handlePlaybackCompleted() {
+ if (hasNext) {
+ onSkipToNext();
+ } else {
+ onStop();
}
}
- Future _setNotification(bool boo) async {
- final Uint8List imageBytes =
- File('${_episode.imagePath}').readAsBytesSync();
- AudioSystem.instance.setMetadata(AudioMetadata(
- title: episode.title,
- artist: episode.feedTitle,
- album: episode.feedTitle,
- genre: "Podcast",
- durationSeconds: _backgroundAudioDurationSeconds,
- artBytes: imageBytes));
- AudioSystem.instance.setPlaybackState(boo, _backgroundAudioPositionSeconds);
- AudioSystem.instance.setAndroidNotificationButtons([
- AndroidMediaButtonType.pause,
- _forwardButton,
- AndroidMediaButtonType.stop,
- ], androidCompactIndices: [
- 0,
- 1
- ]);
-
- AudioSystem.instance.setSupportedMediaActions({
- MediaActionType.playPause,
- MediaActionType.pause,
- MediaActionType.next,
- MediaActionType.previous,
- MediaActionType.skipForward,
- MediaActionType.skipBackward,
- MediaActionType.seekTo,
- MediaActionType.custom,
- }, skipIntervalSeconds: 30);
+ void playPause() {
+ if (AudioServiceBackground.state.basicState == BasicPlaybackState.playing)
+ onPause();
+ else
+ onPlay();
}
- Future _resumeBackgroundAudio() async {
- _backgroundAudio.resume();
-
- _backgroundAudioPlaying = true;
-
- final Uint8List imageBytes =
- File('${_episode.imagePath}').readAsBytesSync();
- AudioSystem.instance.setMetadata(AudioMetadata(
- title: _episode.title,
- artist: _episode.feedTitle,
- album: _episode.feedTitle,
- genre: "Podcast",
- durationSeconds: _backgroundAudioDurationSeconds,
- artBytes: imageBytes));
-
- AudioSystem.instance
- .setPlaybackState(true, _backgroundAudioPositionSeconds);
-
- AudioSystem.instance.setAndroidNotificationButtons([
- AndroidMediaButtonType.pause,
- _forwardButton,
- AndroidMediaButtonType.stop,
- ], androidCompactIndices: [
- 0,
- 1
- ]);
-
- AudioSystem.instance.setSupportedMediaActions({
- MediaActionType.playPause,
- MediaActionType.pause,
- MediaActionType.next,
- MediaActionType.previous,
- MediaActionType.skipForward,
- MediaActionType.skipBackward,
- MediaActionType.seekTo,
- MediaActionType.custom,
- }, skipIntervalSeconds: 30);
+ @override
+ Future onSkipToNext() async {
+ if (_playing == null) {
+ // First time, we want to start playing
+ _playing = true;
+ } else {
+ // Stop current item
+ await _audioPlayer.stop();
+ _queue.removeAt(0);
+ }
+ AudioServiceBackground.setQueue(_queue);
+ AudioServiceBackground.setMediaItem(mediaItem);
+ _skipState = BasicPlaybackState.skippingToNext;
+ await _audioPlayer.setUrl(mediaItem.id);
+ print(mediaItem.id);
+ Duration duration = await _audioPlayer.durationFuture;
+ AudioServiceBackground.setMediaItem(
+ mediaItem.copyWith(duration: duration.inMilliseconds));
+ _skipState = null;
+ // Resume playback if we were playing
+ if (_playing) {
+ onPlay();
+ } else {
+ _setState(state: BasicPlaybackState.paused);
+ }
}
- void _pauseBackgroundAudio() {
- _backgroundAudio?.pause();
- _backgroundAudioPlaying = false;
- AudioSystem.instance
- .setPlaybackState(false, _backgroundAudioPositionSeconds);
- AudioSystem.instance.setAndroidNotificationButtons([
- AndroidMediaButtonType.play,
- _forwardButton,
- AndroidMediaButtonType.stop,
- ], androidCompactIndices: [
- 0,
- 1,
- ]);
-
- AudioSystem.instance.setSupportedMediaActions({
- MediaActionType.playPause,
- MediaActionType.play,
- MediaActionType.next,
- MediaActionType.previous,
- });
+ @override
+ void onPlay() async {
+ if (_skipState == null) {
+ if (_playing == null) {
+ _playing = true;
+ AudioServiceBackground.setQueue(_queue);
+ await _audioPlayer.setUrl(mediaItem.id);
+ Duration duration = await _audioPlayer.durationFuture;
+ AudioServiceBackground.setMediaItem(
+ mediaItem.copyWith(duration: duration.inMilliseconds));
+ }
+ _playing = true;
+ _audioPlayer.play();
+ }
}
- void _stopBackgroundAudio() {
- _backgroundAudio.pause();
- _backgroundAudio.dispose();
- _backgroundAudioPlaying = false;
- AudioSystem.instance.stopBackgroundDisplay();
+ @override
+ void onPause() {
+ if (_skipState == null) {
+ if (_playing == null) {}
+ _playing = false;
+ _audioPlayer.pause();
+ }
}
- void _forwardBackgroundAudio(double seconds) {
- final double forwardposition = _backgroundAudioPositionSeconds + seconds;
- _backgroundAudio.seek(forwardposition);
- AudioSystem.instance
- .setPlaybackState(true, _backgroundAudioPositionSeconds);
+ @override
+ void onSeekTo(int position) {
+ _audioPlayer.seek(Duration(milliseconds: position));
}
- final _pauseButton = AndroidCustomMediaButton(
- 'pausenow', pausenowButtonId, 'ic_stat_pause_circle_filled');
- final _replay10Button = AndroidCustomMediaButton(
- 'replay10', replay10ButtonId, 'ic_stat_replay_10');
- final _forwardButton = AndroidCustomMediaButton(
- 'forward', forwardButtonId, 'ic_stat_forward_30');
- final _playnowButton = AndroidCustomMediaButton(
- 'playnow', likeButtonId, 'ic_stat_play_circle_filled');
+ @override
+ void onClick(MediaButton button) {
+ playPause();
+ }
+
+ @override
+ void onStop() async {
+ await _audioPlayer.stop();
+ _setState(state: BasicPlaybackState.stopped);
+ _completer.complete();
+ }
+
+ @override
+ void onAddQueueItem(MediaItem mediaItem) async {
+ _queue.add(mediaItem);
+ AudioServiceBackground.setQueue(_queue);
+ }
+
+ @override
+ void onRemoveQueueItem(MediaItem mediaItem) async {
+ _queue.removeWhere((item) => item.id == mediaItem.id);
+ await AudioServiceBackground.setQueue(_queue);
+ }
+
+ @override
+ void onAddQueueItemAt(MediaItem mediaItem, int index) async {
+ if (index == 0) {
+ await _audioPlayer.stop();
+ _queue.removeWhere((item) => item.id == mediaItem.id);
+ _queue.insert(0, mediaItem);
+ AudioServiceBackground.setQueue(_queue);
+ AudioServiceBackground.setMediaItem(mediaItem);
+ await _audioPlayer.setUrl(mediaItem.id);
+ Duration duration = await _audioPlayer.durationFuture;
+ AudioServiceBackground.setMediaItem(
+ mediaItem.copyWith(duration: duration.inMilliseconds));
+ onPlay();
+ } else {
+ _queue.insert(index, mediaItem);
+ AudioServiceBackground.setQueue(_queue);
+ }
+ }
+
+ @override
+ void onFastForward() {
+ _audioPlayer.seek(Duration(
+ milliseconds: AudioServiceBackground.state.position + 30 * 1000));
+ }
+
+ @override
+ void onAudioFocusLost() {
+ if (_skipState == null) {
+ if (_playing == null) {}
+ _playing = false;
+ _audioPlayer.pause();
+ }
+ }
+
+ @override
+ void onAudioBecomingNoisy() {
+ if (_skipState == null) {
+ if (_playing == null) {}
+ _playing = false;
+ _audioPlayer.pause();
+ }
+ }
+
+ @override
+ void onAudioFocusGained() {
+ if (_skipState == null) {
+ if (_playing == null) {}
+ _playing = true;
+ _audioPlayer.play();
+ }
+ }
+
+ @override
+ void onCustomAction(funtion, argument) {
+ switch (funtion) {
+ case 'addQueue':
+ break;
+ case 'updateMedia':
+ break;
+ }
+ }
+
+ void _setState({@required BasicPlaybackState state, int position}) {
+ if (position == null) {
+ position = _audioPlayer.playbackEvent.position.inMilliseconds;
+ }
+ AudioServiceBackground.setState(
+ controls: getControls(state),
+ systemActions: [MediaAction.seekTo],
+ basicState: state,
+ position: position,
+ );
+ }
+
+ List getControls(BasicPlaybackState state) {
+ if (_playing) {
+ return [pauseControl, forward30, skipToNextControl, stopControl];
+ } else {
+ return [playControl, forward30, skipToNextControl, stopControl];
+ }
+ }
}
diff --git a/lib/class/episodebrief.dart b/lib/class/episodebrief.dart
index d76bb39..8427652 100644
--- a/lib/class/episodebrief.dart
+++ b/lib/class/episodebrief.dart
@@ -1,4 +1,5 @@
import 'package:intl/intl.dart';
+import 'package:audio_service/audio_service.dart';
class EpisodeBrief {
final String title;
@@ -13,6 +14,7 @@ class EpisodeBrief {
final int duration;
final int explicit;
final String imagePath;
+ final String mediaId;
EpisodeBrief(
this.title,
this.enclosureUrl,
@@ -21,21 +23,31 @@ class EpisodeBrief {
this.feedTitle,
this.primaryColor,
this.liked,
- this.downloaded,
+ this.downloaded,
this.duration,
this.explicit,
- this.imagePath
- );
+ this.imagePath,
+ this.mediaId);
- String dateToString(){
- DateTime date = DateTime.fromMillisecondsSinceEpoch(pubDate);
+ String dateToString() {
+ DateTime date = DateTime.fromMillisecondsSinceEpoch(pubDate, isUtc: true);
var diffrence = DateTime.now().difference(date);
- if(diffrence.inHours < 24) {
+ if (diffrence.inHours < 24) {
return '${diffrence.inHours} hours ago';
- } else if (diffrence.inDays < 7){
- return '${diffrence.inDays} days ago';}
- else {
- return DateFormat.yMMMd().format( DateTime.fromMillisecondsSinceEpoch(pubDate));
- }
+ } else if (diffrence.inDays < 7) {
+ return '${diffrence.inDays} days ago';
+ } else {
+ return DateFormat.yMMMd()
+ .format(DateTime.fromMillisecondsSinceEpoch(pubDate));
}
+ }
+
+ MediaItem toMediaItem() {
+ return MediaItem(
+ id: mediaId,
+ title: title,
+ artist: feedTitle,
+ album: feedTitle,
+ artUri: 'file://$imagePath');
+ }
}
diff --git a/lib/class/podcast_group.dart b/lib/class/podcast_group.dart
index 711cca8..7a79db4 100644
--- a/lib/class/podcast_group.dart
+++ b/lib/class/podcast_group.dart
@@ -55,8 +55,12 @@ class PodcastGroup {
}
List _podcasts;
-
+ List _orderedPodcasts;
+ List get ordereddPodcasts => _orderedPodcasts;
List get podcasts => _podcasts;
+ set setOrderedPodcasts(List list) {
+ _orderedPodcasts = list;
+ }
GroupEntity toEntity() {
return GroupEntity(name, id, color, podcastList);
@@ -82,9 +86,20 @@ class GroupList extends ChangeNotifier {
GroupList({List groups}) : _groups = groups ?? [];
bool _isLoading = false;
-
bool get isLoading => _isLoading;
+ List _orderChanged = [];
+ List get orderChanged => _orderChanged;
+ void addToOrderChanged(String name) {
+ _orderChanged.add(name);
+ notifyListeners();
+ }
+
+ void drlFromOrderChanged(String name) {
+ _orderChanged.remove(name);
+ notifyListeners();
+ }
+
@override
void addListener(VoidCallback listener) {
super.addListener(listener);
@@ -105,14 +120,15 @@ class GroupList extends ChangeNotifier {
}
Future addGroup(PodcastGroup podcastGroup) async {
+ _isLoading = true;
_groups.add(podcastGroup);
_saveGroup();
+ _isLoading = false;
notifyListeners();
}
Future delGroup(PodcastGroup podcastGroup) async {
_isLoading = true;
- notifyListeners();
podcastGroup.podcastList.forEach((podcast) {
if (!_groups.first.podcastList.contains(podcast)) {
_groups[0].podcastList.insert(0, podcast);
@@ -121,11 +137,11 @@ class GroupList extends ChangeNotifier {
_saveGroup();
_groups.remove(podcastGroup);
await _groups[0].getPodcasts();
- _isLoading = false;
+ _isLoading = false;
notifyListeners();
}
- updateGroup(PodcastGroup podcastGroup) async{
+ updateGroup(PodcastGroup podcastGroup) async {
var oldGroup = _groups.firstWhere((it) => it.id == podcastGroup.id);
var index = _groups.indexOf(oldGroup);
_groups.replaceRange(index, index + 1, [podcastGroup]);
@@ -161,7 +177,11 @@ class GroupList extends ChangeNotifier {
_isLoading = true;
notifyListeners();
getPodcastGroup(id).forEach((group) {
- group.podcastList.remove(id);
+ if (list.contains(group)) {
+ list.remove(group);
+ } else {
+ group.podcastList.remove(id);
+ }
});
list.forEach((s) {
s.podcastList.insert(0, id);
@@ -190,8 +210,8 @@ class GroupList extends ChangeNotifier {
notifyListeners();
}
- saveOrder(PodcastGroup group, List podcasts) async {
- group.podcastList = podcasts.map((e) => e.id).toList();
+ saveOrder(PodcastGroup group) async {
+ group.podcastList = group.ordereddPodcasts.map((e) => e.id).toList();
_saveGroup();
await group.getPodcasts();
notifyListeners();
diff --git a/lib/class/settingstate.dart b/lib/class/settingstate.dart
index fbe8631..61dd504 100644
--- a/lib/class/settingstate.dart
+++ b/lib/class/settingstate.dart
@@ -6,16 +6,17 @@ import 'package:tsacdop/local_storage/key_value_storage.dart';
class SettingState extends ChangeNotifier {
KeyValueStorage themestorage = KeyValueStorage('themes');
KeyValueStorage accentstorage = KeyValueStorage('accents');
- bool _isLoading;
- bool get isLoagding => _isLoading;
+ KeyValueStorage autoupdatestorage = KeyValueStorage('autoupdate');
Future initData() async {
await _getTheme();
await _getAccentSetColor();
+ await _getAutoUpdate();
}
ThemeMode _theme;
ThemeMode get theme => _theme;
+
set setTheme(ThemeMode mode) {
_theme = mode;
_saveTheme();
@@ -31,11 +32,20 @@ class SettingState extends ChangeNotifier {
notifyListeners();
}
+ bool _autoUpdate;
+ bool get autoUpdate => _autoUpdate;
+ set autoUpdate(bool boo) {
+ _autoUpdate = boo;
+ _saveAutoUpdate();
+ notifyListeners();
+ }
+
@override
void addListener(VoidCallback listener) {
super.addListener(listener);
_getTheme();
_getAccentSetColor();
+ _getAutoUpdate();
}
_getTheme() async {
@@ -62,6 +72,14 @@ class SettingState extends ChangeNotifier {
_saveAccentSetColor() async {
await accentstorage
.saveString(_accentSetColor.toString().substring(10, 16));
- print(_accentSetColor.toString());
+ }
+
+ _getAutoUpdate() async {
+ int i = await autoupdatestorage.getInt();
+ _autoUpdate = i == 0 ? false : true;
+ }
+
+ _saveAutoUpdate() async {
+ await autoupdatestorage.saveInt(_autoUpdate ? 1 : 0);
}
}
diff --git a/lib/class/sub_history.dart b/lib/class/sub_history.dart
new file mode 100644
index 0000000..2a0291a
--- /dev/null
+++ b/lib/class/sub_history.dart
@@ -0,0 +1,8 @@
+class SubHistory {
+ DateTime subDate;
+ DateTime delDate;
+ bool status;
+ String title;
+ String rssUrl;
+ SubHistory(this.status, this.delDate, this.subDate, this.rssUrl, this.title);
+}
diff --git a/lib/episodes/episodedetail.dart b/lib/episodes/episodedetail.dart
index 0d5a768..5c73a80 100644
--- a/lib/episodes/episodedetail.dart
+++ b/lib/episodes/episodedetail.dart
@@ -10,6 +10,7 @@ import 'package:url_launcher/url_launcher.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:intl/intl.dart';
import 'package:tuple/tuple.dart';
+import 'package:audio_service/audio_service.dart';
import 'package:tsacdop/class/audiostate.dart';
import 'package:tsacdop/class/episodebrief.dart';
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
@@ -211,7 +212,7 @@ class _EpisodeDetailState extends State {
],
),
),
- Selector(
+ Selector(
selector: (_, audio) => audio.playerRunning,
builder: (_, data, __) {
return Container(
@@ -290,7 +291,7 @@ class _MenuBarState extends State {
@override
Widget build(BuildContext context) {
- var audio = Provider.of(context, listen: false);
+ var audio = Provider.of(context, listen: false);
return Container(
height: 50.0,
decoration: BoxDecoration(
@@ -346,7 +347,7 @@ class _MenuBarState extends State {
],
),
DownloadButton(episodeBrief: widget.episodeItem),
- Selector>(
+ Selector>(
selector: (_, audio) =>
audio.queue.playlist.map((e) => e.enclosureUrl).toList(),
builder: (_, data, __) {
@@ -367,9 +368,9 @@ class _MenuBarState extends State {
),
Spacer(),
// Text(audio.audioState.toString()),
- Selector>(
+ Selector>(
selector: (_, audio) =>
- Tuple2(audio.episode, audio.backgroundAudioPlaying),
+ Tuple2(audio.episode, audio.audioState),
builder: (_, data, __) {
return (widget.episodeItem.title != data.item1?.title)
? Material(
@@ -400,7 +401,7 @@ class _MenuBarState extends State {
),
)
: (widget.episodeItem.title == data.item1?.title &&
- data.item2 == true)
+ data.item2 == BasicPlaybackState.playing)
? Container(
padding: EdgeInsets.only(right: 30),
child: SizedBox(
@@ -424,9 +425,10 @@ class _MenuBarState extends State {
class LinePainter extends CustomPainter {
double _fraction;
Paint _paint;
- LinePainter(this._fraction) {
+ Color _maincolor;
+ LinePainter(this._fraction, this._maincolor) {
_paint = Paint()
- ..color = Colors.blue
+ ..color = _maincolor
..strokeWidth = 2.0
..strokeCap = StrokeCap.round;
}
@@ -483,14 +485,15 @@ class _LineLoaderState extends State
@override
Widget build(BuildContext context) {
- return CustomPaint(painter: LinePainter(_fraction));
+ return CustomPaint(painter: LinePainter(_fraction, Theme.of(context).accentColor));
}
}
class WavePainter extends CustomPainter {
double _fraction;
double _value;
- WavePainter(this._fraction);
+ Color _color;
+ WavePainter(this._fraction, this._color);
@override
void paint(Canvas canvas, Size size) {
if (_fraction < 0.5) {
@@ -500,7 +503,7 @@ class WavePainter extends CustomPainter {
}
Path _path = Path();
Paint _paint = Paint()
- ..color = Colors.blue
+ ..color = _color
..strokeWidth = 2.0
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke;
@@ -575,7 +578,7 @@ class _WaveLoaderState extends State
@override
Widget build(BuildContext context) {
- return CustomPaint(painter: WavePainter(_fraction));
+ return CustomPaint(painter: WavePainter(_fraction, Theme.of(context).accentColor));
}
}
diff --git a/lib/episodes/episodedownload.dart b/lib/episodes/episodedownload.dart
index 9b4271e..5566093 100644
--- a/lib/episodes/episodedownload.dart
+++ b/lib/episodes/episodedownload.dart
@@ -1,14 +1,17 @@
import 'dart:isolate';
import 'dart:ui';
import 'dart:io';
+import 'dart:async';
import 'package:flutter/material.dart';
-import 'dart:async';
+import 'package:provider/provider.dart';
import 'package:path_provider/path_provider.dart';
+import 'package:path/path.dart' as path;
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:tsacdop/class/episodebrief.dart';
+import 'package:tsacdop/class/audiostate.dart';
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
class DownloadButton extends StatefulWidget {
@@ -74,6 +77,7 @@ class _DownloadButtonState extends State {
});
}
+
void _unbindBackgroundIsolate() {
IsolateNameServer.removePortNameMapping('downloader_send_port');
}
@@ -96,6 +100,7 @@ class _DownloadButtonState extends State {
openFileFromNotification: false,
);
var dbHelper = DBHelper();
+
await dbHelper.saveDownloaded(task.link, task.taskId);
Fluttertoast.showToast(
msg: 'Downloading',
@@ -103,6 +108,7 @@ class _DownloadButtonState extends State {
);
}
+
void _deleteDownload(_TaskInfo task) async {
await FlutterDownloader.remove(
taskId: task.taskId, shouldDeleteContent: true);
@@ -146,6 +152,16 @@ class _DownloadButtonState extends State {
);
}
+ _saveMediaId(_TaskInfo task) async{
+ final completeTask = await FlutterDownloader.loadTasksWithRawQuery(
+ query: "SELECT * FROM task WHERE url = '${task.link}'");
+ String filePath = 'file://' + path.join(completeTask.first.savedDir, completeTask.first.filename);
+ var dbHelper = DBHelper();
+ await dbHelper.saveMediaId(task.link, filePath);
+ EpisodeBrief episode = await dbHelper.getRssItemWithUrl(task.link);
+ await Provider.of(context, listen: false).updateMediaItem(episode);
+ }
+
Future _prepare() async {
final tasks = await FlutterDownloader.loadTasks();
@@ -161,7 +177,7 @@ class _DownloadButtonState extends State {
}
});
- _localPath = (await _getPath()) + '/' + widget.episodeBrief.feedTitle;
+ _localPath = path.join((await _getPath()) ,widget.episodeBrief.feedTitle);
print(_localPath);
final saveDir = Directory(_localPath);
bool hasExisted = await saveDir.exists();
@@ -173,6 +189,8 @@ class _DownloadButtonState extends State {
});
}
+
+
Future _checkPermmison() async {
PermissionStatus permission = await PermissionHandler()
.checkPermissionStatus(PermissionGroup.storage);
@@ -284,6 +302,7 @@ class _DownloadButtonState extends State {
),
);
} else if (task.status == DownloadTaskStatus.complete) {
+ _saveMediaId(task);
return _buttonOnMenu(
Icon(
Icons.done_all,
@@ -307,4 +326,4 @@ class _TaskInfo {
DownloadTaskStatus status = DownloadTaskStatus.undefined;
_TaskInfo({this.name, this.link});
-}
+}
\ No newline at end of file
diff --git a/lib/home/appbar/about.dart b/lib/home/appbar/about.dart
index 45b511f..05fb01b 100644
--- a/lib/home/appbar/about.dart
+++ b/lib/home/appbar/about.dart
@@ -1,8 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:url_launcher/url_launcher.dart';
-import 'package:font_awesome_flutter/font_awesome_flutter.dart';
-
+import 'package:line_icons/line_icons.dart';
class AboutApp extends StatelessWidget {
_launchUrl(String url) async {
if (await canLaunch(url)) {
@@ -71,7 +70,7 @@ class AboutApp extends StatelessWidget {
image: AssetImage('assets/logo.png'),
height: 80,
),
- Text('Version: 0.1.1'),
+ Text('Version: 0.1.2'),
],
),
),
@@ -79,7 +78,7 @@ class AboutApp extends StatelessWidget {
padding: EdgeInsets.symmetric(horizontal: 50),
height: 50,
child: Text(
- 'Tsacdop is a podcast client developed with flutter, a simple, beautiful, and easy-use player.',
+ 'Tsacdop is a podcasts client developed influtter, a simple, beautiful, and easy-use player.',
textAlign: TextAlign.center,
),
),
@@ -111,17 +110,17 @@ class AboutApp extends StatelessWidget {
_listItem(
context,
'GitHub',
- FontAwesomeIcons.githubSquare,
- 'https://github.com/stonaga/tsacdop'),
+ LineIcons.github,
+ 'https://github.com/stonaga/'),
_listItem(
context,
'Twitter',
- FontAwesomeIcons.twitterSquare,
+ LineIcons.twitter,
'https://twitter.com'),
_listItem(
context,
- 'Gmail',
- FontAwesomeIcons.envelopeSquare,
+ 'Stone Gate',
+ LineIcons.hat_cowboy_solid,
'mailto:?subject=Tsacdop Feedback'),
],
),
diff --git a/lib/home/appbar/addpodcast.dart b/lib/home/appbar/addpodcast.dart
index 6319f67..7a582fe 100644
--- a/lib/home/appbar/addpodcast.dart
+++ b/lib/home/appbar/addpodcast.dart
@@ -36,6 +36,7 @@ class _MyHomePageState extends State {
return AnnotatedRegion(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
+ systemNavigationBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: Theme.of(context).primaryColor,
statusBarColor: Theme.of(context).primaryColor,
),
diff --git a/lib/home/appbar/popupmenu.dart b/lib/home/appbar/popupmenu.dart
index 0953f57..20851d2 100644
--- a/lib/home/appbar/popupmenu.dart
+++ b/lib/home/appbar/popupmenu.dart
@@ -13,6 +13,7 @@ import 'package:color_thief_flutter/color_thief_flutter.dart';
import 'package:image/image.dart' as img;
import 'package:uuid/uuid.dart';
import 'package:fluttertoast/fluttertoast.dart';
+import 'package:line_icons/line_icons.dart';
import 'package:tsacdop/class/podcast_group.dart';
import 'package:tsacdop/settings/settting.dart';
@@ -142,7 +143,7 @@ class PopupMenu extends StatelessWidget {
void _saveOmpl(String path) async {
File file = File(path);
- String opml = file.readAsStringSync();
+ try{String opml = file.readAsStringSync();
var content = xml.parse(opml);
var total = content
@@ -167,6 +168,15 @@ class PopupMenu extends StatelessWidget {
}
}
print('Import fisnished');
+ }}
+ catch(e){
+ print(e);
+ Fluttertoast.showToast(
+ msg: 'File error, Subscribe failed',
+ gravity: ToastGravity.TOP,
+ );
+ await Future.delayed(Duration(seconds: 5));
+ importOmpl.importState = ImportState.stop;
}
}
@@ -195,7 +205,7 @@ class PopupMenu extends StatelessWidget {
padding: EdgeInsets.only(left: 10),
child: Row(
children: [
- Icon(Icons.refresh),
+ Icon(LineIcons.cloud_download_alt_solid),
Padding(padding: EdgeInsets.symmetric(horizontal: 5.0),),
Text('Refresh All'),
],
@@ -208,7 +218,7 @@ class PopupMenu extends StatelessWidget {
padding: EdgeInsets.only(left: 10),
child: Row(
children: [
- Icon(Icons.attachment),
+ Icon(LineIcons.paperclip_solid),
Padding(padding: EdgeInsets.symmetric(horizontal: 5.0),),
Text('Import OMPL'),
],
@@ -226,7 +236,7 @@ class PopupMenu extends StatelessWidget {
padding: EdgeInsets.only(left: 10),
child: Row(
children: [
- Icon(Icons.swap_calls),
+ Icon(LineIcons.cog_solid),
Padding(padding: EdgeInsets.symmetric(horizontal: 5.0),),
Text('Settings'),
],
@@ -239,7 +249,7 @@ class PopupMenu extends StatelessWidget {
padding: EdgeInsets.only(left: 10),
child: Row(
children: [
- Icon(Icons.info_outline),
+ Icon(LineIcons.info_circle_solid),
Padding(padding: EdgeInsets.symmetric(horizontal: 5.0),),
Text('About'),
],
diff --git a/lib/home/audioplayer.dart b/lib/home/audioplayer.dart
index 99d6108..f1bd414 100644
--- a/lib/home/audioplayer.dart
+++ b/lib/home/audioplayer.dart
@@ -7,6 +7,7 @@ import 'package:provider/provider.dart';
import 'package:marquee/marquee.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:tuple/tuple.dart';
+import 'package:audio_service/audio_service.dart';
import 'package:tsacdop/class/episodebrief.dart';
import 'package:tsacdop/class/audiostate.dart';
@@ -15,6 +16,97 @@ import 'package:tsacdop/home/audiopanel.dart';
import 'package:tsacdop/util/pageroute.dart';
import 'package:tsacdop/util/colorize.dart';
+class MyRoundSliderThumpShape extends SliderComponentShape {
+ const MyRoundSliderThumpShape({
+ this.enabledThumbRadius = 10.0,
+ this.disabledThumbRadius,
+ this.thumbCenterColor,
+ });
+ final Color thumbCenterColor;
+
+ /// The preferred radius of the round thumb shape when the slider is enabled.
+ ///
+ /// If it is not provided, then the material default of 10 is used.
+ final double enabledThumbRadius;
+
+ /// The preferred radius of the round thumb shape when the slider is disabled.
+ ///
+ /// If no disabledRadius is provided, then it is equal to the
+ /// [enabledThumbRadius]
+ final double disabledThumbRadius;
+ double get _disabledThumbRadius => disabledThumbRadius ?? enabledThumbRadius;
+
+ @override
+ Size getPreferredSize(bool isEnabled, bool isDiscrete) {
+ return Size.fromRadius(
+ isEnabled == true ? enabledThumbRadius : _disabledThumbRadius);
+ }
+
+ @override
+ void paint(
+ PaintingContext context,
+ Offset center, {
+ Animation activationAnimation,
+ @required Animation enableAnimation,
+ bool isDiscrete,
+ TextPainter labelPainter,
+ RenderBox parentBox,
+ @required SliderThemeData sliderTheme,
+ TextDirection textDirection,
+ double value,
+ }) {
+ assert(context != null);
+ assert(center != null);
+ assert(enableAnimation != null);
+ assert(sliderTheme != null);
+ assert(sliderTheme.disabledThumbColor != null);
+ assert(sliderTheme.thumbColor != null);
+
+ final Canvas canvas = context.canvas;
+ final Tween radiusTween = Tween(
+ begin: _disabledThumbRadius,
+ end: enabledThumbRadius,
+ );
+ final ColorTween colorTween = ColorTween(
+ begin: sliderTheme.disabledThumbColor,
+ end: sliderTheme.thumbColor,
+ );
+
+ canvas.drawCircle(
+ center,
+ radiusTween.evaluate(enableAnimation),
+ Paint()
+ ..color = thumbCenterColor
+ ..style = PaintingStyle.stroke
+ ..strokeWidth = 2,
+ );
+ canvas.drawLine(
+ Offset(center.dx - 6, center.dy),
+ Offset(center.dx + 6, center.dy),
+ Paint()
+ ..color = Colors.grey[300]
+ ..style = PaintingStyle.fill
+ ..strokeWidth = 2,
+ );
+ canvas.drawCircle(
+ center,
+ radiusTween.evaluate(enableAnimation) - 2,
+ Paint()
+ ..color = colorTween.evaluate(enableAnimation)
+ ..style = PaintingStyle.fill
+ ..strokeWidth = 2,
+ );
+ canvas.drawLine(
+ Offset(center.dx - 5, center.dy - 2),
+ Offset(center.dx + 5, center.dy + 2),
+ Paint()
+ ..color = Colors.transparent
+ ..style = PaintingStyle.fill
+ ..strokeWidth = 2,
+ );
+ }
+}
+
class PlayerWidget extends StatefulWidget {
@override
_PlayerWidgetState createState() => _PlayerWidgetState();
@@ -50,17 +142,17 @@ class _PlayerWidgetState extends State {
_timeLeft = _minSelected;
_timer = Timer.periodic(Duration(minutes: 1), (timer) {
setState(() {
- if(_timeLeft < 1){
+ if (_timeLeft < 1) {
_timer.cancel();
- } else{
+ } else {
_timeLeft = _timeLeft - 1;
}
});
- });
+ });
}
Widget _sleepTimer(BuildContext context) {
- var audio = Provider.of(context);
+ var audio = Provider.of(context);
return Container(
height: 50,
margin: EdgeInsets.all(10.0),
@@ -141,7 +233,7 @@ class _PlayerWidgetState extends State {
}
Widget _expandedPanel(BuildContext context) {
- var audio = Provider.of(context, listen: false);
+ var audio = Provider.of(context, listen: false);
return Stack(
children: [
Container(
@@ -156,7 +248,7 @@ class _PlayerWidgetState extends State {
height: 80.0,
padding: EdgeInsets.all(20),
alignment: Alignment.center,
- child: Selector(
+ child: Selector(
selector: (_, audio) => audio.episode.title,
builder: (_, title, __) {
return Container(
@@ -201,8 +293,12 @@ class _PlayerWidgetState extends State {
},
),
),
- Consumer(
+ Consumer(
builder: (_, data, __) {
+ Color _c =
+ (Theme.of(context).brightness == Brightness.light)
+ ? data.episode.primaryColor.colorizedark()
+ : data.episode.primaryColor.colorizeLight();
return Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
@@ -215,10 +311,12 @@ class _PlayerWidgetState extends State {
.accentColor
.withOpacity(0.5),
inactiveTrackColor: Colors.grey[300],
- trackHeight: 3.0,
+ trackHeight: 2.0,
thumbColor: Theme.of(context).accentColor,
- thumbShape: RoundSliderThumbShape(
- enabledThumbRadius: 6.0),
+ thumbShape: MyRoundSliderThumpShape(
+ enabledThumbRadius: 5.0,
+ disabledThumbRadius: 5.0,
+ thumbCenterColor: _c),
overlayColor:
Theme.of(context).accentColor.withAlpha(32),
overlayShape:
@@ -238,7 +336,7 @@ class _PlayerWidgetState extends State {
children: [
Text(
_stringForSeconds(
- data.backgroundAudioPosition) ??
+ data.backgroundAudioPosition / 1000) ??
'',
style: TextStyle(fontSize: 10),
),
@@ -250,7 +348,12 @@ class _PlayerWidgetState extends State {
style: const TextStyle(
color: const Color(0xFFFF0000)))
: Text(
- data.remoteAudioLoading
+ data.audioState ==
+ BasicPlaybackState
+ .buffering ||
+ data.audioState ==
+ BasicPlaybackState
+ .connecting
? 'Buffring...'
: '',
style: TextStyle(
@@ -261,7 +364,7 @@ class _PlayerWidgetState extends State {
),
Text(
_stringForSeconds(
- data.backgroundAudioDuration) ??
+ data.backgroundAudioDuration / 1000) ??
'',
style: TextStyle(fontSize: 10),
),
@@ -274,8 +377,8 @@ class _PlayerWidgetState extends State {
),
Container(
height: 100,
- child: Selector(
- selector: (_, audio) => audio.backgroundAudioPlaying,
+ child: Selector(
+ selector: (_, audio) => audio.audioState,
builder: (_, backplay, __) {
return Material(
color: Colors.transparent,
@@ -285,22 +388,24 @@ class _PlayerWidgetState extends State {
children: [
IconButton(
padding: EdgeInsets.symmetric(horizontal: 30.0),
- onPressed: backplay
- ? () => audio.forwardAudio(-10)
- : null,
+ onPressed:
+ backplay == BasicPlaybackState.playing
+ ? () => audio.forwardAudio(-10)
+ : null,
iconSize: 32.0,
icon: Icon(Icons.replay_10),
color:
Theme.of(context).tabBarTheme.labelColor),
- backplay
+ backplay == BasicPlaybackState.playing
? IconButton(
padding:
EdgeInsets.symmetric(horizontal: 30.0),
- onPressed: backplay
- ? () {
- audio.pauseAduio();
- }
- : null,
+ onPressed:
+ backplay == BasicPlaybackState.playing
+ ? () {
+ audio.pauseAduio();
+ }
+ : null,
iconSize: 40.0,
icon: Icon(Icons.pause_circle_filled),
color: Theme.of(context)
@@ -309,11 +414,12 @@ class _PlayerWidgetState extends State {
: IconButton(
padding:
EdgeInsets.symmetric(horizontal: 30.0),
- onPressed: backplay
- ? null
- : () {
- audio.resumeAudio();
- },
+ onPressed:
+ backplay == BasicPlaybackState.playing
+ ? null
+ : () {
+ audio.resumeAudio();
+ },
iconSize: 40.0,
icon: Icon(Icons.play_circle_filled),
color: Theme.of(context)
@@ -321,9 +427,10 @@ class _PlayerWidgetState extends State {
.labelColor),
IconButton(
padding: EdgeInsets.symmetric(horizontal: 30.0),
- onPressed: backplay
- ? () => audio.forwardAudio(30)
- : null,
+ onPressed:
+ backplay == BasicPlaybackState.playing
+ ? () => audio.forwardAudio(30)
+ : null,
iconSize: 32.0,
icon: Icon(Icons.forward_30),
color:
@@ -344,7 +451,7 @@ class _PlayerWidgetState extends State {
color: Theme.of(context).scaffoldBackgroundColor,
borderRadius: BorderRadius.all(Radius.circular(10.0)),
),
- child: Selector>(
selector: (_, audio) => Tuple3(audio.episode,
audio.stopOnComplete, audio.showStopWatch),
@@ -394,6 +501,7 @@ class _PlayerWidgetState extends State {
],
onSelected: (value) {
if (value == 1) {
+ audio.sleepTimer(_minSelected);
audio.setStopOnComplete = true;
} else if (value == 2) {
setState(() => _showTimer = true);
@@ -414,8 +522,7 @@ class _PlayerWidgetState extends State {
color:
Theme.of(context).accentColor,
),
- child: Text(
- _timeLeft.toString(),
+ child: Text(_timeLeft.toString(),
style: TextStyle(
color: Colors.white)),
),
@@ -475,7 +582,7 @@ class _PlayerWidgetState extends State {
// margin: EdgeInsets.all(20),
//padding: EdgeInsets.only(bottom: 10.0),
decoration: BoxDecoration(
- borderRadius: BorderRadius.all(Radius.circular(10.0)),
+ // borderRadius: BorderRadius.all(Radius.circular(10.0)),
color: Theme.of(context).scaffoldBackgroundColor,
),
child: Column(
@@ -511,7 +618,7 @@ class _PlayerWidgetState extends State {
),
),
Expanded(
- child: Selector>(
+ child: Selector>(
selector: (_, audio) => audio.queue.playlist,
builder: (_, playlist, __) {
return ListView.builder(
@@ -623,7 +730,7 @@ class _PlayerWidgetState extends State {
}
Widget _miniPanel(double width, BuildContext context) {
- var audio = Provider.of(context, listen: false);
+ var audio = Provider.of(context, listen: false);
return Container(
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
@@ -631,7 +738,7 @@ class _PlayerWidgetState extends State {
height: 60,
child:
Column(mainAxisAlignment: MainAxisAlignment.start, children: [
- Selector>(
+ Selector>(
selector: (_, audio) =>
Tuple2(audio.episode?.primaryColor, audio.seekSliderValue),
builder: (_, data, __) {
@@ -657,58 +764,33 @@ class _PlayerWidgetState extends State {
children: [
Expanded(
flex: 4,
- child: Selector(
+ child: Selector(
selector: (_, audio) => audio.episode.title,
builder: (_, title, __) {
- return LayoutBuilder(
- builder: (context, size) {
- var span = TextSpan(
- text: title,
- style: TextStyle(fontWeight: FontWeight.bold),
- );
- var tp = TextPainter(
- text: span,
- maxLines: 2,
- textDirection: TextDirection.ltr);
- tp.layout(maxWidth: size.maxWidth);
- if (tp.didExceedMaxLines) {
- return Marquee(
- text: title,
- style: TextStyle(fontWeight: FontWeight.bold),
- scrollAxis: Axis.vertical,
- crossAxisAlignment: CrossAxisAlignment.start,
- blankSpace: 30.0,
- velocity: 50.0,
- pauseAfterRound: Duration(seconds: 1),
- startPadding: 30.0,
- accelerationDuration: Duration(seconds: 1),
- accelerationCurve: Curves.linear,
- decelerationDuration: Duration(milliseconds: 500),
- decelerationCurve: Curves.easeOut,
- );
- } else {
- return Text(
- title,
- style: TextStyle(fontWeight: FontWeight.bold),
- );
- }
- },
+ return Text(
+ title,
+ style: TextStyle(fontWeight: FontWeight.bold),
+ maxLines: 2,
+ overflow: TextOverflow.clip,
);
},
),
),
Expanded(
flex: 2,
- child: Selector>(
+ child: Selector>(
selector: (_, audio) => Tuple2(
- audio.remoteAudioLoading,
+ audio.audioState,
(audio.backgroundAudioDuration -
- audio.backgroundAudioPosition)),
+ audio.backgroundAudioPosition) /
+ 1000),
builder: (_, data, __) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 10),
alignment: Alignment.center,
- child: data.item1
+ child: data.item1 == BasicPlaybackState.buffering ||
+ data.item1 == BasicPlaybackState.connecting
? Text(
'Buffring...',
style: TextStyle(
@@ -730,30 +812,32 @@ class _PlayerWidgetState extends State {
),
Expanded(
flex: 2,
- child: Selector(
- selector: (_, audio) => audio.backgroundAudioPlaying,
+ child: Selector(
+ selector: (_, audio) => audio.audioState,
builder: (_, audioplay, __) {
return Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
- audioplay
+ audioplay == BasicPlaybackState.playing
? InkWell(
- onTap: audioplay
- ? () {
- audio.pauseAduio();
- }
- : null,
+ onTap:
+ audioplay == BasicPlaybackState.playing
+ ? () {
+ audio.pauseAduio();
+ }
+ : null,
child: ImageRotate(
title: audio.episode.title,
path: audio.episode.imagePath),
)
: InkWell(
- onTap: audioplay
- ? null
- : () {
- audio.resumeAudio();
- },
+ onTap:
+ audioplay == BasicPlaybackState.playing
+ ? null
+ : () {
+ audio.resumeAudio();
+ },
child: Stack(
alignment: Alignment.center,
children: [
@@ -781,11 +865,10 @@ class _PlayerWidgetState extends State {
),
),
IconButton(
- onPressed: audioplay
- ? () => audio.forwardAudio(30)
- : null,
+ onPressed:
+ () => audio.playNext(),
iconSize: 25.0,
- icon: Icon(Icons.forward_30),
+ icon: Icon(Icons.skip_next),
color:
Theme.of(context).tabBarTheme.labelColor),
],
@@ -803,7 +886,7 @@ class _PlayerWidgetState extends State {
@override
Widget build(BuildContext context) {
double _width = MediaQuery.of(context).size.width;
- return Selector(
+ return Selector(
selector: (_, audio) => audio.playerRunning,
builder: (_, playerrunning, __) {
return !playerrunning
diff --git a/lib/home/home.dart b/lib/home/home.dart
index 0cd1b39..0fcfaf4 100644
--- a/lib/home/home.dart
+++ b/lib/home/home.dart
@@ -31,7 +31,7 @@ class _HomeState extends State {
}
_getPlaylist() async {
- await Provider.of(context, listen: false).loadPlaylist();
+ await Provider.of(context, listen: false).loadPlaylist();
setState(() {
_loadPlay = true;
});
@@ -39,7 +39,7 @@ class _HomeState extends State {
@override
Widget build(BuildContext context) {
- var audio = Provider.of(context, listen: false);
+ var audio = Provider.of(context, listen: false);
return Stack(children: [
Column(
mainAxisAlignment: MainAxisAlignment.start,
@@ -58,7 +58,7 @@ class _HomeState extends State {
bottom: 50,
right: _loadPlay ? 5 : -25,
child: Container(
- child: Selector>(
+ child: Selector>(
selector: (_, audio) =>
Tuple3(audio.playerRunning, audio.queue, audio.lastPositin),
builder: (_, data, __) => !_loadPlay
@@ -90,7 +90,7 @@ class _HomeState extends State {
offset: Offset(1, 1)),
]),
height: 40,
- child: Text(_stringForSeconds(data.item3) + '...',
+ child: Text(_stringForSeconds(data.item3~/1000) + '...',
style: TextStyle(color: Colors.white)),
),
CircleAvatar(
diff --git a/lib/home/homescroll.dart b/lib/home/homescroll.dart
index 0570783..fafd987 100644
--- a/lib/home/homescroll.dart
+++ b/lib/home/homescroll.dart
@@ -5,7 +5,9 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:fluttertoast/fluttertoast.dart';
-
+import 'package:tsacdop/class/audiostate.dart';
+import 'package:tuple/tuple.dart';
+import 'package:line_icons/line_icons.dart';
import 'package:tsacdop/class/episodebrief.dart';
import 'package:tsacdop/class/importompl.dart';
import 'package:tsacdop/class/podcast_group.dart';
@@ -89,7 +91,9 @@ class _ScrollPodcastsState extends State {
style: Theme.of(context)
.textTheme
.bodyText1
- .copyWith(color: Colors.red[300]),
+ .copyWith(
+ color: Theme.of(context)
+ .accentColor),
)),
Spacer(),
Container(
@@ -184,7 +188,9 @@ class _ScrollPodcastsState extends State {
style: Theme.of(context)
.textTheme
.bodyText1
- .copyWith(color: Colors.red[300]),
+ .copyWith(
+ color: Theme.of(context)
+ .accentColor),
)),
Spacer(),
Container(
@@ -374,43 +380,78 @@ class ShowEpisode extends StatelessWidget {
final List podcast;
final PodcastLocal podcastLocal;
ShowEpisode({Key key, this.podcast, this.podcastLocal}) : super(key: key);
-
+ Offset offset;
@override
Widget build(BuildContext context) {
double _width = MediaQuery.of(context).size.width;
- _showPopupMenu(Offset offset) async {
- print(offset.dx);
+ _showPopupMenu(Offset offset, EpisodeBrief episode, BuildContext context,
+ bool isPlaying, bool isInPlaylist) async {
+ var audio = Provider.of(context, listen: false);
double left = offset.dx;
double top = offset.dy;
- await showMenu(
+ await showMenu(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10))),
context: context,
position: RelativeRect.fromLTRB(left, top, _width - left, 0),
- items: [
+ items: >[
PopupMenuItem(
+ value: 0,
child: Row(
+ mainAxisAlignment: MainAxisAlignment.start,
+ mainAxisSize: MainAxisSize.max,
children: [
- Icon(Icons.play_circle_outline),
- Padding(padding: EdgeInsets.symmetric(horizontal: 2),),
- Text('Play')
+ Icon(
+ LineIcons.play_circle_solid,
+ color: Theme.of(context).accentColor,
+ ),
+ Padding(
+ padding: EdgeInsets.symmetric(horizontal: 2),
+ ),
+ !isPlaying ? Text('Play') : Text('Playing'),
],
),
),
- PopupMenuItem(child: Row(
- children: [
- Icon(Icons.favorite_border),
- Padding(padding: EdgeInsets.symmetric(horizontal: 2),),
- Text('Like')
- ],
- )),
+ PopupMenuItem(
+ value: 1,
+ child: Row(
+ children: [
+ Icon(
+ LineIcons.clock_solid,
+ color: Colors.red,
+ ),
+ Padding(
+ padding: EdgeInsets.symmetric(horizontal: 2),
+ ),
+ !isInPlaylist ? Text('Later') : Text('Remove')
+ ],
+ )),
],
- elevation: 8.0,
- );
+ elevation: 5.0,
+ ).then((value) {
+ if (value == 0) {
+ if (!isPlaying) audio.episodeLoad(episode);
+ } else if (value == 1) {
+ if (isInPlaylist) {
+ audio.addToPlaylist(episode);
+ Fluttertoast.showToast(
+ msg: 'Added to playlist',
+ gravity: ToastGravity.BOTTOM,
+ );
+ } else {
+ audio.delFromPlaylist(episode);
+ Fluttertoast.showToast(
+ msg: 'Removed from playlist',
+ gravity: ToastGravity.BOTTOM,
+ );
+ }
+ }
+ });
}
return CustomScrollView(
- physics: const AlwaysScrollableScrollPhysics(),
+ // physics: const AlwaysScrollableScrollPhysics(),
+ physics: ClampingScrollPhysics(),
primary: false,
slivers: [
SliverPadding(
@@ -427,88 +468,110 @@ class ShowEpisode extends StatelessWidget {
Color _c = (Theme.of(context).brightness == Brightness.light)
? podcastLocal.primaryColor.colorizedark()
: podcastLocal.primaryColor.colorizeLight();
- return GestureDetector(
- onLongPressStart: (details) => _showPopupMenu(Offset(
- details.globalPosition.dx, details.globalPosition.dy)),
- onTap: () {
- Navigator.push(
- context,
- ScaleRoute(
- page: EpisodeDetail(
- episodeItem: podcast[index],
- heroTag: 'scroll',
- //unique hero tag
- )),
- );
- },
- child: Container(
+ return Selector>>(
+ selector: (_, audio) => Tuple2(
+ audio?.episode,
+ audio.queue.playlist.map((e) => e.enclosureUrl).toList(),
+ ),
+ builder: (_, data, __) => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(5.0)),
color: Theme.of(context).scaffoldBackgroundColor,
- border: Border.all(
- color: Theme.of(context).brightness == Brightness.light
- ? Theme.of(context).primaryColor
- : Theme.of(context).scaffoldBackgroundColor,
- // color: Theme.of(context).primaryColor,
- width: 3.0,
- ),
),
alignment: Alignment.center,
- padding: EdgeInsets.all(10.0),
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Expanded(
- flex: 2,
- child: Row(
- mainAxisAlignment: MainAxisAlignment.start,
+ child: Material(
+ color: Colors.transparent,
+ child: InkWell(
+ borderRadius: BorderRadius.all(Radius.circular(5.0)),
+ onTapDown: (details) => offset = Offset(
+ details.globalPosition.dx,
+ details.globalPosition.dy),
+ onLongPress: () => _showPopupMenu(
+ offset,
+ podcast[index],
+ context,
+ data.item1 == podcast[index],
+ data.item2.contains(podcast[index].enclosureUrl)),
+ onTap: () {
+ Navigator.push(
+ context,
+ ScaleRoute(
+ page: EpisodeDetail(
+ episodeItem: podcast[index],
+ heroTag: 'scroll',
+ //unique hero tag
+ )),
+ );
+ },
+ child: Container(
+ // decoration: BoxDecoration(
+ // border: Border.all(
+ // color: Theme.of(context).brightness ==
+ // Brightness.light
+ // ? Theme.of(context).primaryColor
+ // : Theme.of(context).scaffoldBackgroundColor,
+ // width: 0.0,
+ // ),
+ // ),
+ padding: EdgeInsets.all(10.0),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
children: [
- Hero(
- tag: podcast[index].enclosureUrl + 'scroll',
+ Expanded(
+ flex: 2,
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ Hero(
+ tag: podcast[index].enclosureUrl +
+ 'scroll',
+ child: Container(
+ height: _width / 18,
+ width: _width / 18,
+ child: CircleAvatar(
+ backgroundImage: FileImage(File(
+ "${podcastLocal.imagePath}")),
+ ),
+ ),
+ ),
+ Spacer(),
+ ],
+ ),
+ ),
+ Expanded(
+ flex: 5,
child: Container(
- height: _width / 18,
- width: _width / 18,
- child: CircleAvatar(
- backgroundImage: FileImage(
- File("${podcastLocal.imagePath}")),
+ padding: EdgeInsets.only(top: 2.0),
+ alignment: Alignment.topLeft,
+ child: Text(
+ podcast[index].title,
+ style: TextStyle(
+ fontSize: _width / 32,
+ ),
+ maxLines: 4,
+ overflow: TextOverflow.fade,
),
),
),
- Spacer(),
+ Expanded(
+ flex: 1,
+ child: Container(
+ alignment: Alignment.bottomLeft,
+ child: Text(
+ podcast[index].dateToString(),
+ //podcast[index].pubDate.substring(4, 16),
+ style: TextStyle(
+ fontSize: _width / 35,
+ color: _c,
+ fontStyle: FontStyle.italic,
+ ),
+ ),
+ )),
],
),
),
- Expanded(
- flex: 5,
- child: Container(
- padding: EdgeInsets.only(top: 2.0),
- alignment: Alignment.topLeft,
- child: Text(
- podcast[index].title,
- style: TextStyle(
- fontSize: _width / 32,
- ),
- maxLines: 4,
- overflow: TextOverflow.fade,
- ),
- ),
- ),
- Expanded(
- flex: 1,
- child: Container(
- alignment: Alignment.bottomLeft,
- child: Text(
- podcast[index].dateToString(),
- //podcast[index].pubDate.substring(4, 16),
- style: TextStyle(
- fontSize: _width / 35,
- color: _c,
- fontStyle: FontStyle.italic,
- ),
- ),
- ),
- ),
- ],
+ ),
),
),
);
diff --git a/lib/home/hometab.dart b/lib/home/hometab.dart
index d61cec2..e2aed7b 100644
--- a/lib/home/hometab.dart
+++ b/lib/home/hometab.dart
@@ -2,7 +2,7 @@ import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:tsacdop/class/episodebrief.dart';
-import 'package:tsacdop/home/paly_history.dart';
+import 'package:tsacdop/settings/history.dart';
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
import 'package:tsacdop/util/episodegrid.dart';
@@ -49,8 +49,8 @@ class _MainTabState extends State with TickerProviderStateMixin {
],
onSelected: (value) {
if (value == 0) {
- Navigator.push(
- context, MaterialPageRoute(builder: (context) => PlayedHistory()));
+ Navigator.push(context,
+ MaterialPageRoute(builder: (context) => PlayedHistory()));
}
},
);
@@ -81,6 +81,7 @@ class _MainTabState extends State with TickerProviderStateMixin {
height: 50,
alignment: Alignment.centerLeft,
child: TabBar(
+ indicatorSize: TabBarIndicatorSize.tab,
isScrollable: true,
labelPadding: EdgeInsets.all(10.0),
controller: _controller,
@@ -134,26 +135,73 @@ class RecentUpdate extends StatefulWidget {
}
class _RecentUpdateState extends State {
- Future> _getRssItem() async {
+ Future> _getRssItem(int top) async {
var dbHelper = DBHelper();
- List episodes = await dbHelper.getRecentRssItem();
+ List episodes = await dbHelper.getRecentRssItem(top);
return episodes;
}
+ ScrollController _controller;
+ int _top;
+ bool _loadMore;
+ _scrollListener() async {
+ if (_controller.offset == _controller.position.maxScrollExtent) {
+ if (mounted) setState(() => _loadMore = true);
+ await Future.delayed(Duration(seconds: 3));
+ if (mounted)
+ setState(() {
+ _top = _top + 33;
+ _loadMore = false;
+ });
+ }
+ }
+
+ @override
+ void initState() {
+ super.initState();
+ _loadMore = false;
+ _top = 33;
+ _controller = ScrollController();
+ _controller.addListener(_scrollListener);
+ }
+
+ @override
+ void dispose() {
+ _controller.dispose();
+ super.dispose();
+ }
+
@override
Widget build(BuildContext context) {
return FutureBuilder>(
- future: _getRssItem(),
+ future: _getRssItem(_top),
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
return (snapshot.hasData)
- ? EpisodeGrid(
- podcast: snapshot.data,
- showDownload: false,
- showFavorite: false,
- showNumber: false,
- heroTag: 'recent',
- )
+ ? CustomScrollView(
+ controller: _controller,
+ physics: const AlwaysScrollableScrollPhysics(),
+ primary: false,
+ slivers: [
+ EpisodeGrid(
+ podcast: snapshot.data,
+ showDownload: false,
+ showFavorite: false,
+ showNumber: false,
+ heroTag: 'recent',
+ ),
+ SliverList(
+ delegate: SliverChildBuilderDelegate(
+ (BuildContext context, int index) {
+ return _loadMore
+ ? Container(
+ height: 2, child: LinearProgressIndicator())
+ : Center();
+ },
+ childCount: 1,
+ ),
+ ),
+ ])
: Center(child: CircularProgressIndicator());
},
);
@@ -179,12 +227,18 @@ class _MyFavoriteState extends State {
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
return (snapshot.hasData)
- ? EpisodeGrid(
- podcast: snapshot.data,
- showDownload: false,
- showFavorite: false,
- showNumber: false,
- heroTag: 'favorite',
+ ? CustomScrollView(
+ physics: const AlwaysScrollableScrollPhysics(),
+ primary: false,
+ slivers: [
+ EpisodeGrid(
+ podcast: snapshot.data,
+ showDownload: false,
+ showFavorite: false,
+ showNumber: false,
+ heroTag: 'favorite',
+ )
+ ],
)
: Center(child: CircularProgressIndicator());
},
@@ -211,12 +265,19 @@ class _MyDownloadState extends State {
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
return (snapshot.hasData)
- ? EpisodeGrid(
- podcast: snapshot.data,
- showDownload: true,
- showFavorite: false,
- showNumber: false,
- heroTag: 'download',
+ ? CustomScrollView(
+ physics: const AlwaysScrollableScrollPhysics(),
+ primary: false,
+ slivers: [
+ EpisodeGrid(
+ podcast: snapshot.data,
+ showDownload: true,
+ showFavorite: false,
+ showNumber: false,
+ heroTag: 'download',
+ )
+ ],
+
)
: Center(child: CircularProgressIndicator());
},
diff --git a/lib/home/paly_history.dart b/lib/home/paly_history.dart
deleted file mode 100644
index 1a33c5f..0000000
--- a/lib/home/paly_history.dart
+++ /dev/null
@@ -1,73 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
-import 'package:tsacdop/class/audiostate.dart';
-
-class PlayedHistory extends StatefulWidget{
- @override
- _PlayedHistoryState createState() => _PlayedHistoryState();
-}
-
-class _PlayedHistoryState extends State {
-
- Future> gerPlayHistory() async{
- DBHelper dbHelper = DBHelper();
- List playHistory;
- playHistory = await dbHelper.getPlayHistory();
- await Future.forEach(playHistory, (playHistory) async{
- await playHistory.getEpisode();
- });
- return playHistory;
- }
- static String _stringForSeconds(double seconds) {
- if (seconds == null) return null;
- return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
- }
-
- @override
- Widget build(BuildContext context) {
- return AnnotatedRegion(
- value: SystemUiOverlayStyle(
- statusBarIconBrightness: Theme.of(context).accentColorBrightness,
- systemNavigationBarColor: Theme.of(context).primaryColor,
- statusBarColor: Theme.of(context).primaryColor,
- ),
- child: SafeArea(
- child: Scaffold(
- appBar: AppBar(
- title: Text('History'),
- centerTitle: true,
- elevation: 0,
- backgroundColor: Theme.of(context).primaryColor,
- ),
- body: FutureBuilder>(
- future: gerPlayHistory(),
- builder: (context, snapshot) {
- return
- snapshot.hasData ?
- ListView.builder(
- shrinkWrap: true,
- scrollDirection: Axis.vertical,
- itemCount: snapshot.data.length,
- itemBuilder: (BuildContext context, int index){
- return Column(
- children: [
- ListTile(
- title: Text(snapshot.data[index].title),
- subtitle: Text(_stringForSeconds(snapshot.data[index].seconds)),
- ),
- Divider(height: 2),
- ],
- );
- }
- )
- : Center(
- child: CircularProgressIndicator(),
- );
- },
- ),
- ),
- ),
- );
- }
-}
\ No newline at end of file
diff --git a/lib/local_storage/key_value_storage.dart b/lib/local_storage/key_value_storage.dart
index 2911626..6ea8ef8 100644
--- a/lib/local_storage/key_value_storage.dart
+++ b/lib/local_storage/key_value_storage.dart
@@ -43,7 +43,7 @@ class KeyValueStorage {
return prefs.getInt(key);
}
- Future saveStringlist(List playList) async{
+ Future saveStringList(List playList) async{
SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.setStringList(key, playList);
}
diff --git a/lib/local_storage/sqflite_localpodcast.dart b/lib/local_storage/sqflite_localpodcast.dart
index 83858f3..5bae211 100644
--- a/lib/local_storage/sqflite_localpodcast.dart
+++ b/lib/local_storage/sqflite_localpodcast.dart
@@ -8,6 +8,7 @@ import 'package:tsacdop/class/podcastlocal.dart';
import 'package:tsacdop/class/audiostate.dart';
import 'package:tsacdop/class/episodebrief.dart';
import 'package:tsacdop/webfeed/webfeed.dart';
+import 'package:tsacdop/class/sub_history.dart';
class DBHelper {
static Database _db;
@@ -35,10 +36,13 @@ class DBHelper {
enclosure_url TEXT UNIQUE, enclosure_length INTEGER, pubDate TEXT,
description TEXT, feed_id TEXT, feed_link TEXT, milliseconds INTEGER,
duration INTEGER DEFAULT 0, explicit INTEGER DEFAULT 0, liked INTEGER DEFAULT 0,
- downloaded TEXT DEFAULT 'ND', download_date INTEGER DEFAULT 0)""");
+ downloaded TEXT DEFAULT 'ND', download_date INTEGER DEFAULT 0, media_id TEXT)""");
await db.execute(
"""CREATE TABLE PlayHistory(id INTEGER PRIMARY KEY, title TEXT, enclosure_url TEXT UNIQUE,
seconds REAL, seek_value REAL, add_date INTEGER)""");
+ await db.execute(
+ """CREATE TABLE SubscribeHistory(id TEXT PRIMARY KEY, title TEXT, rss_url TEXT UNIQUE,
+ add_date INTEGER, remove_date INTEGER DEFAULT 0, status INTEGER DEFAULT 0)""");
}
Future> getPodcastLocal(List podcasts) async {
@@ -97,7 +101,7 @@ class DBHelper {
int _milliseconds = DateTime.now().millisecondsSinceEpoch;
var dbClient = await database;
await dbClient.transaction((txn) async {
- return await txn.rawInsert(
+ await txn.rawInsert(
"""INSERT OR IGNORE INTO PodcastLocal (id, title, imageUrl, rssUrl,
primaryColor, author, description, add_date, imagePath, provider, link) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
[
@@ -114,6 +118,16 @@ class DBHelper {
podcastLocal.link
]);
});
+ await dbClient.transaction((txn) async {
+ await txn.rawInsert(
+ """REPLACE INTO SubscribeHistory(id, title, rss_url, add_date) VALUES (?, ?, ?, ?)""",
+ [
+ podcastLocal.id,
+ podcastLocal.title,
+ podcastLocal.rssUrl,
+ _milliseconds
+ ]);
+ });
}
Future saveFiresideData(List list) async {
@@ -146,6 +160,10 @@ class DBHelper {
print('Removed all download tasks');
}
await dbClient.rawDelete('DELETE FROM Episodes WHERE feed_id=?', [id]);
+ int _milliseconds = DateTime.now().millisecondsSinceEpoch;
+ await dbClient.rawUpdate(
+ """UPDATE SubscribeHistory SET remove_date = ? , status = ? WHERE id = ?""",
+ [_milliseconds, 1, id]);
}
Future saveHistory(PlayHistory history) async {
@@ -174,16 +192,46 @@ class DBHelper {
""");
List playHistory = [];
list.forEach((record) {
- playHistory.add(PlayHistory(
- record['title'],
- record['enclosure_url'],
- record['seconds'],
- record['seek_value'],
- ));
+ playHistory.add(PlayHistory(record['title'], record['enclosure_url'],
+ record['seconds'], record['seek_value'],
+ playdate: DateTime.fromMillisecondsSinceEpoch(record['add_date'])));
});
return playHistory;
}
+ Future> getSubHistory() async{
+ var dbClient = await database;
+ List