1
0
mirror of https://github.com/stonega/tsacdop synced 2025-03-10 08:10:09 +01:00

modified: lib/class/audiostate.dart

modified:   lib/class/settingstate.dart
	modified:   lib/episodes/episodedetail.dart
	modified:   lib/home/appbar/addpodcast.dart
	deleted:    lib/home/audio_player.dart
	modified:   lib/home/audiopanel.dart
	modified:   lib/home/home.dart
	modified:   lib/home/homescroll.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:   pubspec.lock
	modified:   pubspec.yaml

	lib/home/audioplayer.dart
This commit is contained in:
stonegate 2020-02-25 17:57:12 +08:00
parent d4ebdf769d
commit 547aef80e9
15 changed files with 1270 additions and 1438 deletions

View File

@ -1,21 +1,432 @@
import 'dart:typed_data';
import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:tsacdop/class/episodebrief.dart'; import 'package:tsacdop/class/episodebrief.dart';
import 'package:audiofileplayer/audiofileplayer.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:logging/logging.dart';
import 'package:audiofileplayer/audio_system.dart';
import 'package:tsacdop/local_storage/key_value_storage.dart';
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
enum AudioState {load, play, pause, complete, error} enum AudioState { load, play, pause, complete, error, stop }
class AudioPlay with ChangeNotifier { class PlayHistory {
String title;
String url;
double seconds;
double seekValue;
PlayHistory(this.title, this.url, this.seconds, this.seekValue);
}
EpisodeBrief _episode; class Playlist {
EpisodeBrief get episode => _episode; String name;
set episodeLoad(EpisodeBrief episdoe){ DBHelper dbHelper = DBHelper();
_episode = episdoe; List<String> urls;
notifyListeners(); List<EpisodeBrief> _playlist;
List<EpisodeBrief> get playlist => _playlist;
KeyValueStorage storage = KeyValueStorage('playlist');
Playlist(this.name, {List<String> urls}) : urls = urls ?? [];
getPlaylist() async{
List<String> _urls = await storage.getPlayList();
if (_urls.length == 0) {
_playlist = [];
} else {
_playlist = [];
await Future.forEach(_urls, (url) async{
EpisodeBrief episode = await dbHelper.getRssItemWithUrl(url);
print(episode.title);
_playlist.add(episode);
});
}
print(_playlist.length);
} }
AudioState _audioState; savePlaylist() async {
AudioState get audioState => _audioState; urls = [];
set audioState(AudioState state){ urls.addAll(_playlist.map((e) => e.enclosureUrl));
_audioState = state; print(urls);
notifyListeners(); await storage.savePlaylist(urls);
}
addToPlayList(EpisodeBrief episodeBrief) async {
_playlist.add(episodeBrief);
await savePlaylist();
}
delFromPlaylist(EpisodeBrief episodeBrief) {
_playlist.remove(episodeBrief);
savePlaylist();
} }
} }
class AudioPlayer extends ChangeNotifier {
static const String replay10ButtonId = 'replay10ButtonId';
static const String newReleasesButtonId = 'newReleasesButtonId';
static const String likeButtonId = 'likeButtonId';
static const String pausenowButtonId = 'pausenowButtonId';
static const String forwardButtonId = 'forwardButtonId';
DBHelper dbHelper = DBHelper();
EpisodeBrief _episode;
Playlist _queue = Playlist('now');
bool _playerRunning = false;
Audio _backgroundAudio;
bool _backgroundAudioPlaying = false;
double _backgroundAudioDurationSeconds = 0;
double _backgroundAudioPositionSeconds = 0;
bool _remoteAudioLoading = false;
String _remoteErrorMessage;
double _seekSliderValue = 0.0;
final Logger _logger = Logger('audiofileplayer');
bool get backgroundAudioPlaying => _backgroundAudioPlaying;
bool get remoteAudioLoading => _remoteAudioLoading;
double get backgroundAudioDuration => _backgroundAudioDurationSeconds;
double get backgroundAudioPosition => _backgroundAudioPositionSeconds;
double get seekSliderValue => _seekSliderValue;
String get remoteErrorMessage => _remoteErrorMessage;
bool get playerRunning => _playerRunning;
Playlist get queue => _queue;
AudioState _audioState = AudioState.stop;
AudioState get audioState => _audioState;
EpisodeBrief get episode => _episode;
episodeLoad(EpisodeBrief episode) async {
AudioSystem.instance.addMediaEventListener(_mediaEventListener);
_episode = episode;
await _queue.getPlaylist();
if (_queue.playlist.contains(_episode)) {
_queue.playlist.remove(_episode);
_queue.playlist.insert(0, _episode);
} else {
_queue.playlist.insert(0, _episode);
}
await _queue.savePlaylist();
await _play(_episode);
_playerRunning = true;
_backgroundAudioPlaying = true;
notifyListeners();
}
playNext() async {
PlayHistory history = PlayHistory(_episode.title, _episode.enclosureUrl,
backgroundAudioDuration, seekSliderValue);
await dbHelper.saveHistory(history);
await _queue.delFromPlaylist(_episode);
if (_queue.playlist.length > 0) {
_episode = _queue.playlist.first;
_play(_episode);
_backgroundAudioPlaying = true;
notifyListeners();
} else {
_backgroundAudioPlaying = false;
_remoteAudioLoading = false;
notifyListeners();
}
}
addToPlaylist(EpisodeBrief episode) async {
_queue.addToPlayList(episode);
await _queue.getPlaylist();
notifyListeners();
}
pauseAduio() async {
_pauseBackgroundAudio();
_audioState = AudioState.pause;
notifyListeners();
PlayHistory history = PlayHistory(_episode.title, _episode.enclosureUrl,
backgroundAudioDuration, seekSliderValue);
await dbHelper.saveHistory(history);
await _queue.delFromPlaylist(_episode);
}
resumeAudio() {
_resumeBackgroundAudio();
_audioState = AudioState.play;
notifyListeners();
}
forwardAudio(double s) {
_forwardBackgroundAudio(s);
notifyListeners();
}
sliderSeek(double val) {
_seekSliderValue = val;
notifyListeners();
final double positionSeconds = val * _backgroundAudioDurationSeconds;
_backgroundAudio.seek(positionSeconds);
AudioSystem.instance.setPlaybackState(true, positionSeconds);
}
disopse() {
AudioSystem.instance.removeMediaEventListener(_mediaEventListener);
_backgroundAudio?.dispose();
}
_play(EpisodeBrief episodeBrief) async {
String url = _queue.playlist.first.enclosureUrl;
_getFile(url).then((result) {
result == 'NotDownload'
? _initbackgroundAudioPlayer(url)
: _initbackgroundAudioPlayerLocal(result);
});
}
Future<String> _getFile(String url) async {
final task = await FlutterDownloader.loadTasksWithRawQuery(
query: "SELECT * FROM task WHERE url = '$url' AND status = 3");
if (task.length != 0) {
String _filePath = task.first.savedDir + '/' + task.first.filename;
return _filePath;
}
return 'NotDownload';
}
ByteData _getAudio(String path) {
File audioFile = File(path);
Uint8List audio = audioFile.readAsBytesSync();
return ByteData.view(audio.buffer);
}
void _initbackgroundAudioPlayerLocal(String path) {
_remoteErrorMessage = null;
_remoteAudioLoading = true;
ByteData audio = _getAudio(path);
if (_backgroundAudioPlaying == true) {
_stopBackgroundAudio();
}
_backgroundAudioPositionSeconds = 0;
_setNotification(false);
_backgroundAudio =
Audio.loadFromByteData(audio, onDuration: (double durationSeconds) {
_backgroundAudioDurationSeconds = durationSeconds;
_remoteAudioLoading = false;
_backgroundAudioPlaying = true;
_setNotification(true);
notifyListeners();
}, onPosition: (double positionSeconds) {
if (_backgroundAudioPositionSeconds < _backgroundAudioDurationSeconds) {
_seekSliderValue =
_backgroundAudioPositionSeconds / _backgroundAudioDurationSeconds;
_backgroundAudioPositionSeconds = positionSeconds;
notifyListeners();
} else {
_seekSliderValue = 1;
_backgroundAudioPositionSeconds = _backgroundAudioDurationSeconds;
notifyListeners();
}
}, onError: (String message) {
_remoteErrorMessage = message;
_backgroundAudio.dispose();
_backgroundAudio = null;
_backgroundAudioPlaying = false;
_remoteAudioLoading = false;
}, onComplete: () {
playNext();
}, looping: false, playInBackground: true)
..play();
}
void _initbackgroundAudioPlayer(String url) {
_remoteErrorMessage = null;
_remoteAudioLoading = true;
notifyListeners();
if (_backgroundAudioPlaying == true) {
_stopBackgroundAudio();
}
_backgroundAudioPositionSeconds = 0;
_setNotification(false);
_backgroundAudio =
Audio.loadFromRemoteUrl(url, onDuration: (double durationSeconds) {
_backgroundAudioDurationSeconds = durationSeconds;
_remoteAudioLoading = false;
_backgroundAudioPlaying = true;
_setNotification(true);
notifyListeners();
}, onPosition: (double positionSeconds) {
if (_backgroundAudioPositionSeconds < _backgroundAudioDurationSeconds) {
_seekSliderValue =
_backgroundAudioPositionSeconds / _backgroundAudioDurationSeconds;
_backgroundAudioPositionSeconds = positionSeconds;
notifyListeners();
} else {
_seekSliderValue = 1;
_backgroundAudioPositionSeconds = _backgroundAudioDurationSeconds;
notifyListeners();
}
}, onError: (String message) {
_remoteErrorMessage = message;
_backgroundAudio.dispose();
_backgroundAudio = null;
_backgroundAudioPlaying = false;
_remoteAudioLoading = false;
}, onComplete: () {
playNext();
}, looping: false, playInBackground: true)
..resume();
}
void _mediaEventListener(MediaEvent mediaEvent) {
_logger.info('App received media event of type: ${mediaEvent.type}');
final MediaActionType type = mediaEvent.type;
if (type == MediaActionType.play) {
_resumeBackgroundAudio();
} else if (type == MediaActionType.pause) {
_pauseBackgroundAudio();
} else if (type == MediaActionType.playPause) {
_backgroundAudioPlaying
? _pauseBackgroundAudio()
: _resumeBackgroundAudio();
} else if (type == MediaActionType.stop) {
_stopBackgroundAudio();
} else if (type == MediaActionType.seekTo) {
_backgroundAudio.seek(mediaEvent.seekToPositionSeconds);
AudioSystem.instance
.setPlaybackState(true, mediaEvent.seekToPositionSeconds);
} else if (type == MediaActionType.skipForward) {
final double skipIntervalSeconds = mediaEvent.skipIntervalSeconds;
_forwardBackgroundAudio(skipIntervalSeconds);
_logger.info(
'Skip-forward event had skipIntervalSeconds $skipIntervalSeconds.');
} else if (type == MediaActionType.skipBackward) {
final double skipIntervalSeconds = mediaEvent.skipIntervalSeconds;
_forwardBackgroundAudio(skipIntervalSeconds);
_logger.info(
'Skip-backward event had skipIntervalSeconds $skipIntervalSeconds.');
} else if (type == MediaActionType.custom) {
if (mediaEvent.customEventId == replay10ButtonId) {
_forwardBackgroundAudio(-10.0);
} else if (mediaEvent.customEventId == likeButtonId) {
_resumeBackgroundAudio();
} else if (mediaEvent.customEventId == forwardButtonId) {
_forwardBackgroundAudio(30.0);
} else if (mediaEvent.customEventId == pausenowButtonId) {
_pauseBackgroundAudio();
}
}
}
Future<void> _setNotification(bool b) async {
final Uint8List imageBytes =
File('${_episode.imagePath}').readAsBytesSync();
AudioSystem.instance.setMetadata(AudioMetadata(
title: episode.title,
artist: episode.feedTitle,
album: episode.feedTitle,
genre: "Podcast",
durationSeconds: _backgroundAudioDurationSeconds,
artBytes: imageBytes));
AudioSystem.instance.setPlaybackState(b, _backgroundAudioPositionSeconds);
AudioSystem.instance.setAndroidNotificationButtons(<dynamic>[
AndroidMediaButtonType.pause,
_forwardButton,
AndroidMediaButtonType.stop,
], androidCompactIndices: <int>[
0,
1
]);
AudioSystem.instance.setSupportedMediaActions(<MediaActionType>{
MediaActionType.playPause,
MediaActionType.pause,
MediaActionType.next,
MediaActionType.previous,
MediaActionType.skipForward,
MediaActionType.skipBackward,
MediaActionType.seekTo,
MediaActionType.custom,
}, skipIntervalSeconds: 30);
}
Future<void> _resumeBackgroundAudio() async {
_backgroundAudio.resume();
_backgroundAudioPlaying = true;
final Uint8List imageBytes =
File('${_episode.imagePath}').readAsBytesSync();
AudioSystem.instance.setMetadata(AudioMetadata(
title: _episode.title,
artist: _episode.feedTitle,
album: _episode.feedTitle,
genre: "Podcast",
durationSeconds: _backgroundAudioDurationSeconds,
artBytes: imageBytes));
AudioSystem.instance
.setPlaybackState(true, _backgroundAudioPositionSeconds);
AudioSystem.instance.setAndroidNotificationButtons(<dynamic>[
AndroidMediaButtonType.pause,
_forwardButton,
AndroidMediaButtonType.stop,
], androidCompactIndices: <int>[
0,
1
]);
AudioSystem.instance.setSupportedMediaActions(<MediaActionType>{
MediaActionType.playPause,
MediaActionType.pause,
MediaActionType.next,
MediaActionType.previous,
MediaActionType.skipForward,
MediaActionType.skipBackward,
MediaActionType.seekTo,
MediaActionType.custom,
}, skipIntervalSeconds: 30);
}
void _pauseBackgroundAudio() {
_backgroundAudio.pause();
_backgroundAudioPlaying = false;
AudioSystem.instance
.setPlaybackState(false, _backgroundAudioPositionSeconds);
AudioSystem.instance.setAndroidNotificationButtons(<dynamic>[
AndroidMediaButtonType.play,
_forwardButton,
AndroidMediaButtonType.stop,
], androidCompactIndices: <int>[
0,
1,
]);
AudioSystem.instance.setSupportedMediaActions(<MediaActionType>{
MediaActionType.playPause,
MediaActionType.play,
MediaActionType.next,
MediaActionType.previous,
});
}
void _stopBackgroundAudio() {
_backgroundAudio..pause();
_backgroundAudio..dispose();
_backgroundAudioPlaying = false;
AudioSystem.instance.stopBackgroundDisplay();
}
void _forwardBackgroundAudio(double seconds) {
final double forwardposition = _backgroundAudioPositionSeconds + seconds;
_backgroundAudio.seek(forwardposition);
//AudioSystem.instance.setPlaybackState(true, _backgroundAudioDurationSeconds);
}
final _pauseButton = AndroidCustomMediaButton(
'pausenow', pausenowButtonId, 'ic_stat_pause_circle_filled');
final _replay10Button = AndroidCustomMediaButton(
'replay10', replay10ButtonId, 'ic_stat_replay_10');
final _forwardButton = AndroidCustomMediaButton(
'forward', forwardButtonId, 'ic_stat_forward_30');
final _playnowButton = AndroidCustomMediaButton(
'playnow', likeButtonId, 'ic_stat_play_circle_filled');
}

