1
0
mirror of https://github.com/stonega/tsacdop synced 2025-02-09 16:18:48 +01:00

modified: .circleci/config.yml

modified:   android/app/build.gradle
	modified:   android/app/src/main/AndroidManifest.xml
	new file:   android/app/src/main/res/drawable/launch_background_night.xml
	new file:   android/app/src/main/res/drawable/normal_background.xml
	new file:   android/app/src/main/res/values-night/styles.xml
	modified:   android/app/src/main/res/values/styles.xml
	new file:   assets/fireside.jpg
	new file:   assets/logo.png
	modified:   lib/class/audiostate.dart
	modified:   lib/class/fireside_data.dart
	modified:   lib/class/podcastlocal.dart
	modified:   lib/class/settingstate.dart
	modified:   lib/episodes/episodedetail.dart
	modified:   lib/episodes/episodedownload.dart
modified:   lib/home/appbar/about.dart
	modified:   lib/home/appbar/addpodcast.dart
	modified:   lib/home/appbar/importompl.dart
	modified:   lib/home/appbar/popupmenu.dart
	modified:   lib/home/audiopanel.dart
	modified:   lib/home/audioplayer.dart
	modified:   lib/home/homescroll.dart
	modified:   lib/home/hometab.dart
	new file:   lib/home/paly_history.dart
	modified:   lib/local_storage/key_value_storage.dart
	modified:   lib/local_storage/sqflite_localpodcast.dart
	modified:   lib/main.dart
	modified:   lib/podcasts/podcastdetail.dart
	modified:   lib/podcasts/podcastgroup.dart
	modified:   lib/podcasts/podcastlist.dart
	modified:   lib/podcasts/podcastmanage.dart
	new file:   lib/settings/settting.dart
	new file:   lib/settings/theme.dart
	new file:   lib/util/colorize.dart
	modified:   lib/util/episodegrid.dart
	modified:   pubspec.lock
	modified:   pubspec.yaml
This commit is contained in:
stonegate 2020-03-01 20:17:06 +08:00
parent 1021f2eea4
commit a6fc34e7bb
38 changed files with 2063 additions and 969 deletions

View File

@ -13,7 +13,13 @@ jobs:
- run: - run:
name: Run Flutter doctor name: Run Flutter doctor
command: flutter doctor command: flutter doctor
- run:
name: flutter pub get
command: flutter pub get
-run:
name: flutter run
command: flutter run
- run: echo $ENCODED_KEYSTORE | base64 -di > ${HOME}/keystore.jks - run: echo $ENCODED_KEYSTORE | base64 -di > ${HOME}/keystore.jks
- run: echo 'export KEYSTORE=${HOME}/keystore.jks' >> $BASH_ENV - run: echo 'export KEYSTORE=${HOME}/keystore.jks' >> $BASH_ENV

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"java.configuration.updateBuildConfiguration": "automatic"
}

View File