View File

@ -8,10 +8,10 @@ class SettingState extends ChangeNotifier {
int _theme; int _theme;
int get theme => _theme; int get theme => _theme;
void setTheme(int theme) { setTheme(int theme) async{
_theme = theme; _theme = theme;
notifyListeners(); notifyListeners();
_saveTheme(theme); await _saveTheme(theme);
} }
@override @override

View File

@ -5,9 +5,11 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_html/flutter_html.dart';
import 'package:tsacdop/home/audioplayer.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:tuple/tuple.dart';
import 'package:tsacdop/class/audiostate.dart'; import 'package:tsacdop/class/audiostate.dart';
import 'package:tsacdop/class/episodebrief.dart'; import 'package:tsacdop/class/episodebrief.dart';
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart'; import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
@ -66,116 +68,137 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
title: Text(widget.episodeItem.feedTitle), title: Text(widget.episodeItem.feedTitle),
centerTitle: true, centerTitle: true,
), ),
body: Container( body: Stack(
color: Theme.of(context).primaryColor, children: <Widget>[
padding: EdgeInsets.all(10.0), Container(
child: Column( color: Theme.of(context).primaryColor,
mainAxisAlignment: MainAxisAlignment.start, padding: EdgeInsets.all(10.0),
mainAxisSize: MainAxisSize.min, child: Column(
children: <Widget>[ mainAxisAlignment: MainAxisAlignment.start,
Container( mainAxisSize: MainAxisSize.min,
child: Column( children: <Widget>[
mainAxisAlignment: MainAxisAlignment.start, Container(
crossAxisAlignment: CrossAxisAlignment.start, child: Column(
children: <Widget>[ mainAxisAlignment: MainAxisAlignment.start,
Container( crossAxisAlignment: CrossAxisAlignment.start,
padding: EdgeInsets.symmetric(horizontal: 12.0), children: <Widget>[
alignment: Alignment.topLeft, Container(
child: Text( padding: EdgeInsets.symmetric(horizontal: 12.0),
widget.episodeItem.title, alignment: Alignment.topLeft,
style: Theme.of(context).textTheme.headline5, child: Text(
), widget.episodeItem.title,
), style: Theme.of(context).textTheme.headline5,
Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.symmetric(horizontal: 12.0),
height: 30.0,
child: Text(
'Published ' +
DateFormat.yMMMd().format(
DateTime.fromMillisecondsSinceEpoch(
widget.episodeItem.pubDate)),
style: TextStyle(color: Colors.blue[500])),
),
Container(
padding: EdgeInsets.all(12.0),
height: 50.0,
child: Row(
children: <Widget>[
(widget.episodeItem.explicit == 1)
? Container(
decoration: BoxDecoration(
color: Colors.red[800],
shape: BoxShape.circle),
height: 25.0,
width: 25.0,
margin: EdgeInsets.only(right: 10.0),
alignment: Alignment.center,
child: Text('E',
style: TextStyle(color: Colors.white)))
: Center(),
Container(
decoration: BoxDecoration(
color: Colors.cyan[300],
borderRadius:
BorderRadius.all(Radius.circular(15.0))),
height: 30.0,
margin: EdgeInsets.only(right: 10.0),
padding: EdgeInsets.symmetric(horizontal: 10.0),
alignment: Alignment.center,
child: Text(
(widget.episodeItem.duration).toString() +
'mins',
style: textstyle),
), ),
Container( ),
decoration: BoxDecoration( Container(
color: Colors.lightBlue[300], alignment: Alignment.centerLeft,
borderRadius: padding: EdgeInsets.symmetric(horizontal: 12.0),
BorderRadius.all(Radius.circular(15.0))), height: 30.0,
height: 30.0, child: Text(
margin: EdgeInsets.only(right: 10.0), 'Published ' +
padding: EdgeInsets.symmetric(horizontal: 10.0), DateFormat.yMMMd().format(
alignment: Alignment.center, DateTime.fromMillisecondsSinceEpoch(
child: Text( widget.episodeItem.pubDate)),
((widget.episodeItem.enclosureLength) ~/ style: TextStyle(color: Colors.blue[500])),
1000000) ),
.toString() + Container(
'MB', padding: EdgeInsets.all(12.0),
style: textstyle), height: 50.0,
child: Row(
children: <Widget>[
(widget.episodeItem.explicit == 1)
? Container(
decoration: BoxDecoration(
color: Colors.red[800],
shape: BoxShape.circle),
height: 25.0,
width: 25.0,
margin: EdgeInsets.only(right: 10.0),
alignment: Alignment.center,
child: Text('E',
style:
TextStyle(color: Colors.white)))
: Center(),
Container(
decoration: BoxDecoration(
color: Colors.cyan[300],
borderRadius: BorderRadius.all(
Radius.circular(15.0))),
height: 30.0,
margin: EdgeInsets.only(right: 10.0),
padding:
EdgeInsets.symmetric(horizontal: 10.0),
alignment: Alignment.center,
child: Text(
(widget.episodeItem.duration).toString() +
'mins',
style: textstyle),
),
Container(
decoration: BoxDecoration(
color: Colors.lightBlue[300],
borderRadius: BorderRadius.all(
Radius.circular(15.0))),
height: 30.0,
margin: EdgeInsets.only(right: 10.0),
padding:
EdgeInsets.symmetric(horizontal: 10.0),
alignment: Alignment.center,
child: Text(
((widget.episodeItem.enclosureLength) ~/
1000000)
.toString() +
'MB',
style: textstyle),
),
],
), ),
], ),
), ],
), ),
],
),
),
Expanded(
child: Container(
padding: EdgeInsets.only(left: 12.0, right: 12.0, top: 5.0),
child: SingleChildScrollView(
child: _loaddes
? (widget.episodeItem.description.contains('<'))
? Html(
data: widget.episodeItem.description,
onLinkTap: (url) {
_launchUrl(url);
},
useRichText: true,
)
: Container(
alignment: Alignment.topLeft,
child: Text(widget.episodeItem.description))
: Center(),
), ),
), Expanded(
child: Container(
padding:
EdgeInsets.only(left: 12.0, right: 12.0, top: 5.0),
child: SingleChildScrollView(
child: _loaddes
? (widget.episodeItem.description.contains('<'))
? Html(
data: widget.episodeItem.description,
onLinkTap: (url) {
_launchUrl(url);
},
useRichText: true,
)
: Container(
alignment: Alignment.topLeft,
child:
Text(widget.episodeItem.description))
: Center(),
),
),
),
],
), ),
MenuBar( ),
episodeItem: widget.episodeItem, Selector<AudioPlayer, bool>(
heroTag: widget.heroTag, selector: (_, audio) => audio.playerRunning,
), builder: (_, data, __) {
], return Container(
), alignment: Alignment.bottomCenter,
padding: EdgeInsets.only(
left: 5.0,
right: 5.0,
bottom: data == true ? 80.0 : 10.0),
child: MenuBar(
episodeItem: widget.episodeItem,
heroTag: widget.heroTag,
),
);
}),
Container(child: PlayerWidget()),
],
), ),
), ),
), ),
@ -233,39 +256,37 @@ class _MenuBarState extends State<MenuBar> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final audioplay = Provider.of<AudioPlay>(context); var audio = Provider.of<AudioPlayer>(context, listen: false);
return Container( return Container(
height: 50.0, height: 50.0,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor, color: Theme.of(context).scaffoldBackgroundColor,
border: Border.all(
color: Theme.of(context).brightness == Brightness.light
? Colors.grey[200]
: Theme.of(context).scaffoldBackgroundColor,
),
borderRadius: BorderRadius.all(Radius.circular(10.0)), borderRadius: BorderRadius.all(Radius.circular(10.0)),
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
(widget.episodeItem.title == audioplay.episode?.title && Hero(
audioplay.audioState == AudioState.play) tag: widget.episodeItem.enclosureUrl + widget.heroTag,
? ImageRotate( child: Container(
title: widget.episodeItem.feedTitle, padding: EdgeInsets.symmetric(horizontal:10.0),
path: widget.episodeItem.imagePath, child: ClipRRect(
) borderRadius: BorderRadius.all(Radius.circular(15.0)),
: Hero( child: Container(
tag: widget.episodeItem.enclosureUrl + widget.heroTag, height: 30.0,
child: Container( width: 30.0,
padding: EdgeInsets.all(10.0), color: Theme.of(context).scaffoldBackgroundColor,
child: ClipRRect( child: Image.file(File("${widget.episodeItem.imagePath}")),
borderRadius: BorderRadius.all(Radius.circular(15.0)),
child: Container(
height: 30.0,
width: 30.0,
color: Theme.of(context).scaffoldBackgroundColor,
child:
Image.file(File("${widget.episodeItem.imagePath}")),
),
),
),
), ),
),
),
),
(_like == 0 && !_liked) (_like == 0 && !_liked)
? _buttonOnMenu( ? _buttonOnMenu(
Icon( Icon(
@ -273,72 +294,86 @@ class _MenuBarState extends State<MenuBar> {
color: Colors.grey[700], color: Colors.grey[700],
), ),
() => saveLiked(widget.episodeItem.enclosureUrl)) () => saveLiked(widget.episodeItem.enclosureUrl))
: Stack( : (_like == 1 && !_liked)
alignment: Alignment.center, ? _buttonOnMenu(
children: <Widget>[ Icon(
LoveOpen(), Icons.favorite,
_buttonOnMenu( color: Colors.red,
Icon( ),
Icons.favorite, () => setUnliked(widget.episodeItem.enclosureUrl))
color: Colors.red, : Stack(
), alignment: Alignment.center,
() => setUnliked(widget.episodeItem.enclosureUrl)), children: <Widget>[
], LoveOpen(),
), _buttonOnMenu(
Icon(
Icons.favorite,
color: Colors.red,
),
() => setUnliked(widget.episodeItem.enclosureUrl)),
],
),
DownloadButton(episodeBrief: widget.episodeItem), DownloadButton(episodeBrief: widget.episodeItem),
_buttonOnMenu(Icon(Icons.playlist_add, color: Colors.grey[700]), () { _buttonOnMenu(Icon(Icons.playlist_add, color: Colors.grey[700]), () {
Fluttertoast.showToast( Fluttertoast.showToast(
msg: 'Not support yet', msg: 'Added to playlist',
gravity: ToastGravity.BOTTOM, gravity: ToastGravity.BOTTOM,
); );
/*TODO*/ audio.addToPlaylist(widget.episodeItem);
}), }),
Spacer(), Spacer(),
(widget.episodeItem.title != audioplay.episode?.title) // Text(audio.audioState.toString()),
? Material( Selector<AudioPlayer, Tuple2<EpisodeBrief, bool>>(
color: Colors.transparent, selector: (_, audio) =>
child: InkWell( Tuple2(audio.episode, audio.backgroundAudioPlaying),
borderRadius: BorderRadius.only( builder: (_, data, __) {
topRight: Radius.circular(5.0), return (widget.episodeItem.title != data.item1?.title)
bottomRight: Radius.circular(5.0)), ? Material(
onTap: () { color: Colors.transparent,
audioplay.episodeLoad = widget.episodeItem; child: InkWell(
}, borderRadius: BorderRadius.only(
child: Container( topRight: Radius.circular(5.0),
alignment: Alignment.center, bottomRight: Radius.circular(5.0)),
height: 50.0, onTap: () {
padding: EdgeInsets.symmetric(horizontal: 20.0), audio.episodeLoad(widget.episodeItem);
child: Row( },
children: <Widget>[ child: Container(
Text('Play Now', alignment: Alignment.center,
style: TextStyle( height: 50.0,
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: Row(
children: <Widget>[
Text('Play Now',
style: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 15,
fontWeight: FontWeight.bold,
)),
Icon(
Icons.play_arrow,
color: Theme.of(context).accentColor, color: Theme.of(context).accentColor,
fontSize: 15, ),
fontWeight: FontWeight.bold, ],
)),
Icon(
Icons.play_arrow,
color: Theme.of(context).accentColor,
), ),
], ),
), ),
), )
), : (widget.episodeItem.title == data.item1?.title &&
) data.item2 == true)
: (widget.episodeItem.title == audioplay.episode?.title && ? Container(
audioplay.audioState == AudioState.play) padding: EdgeInsets.only(right: 30),
? Container( child: SizedBox(
padding: EdgeInsets.only(right: 30), width: 20, height: 15, child: WaveLoader()))
child: : Container(
SizedBox(width: 20, height: 15, child: WaveLoader())) padding: EdgeInsets.only(right: 30),
: Container( child: SizedBox(
padding: EdgeInsets.only(right: 30), width: 20,
child: SizedBox( height: 15,
width: 20, child: LineLoader(),
height: 15, ),
child: LineLoader(), );
), },
), ),
], ],
), ),
); );

View File

@ -303,6 +303,11 @@ class _SearchResultState extends State<SearchResult> {
} }
return Container( return Container(
decoration: BoxDecoration(
border: Border(
bottom: Divider.createBorderSide(context),
),
),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -363,18 +368,19 @@ class _SearchResultState extends State<SearchResult> {
? Container( ? Container(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).primaryColorDark, color: Theme.of(context).accentColor,
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topRight: Radius.circular(15.0), topRight: Radius.circular(15.0),
bottomLeft: Radius.circular(15.0), bottomLeft: Radius.circular(15.0),
bottomRight: Radius.circular(15.0), bottomRight: Radius.circular(15.0),
)), )),
margin: EdgeInsets.only(left: 70, right: 50), margin: EdgeInsets.only(left: 70, right: 50, bottom: 10.0),
padding: EdgeInsets.all(10.0), padding: EdgeInsets.all(15.0),
child: Text( child: Text(
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),
), ),
) )
: Center(), : Center(),

View File

@ -1,718 +0,0 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:provider/provider.dart';
import 'package:audiofileplayer/audiofileplayer.dart';
import 'package:audiofileplayer/audio_system.dart';
import 'package:logging/logging.dart';
import 'package:flutter/material.dart';
import 'package:marquee/marquee.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:tsacdop/class/audiostate.dart';
import 'package:tsacdop/class/episodebrief.dart';
import 'package:tsacdop/episodes/episodedetail.dart';
import 'package:tsacdop/home/audiopanel.dart';
import 'package:tsacdop/util/pageroute.dart';
final Logger _logger = Logger('audiofileplayer');
class PlayerWidget extends StatefulWidget {
@override
_PlayerWidgetState createState() => _PlayerWidgetState();
}
class _PlayerWidgetState extends State<PlayerWidget> {
static const String replay10ButtonId = 'replay10ButtonId';
static const String newReleasesButtonId = 'newReleasesButtonId';
static const String likeButtonId = 'likeButtonId';
static const String pausenowButtonId = 'pausenowButtonId';
static const String forwardButtonId = 'forwardButtonId';
Audio _backgroundAudio;
bool _backgroundAudioPlaying;
double _backgroundAudioDurationSeconds;
double _backgroundAudioPositionSeconds = 0;
bool _remoteAudioLoading;
String _remoteErrorMessage;
double _seekSliderValue = 0.0;
String url;
bool _isLoading;
Color _c;
EpisodeBrief episode;
@override
void initState() {
super.initState();
AudioSystem.instance.addMediaEventListener(_mediaEventListener);
_isLoading = false;
_remoteAudioLoading = true;
}
//open episoddetail page
_gotoEpisode() async {
Navigator.push(
context,
SlideUptRoute(
page: EpisodeDetail(episodeItem: episode, heroTag: 'playpanel')),
);
}
//init audio player from url
void _initbackgroundAudioPlayer(String url) {
_remoteErrorMessage = null;
_remoteAudioLoading = true;
Provider.of<AudioPlay>(context, listen: false).audioState = AudioState.load;
if (_backgroundAudioPlaying == true) {
_backgroundAudio?.pause();
AudioSystem.instance.stopBackgroundDisplay();
}
_backgroundAudio?.dispose();
_backgroundAudio = Audio.loadFromRemoteUrl(url,
onDuration: (double durationSeconds) {
setState(() {
_backgroundAudioDurationSeconds = durationSeconds;
_remoteAudioLoading = false;
Provider.of<AudioPlay>(context, listen: false).audioState =
AudioState.play;
});
_setNotification();
},
onPosition: (double positionSeconds) {
setState(() {
if (_backgroundAudioPositionSeconds <
_backgroundAudioDurationSeconds) {
_seekSliderValue = _backgroundAudioPositionSeconds /
_backgroundAudioDurationSeconds;
_backgroundAudioPositionSeconds = positionSeconds;
} else {
_seekSliderValue = 1;
_backgroundAudioPositionSeconds = _backgroundAudioDurationSeconds;
}
});
},
onError: (String message) => setState(() {
_remoteErrorMessage = message;
_backgroundAudio.dispose();
_backgroundAudio = null;
_backgroundAudioPlaying = false;
_remoteAudioLoading = false;
Provider.of<AudioPlay>(context, listen: false).audioState =
AudioState.error;
}),
onComplete: () => setState(() {
_backgroundAudioPlaying = false;
_remoteAudioLoading = false;
Provider.of<AudioPlay>(context, listen: false).audioState =
AudioState.complete;
}),
looping: false,
playInBackground: true)
..play();
}
//init audio player form local file
void _initbackgroundAudioPlayerLocal(String path) {
_remoteErrorMessage = null;
_remoteAudioLoading = true;
ByteData audio = getAudio(path);
Provider.of<AudioPlay>(context, listen: false).audioState = AudioState.load;
if (_backgroundAudioPlaying == true) {
_backgroundAudio?.pause();
AudioSystem.instance.stopBackgroundDisplay();
}
_backgroundAudio?.dispose();
_backgroundAudio = Audio.loadFromByteData(audio,
onDuration: (double durationSeconds) {
setState(() {
_backgroundAudioDurationSeconds = durationSeconds;
_remoteAudioLoading = false;
});
_setNotification();
Provider.of<AudioPlay>(context, listen: false).audioState =
AudioState.play;
},
onPosition: (double positionSeconds) {
setState(() {
if (_backgroundAudioPositionSeconds <
_backgroundAudioDurationSeconds) {
_seekSliderValue = _backgroundAudioPositionSeconds /
_backgroundAudioDurationSeconds;
_backgroundAudioPositionSeconds = positionSeconds;
} else {
_seekSliderValue = 1;
_backgroundAudioPositionSeconds = _backgroundAudioDurationSeconds;
}
});
},
onError: (String message) => setState(() {
_remoteErrorMessage = message;
_backgroundAudio.dispose();
_backgroundAudio = null;
_backgroundAudioPlaying = false;
_remoteAudioLoading = false;
Provider.of<AudioPlay>(context, listen: false).audioState =
AudioState.error;
}),
onComplete: () => setState(() {
_backgroundAudioPlaying = false;
_remoteAudioLoading = false;
Provider.of<AudioPlay>(context, listen: false).audioState =
AudioState.complete;
}),
looping: false,
playInBackground: true)
..play();
}
//if downloaded
Future<String> _getFile(String url) async {
final task = await FlutterDownloader.loadTasksWithRawQuery(
query: "SELECT * FROM task WHERE url = '$url' AND status = 3");
if (task.length != 0) {
String _filePath = task.first.savedDir + '/' + task.first.filename;
return _filePath;
}
return 'NotDownload';
}
//get local audio file
ByteData getAudio(String path) {
File audioFile = File(path);
Uint8List audio = audioFile.readAsBytesSync();
return ByteData.view(audio.buffer);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
final url = Provider.of<AudioPlay>(context).episode?.enclosureUrl;
if (url != this.url) {
setState(() {
this.url = url;
episode = Provider.of<AudioPlay>(context).episode;
_backgroundAudioPlaying = true;
_isLoading = true;
_getFile(url).then((result) {
result == 'NotDownload'
? _initbackgroundAudioPlayer(url)
: _initbackgroundAudioPlayerLocal(result);
});
});
}
}
@override
void dispose() {
AudioSystem.instance.removeMediaEventListener(_mediaEventListener);
_backgroundAudio?.dispose();
super.dispose();
}
static String _stringForSeconds(double seconds) {
if (seconds == null) return null;
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
}
void _mediaEventListener(MediaEvent mediaEvent) {
_logger.info('App received media event of type: ${mediaEvent.type}');
final MediaActionType type = mediaEvent.type;
if (type == MediaActionType.play) {
_resumeBackgroundAudio();
} else if (type == MediaActionType.pause) {
_pauseBackgroundAudio();
} else if (type == MediaActionType.playPause) {
_backgroundAudioPlaying
? _pauseBackgroundAudio()
: _resumeBackgroundAudio();
} else if (type == MediaActionType.stop) {
_stopBackgroundAudio();
} else if (type == MediaActionType.seekTo) {
_backgroundAudio.seek(mediaEvent.seekToPositionSeconds);
AudioSystem.instance
.setPlaybackState(true, mediaEvent.seekToPositionSeconds);
} else if (type == MediaActionType.skipForward) {
final double skipIntervalSeconds = mediaEvent.skipIntervalSeconds;
_forwardBackgroundAudio(skipIntervalSeconds);
_logger.info(
'Skip-forward event had skipIntervalSeconds $skipIntervalSeconds.');
} else if (type == MediaActionType.skipBackward) {
final double skipIntervalSeconds = mediaEvent.skipIntervalSeconds;
_forwardBackgroundAudio(skipIntervalSeconds);
_logger.info(
'Skip-backward event had skipIntervalSeconds $skipIntervalSeconds.');
} else if (type == MediaActionType.custom) {
if (mediaEvent.customEventId == replay10ButtonId) {
_forwardBackgroundAudio(-10.0);
} else if (mediaEvent.customEventId == likeButtonId) {
_resumeBackgroundAudio();
} else if (mediaEvent.customEventId == forwardButtonId) {
_forwardBackgroundAudio(30.0);
} else if (mediaEvent.customEventId == pausenowButtonId) {
_pauseBackgroundAudio();
}
}
}
final _pauseButton = AndroidCustomMediaButton(
'pausenow', pausenowButtonId, 'ic_stat_pause_circle_filled');
final _replay10Button = AndroidCustomMediaButton(
'replay10', replay10ButtonId, 'ic_stat_replay_10');
final _forwardButton = AndroidCustomMediaButton(
'forward', forwardButtonId, 'ic_stat_forward_30');
final _playnowButton = AndroidCustomMediaButton(
'playnow', likeButtonId, 'ic_stat_play_circle_filled');
Future<void> _setNotification() async {
final Uint8List imageBytes = File('${episode.imagePath}').readAsBytesSync();
AudioSystem.instance.setMetadata(AudioMetadata(
title: episode.title,
artist: episode.feedTitle,
album: episode.feedTitle,
genre: "Podcast",
durationSeconds: _backgroundAudioDurationSeconds,
artBytes: imageBytes));
AudioSystem.instance
.setPlaybackState(true, _backgroundAudioPositionSeconds);
AudioSystem.instance.setAndroidNotificationButtons(<dynamic>[
AndroidMediaButtonType.pause,
_forwardButton,
AndroidMediaButtonType.stop,
], androidCompactIndices: <int>[
0,
1
]);
AudioSystem.instance.setSupportedMediaActions(<MediaActionType>{
MediaActionType.playPause,
MediaActionType.pause,
MediaActionType.next,
MediaActionType.previous,
MediaActionType.skipForward,
MediaActionType.skipBackward,
MediaActionType.seekTo,
MediaActionType.custom,
}, skipIntervalSeconds: 30);
}
Future<void> _resumeBackgroundAudio() async {
_backgroundAudio.resume();
setState(() {
_backgroundAudioPlaying = true;
Provider.of<AudioPlay>(context, listen: false).audioState =
AudioState.play;
});
final Uint8List imageBytes = File('${episode.imagePath}').readAsBytesSync();
AudioSystem.instance.setMetadata(AudioMetadata(
title: episode.title,
artist: episode.feedTitle,
album: episode.feedTitle,
genre: "Podcast",
durationSeconds: _backgroundAudioDurationSeconds,
artBytes: imageBytes));
AudioSystem.instance
.setPlaybackState(true, _backgroundAudioPositionSeconds);
AudioSystem.instance.setAndroidNotificationButtons(<dynamic>[
AndroidMediaButtonType.pause,
_forwardButton,
AndroidMediaButtonType.stop,
], androidCompactIndices: <int>[
0,
1
]);
AudioSystem.instance.setSupportedMediaActions(<MediaActionType>{
MediaActionType.playPause,
MediaActionType.pause,
MediaActionType.next,
MediaActionType.previous,
MediaActionType.skipForward,
MediaActionType.skipBackward,
MediaActionType.seekTo,
MediaActionType.custom,
}, skipIntervalSeconds: 30);
}
void _pauseBackgroundAudio() {
_backgroundAudio.pause();
setState(() {
_backgroundAudioPlaying = false;
Provider.of<AudioPlay>(context, listen: false).audioState =
AudioState.pause;
});
AudioSystem.instance
.setPlaybackState(false, _backgroundAudioPositionSeconds);
AudioSystem.instance.setAndroidNotificationButtons(<dynamic>[
AndroidMediaButtonType.play,
_forwardButton,
AndroidMediaButtonType.stop,
], androidCompactIndices: <int>[
0,
1,
]);
AudioSystem.instance.setSupportedMediaActions(<MediaActionType>{
MediaActionType.playPause,
MediaActionType.play,
MediaActionType.next,
MediaActionType.previous,
});
}
void _stopBackgroundAudio() {
_backgroundAudio.pause();
setState(() => _backgroundAudioPlaying = false);
AudioSystem.instance.stopBackgroundDisplay();
}
void _forwardBackgroundAudio(double seconds) {
final double forwardposition = _backgroundAudioPositionSeconds + seconds;
_backgroundAudio.seek(forwardposition);
AudioSystem.instance.setPlaybackState(true, forwardposition);
}
Widget _expandedPanel(BuildContext context) => Container(
height: 300,
color: Theme.of(context).primaryColor,
padding: EdgeInsets.symmetric(horizontal: 10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
height: 80.0,
padding: EdgeInsets.all(20),
alignment: Alignment.center,
child: (episode.title.length > 10)
? Marquee(
text: episode.title,
style: TextStyle(
fontWeight: FontWeight.bold, fontSize: 18),
scrollAxis: Axis.horizontal,
crossAxisAlignment: CrossAxisAlignment.start,
blankSpace: 30.0,
velocity: 50.0,
pauseAfterRound: Duration(seconds: 1),
startPadding: 30.0,
accelerationDuration: Duration(seconds: 1),
accelerationCurve: Curves.linear,
decelerationDuration: Duration(milliseconds: 500),
decelerationCurve: Curves.easeOut,
)
: Text(
episode.title,
style: TextStyle(
fontWeight: FontWeight.bold, fontSize: 20),
),
),
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: _seekSliderValue,
onChanged: (double val) {
setState(() => _seekSliderValue = val);
final double positionSeconds =
val * _backgroundAudioDurationSeconds;
_backgroundAudio.seek(positionSeconds);
AudioSystem.instance
.setPlaybackState(true, positionSeconds);
}),
),
),
Container(
height: 20.0,
padding: EdgeInsets.symmetric(horizontal: 50.0),
child: Row(
children: <Widget>[
Text(
_stringForSeconds(_backgroundAudioPositionSeconds) ?? '',
style: TextStyle(fontSize: 10),
),
Expanded(
child: Container(
alignment: Alignment.center,
child: _remoteErrorMessage != null
? Text(_remoteErrorMessage,
style: const TextStyle(
color: const Color(0xFFFF0000)))
: Text(
_remoteAudioLoading ? 'Buffring...' : '',
style: TextStyle(
color: Theme.of(context).accentColor),
),
),
),
Text(
_stringForSeconds(_backgroundAudioDurationSeconds) ?? '',
style: TextStyle(fontSize: 10),
),
],
),
),
Container(
height: 100,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Material(
color: Colors.transparent,
child: IconButton(
padding: EdgeInsets.symmetric(horizontal: 30.0),
onPressed: _backgroundAudioPlaying
? () => _forwardBackgroundAudio(-10)
: null,
iconSize: 32.0,
icon: Icon(Icons.replay_10),
color: Theme.of(context).tabBarTheme.labelColor),
),
_backgroundAudioPlaying
? Material(
color: Colors.transparent,
child: IconButton(
padding: EdgeInsets.symmetric(horizontal: 30.0),
onPressed: _backgroundAudioPlaying
? () {
_pauseBackgroundAudio();
}
: null,
iconSize: 40.0,
icon: Icon(Icons.pause_circle_filled),
color:
Theme.of(context).tabBarTheme.labelColor),
)
: Material(
color: Colors.transparent,
child: IconButton(
padding: EdgeInsets.symmetric(horizontal: 30.0),
onPressed: _backgroundAudioPlaying
? null
: () {
_resumeBackgroundAudio();
},
iconSize: 40.0,
icon: Icon(Icons.play_circle_filled),
color:
Theme.of(context).tabBarTheme.labelColor),
),
Material(
color: Colors.transparent,
child: IconButton(
padding: EdgeInsets.symmetric(horizontal: 30.0),
onPressed: _backgroundAudioPlaying
? () => _forwardBackgroundAudio(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: 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: () => _gotoEpisode(),
child: Icon(Icons.info),
),
),
],
))
]),
);
Widget _miniPanel(double width, BuildContext context) {
var color = json.decode(episode?.primaryColor);
if (color) {
if (Theme.of(context).brightness == Brightness.light) {
_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 Container(
height: 60,
color: Theme.of(context).primaryColor,
child:
Column(mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[
SizedBox(
height: 2,
child: LinearProgressIndicator(
value: _seekSliderValue,
backgroundColor: Theme.of(context).primaryColor,
valueColor: AlwaysStoppedAnimation<Color>(_c),
)),
Expanded(
child: Container(
padding: EdgeInsets.only(left: 15, right: 10),
alignment: Alignment.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
flex: 4,
child: Container(
padding: EdgeInsets.symmetric(vertical: 5),
alignment: Alignment.centerLeft,
child: (episode.title.length > 55)
? Marquee(
text: episode.title,
style: TextStyle(fontWeight: FontWeight.bold),
scrollAxis: Axis.vertical,
crossAxisAlignment: CrossAxisAlignment.start,
blankSpace: 30.0,
velocity: 50.0,
pauseAfterRound: Duration(seconds: 1),
startPadding: 30.0,
accelerationDuration: Duration(seconds: 1),
accelerationCurve: Curves.linear,
decelerationDuration: Duration(milliseconds: 500),
decelerationCurve: Curves.easeOut,
)
: Text(
episode.title,
style: TextStyle(fontWeight: FontWeight.bold),
maxLines: 2,
),
),
),
Expanded(
flex: 2,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10),
alignment: Alignment.center,
child: _remoteAudioLoading
? Text(
'Buffring...',
style:
TextStyle(color: Theme.of(context).accentColor),
)
: Row(
children: <Widget>[
Text(
_stringForSeconds(
_backgroundAudioDurationSeconds -
_backgroundAudioPositionSeconds) ??
'',
style: TextStyle(color: _c),
),
Text(
' Left',
style: TextStyle(color: _c),
),
],
),
),
),
Expanded(
flex: 2,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_backgroundAudioPlaying
? Material(
color: Colors.transparent,
child: IconButton(
onPressed: _backgroundAudioPlaying
? () {
_pauseBackgroundAudio();
}
: null,
iconSize: 25.0,
icon: Icon(Icons.pause_circle_filled),
color:
Theme.of(context).tabBarTheme.labelColor),
)
: Material(
color: Colors.transparent,
child: IconButton(
onPressed: _backgroundAudioPlaying
? null
: () {
_resumeBackgroundAudio();
},
iconSize: 25.0,
icon: Icon(Icons.play_circle_filled),
color:
Theme.of(context).tabBarTheme.labelColor),
),
Material(
color: Colors.transparent,
child: IconButton(
onPressed: _backgroundAudioPlaying
? () => _forwardBackgroundAudio(30)
: null,
iconSize: 25.0,
icon: Icon(Icons.forward_30),
color: Theme.of(context).tabBarTheme.labelColor),
),
],
),
),
],
),
),
),
]),
);
}
@override
Widget build(BuildContext context) {
double _width = MediaQuery.of(context).size.width;
return !_isLoading
? Center()
: AudioPanel(
miniPanel: _miniPanel(_width, context),
expandedPanel: _expandedPanel(context));
}
}