@ -54,10 +54,10 @@ android {
signingConfigs { signingConfigs {
release { release {
storeFile file(System.getenv("KEYSTORE") ?: "keystore.jks") storeFile file(System.getenv("KEYSTORE") ?: "keystore.jks")
storePassword System.getenv("KEYSTORE_PASSWORD") storePassword System.getenv("KEYSTORE_PASSWORD")
keyAlias System.getenv("KEY_ALIAS") keyAlias System.getenv("KEY_ALIAS")
keyPassword System.getenv("KEY_PASSWORD") keyPassword System.getenv("KEY_PASSWORD")
// keyAlias keystoreProperties['keyAlias'] // keyAlias keystoreProperties['keyAlias']
// keyPassword keystoreProperties['keyPassword'] // keyPassword keystoreProperties['keyPassword']
// storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null // storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null

View File

@ -12,6 +12,7 @@
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<meta-data android:name="io.flutter.embedding.android.SplashScreenUntilFirstFrame" android:value="true" />
</activity> </activity>
<service android:name="com.google.flutter.plugins.audiofileplayer.AudiofileplayerService"> <service android:name="com.google.flutter.plugins.audiofileplayer.AudiofileplayerService">
<intent-filter> <intent-filter>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/black" />
<!-- You can insert your own image assets here -->
<item>
<bitmap
android:gravity="center"
android:tileMode="disabled"
android:src="@mipmap/ic_launcher" />
</item>
<item android:bottom="100dp">
<bitmap
android:gravity="bottom"
android:src="@mipmap/text" />
</item>
</layer-list>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<item>
<bitmap
android:gravity="center"
android:tileMode="disabled"
android:src="@mipmap/ic_launcher" />
</item>
<item android:bottom="100dp">
<bitmap
android:gravity="bottom"
android:src="@mipmap/text" />
</item>
</layer-list>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@drawable/launch_background_night</item>
</style>
</resources>

View File

@ -1,8 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar"> <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when <!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame --> Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item> <item name="android:windowBackground">@drawable/launch_background</item>
</style> </style>
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">@drawable/normal_background</item>
</style>
</resources> </resources>

BIN
assets/fireside.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -14,11 +14,18 @@ import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
enum AudioState { load, play, pause, complete, error, stop } enum AudioState { load, play, pause, complete, error, stop }
class PlayHistory { class PlayHistory {
DBHelper dbHelper = DBHelper();
String title; String title;
String url; String url;
double seconds; double seconds;
double seekValue; double seekValue;
PlayHistory(this.title, this.url, this.seconds, this.seekValue); PlayHistory(this.title, this.url, this.seconds, this.seekValue);
EpisodeBrief _episode;
EpisodeBrief get episode => _episode;
getEpisode() async {
_episode = await dbHelper.getRssItemWithUrl(url);
}
} }
class Playlist { class Playlist {
@ -30,13 +37,13 @@ class Playlist {
KeyValueStorage storage = KeyValueStorage('playlist'); KeyValueStorage storage = KeyValueStorage('playlist');
Playlist(this.name, {List<String> urls}) : urls = urls ?? []; Playlist(this.name, {List<String> urls}) : urls = urls ?? [];
getPlaylist() async{ getPlaylist() async {
List<String> _urls = await storage.getStringList(); List<String> _urls = await storage.getStringList();
if (_urls.length == 0) { if (_urls.length == 0) {
_playlist = []; _playlist = [];
} else { } else {
_playlist = []; _playlist = [];
await Future.forEach(_urls, (url) async{ await Future.forEach(_urls, (url) async {
EpisodeBrief episode = await dbHelper.getRssItemWithUrl(url); EpisodeBrief episode = await dbHelper.getRssItemWithUrl(url);
print(episode.title); print(episode.title);
_playlist.add(episode); _playlist.add(episode);
@ -57,9 +64,10 @@ class Playlist {
await savePlaylist(); await savePlaylist();
} }
delFromPlaylist(EpisodeBrief episodeBrief) { delFromPlaylist(EpisodeBrief episodeBrief) async {
_playlist.remove(episodeBrief); _playlist
savePlaylist(); .removeWhere((item) => item.enclosureUrl == episodeBrief.enclosureUrl);
await savePlaylist();
} }
} }
@ -71,6 +79,7 @@ class AudioPlayer extends ChangeNotifier {
static const String forwardButtonId = 'forwardButtonId'; static const String forwardButtonId = 'forwardButtonId';
DBHelper dbHelper = DBHelper(); DBHelper dbHelper = DBHelper();
KeyValueStorage storage = KeyValueStorage('audioposition');
EpisodeBrief _episode; EpisodeBrief _episode;
Playlist _queue = Playlist('now'); Playlist _queue = Playlist('now');
bool _playerRunning = false; bool _playerRunning = false;
@ -90,34 +99,43 @@ class AudioPlayer extends ChangeNotifier {
double get seekSliderValue => _seekSliderValue; double get seekSliderValue => _seekSliderValue;
String get remoteErrorMessage => _remoteErrorMessage; String get remoteErrorMessage => _remoteErrorMessage;
bool get playerRunning => _playerRunning; bool get playerRunning => _playerRunning;
int _lastPostion;
int get lastPositin => _lastPostion;
Playlist get queue => _queue; Playlist get queue => _queue;
AudioState _audioState = AudioState.stop;
AudioState get audioState => _audioState;
EpisodeBrief get episode => _episode; EpisodeBrief get episode => _episode;
@override loadPlaylist() async {
void addListener(VoidCallback listener) { await _queue.getPlaylist();
super.addListener(listener); _lastPostion = await storage.getInt();
_queue.getPlaylist(); print(_lastPostion);
} }
episodeLoad(EpisodeBrief episode) async { episodeLoad(EpisodeBrief episode) async {
if (_playerRunning && _episode != null) {
PlayHistory history = PlayHistory(_episode.title, _episode.enclosureUrl,
backgroundAudioDuration, seekSliderValue);
await dbHelper.saveHistory(history);
}
AudioSystem.instance.addMediaEventListener(_mediaEventListener); AudioSystem.instance.addMediaEventListener(_mediaEventListener);
_backgroundAudioPlaying = false;
_episode = episode; _episode = episode;
await _queue.getPlaylist(); await _queue.getPlaylist();
if (_queue.playlist.contains(_episode)) { _queue.playlist
_queue.playlist.remove(_episode); .removeWhere((item) => item.enclosureUrl == _episode.enclosureUrl);
_queue.playlist.insert(0, _episode); _queue.playlist.insert(0, _episode);
} else {
_queue.playlist.insert(0, _episode);
}
await _queue.savePlaylist(); await _queue.savePlaylist();
await _play(_episode); await _play(_episode);
_playerRunning = true; _playerRunning = true;
_backgroundAudioPlaying = true; notifyListeners();
}
playlistLoad() async {
_backgroundAudioPlaying = false;
await _queue.getPlaylist();
_episode = _queue.playlist.first;
await _play(_episode);
_playerRunning = true;
notifyListeners(); notifyListeners();
} }
@ -127,10 +145,7 @@ class AudioPlayer extends ChangeNotifier {
await dbHelper.saveHistory(history); await dbHelper.saveHistory(history);
await _queue.delFromPlaylist(_episode); await _queue.delFromPlaylist(_episode);
if (_queue.playlist.length > 0) { if (_queue.playlist.length > 0) {
_episode = _queue.playlist.first; playlistLoad();
_play(_episode);
_backgroundAudioPlaying = true;
notifyListeners();
} else { } else {
_backgroundAudioPlaying = false; _backgroundAudioPlaying = false;
_remoteAudioLoading = false; _remoteAudioLoading = false;
@ -145,19 +160,22 @@ class AudioPlayer extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
delFromPlaylist(EpisodeBrief episode) async {
_queue.delFromPlaylist(episode);
await _queue.getPlaylist();
notifyListeners();
}
pauseAduio() async { pauseAduio() async {
_pauseBackgroundAudio(); _pauseBackgroundAudio();
_audioState = AudioState.pause;
notifyListeners(); notifyListeners();
PlayHistory history = PlayHistory(_episode.title, _episode.enclosureUrl, PlayHistory history = PlayHistory(_episode.title, _episode.enclosureUrl,
backgroundAudioDuration, seekSliderValue); backgroundAudioDuration, seekSliderValue);
await dbHelper.saveHistory(history); await dbHelper.saveHistory(history);
await _queue.delFromPlaylist(_episode);
} }
resumeAudio() { resumeAudio() {
_resumeBackgroundAudio(); _resumeBackgroundAudio();
_audioState = AudioState.play;
notifyListeners(); notifyListeners();
} }
@ -173,10 +191,13 @@ class AudioPlayer extends ChangeNotifier {
_backgroundAudio.seek(positionSeconds); _backgroundAudio.seek(positionSeconds);
AudioSystem.instance.setPlaybackState(true, positionSeconds); AudioSystem.instance.setPlaybackState(true, positionSeconds);
} }
disopse() { @override
dispose() {
pauseAduio();
AudioSystem.instance.removeMediaEventListener(_mediaEventListener); AudioSystem.instance.removeMediaEventListener(_mediaEventListener);
_backgroundAudio?.dispose(); _backgroundAudio?.dispose();
super.dispose();
} }
_play(EpisodeBrief episodeBrief) async { _play(EpisodeBrief episodeBrief) async {
@ -229,6 +250,7 @@ class AudioPlayer extends ChangeNotifier {
_backgroundAudioPositionSeconds = _backgroundAudioDurationSeconds; _backgroundAudioPositionSeconds = _backgroundAudioDurationSeconds;
notifyListeners(); notifyListeners();
} }
storage.saveInt(positionSeconds.toInt());
}, onError: (String message) { }, onError: (String message) {
_remoteErrorMessage = message; _remoteErrorMessage = message;
_backgroundAudio.dispose(); _backgroundAudio.dispose();
@ -245,7 +267,7 @@ class AudioPlayer extends ChangeNotifier {
_remoteErrorMessage = null; _remoteErrorMessage = null;
_remoteAudioLoading = true; _remoteAudioLoading = true;
notifyListeners(); notifyListeners();
_backgroundAudio?.pause(); _backgroundAudio?.pause();
_backgroundAudioPositionSeconds = 0; _backgroundAudioPositionSeconds = 0;
_setNotification(false); _setNotification(false);
_backgroundAudio = _backgroundAudio =
@ -266,6 +288,7 @@ class AudioPlayer extends ChangeNotifier {
_backgroundAudioPositionSeconds = _backgroundAudioDurationSeconds; _backgroundAudioPositionSeconds = _backgroundAudioDurationSeconds;
notifyListeners(); notifyListeners();
} }
storage.saveInt(positionSeconds.toInt());
}, onError: (String message) { }, onError: (String message) {
_remoteErrorMessage = message; _remoteErrorMessage = message;
_backgroundAudio.dispose(); _backgroundAudio.dispose();
@ -390,7 +413,7 @@ class AudioPlayer extends ChangeNotifier {
} }
void _pauseBackgroundAudio() { void _pauseBackgroundAudio() {
_backgroundAudio.pause(); _backgroundAudio?.pause();
_backgroundAudioPlaying = false; _backgroundAudioPlaying = false;
AudioSystem.instance AudioSystem.instance
.setPlaybackState(false, _backgroundAudioPositionSeconds); .setPlaybackState(false, _backgroundAudioPositionSeconds);
@ -421,7 +444,8 @@ class AudioPlayer extends ChangeNotifier {
void _forwardBackgroundAudio(double seconds) { void _forwardBackgroundAudio(double seconds) {
final double forwardposition = _backgroundAudioPositionSeconds + seconds; final double forwardposition = _backgroundAudioPositionSeconds + seconds;
_backgroundAudio.seek(forwardposition); _backgroundAudio.seek(forwardposition);
AudioSystem.instance.setPlaybackState(true, _backgroundAudioPositionSeconds); AudioSystem.instance
.setPlaybackState(true, _backgroundAudioPositionSeconds);
} }
final _pauseButton = AndroidCustomMediaButton( final _pauseButton = AndroidCustomMediaButton(

View File

@ -33,7 +33,13 @@ class FiresideData {
String name = element.text.trim(); String name = element.text.trim();
String image = String image =
element.children.first.children.first.attributes.toString(); element.children.first.children.first.attributes.toString();
host = PodcastHost(name, reg.stringMatch(image)); print(reg.stringMatch(image));
host = PodcastHost(
name,
reg.stringMatch(image) ??
'http://xuanmei.us/assets/default/avatar_small-170afdc2be97fc6148b283083942d82c101d4c1061f6b28f87c8958b52664af9.jpg');
hosts.add(host); hosts.add(host);
}); });
List<String> data = [ List<String> data = [
@ -45,7 +51,7 @@ class FiresideData {
} }
} }
Future getData() async{ Future getData() async {
List<String> data = await dbHelper.getFiresideData(id); List<String> data = await dbHelper.getFiresideData(id);
_background = data[0]; _background = data[0];
_hosts = json _hosts = json

View File

@ -7,7 +7,6 @@ class PodcastLocal {
final String primaryColor; final String primaryColor;
final String id; final String id;
final String imagePath; final String imagePath;
final String email;
final String provider; final String provider;
final String link; final String link;
PodcastLocal( PodcastLocal(
@ -18,7 +17,6 @@ class PodcastLocal {
this.author, this.author,
this.id, this.id,
this.imagePath, this.imagePath,
this.email,
this.provider, this.provider,
this.link); this.link);
} }

View File

@ -4,27 +4,64 @@ import 'package:flutter/material.dart';
import 'package:tsacdop/local_storage/key_value_storage.dart'; import 'package:tsacdop/local_storage/key_value_storage.dart';
class SettingState extends ChangeNotifier { class SettingState extends ChangeNotifier {
KeyValueStorage storage = KeyValueStorage('themes'); KeyValueStorage themestorage = KeyValueStorage('themes');
int _theme; KeyValueStorage accentstorage = KeyValueStorage('accents');
bool _isLoading;
bool get isLoagding => _isLoading;
int get theme => _theme; Future initData() async {
setTheme(int theme) async{ await _getTheme();
_theme = theme; await _getAccentSetColor();
}
ThemeMode _theme;
ThemeMode get theme => _theme;
set setTheme(ThemeMode mode) {
_theme = mode;
_saveTheme();
notifyListeners();
}
Color _accentSetColor;
Color get accentSetColor => _accentSetColor;
set setAccentColor(Color color) {
_accentSetColor = color;
_saveAccentSetColor();
notifyListeners(); notifyListeners();
await _saveTheme(theme);
} }
@override @override
void addListener(VoidCallback listener) { void addListener(VoidCallback listener) {
super.addListener(listener); super.addListener(listener);
_getTheme(); _getTheme();
_getAccentSetColor();
} }
_getTheme() async { _getTheme() async {
_theme = await storage.getTheme(); int mode = await themestorage.getInt();
_theme = ThemeMode.values[mode];
} }
_saveTheme(theme) async { _saveTheme() async {
await storage.saveTheme(theme); await themestorage.saveInt(_theme.index);
}
_getAccentSetColor() async {
String colorString = await accentstorage.getString();
print(colorString);
if (colorString.isNotEmpty) {
int color = int.parse('FF' + colorString.toUpperCase(), radix: 16);
_accentSetColor = Color(color).withOpacity(1.0);
print(_accentSetColor.toString());
} else {
_accentSetColor = Colors.blue[400];
}
}
_saveAccentSetColor() async {
await accentstorage
.saveString(_accentSetColor.toString().substring(10, 16));
print(_accentSetColor.toString());
} }
} }

View File

@ -28,6 +28,7 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
final textstyle = TextStyle(fontSize: 15.0, color: Colors.black); final textstyle = TextStyle(fontSize: 15.0, color: Colors.black);
double downloadProgress; double downloadProgress;
bool _loaddes; bool _loaddes;
bool _showMenu;
String path; String path;
Future getSDescription(String url) async { Future getSDescription(String url) async {
var dbHelper = DBHelper(); var dbHelper = DBHelper();
@ -38,6 +39,18 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
}); });
} }
ScrollController _controller;
_scrollListener() {
if (_controller.offset > _controller.position.maxScrollExtent * 0.8) {
setState(() {
_showMenu = true;
});
} else
setState(() {
_showMenu = false;
});
}
_launchUrl(String url) async { _launchUrl(String url) async {
if (await canLaunch(url)) { if (await canLaunch(url)) {
await launch(url); await launch(url);
@ -50,7 +63,16 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
void initState() { void initState() {
super.initState(); super.initState();
_loaddes = false; _loaddes = false;
_showMenu = false;
getSDescription(widget.episodeItem.enclosureUrl); getSDescription(widget.episodeItem.enclosureUrl);
_controller = ScrollController();
_controller.addListener(_scrollListener);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
} }
@override @override
@ -119,38 +141,44 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
style: style:
TextStyle(color: Colors.white))) TextStyle(color: Colors.white)))
: Center(), : Center(),
Container( widget.episodeItem.duration != 0
decoration: BoxDecoration( ? Container(
color: Colors.cyan[300], decoration: BoxDecoration(
borderRadius: BorderRadius.all( color: Colors.cyan[300],
Radius.circular(15.0))), borderRadius: BorderRadius.all(
height: 30.0, Radius.circular(15.0))),
margin: EdgeInsets.only(right: 10.0), height: 30.0,
padding: margin: EdgeInsets.only(right: 10.0),
EdgeInsets.symmetric(horizontal: 10.0), padding: EdgeInsets.symmetric(
alignment: Alignment.center, horizontal: 10.0),
child: Text( alignment: Alignment.center,
(widget.episodeItem.duration).toString() + child: Text(
'mins', (widget.episodeItem.duration)
style: textstyle), .toString() +
), 'mins',
Container( style: textstyle),
decoration: BoxDecoration( )
color: Colors.lightBlue[300], : Center(),
borderRadius: BorderRadius.all( widget.episodeItem.enclosureLength != null
Radius.circular(15.0))), ? Container(
height: 30.0, decoration: BoxDecoration(
margin: EdgeInsets.only(right: 10.0), color: Colors.lightBlue[300],
padding: borderRadius: BorderRadius.all(
EdgeInsets.symmetric(horizontal: 10.0), Radius.circular(15.0))),
alignment: Alignment.center, height: 30.0,
child: Text( margin: EdgeInsets.only(right: 10.0),
((widget.episodeItem.enclosureLength) ~/ padding: EdgeInsets.symmetric(
1000000) horizontal: 10.0),
.toString() + alignment: Alignment.center,
'MB', child: Text(
style: textstyle), ((widget.episodeItem
), .enclosureLength) ~/
1000000)
.toString() +
'MB',
style: textstyle),
)
: Center(),
], ],
), ),
), ),
@ -162,6 +190,7 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
padding: padding:
EdgeInsets.only(left: 12.0, right: 12.0, top: 5.0), EdgeInsets.only(left: 12.0, right: 12.0, top: 5.0),
child: SingleChildScrollView( child: SingleChildScrollView(
controller: _controller,
child: _loaddes child: _loaddes
? (widget.episodeItem.description.contains('<')) ? (widget.episodeItem.description.contains('<'))
? Html( ? Html(
@ -187,11 +216,18 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
builder: (_, data, __) { builder: (_, data, __) {
return Container( return Container(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
padding: EdgeInsets.only( padding:
bottom: data == true ? 60.0 : 10.0), EdgeInsets.only(bottom: data == true ? 60.0 : 10.0),
child: MenuBar( child: AnimatedContainer(
episodeItem: widget.episodeItem, duration: Duration(milliseconds: 400),
heroTag: widget.heroTag, height: !_showMenu ? 50 : 0,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: MenuBar(
episodeItem: widget.episodeItem,
heroTag: widget.heroTag,
),
),
), ),
); );
}), }),
@ -264,7 +300,6 @@ class _MenuBarState extends State<MenuBar> {
? Colors.grey[200] ? Colors.grey[200]
: Theme.of(context).primaryColor, : Theme.of(context).primaryColor,
), ),
// borderRadius: BorderRadius.all(Radius.circular(10.0)),
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
@ -274,14 +309,13 @@ class _MenuBarState extends State<MenuBar> {
tag: widget.episodeItem.enclosureUrl + widget.heroTag, tag: widget.episodeItem.enclosureUrl + widget.heroTag,
child: Container( child: Container(
padding: EdgeInsets.symmetric(horizontal: 10.0), padding: EdgeInsets.symmetric(horizontal: 10.0),
child: ClipRRect( child: Container(
borderRadius: BorderRadius.all(Radius.circular(15.0)), height: 30.0,
child: Container( width: 30.0,
height: 30.0, color: Theme.of(context).scaffoldBackgroundColor,
width: 30.0, child: CircleAvatar(
color: Theme.of(context).scaffoldBackgroundColor, backgroundImage:
child: Image.file(File("${widget.episodeItem.imagePath}")), FileImage(File("${widget.episodeItem.imagePath}"))),
),
), ),
), ),
), ),
@ -313,13 +347,14 @@ class _MenuBarState extends State<MenuBar> {
), ),
DownloadButton(episodeBrief: widget.episodeItem), DownloadButton(episodeBrief: widget.episodeItem),
Selector<AudioPlayer, List<String>>( Selector<AudioPlayer, List<String>>(
selector: (_, audio) => audio.queue.playlist.map((e)=>e.enclosureUrl).toList(), selector: (_, audio) =>
audio.queue.playlist.map((e) => e.enclosureUrl).toList(),
builder: (_, data, __) { builder: (_, data, __) {
print(data.length);
return data.contains(widget.episodeItem.enclosureUrl) return data.contains(widget.episodeItem.enclosureUrl)
? _buttonOnMenu( ? _buttonOnMenu(
Icon(Icons.playlist_add_check, color: Theme.of(context).accentColor), Icon(Icons.playlist_add_check,
(){}) color: Theme.of(context).accentColor),
() {})
: _buttonOnMenu( : _buttonOnMenu(
Icon(Icons.playlist_add, color: Colors.grey[700]), () { Icon(Icons.playlist_add, color: Colors.grey[700]), () {
Fluttertoast.showToast( Fluttertoast.showToast(
@ -340,9 +375,6 @@ class _MenuBarState extends State<MenuBar> {
? Material( ? Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
borderRadius: BorderRadius.only(
topRight: Radius.circular(5.0),
bottomRight: Radius.circular(5.0)),
onTap: () { onTap: () {
audio.episodeLoad(widget.episodeItem); audio.episodeLoad(widget.episodeItem);
}, },

View File

@ -211,14 +211,17 @@ class _DownloadButtonState extends State<DownloadButton> {
AnimatedContainer( AnimatedContainer(
duration: Duration(seconds: 1), duration: Duration(seconds: 1),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.cyan[300], color: Theme.of(context).accentColor,
borderRadius: BorderRadius.all(Radius.circular(15.0))), borderRadius: BorderRadius.all(Radius.circular(15.0))),
height: 20.0, height: 20.0,
width: width:
(_task.status == DownloadTaskStatus.running) ? 50.0 : 0, (_task.status == DownloadTaskStatus.running) ? 50.0 : 0,
alignment: Alignment.center, alignment: Alignment.center,
child: Text('${_task.progress}%', child: SingleChildScrollView(
style: TextStyle(color: Colors.white))), scrollDirection: Axis.horizontal,
child: Text('${_task.progress}%',
style: TextStyle(color: Colors.white)),
)),
], ],
); );
} }
@ -236,7 +239,7 @@ class _DownloadButtonState extends State<DownloadButton> {
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
onTap: () { onTap: () {
if(task.progress > 0) _pauseDownload(task); if (task.progress > 0) _pauseDownload(task);
}, },
child: Container( child: Container(
height: 50.0, height: 50.0,
@ -248,7 +251,8 @@ class _DownloadButtonState extends State<DownloadButton> {
child: CircularProgressIndicator( child: CircularProgressIndicator(
backgroundColor: Colors.grey[500], backgroundColor: Colors.grey[500],
strokeWidth: 2, strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.cyan[300]), valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).accentColor),
value: task.progress / 100, value: task.progress / 100,
), ),
), ),
@ -283,7 +287,7 @@ class _DownloadButtonState extends State<DownloadButton> {
return _buttonOnMenu( return _buttonOnMenu(
Icon( Icon(
Icons.done_all, Icons.done_all,
color: Colors.blue, color: Theme.of(context).accentColor,
), ),
() => _deleteDownload(task)); () => _deleteDownload(task));
} else if (task.status == DownloadTaskStatus.failed) { } else if (task.status == DownloadTaskStatus.failed) {

View File

@ -1,35 +1,161 @@
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:font_awesome_flutter/font_awesome_flutter.dart';
class AboutApp extends StatelessWidget { class AboutApp extends StatelessWidget {
TextSpan buildTextSpan() { _launchUrl(String url) async {
return TextSpan(children: [ if (await canLaunch(url)) {
TextSpan(text: 'Tsacdop\n', style: TextStyle(fontSize: 20)), await launch(url);
TextSpan( } else {
text: throw 'Could not launch $url';
'Tsacdop is a podcast client developed by flutter, is a simple, easy-use player.\n'), }
TextSpan(text: 'Github https://github.com/stonga/tsacdop .\n'),
]);
} }
Widget _listItem(
BuildContext context, String text, IconData icons, String url) =>
InkWell(
onTap: () => _launchUrl(url),
child: Container(
height: 50.0,
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment.centerLeft,
decoration: BoxDecoration(
border: Border(
bottom: Divider.createBorderSide(context),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(icons, color: Colors.grey[700]),
Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
),
Text(text),
],
),
),
);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AnnotatedRegion<SystemUiOverlayStyle>( return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle( value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness, statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: Theme.of(context).primaryColor, systemNavigationBarColor: Theme.of(context).primaryColor,
statusBarColor: Theme.of(context).primaryColor, statusBarColor: Theme.of(context).primaryColor,
), ),
child: SafeArea( child: SafeArea(
child: Scaffold( child: Scaffold(
backgroundColor: Theme.of(context).primaryColor,
appBar: AppBar( appBar: AppBar(
title: Text('Tsacdop'), title: Text('About'),
centerTitle: true,
), ),
body: Container( body: Container(
padding: EdgeInsets.all(20), padding: EdgeInsets.all(20),
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
child: Text.rich(buildTextSpan()), child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
height: 200.0,
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Image(
image: AssetImage('assets/logo.png'),
height: 80,
),
Text('Version: 0.1.1'),
],
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 50),
height: 50,
child: Text(
'Tsacdop is a podcast client developed with flutter, a simple, beautiful, and easy-use player.',
textAlign: TextAlign.center,
),
),
Padding(
padding: EdgeInsets.all(5.0),
),
Container(
padding: EdgeInsets.only(
top: 20.0,
bottom: 10.0
),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(10)),
border: Border.all(color: Theme.of(context).accentColor, width: 1),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment.centerLeft,
child: Text(
'Developer',
style:
TextStyle(color: Theme.of(context).accentColor),
),
),
_listItem(
context,
'GitHub',
FontAwesomeIcons.githubSquare,
'https://github.com/stonaga/tsacdop'),
_listItem(
context,
'Twitter',
FontAwesomeIcons.twitterSquare,
'https://twitter.com'),
_listItem(
context,
'Gmail',
FontAwesomeIcons.envelopeSquare,
'mailto:<xijieyin@gmail.com>?subject=Tsacdop Feedback'),
],
),
),
Spacer(),
Container(
height: 50,
alignment: Alignment.center,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image.asset(
'assets/text.png',
height: 25,
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 5),
),
Icon(
Icons.favorite,
color: Colors.blue,
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 5),
),
FlutterLogo(
size: 18,
),
],
),
),
],
),
)), )),
), ),
); );

View File