View File

@ -46,7 +46,9 @@ class _AudioPanelState extends State<AudioPanel>
child: GestureDetector( child: GestureDetector(
onTap: () => _backToMini(), onTap: () => _backToMini(),
child: Container( child: Container(
color: Theme.of(context).scaffoldBackgroundColor.withOpacity(0.5), color: Theme.of(context)
.scaffoldBackgroundColor
.withOpacity(0.5),
), ),
), ),
) )
@ -75,7 +77,16 @@ class _AudioPanelState extends State<AudioPanel>
), ),
) )
: Container( : Container(
color: Theme.of(context).primaryColor, decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
boxShadow: [
BoxShadow(
offset: Offset(0, -1),
blurRadius: 4,
color: Colors.grey[400],
),
],
),
child: SingleChildScrollView( child: SingleChildScrollView(
child: Opacity( child: Opacity(
opacity: _animation.value < (maxSize - 50) opacity: _animation.value < (maxSize - 50)

View File

@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
import 'hometab.dart'; import 'hometab.dart';
import 'package:tsacdop/home/appbar/importompl.dart'; import 'package:tsacdop/home/appbar/importompl.dart';
import 'package:tsacdop/home/audio_player.dart'; import 'package:tsacdop/home/audioplayer.dart';
import 'homescroll.dart'; import 'homescroll.dart';
class Home extends StatelessWidget { class Home extends StatelessWidget {

View File

@ -263,7 +263,10 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
.map<Widget>((PodcastLocal podcastLocal) { .map<Widget>((PodcastLocal podcastLocal) {
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).primaryColor), color: Theme.of(context).brightness ==
Brightness.light
? Theme.of(context).primaryColor
: Colors.black12),
margin: EdgeInsets.symmetric(horizontal: 5.0), margin: EdgeInsets.symmetric(horizontal: 5.0),
key: ObjectKey(podcastLocal.title), key: ObjectKey(podcastLocal.title),
child: PodcastPreview( child: PodcastPreview(
@ -426,19 +429,23 @@ class ShowEpisode extends StatelessWidget {
}, },
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(5.0)), borderRadius: BorderRadius.all(Radius.circular(5.0)),
color: Theme.of(context).scaffoldBackgroundColor, color: Theme.of(context).scaffoldBackgroundColor,
border: Border.all( border: Border.all(
color: Theme.of(context).primaryColor, color: Theme.of(context).brightness == Brightness.light
width: 3.0, ? Theme.of(context).primaryColor
), : Theme.of(context).scaffoldBackgroundColor,
boxShadow: [ // color: Theme.of(context).primaryColor,
BoxShadow( width: 3.0,
color: Theme.of(context).primaryColor, ),
blurRadius: 1.0, // boxShadow: [
spreadRadius: 0.5, // 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),
child: Column( child: Column(

View File

@ -42,4 +42,16 @@ class KeyValueStorage {
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);
} }
Future<bool> savePlaylist(List<String> playList) async{
SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.setStringList(key, playList);
}
Future<List<String>> getPlayList() async{
SharedPreferences prefs = await SharedPreferences.getInstance();
if(prefs.getStringList(key) == null) {await prefs.setStringList(key, []);}
print(prefs.getStringList(key).toString());
return prefs.getStringList(key);
}
} }

View File

@ -7,6 +7,7 @@ import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:tsacdop/class/podcastlocal.dart'; import 'package:tsacdop/class/podcastlocal.dart';
import 'package:tsacdop/class/audiostate.dart';
import 'package:tsacdop/class/episodebrief.dart'; import 'package:tsacdop/class/episodebrief.dart';
import 'package:tsacdop/webfeed/webfeed.dart'; import 'package:tsacdop/webfeed/webfeed.dart';
@ -36,12 +37,14 @@ class DBHelper {
description TEXT, feed_id TEXT, feed_link TEXT, milliseconds INTEGER, description TEXT, feed_id TEXT, feed_link TEXT, milliseconds INTEGER,
duration INTEGER DEFAULT 0, explicit INTEGER DEFAULT 0, liked INTEGER DEFAULT 0, duration INTEGER DEFAULT 0, explicit INTEGER DEFAULT 0, liked INTEGER DEFAULT 0,
downloaded TEXT DEFAULT 'ND', download_date INTEGER DEFAULT 0)"""); downloaded TEXT DEFAULT 'ND', download_date INTEGER DEFAULT 0)""");
await db.execute(
"""CREATE TABLE PlayHistory(id INTEGER PRIMARY KEY, title TEXT, enclosure_url TEXT UNIQUE,
seconds INTEGER, seek_value INTEGER, add_date INTEGER)""");
} }
Future<List<PodcastLocal>> getPodcastLocal(List<String> podcasts) async { Future<List<PodcastLocal>> getPodcastLocal(List<String> podcasts) async {
var dbClient = await database; var dbClient = await database;
List<PodcastLocal> podcastLocal = List(); List<PodcastLocal> podcastLocal = List();
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(
@ -78,7 +81,7 @@ 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['email'],
list.first['provider'], list.first['provider'],
list.first['link'])); list.first['link']));
} }
@ -132,6 +135,24 @@ class DBHelper {
await dbClient.rawDelete('DELETE FROM Episodes WHERE feed_id=?', [id]); await dbClient.rawDelete('DELETE FROM Episodes WHERE feed_id=?', [id]);
} }
Future<int> saveHistory(PlayHistory history) async {
var dbClient = await database;
int _milliseconds = DateTime.now().millisecondsSinceEpoch;
int result = await dbClient.transaction((txn) async {
return await txn.rawInsert(
"""REPLACE INTO PlayHistory (title, enclosure_url, seconds, seek_value, add_date)
VALUES (?, ?, ?, ?, ?) """,
[
history.title,
history.url,
history.seconds,
history.seekValue,
_milliseconds
]);
});
return result;
}
DateTime _parsePubDate(String pubDate) { DateTime _parsePubDate(String pubDate) {
if (pubDate == null) return DateTime.now(); if (pubDate == null) return DateTime.now();
print(pubDate); print(pubDate);
@ -156,11 +177,13 @@ class DBHelper {
if (year != null && time != null && month != null) { if (year != null && time != null && month != null) {
date = DateFormat('dd MMM yyyy HH:mm', 'en_US') date = DateFormat('dd MMM yyyy HH:mm', 'en_US')
.parse(month + year + time); .parse(month + year + time);
} else if(year!=null && time!=null && month == null){ } else if (year != null && time != null && month == null) {
String month = mmDd.stringMatch(pubDate); String month = mmDd.stringMatch(pubDate);
date = DateFormat('mm-dd yyyy HH:mm', 'en_US') date = DateFormat('mm-dd yyyy HH:mm', 'en_US')
.parse(month +' ' + year +' '+ time); .parse(month + ' ' + year + ' ' + time);
} else {date = DateTime.now();} } else {
date = DateTime.now();
}
} }
} }
} }
@ -488,8 +511,8 @@ class DBHelper {
Future<String> getFeedDescription(String id) async { Future<String> getFeedDescription(String id) async {
var dbClient = await database; var dbClient = await database;
List<Map> list = await dbClient.rawQuery( List<Map> list = await dbClient
'SELECT description FROM PodcastLocal WHERE id = ?', [id]); .rawQuery('SELECT description FROM PodcastLocal WHERE id = ?', [id]);
String description = list[0]['description']; String description = list[0]['description'];
return description; return description;
} }

View File

@ -12,7 +12,7 @@ void main() async {
runApp( runApp(
MultiProvider( MultiProvider(
providers: [ providers: [
ChangeNotifierProvider(create: (context) => AudioPlay()), ChangeNotifierProvider(create: (context) => AudioPlayer()),
ChangeNotifierProvider(create: (context) => ImportOmpl()), ChangeNotifierProvider(create: (context) => ImportOmpl()),
ChangeNotifierProvider(create: (context) => SettingState()), ChangeNotifierProvider(create: (context) => SettingState()),
ChangeNotifierProvider(create: (context) => GroupList()), ChangeNotifierProvider(create: (context) => GroupList()),
@ -29,10 +29,11 @@ class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var theme = Provider.of<SettingState>(context).theme; var theme = Provider.of<SettingState>(context).theme;
print(theme);
return MaterialApp( return MaterialApp(
themeMode: theme == 0 themeMode: theme == 0
? ThemeMode.system ? ThemeMode.system
: theme == 1 ? ThemeMode.dark : ThemeMode.light, : theme == 1 ? ThemeMode.light: ThemeMode.dark,
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
title: 'TsacDop', title: 'TsacDop',
theme: ThemeData( theme: ThemeData(

View File

@ -15,6 +15,7 @@ import 'package:tsacdop/episodes/episodedetail.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';
import 'package:tsacdop/util/pageroute.dart'; import 'package:tsacdop/util/pageroute.dart';
import 'package:tsacdop/home/audioplayer.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);
@ -73,10 +74,10 @@ class _PodcastDetailState extends State<PodcastDetail> {
double _width = MediaQuery.of(context).size.width; double _width = MediaQuery.of(context).size.width;
Color _color; Color _color;
var color = json.decode(widget.podcastLocal.primaryColor); var color = json.decode(widget.podcastLocal.primaryColor);
(color[0] > 200 && color[1] > 200 && color[2] > 200) (color[0] > 200 && color[1] > 200 && color[2] > 200)
? _color = Color.fromRGBO( ? _color = Color.fromRGBO(
(255 - color[0]), 255 - color[1], 255 - color[2], 1.0) (255 - color[0]), 255 - color[1], 255 - color[2], 1.0)
: _color = Color.fromRGBO(color[0], color[1], 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(
@ -91,286 +92,317 @@ class _PodcastDetailState extends State<PodcastDetail> {
key: _refreshIndicatorKey, key: _refreshIndicatorKey,
color: Theme.of(context).accentColor, color: Theme.of(context).accentColor,
onRefresh: () => _updateRssItem(widget.podcastLocal), onRefresh: () => _updateRssItem(widget.podcastLocal),
child: FutureBuilder<List<EpisodeBrief>>( child: Stack(
future: _getRssItem(widget.podcastLocal), children: <Widget>[
builder: (context, snapshot) { FutureBuilder<List<EpisodeBrief>>(
if (snapshot.hasError) print(snapshot.error); future: _getRssItem(widget.podcastLocal),
return (snapshot.hasData) builder: (context, snapshot) {
? CustomScrollView( if (snapshot.hasError) print(snapshot.error);
physics: const AlwaysScrollableScrollPhysics(), return (snapshot.hasData)
primary: true, ? CustomScrollView(
slivers: <Widget>[ physics: const AlwaysScrollableScrollPhysics(),
SliverAppBar( primary: true,
elevation: 0, slivers: <Widget>[
iconTheme: IconThemeData(color: Colors.white), SliverAppBar(
expandedHeight: 170, elevation: 0,
backgroundColor: _color, iconTheme: IconThemeData(color: Colors.white),
floating: true, expandedHeight: 170,
pinned: true, backgroundColor: _color,
flexibleSpace: LayoutBuilder(builder: floating: true,
(BuildContext context, pinned: true,
BoxConstraints constraints) { flexibleSpace: LayoutBuilder(builder:
top = constraints.biggest.height; (BuildContext context,
return FlexibleSpaceBar( BoxConstraints constraints) {
background: Stack( top = constraints.biggest.height;
children: <Widget>[ return FlexibleSpaceBar(
Container( background: Stack(
margin: EdgeInsets.only(top: 120), children: <Widget>[
padding: EdgeInsets.only(left: 80, right: 120), Container(
color: Colors.white10, margin: EdgeInsets.only(top: 120),
alignment: Alignment.centerLeft, padding: EdgeInsets.only(
child: Column( left: 80, right: 120),
mainAxisAlignment: color: Colors.white10,
MainAxisAlignment.start, alignment: Alignment.centerLeft,
mainAxisSize: MainAxisSize.min, child: Column(
crossAxisAlignment: mainAxisAlignment:
CrossAxisAlignment.start, MainAxisAlignment.start,
children: <Widget>[ mainAxisSize: MainAxisSize.min,
Text(widget.podcastLocal.author ?? '', crossAxisAlignment:
style: Theme.of(context) CrossAxisAlignment.start,
.textTheme children: <Widget>[
.bodyText1 Text(
.copyWith(color: Colors.grey[300])), widget.podcastLocal.author ??
Text(widget.podcastLocal.provider ?? '', '',
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodyText1 .bodyText1
.copyWith(color: Colors.grey[300])) .copyWith(
], color:
), Colors.grey[300])),
Text(
widget.podcastLocal.provider ??
'',
style: Theme.of(context)
.textTheme
.bodyText1
.copyWith(
color:
Colors.grey[300]))
],
),
),
Container(
alignment: Alignment.centerRight,
padding: EdgeInsets.only(right: 10),
child: SizedBox(
height: 120,
child: Image.file(File(
"${widget.podcastLocal.imagePath}")),
),
),
Container(
alignment: Alignment.center,
child: podcastInfo(context),
),
],
), ),
Container( title: top < 70
alignment: Alignment.centerRight, ? Text(widget.podcastLocal.title,
padding: EdgeInsets.only(right: 10), style: TextStyle(color: Colors.white))
child: SizedBox( : Center(),
height: 120, );
child: Image.file(File( }),
"${widget.podcastLocal.imagePath}")),
),
),
Container(
alignment: Alignment.center,
child: podcastInfo(context),
),
],
),
title: top < 70
? Text(widget.podcastLocal.title,
style: TextStyle(color: Colors.white))
: Center(),
);
}),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
padding: EdgeInsets.only(
left: 10.0,
right: 10.0,
top: 20.0,
bottom: 10.0),
alignment: Alignment.topLeft,
color:
Theme.of(context).scaffoldBackgroundColor,
child: AboutPodcast(
podcastLocal: widget.podcastLocal),
);
},
childCount: 1,
),
),
SliverPadding(
padding: const EdgeInsets.all(5.0),
sliver: SliverGrid(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: 1.0,
crossAxisCount: 3,
mainAxisSpacing: 6.0,
crossAxisSpacing: 6.0,
), ),
delegate: SliverChildBuilderDelegate( SliverList(
(BuildContext context, int index) { delegate: SliverChildBuilderDelegate(
EpisodeBrief episodeBrief = (BuildContext context, int index) {
snapshot.data[index]; return Container(
Color _c; padding: EdgeInsets.only(
var color = json left: 10.0,
.decode(widget.podcastLocal.primaryColor); right: 10.0,
if (Theme.of(context).brightness == top: 20.0,
Brightness.light) { bottom: 10.0),
(color[0] > 200 && alignment: Alignment.topLeft,
color[1] > 200 && color: Theme.of(context)
color[2] > 200) .scaffoldBackgroundColor,
? _c = Color.fromRGBO((255 - color[0]), child: AboutPodcast(
255 - color[1], 255 - color[2], 1.0) podcastLocal: widget.podcastLocal),
: _c = Color.fromRGBO( );
color[0], color[1], color[2], 1.0); },
} else { childCount: 1,
(color[0] < 50 && ),
color[1] < 50 && ),
color[2] < 50) SliverPadding(
? _c = Color.fromRGBO((255 - color[0]), padding: const EdgeInsets.all(5.0),
255 - color[1], 255 - color[2], 1.0) sliver: SliverGrid(
: _c = Color.fromRGBO( gridDelegate:
color[0], color[1], color[2], 1.0); SliverGridDelegateWithFixedCrossAxisCount(
} childAspectRatio: 1.0,
return Material( crossAxisCount: 3,
color: Colors.transparent, mainAxisSpacing: 6.0,
child: InkWell( crossAxisSpacing: 6.0,
onTap: () { ),
Navigator.push( delegate: SliverChildBuilderDelegate(
context, (BuildContext context, int index) {
ScaleRoute( EpisodeBrief episodeBrief =
page: EpisodeDetail( snapshot.data[index];
episodeItem: episodeBrief, Color _c;
heroTag: 'podcast', var color = json.decode(
)), widget.podcastLocal.primaryColor);
); if (Theme.of(context).brightness ==
}, Brightness.light) {
child: Container( (color[0] > 200 &&
decoration: BoxDecoration( color[1] > 200 &&
borderRadius: BorderRadius.all( color[2] > 200)
Radius.circular(5.0)), ? _c = Color.fromRGBO(
color: Theme.of(context) (255 - color[0]),
.scaffoldBackgroundColor, 255 - color[1],
border: Border.all( 255 - color[2],
color: Theme.of(context) 1.0)
.brightness == : _c = Color.fromRGBO(color[0],
Brightness.light color[1], color[2], 1.0);
? Theme.of(context).primaryColor } else {
: Theme.of(context) (color[0] < 50 &&
.scaffoldBackgroundColor, color[1] < 50 &&
width: 3.0, color[2] < 50)
), ? _c = Color.fromRGBO(
boxShadow: [ (255 - color[0]),
BoxShadow( 255 - color[1],
255 - color[2],
1.0)
: _c = Color.fromRGBO(color[0],
color[1], color[2], 1.0);
}
return Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
Navigator.push(
context,
ScaleRoute(
page: EpisodeDetail(
episodeItem: episodeBrief,
heroTag: 'podcast',
)),
);
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(5.0)),
color: Theme.of(context) color: Theme.of(context)
.primaryColor, .scaffoldBackgroundColor,
blurRadius: 0.5, border: Border.all(
spreadRadius: 0.5, color: Theme.of(context)
), .brightness ==
]), Brightness.light
alignment: Alignment.center, ? Theme.of(context)
padding: EdgeInsets.all(8.0), .primaryColor
child: Column( : Theme.of(context)
mainAxisAlignment: .scaffoldBackgroundColor,
MainAxisAlignment.center, width: 3.0,
children: <Widget>[
Expanded(
flex: 2,
child: Row(
mainAxisAlignment:
MainAxisAlignment.start,
children: <Widget>[
Hero(
tag: episodeBrief
.enclosureUrl +
'podcast',
child: Container(
child: ClipRRect(
borderRadius:
BorderRadius.all(
Radius.circular(
_width / 32)),
child: Container(
height: _width / 16,
width: _width / 16,
child: Image.file(File(
"${episodeBrief.imagePath}")),
),
),
),
),
Spacer(),
Container(
alignment: Alignment.topRight,
child: Text(
(snapshot.data.length -
index)
.toString(),
style: GoogleFonts.teko(
textStyle: TextStyle(
fontSize: _width / 24,
color: _c,
),
),
),
)
],
),
),
Expanded(
flex: 5,
child: Container(
alignment: Alignment.topLeft,
padding:
EdgeInsets.only(top: 2.0),
child: Text(
episodeBrief.title,
style: TextStyle(
fontSize: _width / 32,
),
maxLines: 4,
overflow: TextOverflow.fade,
), ),
), boxShadow: [
), BoxShadow(
Expanded( color: Theme.of(context)
flex: 1, .primaryColor,
child: Row( blurRadius: 0.5,
children: <Widget>[ spreadRadius: 0.5,
Align(
alignment:
Alignment.bottomLeft,
child: Text(
episodeBrief.dateToString(),
//podcast[index].pubDate.substring(4, 16),
style: TextStyle(
fontSize: _width / 35,
color: _c,
fontStyle:
FontStyle.italic),
),
), ),
Spacer(), ]),
DownloadIcon( alignment: Alignment.center,
episodeBrief: episodeBrief), padding: EdgeInsets.all(8.0),
Padding( child: Column(
padding: EdgeInsets.all(1), mainAxisAlignment:
), MainAxisAlignment.center,
Container( children: <Widget>[
alignment: Expanded(
Alignment.bottomRight, flex: 2,
child: (episodeBrief.liked == child: Row(
0) mainAxisAlignment:
? Center() MainAxisAlignment.start,
: IconTheme( children: <Widget>[
data: IconThemeData( Hero(
size: 15), tag: episodeBrief
child: Icon( .enclosureUrl +
Icons.favorite, 'podcast',
color: Colors.red, child: Container(
child: ClipRRect(
borderRadius:
BorderRadius.all(
Radius.circular(
_width /
32)),
child: Container(
height: _width / 16,
width: _width / 16,
child: Image.file(File(
"${episodeBrief.imagePath}")),
), ),
), ),
),
),
Spacer(),
Container(
alignment:
Alignment.topRight,
child: Text(
(snapshot.data.length -
index)
.toString(),
style: GoogleFonts.teko(
textStyle: TextStyle(
fontSize:
_width / 24,
color: _c,
),
),
),
)
],
), ),
], ),
), Expanded(
flex: 5,
child: Container(
alignment: Alignment.topLeft,
padding:
EdgeInsets.only(top: 2.0),
child: Text(
episodeBrief.title,
style: TextStyle(
fontSize: _width / 32,
),
maxLines: 4,
overflow: TextOverflow.fade,
),
),
),
Expanded(
flex: 1,
child: Row(
children: <Widget>[
Align(
alignment:
Alignment.bottomLeft,
child: Text(
episodeBrief
.dateToString(),
//podcast[index].pubDate.substring(4, 16),
style: TextStyle(
fontSize:
_width / 35,
color: _c,
fontStyle: FontStyle
.italic),
),
),
Spacer(),
DownloadIcon(
episodeBrief:
episodeBrief),
Padding(
padding:
EdgeInsets.all(1),
),
Container(
alignment:
Alignment.bottomRight,
child: (episodeBrief
.liked ==
0)
? Center()
: IconTheme(
data:
IconThemeData(
size: 15),
child: Icon(
Icons.favorite,
color:
Colors.red,
),
),
),
],
),
),
],
), ),
], ),
), ),
), );
), },
); childCount: snapshot.data.length,
}, ),
childCount: snapshot.data.length, ),
), ),
), ],
), )
], : Center(child: CircularProgressIndicator());
) },
: Center(child: CircularProgressIndicator()); ),
}, Container(child: PlayerWidget()),
],
), ),
)), )),
), ),
@ -428,21 +460,23 @@ class _AboutPodcastState extends State<AboutPodcast> {
}, },
child: !_expand child: !_expand
? Column( ? Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Text( Text(
_description, _description,
maxLines: 3, maxLines: 3,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
Container( Container(
alignment: Alignment.center, alignment: Alignment.center,
child: Icon(Icons.keyboard_arrow_down,), child: Icon(
), Icons.keyboard_arrow_down,
], ),
) ),
],
)
: Text(_description), : Text(_description),
); );
} else { } else {

View File

@ -165,213 +165,223 @@ class _PodcastCardState extends State<PodcastCard> {
var _groupList = Provider.of<GroupList>(context); var _groupList = Provider.of<GroupList>(context);
_belongGroups = _groupList.getPodcastGroup(widget.podcastLocal.id); _belongGroups = _groupList.getPodcastGroup(widget.podcastLocal.id);
return Column( return Container(
mainAxisSize: MainAxisSize.min, decoration: BoxDecoration(
children: <Widget>[ border: Border(
InkWell( bottom: Divider.createBorderSide(context),
onTap: () => setState(() => _loadMenu = !_loadMenu), ),
child: Container( ),
padding: EdgeInsets.symmetric(horizontal: 12), child: Column(
height: 100, mainAxisSize: MainAxisSize.min,
child: Row( children: <Widget>[
mainAxisAlignment: MainAxisAlignment.start, InkWell(
mainAxisSize: MainAxisSize.min, onTap: () => setState(() => _loadMenu = !_loadMenu),
children: <Widget>[ child: Container(
Container( padding: EdgeInsets.symmetric(horizontal: 12),
child: Icon( height: 100,
Icons.unfold_more, child: Row(
color: _c, mainAxisAlignment: MainAxisAlignment.start,
), mainAxisSize: MainAxisSize.min,
), children: <Widget>[
Container( Container(
child: ClipRRect( child: Icon(
borderRadius: BorderRadius.all(Radius.circular(30)), Icons.unfold_more,
child: Container( color: _c,
height: 60,
width: 60,
child: Image.file(
File("${widget.podcastLocal.imagePath}")),
), ),
), ),
), Container(
Container( child: ClipRRect(
width: _width / 2, borderRadius: BorderRadius.all(Radius.circular(30)),
padding: EdgeInsets.symmetric(horizontal: 10), child: Container(
alignment: Alignment.centerLeft, height: 60,
child: Column( width: 60,
mainAxisAlignment: MainAxisAlignment.start, child: Image.file(
mainAxisSize: MainAxisSize.min, File("${widget.podcastLocal.imagePath}")),
children: <Widget>[ ),
Container( ),
alignment: Alignment.centerLeft, ),
child: Text( Container(
widget.podcastLocal.title, width: _width / 2,
maxLines: 2, padding: EdgeInsets.symmetric(horizontal: 10),
overflow: TextOverflow.fade, alignment: Alignment.centerLeft,
style: TextStyle( child: Column(
fontWeight: FontWeight.bold, fontSize: 15),
),
),
Row(
children: _belongGroups.map((group) {
return Container(
padding: EdgeInsets.only(right: 5.0),
child: Text(group.name));
}).toList(),
),
],
)),
Spacer(),
Icon(_loadMenu
? Icons.keyboard_arrow_up
: Icons.keyboard_arrow_down),
Padding(
padding: EdgeInsets.symmetric(horizontal: 5.0),
),
]),
),
),
!_loadMenu
? Center()
: Container(
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
border: Border(
bottom: BorderSide(
color: Theme.of(context).primaryColorDark),
top: BorderSide(
color: Theme.of(context).primaryColorDark))),
height: 50,
child: _addGroup
? Row(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Expanded( Container(
flex: 4, alignment: Alignment.centerLeft,
child: SingleChildScrollView( child: Text(
scrollDirection: Axis.horizontal, widget.podcastLocal.title,
child: Row( maxLines: 2,
children: _groupList.groups overflow: TextOverflow.fade,
.map<Widget>((PodcastGroup group) { style: TextStyle(
return Container( fontWeight: FontWeight.bold, fontSize: 15),
padding: EdgeInsets.only(left: 5.0),
child: FilterChip(
key: ValueKey<String>(group.id),
label: Text(group.name),
selected: _selectedGroups.contains(group),
onSelected: (bool value) {
setState(() {
if (!value) {
_selectedGroups.remove(group);
print(group.name);
} else {
_selectedGroups.add(group);
}
});
},
),
);
}).toList()),
), ),
), ),
Expanded( Row(
flex: 1, children: _belongGroups.map((group) {
child: Row( return Container(
children: <Widget>[ padding: EdgeInsets.only(right: 5.0),
IconButton( child: Text(group.name));
icon: Icon(Icons.clear), }).toList(),
onPressed: () => setState(() { ),
_addGroup = false; ],
}), )),
), Spacer(),
IconButton( Icon(_loadMenu
onPressed: () async { ? Icons.keyboard_arrow_up
print(_selectedGroups); : Icons.keyboard_arrow_down),
if (_selectedGroups.length > 0) { Padding(
setState(() { padding: EdgeInsets.symmetric(horizontal: 5.0),
_addGroup = false; ),
}); ]),
await _groupList.changeGroup( ),
widget.podcastLocal.id, ),
_selectedGroups, !_loadMenu
); ? Center()
Fluttertoast.showToast( : Container(
msg: 'Setting Saved', child: Container(
gravity: ToastGravity.BOTTOM, decoration: BoxDecoration(
); color: Theme.of(context).primaryColor,
} else border: Border(
Fluttertoast.showToast( bottom: BorderSide(
msg: 'At least select one group', color: Theme.of(context).primaryColorDark),
gravity: ToastGravity.BOTTOM, top: BorderSide(
); color: Theme.of(context).primaryColorDark))),
}, height: 50,
icon: Icon(Icons.done), child: _addGroup
), ? Row(
], mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
flex: 4,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: _groupList.groups
.map<Widget>((PodcastGroup group) {
return Container(
padding: EdgeInsets.only(left: 5.0),
child: FilterChip(
key: ValueKey<String>(group.id),
label: Text(group.name),
selected:
_selectedGroups.contains(group),
onSelected: (bool value) {
setState(() {
if (!value) {
_selectedGroups.remove(group);
print(group.name);
} else {
_selectedGroups.add(group);
}
});
},
),
);
}).toList()),
),
), ),
) Expanded(
], flex: 1,
) child: Row(
: Row( children: <Widget>[
mainAxisAlignment: MainAxisAlignment.spaceEvenly, IconButton(
children: <Widget>[ icon: Icon(Icons.clear),
_buttonOnMenu( onPressed: () => setState(() {
Icon(Icons.fullscreen), _addGroup = false;
() => Navigator.push( }),
context, ),
ScaleRoute( IconButton(
page: PodcastDetail( onPressed: () async {
podcastLocal: widget.podcastLocal, print(_selectedGroups);
if (_selectedGroups.length > 0) {
setState(() {
_addGroup = false;
});
await _groupList.changeGroup(
widget.podcastLocal.id,
_selectedGroups,
);
Fluttertoast.showToast(
msg: 'Setting Saved',
gravity: ToastGravity.BOTTOM,
);
} else
Fluttertoast.showToast(
msg: 'At least select one group',
gravity: ToastGravity.BOTTOM,
);
},
icon: Icon(Icons.done),
),
],
),
)
],
)
: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
_buttonOnMenu(
Icon(Icons.fullscreen),
() => Navigator.push(
context,
ScaleRoute(
page: PodcastDetail(
podcastLocal: widget.podcastLocal,
)),
)), )),
)), _buttonOnMenu(Icon(Icons.add), () {
_buttonOnMenu(Icon(Icons.add), () { setState(() {
setState(() { _addGroup = true;
_addGroup = true; });
}); }),
}), _buttonOnMenu(Icon(Icons.notifications), () {}),
_buttonOnMenu(Icon(Icons.notifications), () {}), _buttonOnMenu(Icon(Icons.remove_circle), () {
_buttonOnMenu(Icon(Icons.remove_circle), () { showDialog(
showDialog( context: context,
context: context, child:
child: AnnotatedRegion<SystemUiOverlayStyle>( AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle( value: SystemUiOverlayStyle(
systemNavigationBarColor: systemNavigationBarColor:
Colors.black.withOpacity(0.5), Colors.black.withOpacity(0.5),
statusBarColor: Colors.red, statusBarColor: Colors.red,
), ),
child: AlertDialog( child: AlertDialog(
elevation: 2.0, elevation: 2.0,
title: Text('Remove confirm'), title: Text('Remove confirm'),
content: Text( content: Text(
'${widget.podcastLocal.title} will be removed from device.'), '${widget.podcastLocal.title} will be removed from device.'),
actions: <Widget>[ actions: <Widget>[
FlatButton( FlatButton(
onPressed: () => onPressed: () =>
Navigator.of(context).pop(), Navigator.of(context).pop(),
child: Text('CANCEL'), child: Text('CANCEL'),
),
FlatButton(
onPressed: () {
_groupList.removePodcast(
widget.podcastLocal.id);
Navigator.of(context).pop();
},
child: Text(
'CONFIRM',
style: TextStyle(color: Colors.red),
), ),
) FlatButton(
], onPressed: () {
), _groupList.removePodcast(
)); widget.podcastLocal.id);
}), Navigator.of(context).pop();
], },
), child: Text(
'CONFIRM',
style:
TextStyle(color: Colors.red),
),
)
],
),
));
}),
],
),
),
), ),
), ],
], ),
); );
} }
} }

View File

@ -92,13 +92,6 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "3.0.8" version: "3.0.8"
expandable:
dependency: "direct dev"
description:
name: expandable
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.1.2"
file_picker: file_picker:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -385,6 +378,13 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "0.2.11" version: "0.2.11"
tuple:
dependency: "direct dev"
description:
name: tuple
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.3"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@ -449,5 +449,5 @@ packages:
source: hosted source: hosted
version: "3.5.0" version: "3.5.0"
sdks: sdks:
dart: ">=2.7.0 <3.0.0" dart: ">=2.6.0 <3.0.0"
flutter: ">=1.12.13+hotfix.4 <2.0.0" flutter: ">=1.12.13+hotfix.4 <2.0.0"

View File

@ -48,7 +48,7 @@ dev_dependencies:
image: ^2.1.4 image: ^2.1.4
shared_preferences: ^0.5.6+1 shared_preferences: ^0.5.6+1
uuid: ^2.0.4 uuid: ^2.0.4
expandable: ^4.1.2 tuple: ^1.0.3