@ -10,8 +10,10 @@ import 'package:provider/provider.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:image/image.dart' as img; import 'package:image/image.dart' as img;
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:tsacdop/class/audiostate.dart';
import 'package:tsacdop/class/fireside_data.dart'; import 'package:tsacdop/class/fireside_data.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
import 'package:tuple/tuple.dart';
import 'package:tsacdop/class/importompl.dart'; import 'package:tsacdop/class/importompl.dart';
import 'package:tsacdop/class/podcast_group.dart'; import 'package:tsacdop/class/podcast_group.dart';
@ -30,9 +32,30 @@ class MyHomePage extends StatefulWidget {
class _MyHomePageState extends State<MyHomePage> { class _MyHomePageState extends State<MyHomePage> {
final _MyHomePageDelegate _delegate = _MyHomePageDelegate(); final _MyHomePageDelegate _delegate = _MyHomePageDelegate();
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>(); final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
bool _loadPlay;
static String _stringForSeconds(int seconds) {
if (seconds == null) return null;
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
}
@override
void initState() {
super.initState();
_loadPlay = false;
_getPlaylist();
}
_getPlaylist() async {
await Provider.of<AudioPlayer>(context, listen: false).loadPlaylist();
setState(() {
_loadPlay = true;
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var audio = Provider.of<AudioPlayer>(context, listen: false);
return AnnotatedRegion<SystemUiOverlayStyle>( return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle( value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness, statusBarIconBrightness: Theme.of(context).accentColorBrightness,
@ -62,6 +85,37 @@ class _MyHomePageState extends State<MyHomePage> {
PopupMenu(), PopupMenu(),
], ],
), ),
floatingActionButton: Selector<AudioPlayer, Tuple3<bool, Playlist, int>>(
selector: (_, audio) => Tuple3(audio.playerRunning, audio.queue, audio.lastPositin),
builder: (_, data, __) => !_loadPlay
? Center()
: data.item1 || data.item2.playlist.length == 0
? Center()
: FloatingActionButton.extended(
tooltip: 'Play from playlist',
highlightElevation: 2,
hoverElevation: 2,
onPressed: () => audio.playlistLoad(),
elevation: 1,
backgroundColor: Theme.of(context).accentColor,
label: Text(_stringForSeconds(data.item3), style: TextStyle(color: Colors.white)),
icon: Stack(
alignment: Alignment.center,
children: <Widget>[
CircleAvatar(
radius: 15,
backgroundImage: FileImage(File(
"${data.item2.playlist.first.imagePath}")),
),
Container(
height: 30.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.black38),
),
],
)),
),
body: Home(), body: Home(),
), ),
), ),
@ -235,9 +289,10 @@ class _SearchResultState extends State<SearchResult> {
try { try {
Response response = await Dio().get(rss); Response response = await Dio().get(rss);
var dbHelper = DBHelper(); var dbHelper = DBHelper();
String _realUrl = String _realUrl = response.realUri.toString();
response.isRedirect ? response.realUri.toString() : rss;
bool _checkUrl = await dbHelper.checkPodcast(_realUrl); bool _checkUrl = await dbHelper.checkPodcast(_realUrl);
if (_checkUrl) { if (_checkUrl) {
if (mounted) setState(() => _issubscribe = true); if (mounted) setState(() => _issubscribe = true);
@ -261,7 +316,6 @@ class _SearchResultState extends State<SearchResult> {
String _imagePath = "${dir.path}/$_uuid.png"; String _imagePath = "${dir.path}/$_uuid.png";
String _primaryColor = await getColor(File("${dir.path}/$_uuid.png")); String _primaryColor = await getColor(File("${dir.path}/$_uuid.png"));
String _author = _p.itunes.author ?? _p.author ?? ''; String _author = _p.itunes.author ?? _p.author ?? '';
String _email = _p.itunes.owner?.email ?? '';
String _provider = _p.generator ?? ''; String _provider = _p.generator ?? '';
String _link = _p.link ?? ''; String _link = _p.link ?? '';
PodcastLocal podcastLocal = PodcastLocal( PodcastLocal podcastLocal = PodcastLocal(
@ -272,13 +326,12 @@ class _SearchResultState extends State<SearchResult> {
_author, _author,
_uuid, _uuid,
_imagePath, _imagePath,
_email,
_provider, _provider,
_link); _link);
podcastLocal.description = _p.description; podcastLocal.description = _p.description;
await groupList.subscribe(podcastLocal); await groupList.subscribe(podcastLocal);
if(_provider.contains('fireside'))
{ if (_provider.contains('fireside')) {
FiresideData data = FiresideData(_uuid, _link); FiresideData data = FiresideData(_uuid, _link);
await data.fatchData(); await data.fatchData();
} }
@ -386,7 +439,10 @@ class _SearchResultState extends State<SearchResult> {
widget.onlinePodcast.description.trim(), widget.onlinePodcast.description.trim(),
maxLines: 3, maxLines: 3,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyText1.copyWith(color: Colors.white), style: Theme.of(context)
.textTheme
.bodyText1
.copyWith(color: Colors.white),
), ),
) )
: Center(), : Center(),

View File

@ -6,7 +6,7 @@ class Import extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer<ImportOmpl>( return Consumer<ImportOmpl>(
builder: (context, importOmpl, _) => Container( builder: (_, importOmpl, __) => Container(
color: Theme.of(context).primaryColorDark, color: Theme.of(context).primaryColorDark,
child: importOmpl.importState == ImportState.start child: importOmpl.importState == ImportState.start
? Column( ? Column(

View File

@ -4,8 +4,7 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tsacdop/class/podcast_group.dart'; import 'package:tsacdop/class/fireside_data.dart';
import 'package:tsacdop/class/settingstate.dart';
import 'package:xml/xml.dart' as xml; import 'package:xml/xml.dart' as xml;
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -15,6 +14,8 @@ import 'package:image/image.dart' as img;
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:tsacdop/class/podcast_group.dart';
import 'package:tsacdop/settings/settting.dart';
import 'about.dart'; import 'about.dart';
import 'package:tsacdop/class/podcastlocal.dart'; import 'package:tsacdop/class/podcastlocal.dart';
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart'; import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
@ -48,7 +49,6 @@ class PopupMenu extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
ImportOmpl importOmpl = Provider.of<ImportOmpl>(context, listen: false); ImportOmpl importOmpl = Provider.of<ImportOmpl>(context, listen: false);
GroupList groupList = Provider.of<GroupList>(context, listen: false); GroupList groupList = Provider.of<GroupList>(context, listen: false);
SettingState setting = Provider.of<SettingState>(context);
_refreshAll() async { _refreshAll() async {
var dbHelper = DBHelper(); var dbHelper = DBHelper();
List<PodcastLocal> podcastList = await dbHelper.getPodcastLocalAll(); List<PodcastLocal> podcastList = await dbHelper.getPodcastLocalAll();
@ -68,28 +68,29 @@ class PopupMenu extends StatelessWidget {
Response response = await Dio().get(rss); Response response = await Dio().get(rss);
if (response.statusCode == 200) { if (response.statusCode == 200) {
var _p = RssFeed.parse(response.data); var _p = RssFeed.parse(response.data);
var dir = await getApplicationDocumentsDirectory(); var dir = await getApplicationDocumentsDirectory();
Response<List<int>> imageResponse = await Dio().get<List<int>>( String _realUrl = response.redirects.isEmpty ? rss : response.realUri.toString();
_p.itunes.image.href,
options: Options(responseType: ResponseType.bytes));
img.Image image = img.decodeImage(imageResponse.data);
img.Image thumbnail = img.copyResize(image, width: 300);
String _uuid = Uuid().v4();
String _realUrl =
response.isRedirect ? response.realUri.toString() : rss;
print(_realUrl); print(_realUrl);
bool _checkUrl = await dbHelper.checkPodcast(_realUrl); bool _checkUrl = await dbHelper.checkPodcast(_realUrl);
if (_checkUrl) { if (_checkUrl) {
Response<List<int>> imageResponse = await Dio().get<List<int>>(
_p.itunes.image.href,
options: Options(responseType: ResponseType.bytes));
img.Image image = img.decodeImage(imageResponse.data);
img.Image thumbnail = img.copyResize(image, width: 300);
String _uuid = Uuid().v4();
File("${dir.path}/$_uuid.png") File("${dir.path}/$_uuid.png")
..writeAsBytesSync(img.encodePng(thumbnail)); ..writeAsBytesSync(img.encodePng(thumbnail));
String _imagePath = "${dir.path}/$_uuid.png"; String _imagePath = "${dir.path}/$_uuid.png";
String _primaryColor = await getColor(File("${dir.path}/$_uuid.png")); String _primaryColor = await getColor(File("${dir.path}/$_uuid.png"));
String _author = _p.itunes.author ?? _p.author??''; String _author = _p.itunes.author ?? _p.author ?? '';
String _email = _p.itunes.owner.email?? ''; String _provider = _p.generator ?? '';
String _provider = _p.generator??''; String _link = _p.link ?? '';
String _link = _p.link??'';
PodcastLocal podcastLocal = PodcastLocal( PodcastLocal podcastLocal = PodcastLocal(
_p.title, _p.title,
_p.itunes.image.href, _p.itunes.image.href,
@ -98,13 +99,18 @@ class PopupMenu extends StatelessWidget {
_author, _author,
_uuid, _uuid,
_imagePath, _imagePath,
_email,
_provider, _provider,
_link); _link);
podcastLocal.description = _p.description; podcastLocal.description = _p.description;
groupList.subscribe(podcastLocal); await groupList.subscribe(podcastLocal);
if (_provider.contains('fireside'))
{
FiresideData data = FiresideData(_uuid, _link);
await data.fatchData();
}
importOmpl.importState = ImportState.parse; importOmpl.importState = ImportState.parse;
@ -178,28 +184,70 @@ class PopupMenu extends StatelessWidget {
} }
return PopupMenuButton<int>( return PopupMenuButton<int>(
elevation: 3, shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10))),
elevation: 1,
tooltip: 'Menu', tooltip: 'Menu',
itemBuilder: (context) => [ itemBuilder: (context) => [
PopupMenuItem( PopupMenuItem(
value: 1, value: 1,
child: Text('Refresh All'), child: Container(
padding: EdgeInsets.only(left: 10),
child: Row(
children: <Widget>[
Icon(Icons.refresh),
Padding(padding: EdgeInsets.symmetric(horizontal: 5.0),),
Text('Refresh All'),
],
),
),
), ),
PopupMenuItem( PopupMenuItem(
value: 2, value: 2,
child: Text('Impoer OMPL'), child: Container(
), padding: EdgeInsets.only(left: 10),
PopupMenuItem( child: Row(
value: 3, children: <Widget>[
child: setting.theme != 2 ? Text('Night Mode') : Text('Light Mode'), Icon(Icons.attachment),
), Padding(padding: EdgeInsets.symmetric(horizontal: 5.0),),
PopupMenuItem( Text('Import OMPL'),
],
),
),
),
// PopupMenuItem(
// value: 3,
// child: setting.theme != 2 ? Text('Night Mode') : Text('Light Mode'),
// ),
PopupMenuItem(
value: 4, value: 4,
child: Text('About'), child: Container(
padding: EdgeInsets.only(left: 10),
child: Row(
children: <Widget>[
Icon(Icons.swap_calls),
Padding(padding: EdgeInsets.symmetric(horizontal: 5.0),),
Text('Settings'),
],
),
),
),
PopupMenuItem(
value: 5,
child: Container(
padding: EdgeInsets.only(left: 10),
child: Row(
children: <Widget>[
Icon(Icons.info_outline),
Padding(padding: EdgeInsets.symmetric(horizontal: 5.0),),
Text('About'),
],
),
),
), ),
], ],
onSelected: (value) { onSelected: (value) {
if (value == 4) { if (value == 5) {
Navigator.push( Navigator.push(
context, MaterialPageRoute(builder: (context) => AboutApp())); context, MaterialPageRoute(builder: (context) => AboutApp()));
} else if (value == 2) { } else if (value == 2) {
@ -207,8 +255,11 @@ class PopupMenu extends StatelessWidget {
} else if (value == 1) { } else if (value == 1) {
_refreshAll(); _refreshAll();
} else if (value == 3) { } else if (value == 3) {
setting.theme != 2 ? setting.setTheme(2) : setting.setTheme(1); // setting.theme != 2 ? setting.setTheme(2) : setting.setTheme(1);
} } else if (value == 4) {
Navigator.push(
context, MaterialPageRoute(builder: (context) => Settings()));
}
}, },
); );
} }

View File

@ -83,7 +83,9 @@ class _AudioPanelState extends State<AudioPanel>
BoxShadow( BoxShadow(
offset: Offset(0, -1), offset: Offset(0, -1),
blurRadius: 4, blurRadius: 4,
color: Colors.grey[400], color: Theme.of(context).brightness == Brightness.light
? Colors.grey[400]
: Colors.grey[800],
), ),
], ],
), ),

View File

@ -1,16 +1,18 @@
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:math' as math; import 'dart:math' as math;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:marquee/marquee.dart'; import 'package:marquee/marquee.dart';
import 'package:tsacdop/class/episodebrief.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import 'package:tsacdop/class/episodebrief.dart';
import 'package:tsacdop/class/audiostate.dart'; import 'package:tsacdop/class/audiostate.dart';
import 'package:tsacdop/episodes/episodedetail.dart'; import 'package:tsacdop/episodes/episodedetail.dart';
import 'package:tsacdop/home/audiopanel.dart'; import 'package:tsacdop/home/audiopanel.dart';
import 'package:tsacdop/util/pageroute.dart'; import 'package:tsacdop/util/pageroute.dart';
import 'package:tsacdop/util/colorize.dart';
class PlayerWidget extends StatefulWidget { class PlayerWidget extends StatefulWidget {
@override @override
@ -33,325 +35,402 @@ class _PlayerWidgetState extends State<PlayerWidget> {
Widget _expandedPanel(BuildContext context) { Widget _expandedPanel(BuildContext context) {
var audio = Provider.of<AudioPlayer>(context, listen: false); var audio = Provider.of<AudioPlayer>(context, listen: false);
return !_showlist return Stack(
? Container( children: <Widget>[
color: Theme.of(context).primaryColor, Container(
height: 300, color: Theme.of(context).primaryColor,
padding: EdgeInsets.symmetric(horizontal: 10.0), height: 300,
child: Column( padding: EdgeInsets.symmetric(horizontal: 10.0),
mainAxisAlignment: MainAxisAlignment.start, child: Column(
mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[ mainAxisSize: MainAxisSize.min,
Container( children: <Widget>[
height: 80.0, Container(
padding: EdgeInsets.all(20), height: 80.0,
alignment: Alignment.center, padding: EdgeInsets.all(20),
child: Selector<AudioPlayer, String>( alignment: Alignment.center,
selector: (_, audio) => audio.episode.title, child: Selector<AudioPlayer, String>(
builder: (_, title, __) { selector: (_, audio) => audio.episode.title,
return Container( builder: (_, title, __) {
child: LayoutBuilder( return Container(
builder: (context, size) { child: LayoutBuilder(
var span = TextSpan(text: title,style: TextStyle( builder: (context, size) {
fontWeight: FontWeight.bold, fontSize: 20)); var span = TextSpan(
var tp = TextPainter( text: title,
text: span, style: TextStyle(
maxLines: 1, fontWeight: FontWeight.bold, fontSize: 20));
textDirection: TextDirection.ltr); var tp = TextPainter(
tp.layout(maxWidth: size.maxWidth); text: span,
if (tp.didExceedMaxLines) { maxLines: 1,
return Marquee( textDirection: TextDirection.ltr);
text: title, tp.layout(maxWidth: size.maxWidth);
style: TextStyle( if (tp.didExceedMaxLines) {
fontWeight: FontWeight.bold, fontSize: 18), return Marquee(
scrollAxis: Axis.horizontal, text: title,
crossAxisAlignment: CrossAxisAlignment.start, style: TextStyle(
blankSpace: 30.0, fontWeight: FontWeight.bold, fontSize: 18),
velocity: 50.0, scrollAxis: Axis.horizontal,
pauseAfterRound: Duration(seconds: 1), crossAxisAlignment: CrossAxisAlignment.start,
startPadding: 30.0, blankSpace: 30.0,
accelerationDuration: Duration(seconds: 1), velocity: 50.0,
accelerationCurve: Curves.linear, pauseAfterRound: Duration(seconds: 1),
decelerationDuration: startPadding: 30.0,
Duration(milliseconds: 500), accelerationDuration: Duration(seconds: 1),
decelerationCurve: Curves.easeOut, accelerationCurve: Curves.linear,
); decelerationDuration:
} else { Duration(milliseconds: 500),
return Text( decelerationCurve: Curves.easeOut,
title, );
style: TextStyle( } else {
fontWeight: FontWeight.bold, fontSize: 20), return Text(
); title,
} style: TextStyle(
}, fontWeight: FontWeight.bold, fontSize: 20),
), );
); }
}, },
), ),
);
},
), ),
Consumer<AudioPlayer>( ),
builder: (_, data, __) { Consumer<AudioPlayer>(
return Column( builder: (_, data, __) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 30, right: 30),
child: SliderTheme(
data: SliderTheme.of(context).copyWith(
activeTrackColor: Colors.blue[100],
inactiveTrackColor: Colors.grey[300],
trackHeight: 3.0,
thumbColor: Colors.blue[400],
thumbShape: RoundSliderThumbShape(
enabledThumbRadius: 6.0),
overlayColor: Colors.blue.withAlpha(32),
overlayShape:
RoundSliderOverlayShape(overlayRadius: 14.0),
),
child: Slider(
value: data.seekSliderValue,
onChanged: (double val) {
audio.sliderSeek(val);
}),
),
),
Container(
height: 20.0,
padding: EdgeInsets.symmetric(horizontal: 50.0),
child: Row(
children: <Widget>[
Text(
_stringForSeconds(
data.backgroundAudioPosition) ??
'',
style: TextStyle(fontSize: 10),
),
Expanded(
child: Container(
alignment: Alignment.center,
child: data.remoteErrorMessage != null
? Text(data.remoteErrorMessage,
style: const TextStyle(
color: const Color(0xFFFF0000)))
: Text(
data.remoteAudioLoading
? 'Buffring...'
: '',
style: TextStyle(
color: Theme.of(context)
.accentColor),
),
),
),
Text(
_stringForSeconds(
data.backgroundAudioDuration) ??
'',
style: TextStyle(fontSize: 10),
),
],
),
),
],
);
},
),
Container(
height: 100,
child: Selector<AudioPlayer, bool>(
selector: (_, audio) => audio.backgroundAudioPlaying,
builder: (_, backplay, __) {
return Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
padding: EdgeInsets.symmetric(horizontal: 30.0),
onPressed: backplay
? () => audio.forwardAudio(-10)
: null,
iconSize: 32.0,
icon: Icon(Icons.replay_10),
color: Theme.of(context).tabBarTheme.labelColor),
backplay
? IconButton(
padding:
EdgeInsets.symmetric(horizontal: 30.0),
onPressed: backplay
? () {
audio.pauseAduio();
}
: null,
iconSize: 40.0,
icon: Icon(Icons.pause_circle_filled),
color:
Theme.of(context).tabBarTheme.labelColor)
: IconButton(
padding:
EdgeInsets.symmetric(horizontal: 30.0),
onPressed: backplay
? null
: () {
audio.resumeAudio();
},
iconSize: 40.0,
icon: Icon(Icons.play_circle_filled),
color:
Theme.of(context).tabBarTheme.labelColor),
IconButton(
padding: EdgeInsets.symmetric(horizontal: 30.0),
onPressed: backplay
? () => audio.forwardAudio(30)
: null,
iconSize: 32.0,
icon: Icon(Icons.forward_30),
color: Theme.of(context).tabBarTheme.labelColor),
],
);
},
),
),
Spacer(),
Container(
height: 50.0,
margin: EdgeInsets.all(10.0),
decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor,
borderRadius: BorderRadius.all(Radius.circular(10.0)),
),
child: Selector<AudioPlayer, EpisodeBrief>(
selector: (_, audio) => audio.episode,
builder: (_, episode, __) {
return Row(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Container( Padding(
padding: EdgeInsets.only(left: 30, right: 30), padding: EdgeInsets.all(5.0),
child: SliderTheme(
data: SliderTheme.of(context).copyWith(
activeTrackColor: Colors.blue[100],
inactiveTrackColor: Colors.grey[300],
trackHeight: 3.0,
thumbColor: Colors.blue[400],
thumbShape: RoundSliderThumbShape(
enabledThumbRadius: 6.0),
overlayColor: Colors.blue.withAlpha(32),
overlayShape: RoundSliderOverlayShape(
overlayRadius: 14.0),
),
child: Slider(
value: data.seekSliderValue,
onChanged: (double val) {
audio.sliderSeek(val);
}),
),
), ),
Container( Container(
height: 20.0, height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 50.0), width: 30.0,
child: Row( child: CircleAvatar(
children: <Widget>[ backgroundImage:
Text( FileImage(File("${episode.imagePath}")),
_stringForSeconds(
data.backgroundAudioPosition) ??
'',
style: TextStyle(fontSize: 10),
),
Expanded(
child: Container(
alignment: Alignment.center,
child: data.remoteErrorMessage != null
? Text(data.remoteErrorMessage,
style: const TextStyle(
color: const Color(0xFFFF0000)))
: Text(
data.remoteAudioLoading
? 'Buffring...'
: '',
style: TextStyle(
color: Theme.of(context)
.accentColor),
),
),
),
Text(
_stringForSeconds(
data.backgroundAudioDuration) ??
'',
style: TextStyle(fontSize: 10),
),
],
), ),
), ),
Spacer(),
Material(
color: Colors.transparent,
child: IconButton(
onPressed: () => Navigator.push(
context,
SlideUptRoute(
page: EpisodeDetail(
episodeItem: episode,
heroTag: 'playpanel')),
),
icon: Icon(Icons.info),
),
),
Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.only(
topRight: Radius.circular(10.0),
bottomRight: Radius.circular(10.0)),
child: Container(
height: 50.0,
width: 50.0,
child: Icon(Icons.keyboard_arrow_up)),
onTap: () => setState(() => _showlist = true)),
),
], ],
); );
}, },
), ),
Container( ),
height: 100, ]),
child: Selector<AudioPlayer, bool>( ),
selector: (_, audio) => audio.backgroundAudioPlaying, Container(
builder: (_, backplay, __) { alignment: Alignment.bottomLeft,
return Row( child: AnimatedContainer(
mainAxisSize: MainAxisSize.min, duration: Duration(milliseconds: 400),
mainAxisAlignment: MainAxisAlignment.center, height: _showlist ? 300 : 0,
children: [
IconButton(
padding: EdgeInsets.symmetric(horizontal: 30.0),
onPressed: backplay
? () => audio.forwardAudio(-10)
: null,
iconSize: 32.0,
icon: Icon(Icons.replay_10),
color:
Theme.of(context).tabBarTheme.labelColor),
backplay
? IconButton(
padding:
EdgeInsets.symmetric(horizontal: 30.0),
onPressed: backplay
? () {
audio.pauseAduio();
}
: null,
iconSize: 40.0,
icon: Icon(Icons.pause_circle_filled),
color: Theme.of(context)
.tabBarTheme
.labelColor)
: IconButton(
padding:
EdgeInsets.symmetric(horizontal: 30.0),
onPressed: backplay
? null
: () {
audio.resumeAudio();
},
iconSize: 40.0,
icon: Icon(Icons.play_circle_filled),
color: Theme.of(context)
.tabBarTheme
.labelColor),
IconButton(
padding: EdgeInsets.symmetric(horizontal: 30.0),
onPressed: backplay
? () => audio.forwardAudio(30)
: null,
iconSize: 32.0,
icon: Icon(Icons.forward_30),
color:
Theme.of(context).tabBarTheme.labelColor),
],
);
},
),
),
Spacer(),
Container(
height: 50.0,
margin: EdgeInsets.symmetric(vertical: 10.0),
padding: EdgeInsets.symmetric(horizontal: 10.0),
decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor,
borderRadius: BorderRadius.all(Radius.circular(10.0)),
),
child: Selector<AudioPlayer, EpisodeBrief>(
selector: (_, audio) => audio.episode,
builder: (_, episode, __) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
padding: EdgeInsets.all(10.0),
child: ClipRRect(
borderRadius:
BorderRadius.all(Radius.circular(15.0)),
child: Container(
height: 30.0,
width: 30.0,
child: Image.file(
File("${episode.imagePath}"))),
),
),
Spacer(),
Material(
color: Colors.transparent,
child: InkWell(
onTap: () => Navigator.push(
context,
SlideUptRoute(
page: EpisodeDetail(
episodeItem: episode,
heroTag: 'playpanel')),
),
child: Icon(Icons.info),
),
),
IconButton(
icon: Icon(Icons.keyboard_arrow_up),
onPressed: () =>
setState(() => _showlist = true)),
],
);
},
),
),
]),
)
: Container(
height: 300,
width: MediaQuery.of(context).size.width, width: MediaQuery.of(context).size.width,
alignment: Alignment.center, alignment: Alignment.center,
margin: EdgeInsets.all(20), margin: EdgeInsets.all(20),
padding: EdgeInsets.all(10.0), padding: EdgeInsets.only(bottom: 10.0),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(10)), borderRadius: BorderRadius.all(Radius.circular(10.0)),
color: Theme.of(context).scaffoldBackgroundColor, color: Theme.of(context).scaffoldBackgroundColor,
), ),
child: Selector<AudioPlayer, List<EpisodeBrief>>( child: Column(
selector: (_, audio) => audio.queue.playlist, mainAxisAlignment: MainAxisAlignment.start,
builder: (_, playlist, __) { mainAxisSize: MainAxisSize.min,
print(playlist.first.title); children: <Widget>[
double _width = MediaQuery.of(context).size.width; SingleChildScrollView(
return Column( scrollDirection: Axis.vertical,
mainAxisAlignment: MainAxisAlignment.start, child: Container(
mainAxisSize: MainAxisSize.min, padding: EdgeInsets.symmetric(horizontal: 20.0),
children: <Widget>[ alignment: Alignment.center,
Container( child: Row(
height: 30.0, mainAxisAlignment: MainAxisAlignment.center,
alignment: Alignment.centerRight, mainAxisSize: MainAxisSize.min,
child: IconButton( children: <Widget>[
icon: Icon(Icons.keyboard_arrow_down), Text(
onPressed: () => setState(() => _showlist = false), 'NEXT',
), style: TextStyle(
), color: Theme.of(context).accentColor,
Expanded( fontWeight: FontWeight.bold),
child: Container(
child: ListView.builder(
itemCount: playlist.length,
itemBuilder: (BuildContext context, int index) {
print(playlist.length);
return Container(
height: 50,
alignment: Alignment.centerLeft,
decoration: BoxDecoration(
border: Border(
bottom: Divider.createBorderSide(context),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
padding: EdgeInsets.all(10.0),
child: ClipRRect(
borderRadius: BorderRadius.all(
Radius.circular(15.0)),
child: Container(
height: 30.0,
width: 30.0,
child: Image.file(File(
"${playlist[index].imagePath}"))),
),
),
Container(
alignment: Alignment.centerLeft,
width: _width - 200,
child: Text(
playlist[index].title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
Spacer(),
IconButton(
icon: Icon(Icons.play_arrow),
onPressed: null),
],
),
);
},
), ),
), Spacer(),
Container(
height: 40.0,
alignment: Alignment.centerRight,
child: IconButton(
icon: Icon(Icons.keyboard_arrow_down),
onPressed: () => setState(() => _showlist = false),
),
),
],
), ),
], ),
); ),
}, Expanded(
child: Selector<AudioPlayer, List<EpisodeBrief>>(
selector: (_, audio) => audio.queue.playlist,
builder: (_, playlist, __) {
return ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.vertical,
itemCount: playlist.length,
itemBuilder: (BuildContext context, int index) {
print(playlist.length);
return index == 0
? Center()
: Dismissible(
background: Container(
padding:
EdgeInsets.symmetric(horizontal: 20.0),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: <Widget>[
Icon(
Icons.delete,
color: Theme.of(context).accentColor,
),
Icon(
Icons.delete,
color: Theme.of(context).accentColor,
),
],
),
height: 50,
color: Colors.grey[400],
),
key: Key(playlist[index].enclosureUrl),
onDismissed: (direction) async {
await audio
.delFromPlaylist(playlist[index]);
Fluttertoast.showToast(
msg: 'Removed From Playlist',
gravity: ToastGravity.BOTTOM,
);
},
child: Column(
children: <Widget>[
Container(
height: 50,
padding: EdgeInsets.symmetric(
horizontal: 10),
alignment: Alignment.centerLeft,
decoration: BoxDecoration(
color: Theme.of(context)
.scaffoldBackgroundColor,
),
child: InkWell(
onTap: () {
audio.episodeLoad(playlist[index]);
setState(() => _showlist = false);
},
child: Row(
mainAxisAlignment:
MainAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
padding: EdgeInsets.all(10.0),
child: ClipRRect(
borderRadius:
BorderRadius.all(
Radius.circular(
15.0)),
child: Container(
height: 30.0,
width: 30.0,
child: Image.file(File(
"${playlist[index].imagePath}"))),
),
),
Expanded(
child: Container(
alignment:
Alignment.centerLeft,
child: Text(
playlist[index].title,
maxLines: 1,
overflow:
TextOverflow.ellipsis,
),
),
),
],
),
),
),
Divider(height: 2),
],
),
);
},
);
},
),
),
],
), ),
); ),
),
],
);
} }
Widget _miniPanel(double width, BuildContext context) { Widget _miniPanel(double width, BuildContext context) {
@ -359,13 +438,6 @@ class _PlayerWidgetState extends State<PlayerWidget> {
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).primaryColor, color: Theme.of(context).primaryColor,
// boxShadow: [
// BoxShadow(
// offset: Offset(0, -1),
// blurRadius: 4,
// color: Colors.grey[400],
// ),
// ],
), ),
height: 60, height: 60,
child: child:
@ -374,19 +446,9 @@ class _PlayerWidgetState extends State<PlayerWidget> {
selector: (_, audio) => selector: (_, audio) =>
Tuple2(audio.episode?.primaryColor, audio.seekSliderValue), Tuple2(audio.episode?.primaryColor, audio.seekSliderValue),
builder: (_, data, __) { builder: (_, data, __) {
var color = json.decode(data.item1); Color _c = (Theme.of(context).brightness == Brightness.light)
Color _c; ? data.item1.colorizedark()
if (Theme.of(context).brightness == Brightness.light) { : data.item1.colorizeLight();
_c = (color[0] > 200 && color[1] > 200 && color[2] > 200)
? Color.fromRGBO(
(255 - color[0]), 255 - color[1], 255 - color[2], 1.0)
: Color.fromRGBO(color[0], color[1], color[2], 1.0);
} else {
_c = (color[0] < 50 && color[1] < 50 && color[2] < 50)
? Color.fromRGBO(
(255 - color[0]), 255 - color[1], 255 - color[2], 1.0)
: Color.fromRGBO(color[0], color[1], color[2], 1.0);
}
return SizedBox( return SizedBox(
height: 2, height: 2,
child: LinearProgressIndicator( child: LinearProgressIndicator(
@ -411,7 +473,10 @@ class _PlayerWidgetState extends State<PlayerWidget> {
builder: (_, title, __) { builder: (_, title, __) {
return LayoutBuilder( return LayoutBuilder(
builder: (context, size) { builder: (context, size) {
var span = TextSpan(text: title, style: TextStyle(fontWeight: FontWeight.bold),); var span = TextSpan(
text: title,
style: TextStyle(fontWeight: FontWeight.bold),
);
var tp = TextPainter( var tp = TextPainter(
text: span, text: span,
maxLines: 2, maxLines: 2,
@ -500,17 +565,30 @@ class _PlayerWidgetState extends State<PlayerWidget> {
: () { : () {
audio.resumeAudio(); audio.resumeAudio();
}, },
child: Container( child: Stack(
padding: EdgeInsets.all(10.0), alignment: Alignment.center,
child: ClipRRect( children: <Widget>[
borderRadius: BorderRadius.all( Container(
Radius.circular(15.0)), padding: EdgeInsets.all(10.0),
child: Container( child: Container(
height: 30.0, height: 30.0,
width: 30.0, width: 30.0,
child: Image.file(File( child: CircleAvatar(
"${audio.episode.imagePath}"))), backgroundImage: FileImage(File(
), "${audio.episode.imagePath}")),
)),
),
Container(
height: 50.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.black),
),
Icon(
Icons.play_arrow,
color: Colors.white,
)
],
), ),
), ),
IconButton( IconButton(

View File

@ -1,4 +1,3 @@
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:async'; import 'dart:async';
@ -8,14 +7,15 @@ import 'package:provider/provider.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:tsacdop/class/episodebrief.dart'; import 'package:tsacdop/class/episodebrief.dart';
import 'package:tsacdop/class/importompl.dart';
import 'package:tsacdop/class/podcast_group.dart'; import 'package:tsacdop/class/podcast_group.dart';
import 'package:tsacdop/class/podcastlocal.dart'; import 'package:tsacdop/class/podcastlocal.dart';
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart'; import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
import 'package:tsacdop/episodes/episodedetail.dart'; import 'package:tsacdop/episodes/episodedetail.dart';
import 'package:tsacdop/podcasts/podcastdetail.dart'; import 'package:tsacdop/podcasts/podcastdetail.dart';
import 'package:tsacdop/podcasts/podcastmanage.dart'; import 'package:tsacdop/podcasts/podcastmanage.dart';
import 'package:tsacdop/util/pageroute.dart'; import 'package:tsacdop/util/pageroute.dart';
import 'package:tsacdop/util/colorize.dart';
class ScrollPodcasts extends StatefulWidget { class ScrollPodcasts extends StatefulWidget {
@override @override
@ -228,7 +228,8 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
left: 6.0, left: 6.0,
right: 6.0), right: 6.0),
indicator: CircleTabIndicator( indicator: CircleTabIndicator(
color: Colors.blue, radius: 3), color: Theme.of(context).accentColor,
radius: 3),
isScrollable: true, isScrollable: true,
tabs: groups[_groupIndex] tabs: groups[_groupIndex]
.podcasts .podcasts
@ -251,29 +252,31 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
], ],
), ),
), ),
Container( Consumer<ImportOmpl>(
height: (_width - 20) / 3 + 40, builder: (_, ompl, __) => Container(
margin: EdgeInsets.only(left: 10, right: 10), height: (_width - 20) / 3 + 40,
decoration: BoxDecoration( margin: EdgeInsets.only(left: 10, right: 10),
color: Theme.of(context).scaffoldBackgroundColor, decoration: BoxDecoration(
), color: Theme.of(context).scaffoldBackgroundColor,
child: TabBarView( ),
children: groups[_groupIndex] child: TabBarView(
.podcasts children: groups[_groupIndex]
.map<Widget>((PodcastLocal podcastLocal) { .podcasts
return Container( .map<Widget>((PodcastLocal podcastLocal) {
decoration: BoxDecoration( return Container(
color: Theme.of(context).brightness == decoration: BoxDecoration(
Brightness.light color: Theme.of(context).brightness ==
? Theme.of(context).primaryColor Brightness.light
: Colors.black12), ? Theme.of(context).primaryColor
margin: EdgeInsets.symmetric(horizontal: 5.0), : Colors.black12),
key: ObjectKey(podcastLocal.title), margin: EdgeInsets.symmetric(horizontal: 5.0),
child: PodcastPreview( key: ObjectKey(podcastLocal.title),
podcastLocal: podcastLocal, child: PodcastPreview(
), podcastLocal: podcastLocal,
); ),
}).toList(), );
}).toList(),
),
), ),
), ),
], ],
@ -293,27 +296,15 @@ class PodcastPreview extends StatefulWidget {
class _PodcastPreviewState extends State<PodcastPreview> { class _PodcastPreviewState extends State<PodcastPreview> {
Future<List<EpisodeBrief>> _getRssItemTop(PodcastLocal podcastLocal) async { Future<List<EpisodeBrief>> _getRssItemTop(PodcastLocal podcastLocal) async {
var dbHelper = DBHelper(); var dbHelper = DBHelper();
Future<List<EpisodeBrief>> episodes = List<EpisodeBrief> episodes = await dbHelper.getRssItemTop(podcastLocal.id);
dbHelper.getRssItemTop(podcastLocal.id);
return episodes; return episodes;
} }
Color _c;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var color = json.decode(widget.podcastLocal.primaryColor); Color _c = (Theme.of(context).brightness == Brightness.light)
if (Theme.of(context).brightness == Brightness.light) { ? widget.podcastLocal.primaryColor.colorizedark()
(color[0] > 200 && color[1] > 200 && color[2] > 200) : widget.podcastLocal.primaryColor.colorizeLight();
? _c = Color.fromRGBO(
(255 - color[0]), 255 - color[1], 255 - color[2], 1.0)
: _c = Color.fromRGBO(color[0], color[1], color[2], 1.0);
} else {
(color[0] < 50 && color[1] < 50 && color[2] < 50)
? _c = Color.fromRGBO(
(255 - color[0]), 255 - color[1], 255 - color[2], 1.0)
: _c = Color.fromRGBO(color[0], color[1], color[2], 1.0);
}
return Column( return Column(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
@ -401,20 +392,10 @@ class ShowEpisode extends StatelessWidget {
), ),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) { (BuildContext context, int index) {
Color _c; Color _c =
var color = json.decode(podcast[index].primaryColor); (Theme.of(context).brightness == Brightness.light)
? podcastLocal.primaryColor.colorizedark()
if (Theme.of(context).brightness == Brightness.light) { : podcastLocal.primaryColor.colorizeLight();
(color[0] > 200 && color[1] > 200 && color[2] > 200)
? _c = Color.fromRGBO(
(255 - color[0]), 255 - color[1], 255 - color[2], 1.0)
: _c = Color.fromRGBO(color[0], color[1], color[2], 1.0);
} else {
(color[0] < 50 && color[1] < 50 && color[2] < 50)
? _c = Color.fromRGBO(
(255 - color[0]), 255 - color[1], 255 - color[2], 1.0)
: _c = Color.fromRGBO(color[0], color[1], color[2], 1.0);
}
return InkWell( return InkWell(
onTap: () { onTap: () {
Navigator.push( Navigator.push(
@ -438,13 +419,6 @@ class ShowEpisode extends StatelessWidget {
// color: Theme.of(context).primaryColor, // color: Theme.of(context).primaryColor,
width: 3.0, width: 3.0,
), ),
// boxShadow: [
// BoxShadow(
// color: Theme.of(context).primaryColor,
// blurRadius: 1.0,
// spreadRadius: 0.5,
// ),
// ]
), ),
alignment: Alignment.center, alignment: Alignment.center,
padding: EdgeInsets.all(10.0), padding: EdgeInsets.all(10.0),
@ -459,15 +433,11 @@ class ShowEpisode extends StatelessWidget {
Hero( Hero(
tag: podcast[index].enclosureUrl + 'scroll', tag: podcast[index].enclosureUrl + 'scroll',
child: Container( child: Container(
child: ClipRRect( height: _width / 18,
borderRadius: BorderRadius.all( width: _width / 18,
Radius.circular(_width / 36)), child: CircleAvatar(
child: Container( backgroundImage: FileImage(
height: _width / 18, File("${podcastLocal.imagePath}")),
width: _width / 18,
child: Image.file(
File("${podcastLocal.imagePath}")),
),
), ),
), ),
), ),

View File

@ -2,6 +2,7 @@ import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:tsacdop/class/episodebrief.dart'; import 'package:tsacdop/class/episodebrief.dart';
import 'package:tsacdop/home/paly_history.dart';
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart'; import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
import 'package:tsacdop/util/episodegrid.dart'; import 'package:tsacdop/util/episodegrid.dart';
@ -22,6 +23,39 @@ class _MainTabState extends State<MainTab> with TickerProviderStateMixin {
)); ));
} }
Widget playHistory() {
return PopupMenuButton<int>(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10))),
elevation: 1,
icon: Icon(Icons.history),
tooltip: "Menu",
itemBuilder: (context) => [
PopupMenuItem(
value: 0,
child: Container(
padding: EdgeInsets.only(left: 10),
child: Row(
children: <Widget>[
Icon(Icons.history),
Padding(
padding: EdgeInsets.symmetric(horizontal: 5.0),
),
Text('Play History'),
],
),
),
),
],
onSelected: (value) {
if (value == 0) {
Navigator.push(
context, MaterialPageRoute(builder: (context) => PlayedHistory()));
}
},
);
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -40,30 +74,36 @@ class _MainTabState extends State<MainTab> with TickerProviderStateMixin {
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Container( Row(
padding: EdgeInsets.symmetric(horizontal: 10.0), children: <Widget>[
height: 50, Container(
alignment: Alignment.centerLeft, padding: EdgeInsets.symmetric(horizontal: 10.0),
child: TabBar( height: 50,
isScrollable: true, alignment: Alignment.centerLeft,
labelPadding: EdgeInsets.all(10.0), child: TabBar(
controller: _controller, isScrollable: true,
indicator: getIndicator(context), labelPadding: EdgeInsets.all(10.0),
tabs: <Widget>[ controller: _controller,
Text( indicator: getIndicator(context),
'Recent Update', tabs: <Widget>[
style: TextStyle(fontWeight: FontWeight.bold), Text(
'Recent Update',
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(
'Favorites',
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(
'Downloads',
style: TextStyle(fontWeight: FontWeight.bold),
),
],
), ),
Text( ),
'Favorites', Spacer(),
style: TextStyle(fontWeight: FontWeight.bold), playHistory(),
), ],
Text(
'Downloads',
style: TextStyle(fontWeight: FontWeight.bold),
),
],
),
), ),
Expanded( Expanded(
child: Container( child: Container(

View File

@ -0,0 +1,73 @@
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<PlayedHistory> {
Future<List<PlayHistory>> gerPlayHistory() async{
DBHelper dbHelper = DBHelper();
List<PlayHistory> 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<SystemUiOverlayStyle>(
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<List<PlayHistory>>(
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: <Widget>[
ListTile(
title: Text(snapshot.data[index].title),
subtitle: Text(_stringForSeconds(snapshot.data[index].seconds)),
),
Divider(height: 2),
],
);
}
)
: Center(
child: CircularProgressIndicator(),
);
},
),
),
),
);
}
}

View File

@ -32,12 +32,13 @@ class KeyValueStorage {
{'groups': groupList.map((group) => group.toJson()).toList()})); {'groups': groupList.map((group) => group.toJson()).toList()}));
} }
Future<bool> saveTheme(int setting) async{ Future<bool> saveInt(int setting) async{
SharedPreferences prefs = await SharedPreferences.getInstance(); SharedPreferences prefs = await SharedPreferences.getInstance();
print(setting.toString());
return prefs.setInt(key, setting); return prefs.setInt(key, setting);
} }
Future<int> getTheme() async{ Future<int> getInt() async{
SharedPreferences prefs = await SharedPreferences.getInstance(); SharedPreferences prefs = await SharedPreferences.getInstance();
if(prefs.getInt(key) == null) await prefs.setInt(key, 0); if(prefs.getInt(key) == null) await prefs.setInt(key, 0);
return prefs.getInt(key); return prefs.getInt(key);
@ -51,7 +52,18 @@ class KeyValueStorage {
Future<List<String>> getStringList() async{ Future<List<String>> getStringList() async{
SharedPreferences prefs = await SharedPreferences.getInstance(); SharedPreferences prefs = await SharedPreferences.getInstance();
if(prefs.getStringList(key) == null) {await prefs.setStringList(key, []);} if(prefs.getStringList(key) == null) {await prefs.setStringList(key, []);}
print(prefs.getStringList(key).toString());
return prefs.getStringList(key); return prefs.getStringList(key);
} }
Future<bool> saveString(String string) async{
SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.setString(key, string);
}
Future<String> getString() async{
SharedPreferences prefs = await SharedPreferences.getInstance();
if(prefs.getString(key) == null) {await prefs.setString(key, '');}
return prefs.getString(key);
}
} }

View File

@ -30,7 +30,7 @@ class DBHelper {
await db await db
.execute("""CREATE TABLE PodcastLocal(id TEXT PRIMARY KEY,title TEXT, .execute("""CREATE TABLE PodcastLocal(id TEXT PRIMARY KEY,title TEXT,
imageUrl TEXT,rssUrl TEXT UNIQUE,primaryColor TEXT,author TEXT, imageUrl TEXT,rssUrl TEXT UNIQUE,primaryColor TEXT,author TEXT,
description TEXT, add_date INTEGER, imagePath TEXT, email TEXT, provider TEXT, link TEXT, description TEXT, add_date INTEGER, imagePath TEXT, provider TEXT, link TEXT,
background_image TEXT DEFAULT '',hosts TEXT DEFAULT '')"""); background_image TEXT DEFAULT '',hosts TEXT DEFAULT '')""");
await db await db
.execute("""CREATE TABLE Episodes(id INTEGER PRIMARY KEY,title TEXT, .execute("""CREATE TABLE Episodes(id INTEGER PRIMARY KEY,title TEXT,
@ -40,7 +40,7 @@ class DBHelper {
downloaded TEXT DEFAULT 'ND', download_date INTEGER DEFAULT 0)"""); downloaded TEXT DEFAULT 'ND', download_date INTEGER DEFAULT 0)""");
await db.execute( await db.execute(
"""CREATE TABLE PlayHistory(id INTEGER PRIMARY KEY, title TEXT, enclosure_url TEXT UNIQUE, """CREATE TABLE PlayHistory(id INTEGER PRIMARY KEY, title TEXT, enclosure_url TEXT UNIQUE,
seconds INTEGER, seek_value INTEGER, add_date INTEGER)"""); seconds REAL, seek_value REAL, add_date INTEGER)""");
} }
Future<List<PodcastLocal>> getPodcastLocal(List<String> podcasts) async { Future<List<PodcastLocal>> getPodcastLocal(List<String> podcasts) async {
@ -49,7 +49,7 @@ class DBHelper {
await Future.forEach(podcasts, (s) async { await Future.forEach(podcasts, (s) async {
List<Map> list; List<Map> list;
list = await dbClient.rawQuery( list = await dbClient.rawQuery(
'SELECT id, title, imageUrl, rssUrl, primaryColor, author, imagePath , email, provider, link FROM PodcastLocal WHERE id = ?', 'SELECT id, title, imageUrl, rssUrl, primaryColor, author, imagePath , provider, link FROM PodcastLocal WHERE id = ?',
[s]); [s]);
podcastLocal.add(PodcastLocal( podcastLocal.add(PodcastLocal(
list.first['title'], list.first['title'],
@ -59,7 +59,6 @@ class DBHelper {
list.first['author'], list.first['author'],
list.first['id'], list.first['id'],
list.first['imagePath'], list.first['imagePath'],
list.first['email'],
list.first['provider'], list.first['provider'],
list.first['link'])); list.first['link']));
}); });
@ -69,7 +68,7 @@ class DBHelper {
Future<List<PodcastLocal>> getPodcastLocalAll() async { Future<List<PodcastLocal>> getPodcastLocalAll() async {
var dbClient = await database; var dbClient = await database;
List<Map> list = await dbClient.rawQuery( List<Map> list = await dbClient.rawQuery(
'SELECT id, title, imageUrl, rssUrl, primaryColor, author, imagePath, email, provider, link FROM PodcastLocal ORDER BY add_date DESC'); 'SELECT id, title, imageUrl, rssUrl, primaryColor, author, imagePath, provider, link FROM PodcastLocal ORDER BY add_date DESC');
List<PodcastLocal> podcastLocal = List(); List<PodcastLocal> podcastLocal = List();
@ -82,7 +81,6 @@ class DBHelper {
list[i]['author'], list[i]['author'],
list[i]['id'], list[i]['id'],
list[i]['imagePath'], list[i]['imagePath'],
list.first['email'],
list.first['provider'], list.first['provider'],
list.first['link'])); list.first['link']));
} }
@ -103,7 +101,7 @@ class DBHelper {
await dbClient.transaction((txn) async { await dbClient.transaction((txn) async {
return await txn.rawInsert( return await txn.rawInsert(
"""INSERT OR IGNORE INTO PodcastLocal (id, title, imageUrl, rssUrl, """INSERT OR IGNORE INTO PodcastLocal (id, title, imageUrl, rssUrl,
primaryColor, author, description, add_date, imagePath, email, provider, link) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", primaryColor, author, description, add_date, imagePath, provider, link) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
[ [
podcastLocal.id, podcastLocal.id,
podcastLocal.title, podcastLocal.title,
@ -114,28 +112,26 @@ class DBHelper {
podcastLocal.description, podcastLocal.description,
_milliseconds, _milliseconds,
podcastLocal.imagePath, podcastLocal.imagePath,
podcastLocal.email,
podcastLocal.provider, podcastLocal.provider,
podcastLocal.link podcastLocal.link
]); ]);
}); });
} }
Future<int> saveFiresideData(List<String> list)async{ Future<int> saveFiresideData(List<String> list) async {
var dbClient = await database; var dbClient = await database;
int result = await dbClient.rawUpdate( int result = await dbClient.rawUpdate(
'UPDATE PodcastLocal SET background_image = ? , hosts = ? WHERE id = ?',[list[1],list[2],list[0]] 'UPDATE PodcastLocal SET background_image = ? , hosts = ? WHERE id = ?',
); [list[1], list[2], list[0]]);
print('Fireside data save in sqllite'); print('Fireside data save in sqllite');
return result; return result;
} }
Future<List<String>> getFiresideData(String id)async{ Future<List<String>> getFiresideData(String id) async {
var dbClient = await database; var dbClient = await database;
List<Map> list = await dbClient.rawQuery( List<Map> list = await dbClient.rawQuery(
'SELECT background_image, hosts FROM PodcastLocal WHERE id = ?', [id] 'SELECT background_image, hosts FROM PodcastLocal WHERE id = ?', [id]);
); List<String> data = [list.first['background_image'], list.first['hosts']];
List<String> data = [list.first['background_image]'],list.first['hosts']];
return data; return data;
} }
@ -172,6 +168,32 @@ class DBHelper {
return result; return result;
} }
Future<List<PlayHistory>> getPlayHistory() async {
var dbClient = await database;
List<Map> list = await dbClient.rawQuery(
"""SELECT title, enclosure_url, seconds, seek_value, add_date FROM PlayHistory
ORDER BY add_date DESC
""");
List<PlayHistory> playHistory = [];
list.forEach((record) {
playHistory.add(PlayHistory(
record['title'],
record['enclosure_url'],
record['seconds'],
record['seek_value'],
));
});
return playHistory;
}
Future<int> getPosition(EpisodeBrief episodeBrief) async {
var dbClient = await database;
List<Map> list = await dbClient.rawQuery(
"SELECT seconds FROM PlayHistory Where enclosure_url = ?",
[episodeBrief.enclosureUrl]);
return list.length > 0 ? list.first['seconds'] : 0;
}
DateTime _parsePubDate(String pubDate) { DateTime _parsePubDate(String pubDate) {
if (pubDate == null) return DateTime.now(); if (pubDate == null) return DateTime.now();
print(pubDate); print(pubDate);

View File

@ -2,61 +2,100 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_downloader/flutter_downloader.dart'; import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:workmanager/workmanager.dart';
import 'package:tsacdop/class/podcastlocal.dart';
import 'package:tsacdop/class/podcast_group.dart'; import 'package:tsacdop/class/podcast_group.dart';
import 'package:tsacdop/home/appbar/addpodcast.dart'; import 'package:tsacdop/home/appbar/addpodcast.dart';
import 'package:tsacdop/class/audiostate.dart'; import 'package:tsacdop/class/audiostate.dart';
import 'package:tsacdop/class/importompl.dart'; import 'package:tsacdop/class/importompl.dart';
import 'package:tsacdop/class/settingstate.dart'; import 'package:tsacdop/class/settingstate.dart';
import 'local_storage/sqflite_localpodcast.dart';
void main() async { final SettingState themeSetting = SettingState();
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
await themeSetting.initData();
runApp( runApp(
MultiProvider( MultiProvider(
providers: [ providers: [
ChangeNotifierProvider(create: (context) => AudioPlayer()), ChangeNotifierProvider(create: (_) => themeSetting),
ChangeNotifierProvider(create: (context) => ImportOmpl()), ChangeNotifierProvider(create: (_) => AudioPlayer()),
ChangeNotifierProvider(create: (context) => SettingState()), ChangeNotifierProvider(create: (_) => GroupList()),
ChangeNotifierProvider(create: (context) => GroupList()), ChangeNotifierProvider(create: (_) => ImportOmpl()),
], ],
child: MyApp(), child: MyApp(),
), ),
); );
WidgetsFlutterBinding.ensureInitialized(); Workmanager.initialize(
callbackDispatcher,
isInDebugMode: true,
);
Workmanager.registerPeriodicTask("update", "simplePeriodicTask",
frequency: Duration(hours: 1),
initialDelay: Duration(seconds: 10),
constraints: Constraints(
networkType: NetworkType.connected,
requiresBatteryNotLow: true,
requiresCharging: false,
requiresDeviceIdle: true,
requiresStorageNotLow: true));
await FlutterDownloader.initialize(); await FlutterDownloader.initialize();
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); await SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
}
void callbackDispatcher() {
Workmanager.executeTask((task, inputData) async {
var dbHelper = DBHelper();
List<PodcastLocal> podcastList = await dbHelper.getPodcastLocalAll();
await Future.forEach(podcastList, (podcastLocal) async {
await dbHelper.updatePodcastRss(podcastLocal);
print('Refresh ' + podcastLocal.title);
});
return Future.value(true);
});
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var theme = Provider.of<SettingState>(context).theme; return Consumer<SettingState>(
print(theme); builder: (_, setting, __) {
return MaterialApp( return MaterialApp(
themeMode: ThemeMode.system, themeMode: setting.theme,
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
title: 'TsacDop', title: 'Tsacdop',
theme: ThemeData( theme: ThemeData(
accentColorBrightness: Brightness.dark, accentColorBrightness: Brightness.dark,
primaryColor: Colors.grey[100], primaryColor: Colors.grey[100],
accentColor: Colors.blue[400], accentColor: setting.accentSetColor,
primaryColorLight: Colors.white, primaryColorLight: Colors.white,
primaryColorDark: Colors.grey[300], primaryColorDark: Colors.grey[300],
dialogBackgroundColor: Colors.white, dialogBackgroundColor: Colors.white,
backgroundColor: Colors.grey[100], backgroundColor: Colors.grey[100],
appBarTheme: AppBarTheme( appBarTheme: AppBarTheme(
color: Colors.grey[100], color: Colors.grey[100],
elevation: 0, elevation: 0,
), ),
textTheme: TextTheme( textTheme: TextTheme(
headline1: TextStyle(fontSize: 72.0, fontWeight: FontWeight.bold), headline1: TextStyle(fontSize: 72.0, fontWeight: FontWeight.bold),
bodyText2: TextStyle(fontSize: 15.0, fontWeight: FontWeight.normal), bodyText2:
), TextStyle(fontSize: 15.0, fontWeight: FontWeight.normal),
tabBarTheme: TabBarTheme( ),
labelColor: Colors.black, tabBarTheme: TabBarTheme(
unselectedLabelColor: Colors.grey[400], labelColor: Colors.black,
), unselectedLabelColor: Colors.grey[400],
), ),
darkTheme: ThemeData.dark().copyWith(accentColor: Colors.blue[400],), ),
home: MyHomePage(), darkTheme: ThemeData.dark().copyWith(
accentColor: setting.accentSetColor,
),
home: MyHomePage(),
);
},
); );
} }
} }

View File

@ -1,4 +1,3 @@
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:async'; import 'dart:async';
@ -6,9 +5,11 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:html/parser.dart'; import 'package:html/parser.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:tsacdop/class/podcastlocal.dart'; import 'package:tsacdop/class/podcastlocal.dart';
import 'package:tsacdop/class/episodebrief.dart'; import 'package:tsacdop/class/episodebrief.dart';
@ -18,6 +19,7 @@ import 'package:tsacdop/util/episodegrid.dart';
import 'package:tsacdop/util/pageroute.dart'; import 'package:tsacdop/util/pageroute.dart';
import 'package:tsacdop/home/audioplayer.dart'; import 'package:tsacdop/home/audioplayer.dart';
import 'package:tsacdop/class/fireside_data.dart'; import 'package:tsacdop/class/fireside_data.dart';
import 'package:tsacdop/util/colorize.dart';
class PodcastDetail extends StatefulWidget { class PodcastDetail extends StatefulWidget {
PodcastDetail({Key key, this.podcastLocal}) : super(key: key); PodcastDetail({Key key, this.podcastLocal}) : super(key: key);
@ -29,7 +31,7 @@ class PodcastDetail extends StatefulWidget {
class _PodcastDetailState extends State<PodcastDetail> { class _PodcastDetailState extends State<PodcastDetail> {
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey = final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
GlobalKey<RefreshIndicatorState>(); GlobalKey<RefreshIndicatorState>();
String background; String backgroundImage;
List<PodcastHost> hosts; List<PodcastHost> hosts;
Future _updateRssItem(PodcastLocal podcastLocal) async { Future _updateRssItem(PodcastLocal podcastLocal) async {
var dbHelper = DBHelper(); var dbHelper = DBHelper();
@ -52,12 +54,23 @@ class _PodcastDetailState extends State<PodcastDetail> {
if (podcastLocal.provider.contains('fireside')) { if (podcastLocal.provider.contains('fireside')) {
FiresideData data = FiresideData(podcastLocal.id, podcastLocal.link); FiresideData data = FiresideData(podcastLocal.id, podcastLocal.link);
await data.getData(); await data.getData();
background = data.background; backgroundImage = data.background;
hosts = data.hosts; hosts = data.hosts;
} }
return episodes; return episodes;
} }
_launchUrl(String url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
Fluttertoast.showToast(
msg: '$url Invalid Link',
gravity: ToastGravity.TOP,
);
}
}
Widget podcastInfo(BuildContext context) { Widget podcastInfo(BuildContext context) {
return Container( return Container(
height: 170, height: 170,
@ -75,24 +88,87 @@ class _PodcastDetailState extends State<PodcastDetail> {
); );
} }
Widget hostsList(BuildContext context, List<PodcastHost> hosts) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
hosts != null
? Container(
decoration: BoxDecoration(
image: DecorationImage(
// colorFilter: ColorFilter.mode(_color, BlendMode.color),
image: CachedNetworkImageProvider(
backgroundImage,
),
fit: BoxFit.cover)),
alignment: Alignment.centerRight,
child: Container(
color: Theme.of(context).scaffoldBackgroundColor.withOpacity(0.5),
padding: EdgeInsets.symmetric(vertical: 5.0),
width: MediaQuery.of(context).size.width,
alignment: Alignment.centerRight,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: hosts
.map((host) => Container(
padding: EdgeInsets.all(5.0),
width: 80.0,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
CircleAvatar(
backgroundColor: Colors.grey[400],
backgroundImage: CachedNetworkImageProvider(
host.image,
)),
Padding(
padding: EdgeInsets.all(2),
),
Text(
host.name,
style: TextStyle(
backgroundColor:
Colors.black.withOpacity(0.5),
color: Colors.white,
),
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.fade,
),
],
)))
.toList()
.cast<Widget>(),
),
),
))
: Center(),
Padding(padding: EdgeInsets.all(10.0)),
Container(
padding: EdgeInsets.only(left: 15.0, right: 15.0, bottom: 10.0),
alignment: Alignment.topLeft,
color: Theme.of(context).scaffoldBackgroundColor,
child: AboutPodcast(podcastLocal: widget.podcastLocal),
),
],
);
}
double top = 0; double top = 0;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
double _width = MediaQuery.of(context).size.width; double _width = MediaQuery.of(context).size.width;
Color _color; Color _color = widget.podcastLocal.primaryColor.colorizedark();
var color = json.decode(widget.podcastLocal.primaryColor);
(color[0] > 200 && color[1] > 200 && color[2] > 200)
? _color = Color.fromRGBO(
(255 - color[0]), 255 - color[1], 255 - color[2], 1.0)
: _color = Color.fromRGBO(color[0], color[1], color[2], 1.0);
return AnnotatedRegion<SystemUiOverlayStyle>( return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle( value: SystemUiOverlayStyle(
statusBarIconBrightness: Brightness.light, statusBarIconBrightness: Brightness.light,
systemNavigationBarColor: Theme.of(context).primaryColor, systemNavigationBarColor: Theme.of(context).primaryColor,
statusBarColor: _color, statusBarColor: _color,
// statusBarColor: Theme.of(context).primaryColor,
), ),
child: SafeArea( child: SafeArea(
child: Scaffold( child: Scaffold(
@ -112,6 +188,64 @@ class _PodcastDetailState extends State<PodcastDetail> {
primary: true, primary: true,
slivers: <Widget>[ slivers: <Widget>[
SliverAppBar( SliverAppBar(
actions: <Widget>[
PopupMenuButton<String>(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10))),
elevation: 2,
tooltip: 'Menu',
itemBuilder: (context) => [
widget.podcastLocal.link != null
? PopupMenuItem(
value: widget.podcastLocal.link,
child: Container(
padding:
EdgeInsets.only(left: 10),
child: Row(
children: <Widget>[
Icon(Icons.link,
color: Theme.of(context)
.tabBarTheme
.labelColor),
Padding(
padding:
EdgeInsets.symmetric(
horizontal: 5.0),
),
Text('Visit Site'),
],
),
),
)
: Center(),
PopupMenuItem(
value: widget.podcastLocal.rssUrl,
child: Container(
padding: EdgeInsets.only(left: 10),
child: Row(
children: <Widget>[
Icon(
Icons.rss_feed,
color: Theme.of(context)
.tabBarTheme
.labelColor,
),
Padding(
padding: EdgeInsets.symmetric(
horizontal: 5.0),
),
Text('View Rss Feed'),
],
),
),
),
],
onSelected: (url) {
_launchUrl(url);
},
)
],
elevation: 0, elevation: 0,
iconTheme: IconThemeData(color: Colors.white), iconTheme: IconThemeData(color: Colors.white),
expandedHeight: 170, expandedHeight: 170,
@ -141,23 +275,19 @@ class _PodcastDetailState extends State<PodcastDetail> {
Text( Text(
widget.podcastLocal.author ?? widget.podcastLocal.author ??
'', '',
style: Theme.of(context) style: TextStyle(
.textTheme color: Colors.white)),
.bodyText1 widget.podcastLocal.provider
.copyWith( .isNotEmpty
color: ? Text(
Colors.grey[300])), 'Hosted on ' +
Text(
'Hosted on ' +
widget.podcastLocal widget.podcastLocal
.provider ?? .provider,
'', maxLines: 1,
style: Theme.of(context) style: TextStyle(
.textTheme color: Colors.white),
.bodyText1 )
.copyWith( : Center(),
color:
Colors.grey[300]))
], ],
), ),
), ),
@ -186,61 +316,14 @@ class _PodcastDetailState extends State<PodcastDetail> {
SliverList( SliverList(
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) { (BuildContext context, int index) {
return Column( return hostsList(context, hosts);
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: EdgeInsets.all(10.0),
),
Container(
alignment: Alignment.centerRight,
padding: EdgeInsets.symmetric(
horizontal: 10.0),
child: hosts != null
? Wrap(
children: hosts
.map((host) => Container(
padding:
EdgeInsets.all(5.0),
width: 60.0,
child: Column(
mainAxisAlignment:
MainAxisAlignment
.center,
mainAxisSize:
MainAxisSize.min,
children: <Widget>[
CachedNetworkImage(
imageUrl:
host.image),
Text(host.name),
],
)))
.toList()
.cast<Widget>(),
)
: Center(),
),
Container(
padding: EdgeInsets.only(
left: 15.0,
right: 15.0,
bottom: 10.0),
alignment: Alignment.topLeft,
color: Theme.of(context)
.scaffoldBackgroundColor,
child: AboutPodcast(
podcastLocal: widget.podcastLocal),
),
],
);
}, },
childCount: 1, childCount: 1,
), ),
), ),
SliverPadding( SliverPadding(
padding: const EdgeInsets.symmetric(horizontal:15.0), padding:
const EdgeInsets.symmetric(horizontal: 15.0),
sliver: SliverGrid( sliver: SliverGrid(
gridDelegate: gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount( SliverGridDelegateWithFixedCrossAxisCount(
@ -253,33 +336,13 @@ class _PodcastDetailState extends State<PodcastDetail> {
(BuildContext context, int index) { (BuildContext context, int index) {
EpisodeBrief episodeBrief = EpisodeBrief episodeBrief =
snapshot.data[index]; snapshot.data[index];
Color _c; Color _c = (Theme.of(context).brightness ==
var color = json.decode( Brightness.light)
widget.podcastLocal.primaryColor); ? widget.podcastLocal.primaryColor
if (Theme.of(context).brightness == .colorizedark()
Brightness.light) { : widget.podcastLocal.primaryColor
(color[0] > 200 && .colorizeLight();
color[1] > 200 &&
color[2] > 200)
? _c = Color.fromRGBO(
(255 - color[0]),
255 - color[1],
255 - color[2],
1.0)
: _c = Color.fromRGBO(color[0],
color[1], color[2], 1.0);
} else {
(color[0] < 50 &&
color[1] < 50 &&
color[2] < 50)
? _c = Color.fromRGBO(
(255 - color[0]),
255 - color[1],
255 - color[2],
1.0)
: _c = Color.fromRGBO(color[0],
color[1], color[2], 1.0);
}
return Material( return Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
@ -334,18 +397,12 @@ class _PodcastDetailState extends State<PodcastDetail> {
.enclosureUrl + .enclosureUrl +
'podcast', 'podcast',
child: Container( child: Container(
child: ClipRRect( height: _width / 16,
borderRadius: width: _width / 16,
BorderRadius.all( child: CircleAvatar(
Radius.circular( backgroundImage:
_width / FileImage(File(
32)), "${episodeBrief.imagePath}")),
child: Container(
height: _width / 16,
width: _width / 16,
child: Image.file(File(
"${episodeBrief.imagePath}")),
),
), ),
), ),
), ),
@ -527,7 +584,10 @@ class _AboutPodcastState extends State<AboutPodcast> {
: Text(_description), : Text(_description),
); );
} else { } else {
return SelectableText(_description, toolbarOptions: ToolbarOptions(copy: true),); return SelectableText(
_description,
toolbarOptions: ToolbarOptions(copy: true),
);
} }
}, },
); );

View File

@ -1,4 +1,3 @@
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -10,6 +9,7 @@ import 'package:tsacdop/class/podcast_group.dart';
import 'package:tsacdop/class/podcastlocal.dart'; import 'package:tsacdop/class/podcastlocal.dart';
import 'package:tsacdop/podcasts/podcastdetail.dart'; import 'package:tsacdop/podcasts/podcastdetail.dart';
import 'package:tsacdop/util/pageroute.dart'; import 'package:tsacdop/util/pageroute.dart';
import 'package:tsacdop/util/colorize.dart';
class PodcastGroupList extends StatefulWidget { class PodcastGroupList extends StatefulWidget {
final PodcastGroup group; final PodcastGroup group;
@ -126,7 +126,6 @@ class _PodcastCardState extends State<PodcastCard> {
bool _addGroup; bool _addGroup;
List<PodcastGroup> _selectedGroups; List<PodcastGroup> _selectedGroups;
List<PodcastGroup> _belongGroups; List<PodcastGroup> _belongGroups;
Color _c;
@override @override
void initState() { void initState() {
@ -149,18 +148,10 @@ class _PodcastCardState extends State<PodcastCard> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var color = json.decode(widget.podcastLocal.primaryColor); Color _c = (Theme.of(context).brightness == Brightness.light)
if (Theme.of(context).brightness == Brightness.light) { ? widget.podcastLocal.primaryColor.colorizedark()
(color[0] > 200 && color[1] > 200 && color[2] > 200) : widget.podcastLocal.primaryColor.colorizeLight();
? _c = Color.fromRGBO(
(255 - color[0]), 255 - color[1], 255 - color[2], 1.0)
: _c = Color.fromRGBO(color[0], color[1], color[2], 1.0);
} else {
(color[0] < 50 && color[1] < 50 && color[2] < 50)
? _c = Color.fromRGBO(
(255 - color[0]), 255 - color[1], 255 - color[2], 1.0)
: _c = Color.fromRGBO(color[0], color[1], color[2], 1.0);
}
double _width = MediaQuery.of(context).size.width; double _width = MediaQuery.of(context).size.width;
var _groupList = Provider.of<GroupList>(context); var _groupList = Provider.of<GroupList>(context);
_belongGroups = _groupList.getPodcastGroup(widget.podcastLocal.id); _belongGroups = _groupList.getPodcastGroup(widget.podcastLocal.id);
@ -340,25 +331,51 @@ class _PodcastCardState extends State<PodcastCard> {
}), }),
_buttonOnMenu(Icon(Icons.notifications), () {}), _buttonOnMenu(Icon(Icons.notifications), () {}),
_buttonOnMenu(Icon(Icons.remove_circle), () { _buttonOnMenu(Icon(Icons.remove_circle), () {
showDialog( showGeneralDialog(
context: context, context: context,
child: barrierDismissible: true,
AnnotatedRegion<SystemUiOverlayStyle>( barrierLabel:
value: SystemUiOverlayStyle( MaterialLocalizations.of(context)
systemNavigationBarColor: .modalBarrierDismissLabel,
Colors.black.withOpacity(0.5), barrierColor: Colors.black54,
statusBarColor: Colors.red, transitionDuration:
), const Duration(milliseconds: 200),
pageBuilder: (BuildContext context,
Animation animaiton,
Animation secondaryAnimation) =>
AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Brightness.light,
systemNavigationBarColor:
Theme.of(context).brightness ==
Brightness.light
? Color.fromRGBO(113, 113, 113, 1)
: Color.fromRGBO(15, 15, 15, 1),
statusBarColor:
Theme.of(context).brightness ==
Brightness.light
? Color.fromRGBO(113, 113, 113, 1)
: Color.fromRGBO(5, 5, 5, 1),
),
child: SafeArea(
child: AlertDialog( child: AlertDialog(
elevation: 2.0, elevation: 1,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0))),
titlePadding: EdgeInsets.only(
top: 20,
left: 20,
right: 200,
bottom: 20),
title: Text('Remove confirm'), title: Text('Remove confirm'),
content: Text( content: Text(
'${widget.podcastLocal.title} will be removed from device.'), 'Are you sure you want to unsubscribe?'),
actions: <Widget>[ actions: <Widget>[
FlatButton( FlatButton(
onPressed: () => onPressed: () =>
Navigator.of(context).pop(), Navigator.of(context).pop(),
child: Text('CANCEL'), child: Text('CANCEL', style: TextStyle(color: Colors.grey[600]),),
), ),
FlatButton( FlatButton(
onPressed: () { onPressed: () {
@ -374,7 +391,9 @@ class _PodcastCardState extends State<PodcastCard> {
) )
], ],
), ),
)); ),
),
);
}), }),
], ],
), ),

View File

@ -99,7 +99,7 @@ class _PodcastListState extends State<PodcastList> {
statusBarColor: Theme.of(context).primaryColor, statusBarColor: Theme.of(context).primaryColor,
), ),
child: SafeArea( child: SafeArea(
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text('Podcasts'), title: Text('Podcasts'),
centerTitle: true, centerTitle: true,
@ -116,8 +116,7 @@ class _PodcastListState extends State<PodcastList> {
SliverPadding( SliverPadding(
padding: const EdgeInsets.all(10.0), padding: const EdgeInsets.all(10.0),
sliver: SliverGrid( sliver: SliverGrid(
gridDelegate: gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: 0.8, childAspectRatio: 0.8,
crossAxisCount: 3, crossAxisCount: 3,
), ),
@ -164,7 +163,9 @@ class _PodcastListState extends State<PodcastList> {
child: Text( child: Text(
snapshot.data[index].title, snapshot.data[index].title,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyText1, style: Theme.of(context)
.textTheme
.bodyText1,
maxLines: 2, maxLines: 2,
), ),
), ),

View File

@ -34,76 +34,85 @@ class _PodcastManageState extends State<PodcastManage> {
statusBarColor: Theme.of(context).primaryColor, statusBarColor: Theme.of(context).primaryColor,
), ),
child: SafeArea( child: SafeArea(
child: Scaffold( child: SafeArea(
appBar: AppBar( child: Scaffold(
centerTitle: true, appBar: AppBar(
title: Text('Groups'), centerTitle: true,
actions: <Widget>[ title: Text('Groups'),
IconButton( actions: <Widget>[
onPressed: () => showDialog( IconButton(
context: context, onPressed: () => showGeneralDialog(
builder: (BuildContext context) => AddGroup()), context: context,
icon: Icon(Icons.add)), barrierDismissible: true,
OrderMenu(), barrierLabel: MaterialLocalizations.of(context)
], .modalBarrierDismissLabel,
), barrierColor: Colors.black54,
body: Consumer<GroupList>(builder: (_, groupList, __) { transitionDuration: const Duration(milliseconds: 200),
bool _isLoading = groupList.isLoading; pageBuilder: (BuildContext context, Animation animaiton,
List<PodcastGroup> _groups = groupList.groups; Animation secondaryAnimation) =>
return _isLoading AddGroup()),
? Center() icon: Icon(Icons.add)),
: DefaultTabController( OrderMenu(),
length: _groups.length, ],
child: Column( ),
mainAxisAlignment: MainAxisAlignment.start, body: Consumer<GroupList>(builder: (_, groupList, __) {
mainAxisSize: MainAxisSize.min, bool _isLoading = groupList.isLoading;
children: <Widget>[ List<PodcastGroup> _groups = groupList.groups;
Container( return _isLoading
height: 50, ? Center()
padding: EdgeInsets.symmetric(horizontal: 10.0), : DefaultTabController(
alignment: Alignment.centerLeft, length: _groups.length,
child: TabBar( child: Column(
// labelColor: Colors.black, mainAxisAlignment: MainAxisAlignment.start,
// unselectedLabelColor: Colors.grey[500], mainAxisSize: MainAxisSize.min,
labelPadding: EdgeInsets.all(5.0), children: <Widget>[
indicator: getIndicator(), Container(
isScrollable: true, height: 50,
tabs: _groups.map<Tab>((group) { padding: EdgeInsets.symmetric(horizontal: 10.0),
return Tab( alignment: Alignment.centerLeft,
child: Container( child: TabBar(
height: 30.0, // labelColor: Colors.black,
padding: // unselectedLabelColor: Colors.grey[500],
EdgeInsets.symmetric(horizontal: 10.0), labelPadding: EdgeInsets.all(5.0),
alignment: Alignment.center, indicator: getIndicator(),
decoration: BoxDecoration( isScrollable: true,
color: Theme.of(context).brightness == tabs: _groups.map<Tab>((group) {
Brightness.light return Tab(
? Theme.of(context).primaryColorDark child: Container(
: Colors.grey[800], height: 30.0,
borderRadius: padding: EdgeInsets.symmetric(
BorderRadius.all(Radius.circular(15)), horizontal: 10.0),
), alignment: Alignment.center,
child: Text( decoration: BoxDecoration(
group.name, color: Theme.of(context).brightness ==
)), Brightness.light
); ? Theme.of(context).primaryColorDark
}).toList(), : Colors.grey[800],
), borderRadius: BorderRadius.all(
), Radius.circular(15)),
Expanded( ),
child: Container( child: Text(
child: TabBarView( group.name,
children: _groups.map<Widget>((group) { )),
return Container( );
key: ObjectKey(group),
child: PodcastGroupList(group: group));
}).toList(), }).toList(),
), ),
), ),
) Expanded(
], child: Container(
)); child: TabBarView(
}), children: _groups.map<Widget>((group) {
return Container(
key: ObjectKey(group),
child: PodcastGroupList(group: group));
}).toList(),
),
),
)
],
));
}),
),
), ),
), ),
); );
@ -114,26 +123,28 @@ class OrderMenu extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return PopupMenuButton( return PopupMenuButton(
elevation: 3, shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10))),
elevation: 2,
tooltip: 'Menu', tooltip: 'Menu',
itemBuilder: (context) => [ itemBuilder: (context) => [
PopupMenuItem( PopupMenuItem(
value: 1, value: 1,
child: Text('All Podcasts'), child: Row(
children: <Widget>[
Icon(Icons.all_out),
Padding(
padding: EdgeInsets.symmetric(horizontal: 5.0),
),
Text('All Podcasts'),
],
),
), ),
PopupMenuItem(
value: 2,
child: Text('New group'),
)
], ],
onSelected: (value) { onSelected: (value) {
if (value == 1) { if (value == 1) {
Navigator.push(context, ScaleRoute(page: PodcastList())); Navigator.push(context, ScaleRoute(page: PodcastList()));
} }
if (value == 2) {
showDialog(
context: context, builder: (BuildContext context) => AddGroup());
}
}, },
); );
} }
@ -167,72 +178,82 @@ class _AddGroupState extends State<AddGroup> {
var groupList = Provider.of<GroupList>(context); var groupList = Provider.of<GroupList>(context);
List list = groupList.groups.map((e) => e.name).toList(); List list = groupList.groups.map((e) => e.name).toList();
return AnnotatedRegion<SystemUiOverlayStyle>( return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle( value: SystemUiOverlayStyle(
systemNavigationBarColor: Colors.black.withOpacity(0.5), statusBarIconBrightness: Brightness.light,
statusBarColor: Colors.red, systemNavigationBarColor:
), Theme.of(context).brightness == Brightness.light
child: AlertDialog( ? Color.fromRGBO(113, 113, 113, 1)
elevation: 1, : Color.fromRGBO(5, 5, 5, 1),
contentPadding: EdgeInsets.symmetric(horizontal: 20), statusBarColor: Theme.of(context).brightness == Brightness.light
titlePadding: EdgeInsets.only(top: 20, left: 20, right: 20, bottom: 20), ? Color.fromRGBO(113, 113, 113, 1)
actionsPadding: EdgeInsets.all(0), : Color.fromRGBO(15, 15, 15, 1),
actions: <Widget>[ ),
FlatButton( child: SafeArea(
onPressed: () => Navigator.of(context).pop(), child: AlertDialog(
child: Text( shape: RoundedRectangleBorder(
'CANCEL', borderRadius: BorderRadius.all(Radius.circular(10))),
style: TextStyle(color: Colors.grey[600]), elevation: 1,
), contentPadding: EdgeInsets.symmetric(horizontal: 20),
), titlePadding:
FlatButton( EdgeInsets.only(top: 20, left: 20, right: 200, bottom: 20),
onPressed: () async { actionsPadding: EdgeInsets.all(0),
if (list.contains(_newGroup)) { actions: <Widget>[
setState(() => _error = 1); FlatButton(
} else { onPressed: () => Navigator.of(context).pop(),
groupList.addGroup(PodcastGroup(_newGroup)); child: Text(
Navigator.of(context).pop(); 'CANCEL',
} style: TextStyle(color: Colors.grey[600]),
},
child: Text('DONE'),
)
],
title: Text('Create new group'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextField(
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(horizontal: 10),
hintText: 'New Group',
hintStyle: TextStyle(fontSize: 18),
filled: true,
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.blue, width: 2.0),
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.blue, width: 2.0),
), ),
), ),
cursorRadius: Radius.circular(2), FlatButton(
autofocus: true, onPressed: () async {
maxLines: 1, if (list.contains(_newGroup)) {
controller: _controller, setState(() => _error = 1);
onChanged: (value) { } else {
_newGroup = value; groupList.addGroup(PodcastGroup(_newGroup));
}, Navigator.of(context).pop();
}
},
child: Text('DONE',style: TextStyle(color: Theme.of(context).accentColor)),
)
],
title: Text('Create new group'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextField(
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(horizontal: 10),
hintText: 'New Group',
hintStyle: TextStyle(fontSize: 18),
filled: true,
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Theme.of(context).accentColor, width: 2.0),
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Theme.of(context).accentColor, width: 2.0),
),
),
cursorRadius: Radius.circular(2),
autofocus: true,
maxLines: 1,
controller: _controller,
onChanged: (value) {
_newGroup = value;
},
),
Container(
alignment: Alignment.centerLeft,
child: (_error == 1)
? Text(
'Group existed',
style: TextStyle(color: Colors.red[400]),
)
: Center(),
),
],
), ),
Container( ),
alignment: Alignment.centerLeft, ));
child: (_error == 1)
? Text(
'Group existed',
style: TextStyle(color: Colors.red[400]),
)
: Center(),
),
],
),
),
);
} }
} }

141
lib/settings/settting.dart Normal file
View File

@ -0,0 +1,141 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:tsacdop/settings/theme.dart';
class Settings extends StatelessWidget {
@override
Widget build(BuildContext context) {
return AnnotatedRegion<SystemUiOverlayStyle>(
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('Settings'),
elevation: 0,
backgroundColor: Theme.of(context).primaryColor,
),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: EdgeInsets.all(10.0),
),
Container(
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 80),
alignment: Alignment.centerLeft,
child: Text('Prefrence',
style: Theme.of(context)
.textTheme
.bodyText1
.copyWith(color: Theme.of(context).accentColor)),
),
ListView(
shrinkWrap: true,
scrollDirection: Axis.vertical,
children: <Widget>[
ListTile(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ThemeSetting())),
contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(Icons.colorize),
title: Text('Appearance'),
subtitle: Text('Colors and themes'),
),
Divider(height: 2),
ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(Icons.network_check),
title: Text('Network'),
subtitle: Text('Download network setting'),
),
Divider(height: 2),
ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(Icons.storage),
title: Text('Cache'),
subtitle: Text('Manage and clear cache'),
),
Divider(height: 2),
ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(Icons.update),
title: Text('Update'),
subtitle: Text('Update in background'),
),
Divider(height: 2),
],
),
],
),
Padding(
padding: EdgeInsets.all(10.0),
),
Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 80),
alignment: Alignment.centerLeft,
child: Text('Info',
style: Theme.of(context)
.textTheme
.bodyText1
.copyWith(color: Theme.of(context).accentColor)),
),
ListView(
shrinkWrap: true,
scrollDirection: Axis.vertical,
children: <Widget>[
ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(Icons.colorize),
title: Text('Changelog'),
subtitle: Text('List of chagnes'),
),
Divider(height: 2),
ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(Icons.network_check),
title: Text('Credit'),
subtitle: Text('Open source libraried in application'),
),
Divider(height: 2),
ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(Icons.storage),
title: Text('Cache'),
subtitle: Text('Manage and clear cache'),
),
Divider(height: 2),
ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(Icons.update),
title: Text('Update'),
subtitle: Text('Update in background'),
),
Divider(height: 2),
],
),
],
),
],
),
),
),
);
}
}

191
lib/settings/theme.dart Normal file
View File

@ -0,0 +1,191 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
import 'package:tsacdop/class/settingstate.dart';
class ThemeSetting extends StatelessWidget {
@override
Widget build(BuildContext context) {
var settings = Provider.of<SettingState>(context);
return AnnotatedRegion<SystemUiOverlayStyle>(
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('Appearance'),
elevation: 0,
backgroundColor: Theme.of(context).primaryColor,
),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: EdgeInsets.all(10.0),
),
Container(
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 80),
alignment: Alignment.centerLeft,
child: Text('Interface',
style: Theme.of(context)
.textTheme
.bodyText1
.copyWith(color: Theme.of(context).accentColor)),
),
ListView(
shrinkWrap: true,
scrollDirection: Axis.vertical,
children: <Widget>[
ListTile(
onTap: () => showGeneralDialog(
context: context,
barrierDismissible: true,
barrierLabel: MaterialLocalizations.of(context)
.modalBarrierDismissLabel,
barrierColor: Colors.black54,
transitionDuration:
const Duration(milliseconds: 200),
pageBuilder: (BuildContext context,
Animation animaiton,
Animation secondaryAnimation) =>
AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Brightness.light,
systemNavigationBarColor:
Theme.of(context).brightness ==
Brightness.light
? Color.fromRGBO(113, 113, 113, 1)
: Color.fromRGBO(15, 15, 15, 1),
statusBarColor:
Theme.of(context).brightness ==
Brightness.light
? Color.fromRGBO(113, 113, 113, 1)
: Color.fromRGBO(5, 5, 5, 1),
),
child: SafeArea(
child: AlertDialog(
titlePadding: EdgeInsets.only(
top: 20,
left: 40,
right: 200,
),
elevation: 1,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0))),
title: Text('Theme'),
content: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment:
MainAxisAlignment.start,
children: <Widget>[
RadioListTile(
title: Container(
padding: EdgeInsets.only(
right: 80),
child:
Text('System default')),
value: ThemeMode.system,
groupValue: settings.theme,
onChanged: (value) {
settings.setTheme = value;
Navigator.of(context).pop();
}),
RadioListTile(
title: Text('Dark mode'),
value: ThemeMode.dark,
groupValue: settings.theme,
onChanged: (value) {
settings.setTheme = value;
Navigator.of(context).pop();
}),
RadioListTile(
title: Text('Light mode'),
value: ThemeMode.light,
groupValue: settings.theme,
onChanged: (value) {
settings.setTheme = value;
Navigator.of(context).pop();
}),
],
),
),
))),
contentPadding: EdgeInsets.symmetric(horizontal: 80.0),
// leading: Icon(Icons.colorize),
title: Text('Theme'),
subtitle: Text('System default'),
),
Divider(height: 2),
ListTile(
onTap: () => showGeneralDialog(
context: context,
barrierDismissible: true,
barrierLabel: MaterialLocalizations.of(context)
.modalBarrierDismissLabel,
barrierColor: Colors.black54,
transitionDuration:
const Duration(milliseconds: 200),
pageBuilder: (BuildContext context,
Animation animaiton,
Animation secondaryAnimation) =>
AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Brightness.light,
systemNavigationBarColor:
Theme.of(context).brightness ==
Brightness.light
? Color.fromRGBO(113, 113, 113, 1)
: Color.fromRGBO(15, 15, 15, 1),
statusBarColor:
Theme.of(context).brightness ==
Brightness.light
? Color.fromRGBO(113, 113, 113, 1)
: Color.fromRGBO(5, 5, 5, 1),
),
child: SafeArea(
child: AlertDialog(
elevation: 1,
titlePadding: EdgeInsets.only(
top: 20,
left: 40,
right: 200,
bottom: 20),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0))),
title: Text('Choose a color'),
content: SingleChildScrollView(
child: MaterialPicker(
onColorChanged: (value) {
settings.setAccentColor = value;
},
pickerColor: Colors.blue,
),
),
)))),
contentPadding: EdgeInsets.symmetric(horizontal: 80.0),
title: Text('Accent color'),
subtitle: Text('Include the overlay color'),
),
Divider(height: 2),
],
),
],
),
],
),
),
),
);
}
}

29
lib/util/colorize.dart Normal file
View File

@ -0,0 +1,29 @@
import 'dart:convert';
import 'package:flutter/material.dart';
extension Colorize on String {
Color colorizedark() {
Color _c;
var color = json.decode(this);
if (color[0] > 200 && color[1] > 200 && color[2] > 200) {
_c =
Color.fromRGBO((255 - color[0]), 255 - color[1], 255 - color[2], 1.0);
} else {
_c = Color.fromRGBO(color[0], color[1] > 200 ? 190 : color[1],
color[2] > 200 ? 190 : color[2], 1);
}
return _c;
}
Color colorizeLight() {
Color _c;
var color = json.decode(this);
if (color[0] < 50 && color[1] < 50 && color[2] < 50) {
_c =
Color.fromRGBO((255 - color[0]), 255 - color[1], 255 - color[2], 1.0);
} else {
_c = Color.fromRGBO(color[0], color[1], color[2], 1.0);
}
return _c;
}
}

View File

@ -1,4 +1,3 @@
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:isolate'; import 'dart:isolate';
import 'dart:ui'; import 'dart:ui';
@ -9,6 +8,7 @@ import 'package:google_fonts/google_fonts.dart';
import 'package:tsacdop/class/episodebrief.dart'; import 'package:tsacdop/class/episodebrief.dart';
import 'package:tsacdop/episodes/episodedetail.dart'; import 'package:tsacdop/episodes/episodedetail.dart';
import 'package:tsacdop/util/pageroute.dart'; import 'package:tsacdop/util/pageroute.dart';
import 'package:tsacdop/util/colorize.dart';
class EpisodeGrid extends StatelessWidget { class EpisodeGrid extends StatelessWidget {
final List<EpisodeBrief> podcast; final List<EpisodeBrief> podcast;
@ -28,8 +28,7 @@ class EpisodeGrid extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
double _width = MediaQuery.of(context).size.width; double _width = MediaQuery.of(context).size.width;
return return CustomScrollView(
CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
primary: false, primary: false,
slivers: <Widget>[ slivers: <Widget>[
@ -44,19 +43,10 @@ class EpisodeGrid extends StatelessWidget {
), ),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) { (BuildContext context, int index) {
Color _c; Color _c =
var color = json.decode(podcast[index].primaryColor); (Theme.of(context).brightness == Brightness.light)
if (Theme.of(context).brightness == Brightness.light) { ? podcast[index].primaryColor.colorizedark()
(color[0] > 200 && color[1] > 200 && color[2] > 200) : podcast[index].primaryColor.colorizeLight();
? _c = Color.fromRGBO(
(255 - color[0]), 255 - color[1], 255 - color[2], 1.0)
: _c = Color.fromRGBO(color[0], color[1], color[2], 1.0);
} else {
(color[0] < 50 && color[1] < 50 && color[2] < 50)
? _c = Color.fromRGBO(
(255 - color[0]), 255 - color[1], 255 - color[2], 1.0)
: _c = Color.fromRGBO(color[0], color[1], color[2], 1.0);
}
return Material( return Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
@ -101,15 +91,11 @@ class EpisodeGrid extends StatelessWidget {
Hero( Hero(
tag: podcast[index].enclosureUrl + heroTag, tag: podcast[index].enclosureUrl + heroTag,
child: Container( child: Container(
child: ClipRRect( height: _width / 16,
borderRadius: BorderRadius.all( width: _width / 16,
Radius.circular(_width / 32)), child: CircleAvatar(
child: Container( backgroundImage: FileImage(
height: _width / 16, File("${podcast[index].imagePath}")),
width: _width / 16,
child: Image.file(File(
"${podcast[index].imagePath}")),
),
), ),
), ),
), ),

View File

@ -118,6 +118,13 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.1.3" version: "1.1.3"
flutter_colorpicker:
dependency: "direct dev"
description:
name: flutter_colorpicker
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.3.2"
flutter_downloader: flutter_downloader:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -149,6 +156,13 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "3.1.3" version: "3.1.3"
font_awesome_flutter:
dependency: "direct dev"
description:
name: font_awesome_flutter
url: "https://pub.flutter-io.cn"
source: hosted
version: "8.7.0"
google_fonts: google_fonts:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -448,6 +462,13 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "2.0.8" version: "2.0.8"
workmanager:
dependency: "direct dev"
description:
name: workmanager
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.0"
xml: xml:
dependency: "direct dev" dependency: "direct dev"
description: description:

View File

@ -14,7 +14,7 @@ description: An easy-use podacasts player.
version: 0.1.1 version: 0.1.1
environment: environment:
sdk: ">=2.3.0 <3.0.0" sdk: ">=2.6.0 <3.0.0"
dependencies: dependencies:
flutter: flutter:
@ -27,14 +27,14 @@ dependencies:
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
json_annotation: any json_annotation: ^3.0.1
sqflite: any sqflite: ^1.2.1
flutter_html: ^0.11.1 flutter_html: ^0.11.1
path_provider: any path_provider: ^1.6.1
color_thief_flutter: ^1.0.1 color_thief_flutter: ^1.0.1
provider: ^4.0.1 provider: ^4.0.1
google_fonts: ^0.3.2 google_fonts: ^0.3.2
dio: ^3.0.8 dio: ^3.0.9
file_picker: ^1.2.0 file_picker: ^1.2.0
xml: ^3.5.0 xml: ^3.5.0
marquee: ^1.3.1 marquee: ^1.3.1
@ -49,7 +49,9 @@ dev_dependencies:
uuid: ^2.0.4 uuid: ^2.0.4
tuple: ^1.0.3 tuple: ^1.0.3
cached_network_image: ^2.0.0 cached_network_image: ^2.0.0
workmanager: ^0.2.0
font_awesome_flutter: ^8.7.0
flutter_colorpicker: ^0.3.2