deleted: .vscode/launch.json

deleted:    .vscode/settings.json
	modified:   android/app/src/main/res/drawable/launch_background.xml
	modified:   android/app/src/main/res/drawable/launch_background_night.xml
	deleted:    android/app/src/main/res/drawable/normal_background.xml
	modified:   android/app/src/main/res/values/styles.xml
	modified:   lib/class/audiostate.dart
	modified:   lib/class/importompl.dart
	modified:   lib/class/podcast_group.dart
	modified:   lib/home/appbar/about.dart
	modified:   lib/home/appbar/addpodcast.dart
	modified:   lib/home/appbar/popupmenu.dart
	modified:   lib/home/audioplayer.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/podcastgroup.dart
	modified:   lib/podcasts/podcastlist.dart
	modified:   lib/podcasts/podcastmanage.dart
	deleted:    pubspec.lock
	modified:   pubspec.yaml
	android/app/src/main/res/mipmap-hdpi/text_light.png
	android/app/src/main/res/mipmap-mdpi/text_light.png
	android/app/src/main/res/mipmap-xhdpi/text_light.png
	android/app/src/main/res/mipmap-xxhdpi/text_light.png
	android/app/src/main/res/mipmap-xxxhdpi/text_light.png
	android/app/src/main/res/values/colors.xml
	assets/listennotes_light.png
	assets/text_light.png
This commit is contained in:
stonegate 2020-03-04 00:04:23 +08:00
parent a6fc34e7bb
commit f195d62b07
23 changed files with 1268 additions and 943 deletions

13
.vscode/launch.json vendored
View File

@ -1,13 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Flutter",
"request": "launch",
"type": "dart"
}
]
}

View File

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

View File

@ -14,4 +14,6 @@
android:gravity="bottom"
android:src="@mipmap/text" />
</item>
<!-- <item name="android:navigationBarColor">@android:color/white</item>
<item name="android:windowLightNavigationBar">true</item> -->
</layer-list>

View File

@ -1,7 +1,7 @@
<?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" />
<item android:drawable="@color/blackGrey" />
<!-- You can insert your own image assets here -->
<item>
<bitmap
@ -12,6 +12,6 @@
<item android:bottom="100dp">
<bitmap
android:gravity="bottom"
android:src="@mipmap/text" />
android:src="@mipmap/text_light" />
</item>
</layer-list>

View File

@ -1,17 +0,0 @@
<?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

@ -5,7 +5,4 @@
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">@drawable/normal_background</item>
</style>
</resources>

View File

@ -11,7 +11,7 @@ import 'package:audiofileplayer/audio_system.dart';
import 'package:tsacdop/local_storage/key_value_storage.dart';
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
enum AudioState { load, play, pause, complete, error, stop }
//enum AudioState { load, play, pause, complete, error, stop }
class PlayHistory {
DBHelper dbHelper = DBHelper();
@ -90,6 +90,14 @@ class AudioPlayer extends ChangeNotifier {
bool _remoteAudioLoading = false;
String _remoteErrorMessage;
double _seekSliderValue = 0.0;
int _lastPostion;
bool _skip = false;
bool _stopOnComplete = false;
Timer _stopTimer;
//Show stopwatch after user setting timer.
bool _showStopWatch = false;
final Logger _logger = Logger('audiofileplayer');
bool get backgroundAudioPlaying => _backgroundAudioPlaying;
@ -99,16 +107,20 @@ class AudioPlayer extends ChangeNotifier {
double get seekSliderValue => _seekSliderValue;
String get remoteErrorMessage => _remoteErrorMessage;
bool get playerRunning => _playerRunning;
int _lastPostion;
int get lastPositin => _lastPostion;
Playlist get queue => _queue;
EpisodeBrief get episode => _episode;
bool get stopOnComplete => _stopOnComplete;
bool get showStopWatch => _showStopWatch;
set setStopOnComplete(bool boo) {
_stopOnComplete = boo;
}
loadPlaylist() async {
await _queue.getPlaylist();
_lastPostion = await storage.getInt();
print(_lastPostion);
}
episodeLoad(EpisodeBrief episode) async {
@ -134,22 +146,27 @@ class AudioPlayer extends ChangeNotifier {
_backgroundAudioPlaying = false;
await _queue.getPlaylist();
_episode = _queue.playlist.first;
_skip = true;
await _play(_episode);
_playerRunning = true;
notifyListeners();
}
playNext() async {
storage.saveInt(0);
_lastPostion = 0;
PlayHistory history = PlayHistory(_episode.title, _episode.enclosureUrl,
backgroundAudioDuration, seekSliderValue);
await dbHelper.saveHistory(history);
await _queue.delFromPlaylist(_episode);
if (_queue.playlist.length > 0) {
if (_queue.playlist.length > 0 && !_stopOnComplete) {
playlistLoad();
} else {
_stopOnComplete = false;
_backgroundAudioPlaying = false;
_remoteAudioLoading = false;
_playerRunning = false;
_disposeAudio();
notifyListeners();
}
}
@ -170,7 +187,7 @@ class AudioPlayer extends ChangeNotifier {
_pauseBackgroundAudio();
notifyListeners();
PlayHistory history = PlayHistory(_episode.title, _episode.enclosureUrl,
backgroundAudioDuration, seekSliderValue);
backgroundAudioPosition, seekSliderValue);
await dbHelper.saveHistory(history);
}
@ -191,16 +208,42 @@ class AudioPlayer extends ChangeNotifier {
_backgroundAudio.seek(positionSeconds);
AudioSystem.instance.setPlaybackState(true, positionSeconds);
}
//Set sleep time
sleepTimer(int mins) {
_showStopWatch = true;
notifyListeners();
_stopTimer = Timer(Duration(minutes: mins),(){
_stopOnComplete = false;
_backgroundAudioPlaying = false;
_remoteAudioLoading = false;
_playerRunning = false;
_showStopWatch = false;
_disposeAudio();
notifyListeners();
});
}
//Cancel sleep timer
cancelTimer(){
_stopTimer.cancel();
_showStopWatch = false;
notifyListeners();
}
_disposeAudio() {
pauseAduio();
AudioSystem.instance?.stopBackgroundDisplay();
AudioSystem.instance?.removeMediaEventListener(_mediaEventListener);
_backgroundAudio?.dispose();
}
@override
dispose() {
pauseAduio();
AudioSystem.instance.removeMediaEventListener(_mediaEventListener);
_backgroundAudio?.dispose();
_disposeAudio();
super.dispose();
}
_play(EpisodeBrief episodeBrief) async {
AudioSystem.instance.addMediaEventListener(_mediaEventListener);
String url = _queue.playlist.first.enclosureUrl;
_getFile(url).then((result) {
result == 'NotDownload'
@ -225,6 +268,42 @@ class AudioPlayer extends ChangeNotifier {
return ByteData.view(audio.buffer);
}
onDuration(double durationSeconds) {
_backgroundAudioDurationSeconds = durationSeconds;
_remoteAudioLoading = false;
_backgroundAudioPlaying = true;
if (_skip) {
_forwardBackgroundAudio(_lastPostion.toDouble());
_backgroundAudioPositionSeconds = _lastPostion.toDouble();
}
_skip = false;
_setNotification(true);
notifyListeners();
}
onPosition(double positionSeconds) {
if (_backgroundAudioPositionSeconds < _backgroundAudioDurationSeconds) {
_seekSliderValue =
_backgroundAudioPositionSeconds / _backgroundAudioDurationSeconds;
_backgroundAudioPositionSeconds = positionSeconds;
notifyListeners();
} else {
_seekSliderValue = 1;
_backgroundAudioPositionSeconds = _backgroundAudioDurationSeconds;
notifyListeners();
}
_lastPostion = positionSeconds.toInt();
storage.saveInt(_lastPostion);
}
onError(String message) {
_remoteErrorMessage = message;
_backgroundAudio.dispose();
_backgroundAudio = null;
_backgroundAudioPlaying = false;
_remoteAudioLoading = false;
}
void _initbackgroundAudioPlayerLocal(String path) {
_remoteErrorMessage = null;
_remoteAudioLoading = true;
@ -232,35 +311,14 @@ class AudioPlayer extends ChangeNotifier {
_backgroundAudio?.pause();
_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();
}
storage.saveInt(positionSeconds.toInt());
}, onError: (String message) {
_remoteErrorMessage = message;
_backgroundAudio.dispose();
_backgroundAudio = null;
_backgroundAudioPlaying = false;
_remoteAudioLoading = false;
}, onComplete: () {
playNext();
}, looping: false, playInBackground: true)
..play();
_backgroundAudio = Audio.loadFromByteData(audio,
onDuration: (double durationSeconds) => onDuration(durationSeconds),
onPosition: (double positionSeconds) => onPosition(positionSeconds),
onError: (String message) => onError(message),
onComplete: () => playNext(),
looping: false,
playInBackground: true)
..play();
}
void _initbackgroundAudioPlayer(String url) {
@ -270,35 +328,14 @@ class AudioPlayer extends ChangeNotifier {
_backgroundAudio?.pause();
_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();
}
storage.saveInt(positionSeconds.toInt());
}, onError: (String message) {
_remoteErrorMessage = message;
_backgroundAudio.dispose();
_backgroundAudio = null;
_backgroundAudioPlaying = false;
_remoteAudioLoading = false;
}, onComplete: () {
playNext();
}, looping: false, playInBackground: true)
..resume();
_backgroundAudio = Audio.loadFromRemoteUrl(url,
onDuration: (double durationSeconds) => onDuration(durationSeconds),
onPosition: (double positionSeconds) => onPosition(positionSeconds),
onError: (String message) => onError(message),
onComplete: () => playNext(),
looping: false,
playInBackground: true)
..resume();
}
void _mediaEventListener(MediaEvent mediaEvent) {
@ -341,7 +378,7 @@ class AudioPlayer extends ChangeNotifier {
}
}
Future<void> _setNotification(bool b) async {
Future<void> _setNotification(bool boo) async {
final Uint8List imageBytes =
File('${_episode.imagePath}').readAsBytesSync();
AudioSystem.instance.setMetadata(AudioMetadata(
@ -351,7 +388,7 @@ class AudioPlayer extends ChangeNotifier {
genre: "Podcast",
durationSeconds: _backgroundAudioDurationSeconds,
artBytes: imageBytes));
AudioSystem.instance.setPlaybackState(b, _backgroundAudioPositionSeconds);
AudioSystem.instance.setPlaybackState(boo, _backgroundAudioPositionSeconds);
AudioSystem.instance.setAndroidNotificationButtons(<dynamic>[
AndroidMediaButtonType.pause,
_forwardButton,

View File

@ -10,6 +10,7 @@ class ImportOmpl extends ChangeNotifier{
set rssTitle(String title){
_rssTitle = title;
notifyListeners();
}
ImportState get importState => _importState;

View File

@ -45,6 +45,15 @@ class PodcastGroup {
}
}
Color getColor() {
if (color != '#000000') {
int colorInt = int.parse('FF' + color.toUpperCase(), radix: 16);
return Color(colorInt).withOpacity(1.0);
} else {
return Colors.blue[400];
}
}
List<PodcastLocal> _podcasts;
List<PodcastLocal> get podcasts => _podcasts;
@ -102,15 +111,25 @@ class GroupList extends ChangeNotifier {
}
Future delGroup(PodcastGroup podcastGroup) async {
_groups.remove(podcastGroup);
_isLoading = true;
notifyListeners();
podcastGroup.podcastList.forEach((podcast) {
if (!_groups.first.podcastList.contains(podcast)) {
_groups[0].podcastList.insert(0, podcast);
}
});
_saveGroup();
_groups.remove(podcastGroup);
await _groups[0].getPodcasts();
_isLoading = false;
notifyListeners();
}
void updateGroup(PodcastGroup podcastGroup) {
updateGroup(PodcastGroup podcastGroup) async{
var oldGroup = _groups.firstWhere((it) => it.id == podcastGroup.id);
var index = _groups.indexOf(oldGroup);
_groups.replaceRange(index, index + 1, [podcastGroup]);
await podcastGroup.getPodcasts();
notifyListeners();
_saveGroup();
}
@ -137,6 +156,7 @@ class GroupList extends ChangeNotifier {
return result;
}
//Change podcast groups
changeGroup(String id, List<PodcastGroup> list) async {
_isLoading = true;
notifyListeners();
@ -154,15 +174,16 @@ class GroupList extends ChangeNotifier {
notifyListeners();
}
//Unsubscribe podcast
removePodcast(String id) async {
_isLoading = true;
notifyListeners();
_groups.forEach((group) async {
group.podcastList.remove(id);
group.podcastList.remove(id);
});
_saveGroup();
await dbHelper.delPodcastLocal(id);
await Future.forEach(_groups, (group) async {
await Future.forEach(_groups, (group) async {
await group.getPodcasts();
});
_isLoading = false;

View File

@ -29,7 +29,7 @@ class AboutApp extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(icons, color: Colors.grey[700]),
Icon(icons, color: Theme.of(context).accentColor),
Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
),

View File

@ -10,10 +10,8 @@ import 'package:provider/provider.dart';
import 'package:path_provider/path_provider.dart';
import 'package:image/image.dart' as img;
import 'package:fluttertoast/fluttertoast.dart';
import 'package:tsacdop/class/audiostate.dart';
import 'package:tsacdop/class/fireside_data.dart';
import 'package:uuid/uuid.dart';
import 'package:tuple/tuple.dart';
import 'package:tsacdop/class/importompl.dart';
import 'package:tsacdop/class/podcast_group.dart';
@ -32,30 +30,9 @@ class MyHomePage extends StatefulWidget {
class _MyHomePageState extends State<MyHomePage> {
final _MyHomePageDelegate _delegate = _MyHomePageDelegate();
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
Widget build(BuildContext context) {
var audio = Provider.of<AudioPlayer>(context, listen: false);
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
@ -78,44 +55,14 @@ class _MyHomePageState extends State<MyHomePage> {
},
),
title: Image(
image: AssetImage('assets/text.png'),
image: Theme.of(context).brightness == Brightness.light
? AssetImage('assets/text.png') : AssetImage('assets/text_light.png'),
height: 30,
),
actions: <Widget>[
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(),
),
),
@ -163,7 +110,8 @@ class _MyHomePageDelegate extends SearchDelegate<int> {
child: Container(
padding: EdgeInsets.only(top: 400),
child: Image(
image: AssetImage('assets/listennotes.png'),
image: Theme.of(context).brightness == Brightness.light
? AssetImage('assets/listennotes.png') : AssetImage('assets/listennotes_light.png'),
height: 20,
),
));

View File

@ -49,6 +49,7 @@ class PopupMenu extends StatelessWidget {
Widget build(BuildContext context) {
ImportOmpl importOmpl = Provider.of<ImportOmpl>(context, listen: false);
GroupList groupList = Provider.of<GroupList>(context, listen: false);
_refreshAll() async {
var dbHelper = DBHelper();
List<PodcastLocal> podcastList = await dbHelper.getPodcastLocalAll();

View File

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:io';
import 'dart:math' as math;
@ -25,12 +26,118 @@ class _PlayerWidgetState extends State<PlayerWidget> {
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
}
List minsToSelect = [1, 5, 10, 15, 20, 25, 30, 45, 60, 70, 80, 90, 99];
//Show playlist widget.
bool _showlist;
//Show timer choose widget.
bool _showTimer;
//Store selected timer mins.
int _minSelected;
//Left time after user setting timer.
int _timeLeft;
Timer _timer;
@override
void initState() {
super.initState();
_showlist = false;
_showTimer = false;
_minSelected = 5;
_timeLeft = 0;
}
setTimer() {
_timeLeft = _minSelected;
_timer = Timer.periodic(Duration(minutes: 1), (timer) {
setState(() {
if(_timeLeft < 1){
_timer.cancel();
} else{
_timeLeft = _timeLeft - 1;
}
});
});
}
Widget _sleepTimer(BuildContext context) {
var audio = Provider.of<AudioPlayer>(context);
return Container(
height: 50,
margin: EdgeInsets.all(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.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: minsToSelect
.map((e) => InkWell(
onTap: () => setState(() => _minSelected = e),
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.elasticOut,
margin: EdgeInsets.symmetric(horizontal: 10.0),
decoration: BoxDecoration(
color: (e == _minSelected)
? Theme.of(context).accentColor
: Colors.grey[400],
shape: BoxShape.circle,
),
alignment: Alignment.center,
height: (e == _minSelected) ? 40 : 30,
width: (e == _minSelected) ? 40 : 30,
child: Text(e.toString(),
style: TextStyle(
color: (e == _minSelected)
? Colors.white
: Colors.black)),
),
))
.toList(),
),
),
),
Container(
width: 100,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Material(
color: Colors.transparent,
child: IconButton(
icon: Icon(Icons.clear),
onPressed: () {
setState(() => _showTimer = false);
},
),
),
Material(
color: Colors.transparent,
child: IconButton(
icon: Icon(Icons.done),
onPressed: () {
setState(() {
_showTimer = false;
});
audio.sleepTimer(_minSelected);
setTimer();
},
),
),
],
),
),
],
),
);
}
Widget _expandedPanel(BuildContext context) {
@ -104,13 +211,16 @@ class _PlayerWidgetState extends State<PlayerWidget> {
padding: EdgeInsets.only(left: 30, right: 30),
child: SliderTheme(
data: SliderTheme.of(context).copyWith(
activeTrackColor: Colors.blue[100],
activeTrackColor: Theme.of(context)
.accentColor
.withOpacity(0.5),
inactiveTrackColor: Colors.grey[300],
trackHeight: 3.0,
thumbColor: Colors.blue[400],
thumbColor: Theme.of(context).accentColor,
thumbShape: RoundSliderThumbShape(
enabledThumbRadius: 6.0),
overlayColor: Colors.blue.withAlpha(32),
overlayColor:
Theme.of(context).accentColor.withAlpha(32),
overlayShape:
RoundSliderOverlayShape(overlayRadius: 14.0),
),
@ -167,113 +277,192 @@ class _PlayerWidgetState extends State<PlayerWidget> {
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),
],
return Material(
color: Colors.transparent,
child: 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,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: EdgeInsets.all(5.0),
),
Container(
height: 30.0,
width: 30.0,
child: CircleAvatar(
backgroundImage:
FileImage(File("${episode.imagePath}")),
),
),
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)),
),
],
);
},
),
),
!_showTimer
// Setting sleep timer
? 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,
Tuple3<EpisodeBrief, bool, bool>>(
selector: (_, audio) => Tuple3(audio.episode,
audio.stopOnComplete, audio.showStopWatch),
builder: (_, data, __) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: EdgeInsets.all(5.0),
),
Container(
height: 30.0,
width: 30.0,
child: CircleAvatar(
backgroundImage: FileImage(
File("${data.item1.imagePath}")),
),
),
Spacer(),
Material(
color: Colors.transparent,
child: !data.item3
? PopupMenuButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10))),
elevation: 1,
tooltip: 'Sleep Timer',
icon: Icon(
Icons.brightness_2,
color: data.item2
? Theme.of(context).accentColor
: Theme.of(context)
.iconTheme
.color,
),
itemBuilder: (context) => [
PopupMenuItem(
value: 1,
child: Text(
'End of this episode')),
PopupMenuItem(
value: 2,
child: Text('Timer'),
),
],
onSelected: (value) {
if (value == 1) {
audio.setStopOnComplete = true;
} else if (value == 2) {
setState(() => _showTimer = true);
}
})
: PopupMenuButton(
tooltip: 'Time Left',
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10))),
elevation: 1,
icon: Container(
alignment: Alignment.center,
height: 25,
width: 25,
decoration: BoxDecoration(
shape: BoxShape.circle,
color:
Theme.of(context).accentColor,
),
child: Text(
_timeLeft.toString(),
style: TextStyle(
color: Colors.white)),
),
itemBuilder: (context) => [
PopupMenuItem(
value: 1,
child: Text('Cancel')),
],
onSelected: (value) {
audio.cancelTimer();
_timer.cancel();
setState(() => _timeLeft = 0);
},
),
),
Material(
color: Colors.transparent,
child: IconButton(
onPressed: () => Navigator.push(
context,
SlideUptRoute(
page: EpisodeDetail(
episodeItem: data.item1,
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)),
),
],
);
},
),
)
: _sleepTimer(context),
]),
),
Container(
@ -283,8 +472,8 @@ class _PlayerWidgetState extends State<PlayerWidget> {
height: _showlist ? 300 : 0,
width: MediaQuery.of(context).size.width,
alignment: Alignment.center,
margin: EdgeInsets.all(20),
padding: EdgeInsets.only(bottom: 10.0),
// margin: EdgeInsets.all(20),
//padding: EdgeInsets.only(bottom: 10.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
color: Theme.of(context).scaffoldBackgroundColor,

View File

@ -1,15 +1,45 @@
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:tuple/tuple.dart';
import 'hometab.dart';
import 'package:tsacdop/home/appbar/importompl.dart';
import 'package:tsacdop/home/audioplayer.dart';
import 'package:tsacdop/class/audiostate.dart';
import 'homescroll.dart';
class Home extends StatelessWidget {
class Home extends StatefulWidget {
@override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
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
Widget build(BuildContext context) {
var audio = Provider.of<AudioPlayer>(context, listen: false);
return Stack(children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.start,
@ -22,8 +52,70 @@ class Home extends StatelessWidget {
),
],
),
Container(
child: PlayerWidget()),
AnimatedPositioned(
duration: Duration(milliseconds: 2000),
curve: Curves.elasticOut,
bottom: 50,
right: _loadPlay ? 5 : -25,
child: Container(
child: 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()
: InkWell(
onTap: () => audio.playlistLoad(),
child: Stack(
alignment: Alignment.centerLeft,
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 45, right: 10.0),
alignment: Alignment.centerRight,
decoration: BoxDecoration(
color: Theme.of(context).accentColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20.0),
bottomLeft: Radius.circular(20.0),
bottomRight: Radius.circular(10.0),
topRight: Radius.circular(10.0)),
boxShadow: [
BoxShadow(
color: Theme.of(context).brightness ==
Brightness.light
? Colors.grey[400]
: Colors.grey[800],
blurRadius: 4,
offset: Offset(1, 1)),
]),
height: 40,
child: Text(_stringForSeconds(data.item3) + '...',
style: TextStyle(color: Colors.white)),
),
CircleAvatar(
radius: 20,
backgroundImage: FileImage(File(
"${data.item2.playlist.first.imagePath}")),
),
Container(
height: 40.0,
width: 40,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.black12),
child: Icon(
Icons.play_arrow,
color: Colors.white,
),
),
],
),
),
),
),
),
Container(child: PlayerWidget()),
]);
}
}

View File

@ -303,7 +303,7 @@ class _PodcastPreviewState extends State<PodcastPreview> {
@override
Widget build(BuildContext context) {
Color _c = (Theme.of(context).brightness == Brightness.light)
? widget.podcastLocal.primaryColor.colorizedark()
? widget.podcastLocal.primaryColor.colorizedark()
: widget.podcastLocal.primaryColor.colorizeLight();
return Column(
children: <Widget>[
@ -374,9 +374,41 @@ class ShowEpisode extends StatelessWidget {
final List<EpisodeBrief> podcast;
final PodcastLocal podcastLocal;
ShowEpisode({Key key, this.podcast, this.podcastLocal}) : super(key: key);
@override
Widget build(BuildContext context) {
double _width = MediaQuery.of(context).size.width;
_showPopupMenu(Offset offset) async {
print(offset.dx);
double left = offset.dx;
double top = offset.dy;
await showMenu(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10))),
context: context,
position: RelativeRect.fromLTRB(left, top, _width - left, 0),
items: [
PopupMenuItem(
child: Row(
children: <Widget>[
Icon(Icons.play_circle_outline),
Padding(padding: EdgeInsets.symmetric(horizontal: 2),),
Text('Play')
],
),
),
PopupMenuItem(child: Row(
children: <Widget>[
Icon(Icons.favorite_border),
Padding(padding: EdgeInsets.symmetric(horizontal: 2),),
Text('Like')
],
)),
],
elevation: 8.0,
);
}
return CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
primary: false,
@ -392,11 +424,12 @@ class ShowEpisode extends StatelessWidget {
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
Color _c =
(Theme.of(context).brightness == Brightness.light)
? podcastLocal.primaryColor.colorizedark()
: podcastLocal.primaryColor.colorizeLight();
return InkWell(
Color _c = (Theme.of(context).brightness == Brightness.light)
? podcastLocal.primaryColor.colorizedark()
: podcastLocal.primaryColor.colorizeLight();
return GestureDetector(
onLongPressStart: (details) => _showPopupMenu(Offset(
details.globalPosition.dx, details.globalPosition.dy)),
onTap: () {
Navigator.push(
context,

View File

@ -34,7 +34,6 @@ class KeyValueStorage {
Future<bool> saveInt(int setting) async{
SharedPreferences prefs = await SharedPreferences.getInstance();
print(setting.toString());
return prefs.setInt(key, setting);
}

View File

@ -1,10 +1,8 @@
import 'package:sqflite/sqflite.dart';
import 'dart:async';
import 'dart:io' as io;
import 'package:path/path.dart';
import 'package:intl/intl.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:path_provider/path_provider.dart';
import 'package:dio/dio.dart';
import 'package:tsacdop/class/podcastlocal.dart';
import 'package:tsacdop/class/audiostate.dart';
@ -20,8 +18,8 @@ class DBHelper {
}
initDb() async {
io.Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, "podcasts.db");
var documentsDirectory = await getDatabasesPath();
String path = join(documentsDirectory, "podcasts.db");
Database theDb = await openDatabase(path, version: 1, onCreate: _onCreate);
return theDb;
}

View File

@ -32,15 +32,12 @@ Future main() async {
callbackDispatcher,
isInDebugMode: true,
);
Workmanager.registerPeriodicTask("update", "simplePeriodicTask",
frequency: Duration(hours: 1),
initialDelay: Duration(seconds: 10),
Workmanager.registerPeriodicTask("2", "update_podcasts",
frequency: Duration(minutes: 1),
initialDelay: Duration(seconds: 5),
constraints: Constraints(
networkType: NetworkType.connected,
requiresBatteryNotLow: true,
requiresCharging: false,
requiresDeviceIdle: true,
requiresStorageNotLow: true));
));
await FlutterDownloader.initialize();
await SystemChrome.setPreferredOrientations(
@ -55,7 +52,7 @@ void callbackDispatcher() {
await dbHelper.updatePodcastRss(podcastLocal);
print('Refresh ' + podcastLocal.title);
});
return Future.value(true);
return true;
});
}

View File

@ -1,10 +1,13 @@
import 'dart:io';
import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:provider/provider.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
import 'package:tsacdop/class/podcast_group.dart';
import 'package:tsacdop/class/podcastlocal.dart';
import 'package:tsacdop/podcasts/podcastdetail.dart';
@ -18,97 +21,460 @@ class PodcastGroupList extends StatefulWidget {
_PodcastGroupListState createState() => _PodcastGroupListState();
}
class _PodcastGroupListState extends State<PodcastGroupList> {
bool _loadSave;
class _PodcastGroupListState extends State<PodcastGroupList>
with SingleTickerProviderStateMixin {
bool _showSetting;
AnimationController _controller;
Animation _animation;
double _fraction;
@override
void initState() {
super.initState();
_loadSave = false;
_showSetting = false;
_fraction = 0;
_controller = AnimationController(
duration: const Duration(milliseconds: 500), vsync: this);
_animation = Tween(begin: 0.0, end: 1.0).animate(_controller)
..addListener(() {
if (mounted)
setState(() {
_fraction = _animation.value;
});
});
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.stop();
} else if (status == AnimationStatus.dismissed) {
_controller.stop();
}
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
Widget _saveButton(BuildContext context) {
var podcastList = widget.group.podcasts;
var _groupList = Provider.of<GroupList>(context);
return Container(
child: InkWell(
child: AnimatedContainer(
duration: Duration(milliseconds: 800),
width: _loadSave ? 70 : 0,
height: 60,
decoration: BoxDecoration(
color: Colors.blue,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.grey[700],
blurRadius: 5,
offset: Offset(1, 1),
),
]),
alignment: Alignment.center,
child: Text(
'Save',
style: TextStyle(color: Colors.white),
maxLines: 1,
)),
onTap: () async {
await _groupList.saveOrder(widget.group, podcastList);
Fluttertoast.showToast(
msg: 'Setting Saved',
gravity: ToastGravity.BOTTOM,
);
setState(() {
_loadSave = false;
});
},
var _groupList = Provider.of<GroupList>(context, listen: false);
return Transform(
alignment: FractionalOffset(0.5, 0.5),
transform: Matrix4.rotationY(math.pi * _fraction),
child: Container(
child: InkWell(
child: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: _fraction > 0.5 ? Colors.red : widget.group.getColor(),
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.grey[700],
blurRadius: 5,
offset: Offset(1, 1),
),
]),
alignment: Alignment.center,
child: Icon(
_fraction > 0.5 ? Icons.save : Icons.settings,
color: Colors.white,
)),
onTap: () async {
if (_fraction == 0) {
setState(() {
_showSetting = true;
});
} else {
await _groupList.saveOrder(widget.group, podcastList);
Fluttertoast.showToast(
msg: 'Setting Saved',
gravity: ToastGravity.BOTTOM,
);
_controller.reverse();
}
},
),
),
);
}
@override
Widget build(BuildContext context) {
var groupList = Provider.of<GroupList>(context, listen: false);
return widget.group.podcastList.length == 0
? Container(
color: Theme.of(context).primaryColor,
)
: Container(
color: Theme.of(context).primaryColor,
child: Stack(
children: <Widget>[
ReorderableListView(
onReorder: (int oldIndex, int newIndex) {
setState(() {
if (newIndex > oldIndex) {
newIndex -= 1;
}
final PodcastLocal podcast =
widget.group.podcasts.removeAt(oldIndex);
widget.group.podcasts.insert(newIndex, podcast);
_loadSave = true;
});
},
children: widget.group.podcasts
.map<Widget>((PodcastLocal podcastLocal) {
return Container(
decoration:
BoxDecoration(color: Theme.of(context).primaryColor),
key: ObjectKey(podcastLocal.title),
child: PodcastCard(
podcastLocal: podcastLocal,
group: widget.group,
: Stack(
children: <Widget>[
Container(
color: Theme.of(context).primaryColor,
child: Stack(
children: <Widget>[
ReorderableListView(
onReorder: (int oldIndex, int newIndex) {
setState(() {
if (newIndex > oldIndex) {
newIndex -= 1;
}
final PodcastLocal podcast =
widget.group.podcasts.removeAt(oldIndex);
widget.group.podcasts.insert(newIndex, podcast);
_controller.forward();
});
},
children: widget.group.podcasts
.map<Widget>((PodcastLocal podcastLocal) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).primaryColor),
key: ObjectKey(podcastLocal.title),
child: PodcastCard(
podcastLocal: podcastLocal,
group: widget.group,
),
);
}).toList(),
),
Positioned(
bottom: 30,
right: 30,
child: _saveButton(context),
),
],
),
),
_showSetting
? Positioned.fill(
child: GestureDetector(
onTap: () => setState(() => _showSetting = false),
child: Container(
color: Theme.of(context)
.scaffoldBackgroundColor
.withOpacity(0.5),
),
),
);
}).toList(),
),
AnimatedPositioned(
duration: Duration(seconds: 1),
bottom: 30,
right: _loadSave ? 50 : 0,
child: _saveButton(context),
),
],
),
)
: Center(),
_showSetting
? Container(
alignment: Alignment.bottomCenter,
child: Container(
height: 150.0,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
boxShadow: [
BoxShadow(
offset: Offset(0, -1),
blurRadius: 4,
color: Theme.of(context).brightness ==
Brightness.light
? Colors.grey[400]
: Colors.grey[800],
),
],
),
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Container(
height: 150,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
// mainAxisSize: MainAxisSize.min,
children: <Widget>[
Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
setState(() => _showSetting = false);
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) {
PodcastGroup newGroup =
PodcastGroup(
widget
.group.name,
color: value
.toString()
.substring(
10,
16),
id: widget
.group.id,
podcastList:
widget
.group
.podcastList);
groupList.updateGroup(
newGroup);
Navigator.of(context)
.pop();
},
pickerColor:
Colors.blue,
),
),
))));
},
child: Container(
height: 50.0,
padding:
EdgeInsets.symmetric(horizontal: 20),
child: Row(
children: <Widget>[
Icon(Icons.colorize),
Padding(
padding: EdgeInsets.symmetric(
horizontal: 5.0),
),
Text('Change Color'),
],
),
),
),
),
Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
setState(() => _showSetting = false);
widget.group.name == 'Home'
? Fluttertoast.showToast(
msg:
'Home group is not supported',
gravity: ToastGravity.BOTTOM,
)
: 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) =>
RenameGroup(
group: widget.group,
));
},
child: Container(
height: 50.0,
padding:
EdgeInsets.symmetric(horizontal: 20),
child: Row(
children: <Widget>[
Icon(Icons.text_fields),
Padding(
padding: EdgeInsets.symmetric(
horizontal: 5.0),
),
Text('Rename'),
],
),
),
),
),
Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
setState(() => _showSetting = false);
widget.group.name == 'Home'
? Fluttertoast.showToast(
msg:
'Home group is not supported',
gravity: ToastGravity.BOTTOM,
)
: 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,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.all(
Radius.circular(
10.0))),
titlePadding:
EdgeInsets.only(
top: 20,
left: 20,
right: 200,
bottom: 20),
title: Text(
'Delete confirm'),
content: Text(
'Are you sure you want to delete this group? Podcasts will be moved to Home group.'),
actions: <Widget>[
FlatButton(
onPressed: () =>
Navigator.of(
context)
.pop(),
child: Text(
'CANCEL',
style: TextStyle(
color: Colors
.grey[
600]),
),
),
FlatButton(
onPressed: () {
groupList.delGroup(
widget.group);
Navigator.of(
context)
.pop();
},
child: Text(
'CONFIRM',
style: TextStyle(
color: Colors
.red),
),
)
],
),
),
));
},
child: Container(
height: 50,
padding:
EdgeInsets.symmetric(horizontal: 20),
child: Row(
children: <Widget>[
Icon(Icons.delete_outline),
Padding(
padding: EdgeInsets.symmetric(
horizontal: 5.0),
),
Text('Delete'),
],
),
),
),
),
],
),
),
),
),
)
: Center(),
],
);
}
}
@ -330,7 +696,11 @@ class _PodcastCardState extends State<PodcastCard> {
});
}),
_buttonOnMenu(Icon(Icons.notifications), () {}),
_buttonOnMenu(Icon(Icons.remove_circle), () {
_buttonOnMenu(
Icon(
Icons.delete,
color: Colors.red,
), () {
showGeneralDialog(
context: context,
barrierDismissible: true,
@ -375,7 +745,11 @@ class _PodcastCardState extends State<PodcastCard> {
FlatButton(
onPressed: () =>
Navigator.of(context).pop(),
child: Text('CANCEL', style: TextStyle(color: Colors.grey[600]),),
child: Text(
'CANCEL',
style: TextStyle(
color: Colors.grey[600]),
),
),
FlatButton(
onPressed: () {
@ -404,3 +778,120 @@ class _PodcastCardState extends State<PodcastCard> {
);
}
}
class RenameGroup extends StatefulWidget {
final PodcastGroup group;
RenameGroup({this.group, Key key}) : super(key: key);
@override
_RenameGroupState createState() => _RenameGroupState();
}
class _RenameGroupState extends State<RenameGroup> {
TextEditingController _controller;
String _newName;
int _error;
@override
void initState() {
super.initState();
_error = 0;
_controller = TextEditingController();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
var groupList = Provider.of<GroupList>(context, listen: false);
List list = groupList.groups.map((e) => e.name).toList();
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Brightness.light,
systemNavigationBarColor:
Theme.of(context).brightness == Brightness.light
? Color.fromRGBO(113, 113, 113, 1)
: Color.fromRGBO(5, 5, 5, 1),
statusBarColor: Theme.of(context).brightness == Brightness.light
? Color.fromRGBO(113, 113, 113, 1)
: Color.fromRGBO(15, 15, 15, 1),
),
child: SafeArea(
child: AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10))),
elevation: 1,
contentPadding: EdgeInsets.symmetric(horizontal: 20),
titlePadding:
EdgeInsets.only(top: 20, left: 20, right: 200, bottom: 20),
actionsPadding: EdgeInsets.all(0),
actions: <Widget>[
FlatButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(
'CANCEL',
style: TextStyle(color: Colors.grey[600]),
),
),
FlatButton(
onPressed: () async {
if (list.contains(_newName)) {
setState(() => _error = 1);
} else {
PodcastGroup newGroup = PodcastGroup(_newName,
color: widget.group.color,
id: widget.group.id,
podcastList: widget.group.podcastList);
groupList.updateGroup(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) {
_newName = value;
},
),
Container(
alignment: Alignment.centerLeft,
child: (_error == 1)
? Text(
'Group existed',
style: TextStyle(color: Colors.red[400]),
)
: Center(),
),
],
),
),
));
}
}

View File

@ -46,6 +46,9 @@ class _AboutPodcastState extends State<AboutPodcast> {
Widget build(BuildContext context) {
var _groupList = Provider.of<GroupList>(context, listen: false);
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0))),
titlePadding: EdgeInsets.only(top: 20, left: 20, right: 200, bottom: 20),
actions: <Widget>[
FlatButton(
padding: EdgeInsets.all(10.0),
@ -99,7 +102,7 @@ class _PodcastListState extends State<PodcastList> {
statusBarColor: Theme.of(context).primaryColor,
),
child: SafeArea(
child: Scaffold(
child: Scaffold(
appBar: AppBar(
title: Text('Podcasts'),
centerTitle: true,
@ -116,7 +119,8 @@ class _PodcastListState extends State<PodcastList> {
SliverPadding(
padding: const EdgeInsets.all(10.0),
sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: 0.8,
crossAxisCount: 3,
),
@ -133,12 +137,43 @@ class _PodcastListState extends State<PodcastList> {
);
},
onLongPress: () {
showDialog(
context: context,
builder: (BuildContext context) =>
AboutPodcast(
podcastLocal: snapshot.data[index]),
).then((_) => setState(() {}));
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: AboutPodcast(
podcastLocal:
snapshot.data[index]),
),
));
},
child: Container(
alignment: Alignment.center,

View File

@ -20,12 +20,6 @@ class _PodcastManageState extends State<PodcastManage> {
));
}
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
@ -34,85 +28,85 @@ class _PodcastManageState extends State<PodcastManage> {
statusBarColor: Theme.of(context).primaryColor,
),
child: SafeArea(
child: SafeArea(
child: Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('Groups'),
actions: <Widget>[
IconButton(
onPressed: () => 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) =>
AddGroup()),
icon: Icon(Icons.add)),
OrderMenu(),
],
),
body: Consumer<GroupList>(builder: (_, groupList, __) {
bool _isLoading = groupList.isLoading;
List<PodcastGroup> _groups = groupList.groups;
return _isLoading
? Center()
: DefaultTabController(
length: _groups.length,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
height: 50,
padding: EdgeInsets.symmetric(horizontal: 10.0),
alignment: Alignment.centerLeft,
child: TabBar(
// labelColor: Colors.black,
// unselectedLabelColor: Colors.grey[500],
labelPadding: EdgeInsets.all(5.0),
indicator: getIndicator(),
isScrollable: true,
tabs: _groups.map<Tab>((group) {
return Tab(
child: Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('Groups'),
actions: <Widget>[
IconButton(
onPressed: () => 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) =>
AddGroup()),
icon: Icon(Icons.add)),
OrderMenu(),
],
),
body: Consumer<GroupList>(builder: (_, groupList, __) {
bool _isLoading = groupList.isLoading;
List<PodcastGroup> _groups = groupList.groups;
return _isLoading
? Center()
: DefaultTabController(
length: _groups.length,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
height: 50,
padding: EdgeInsets.symmetric(horizontal: 10.0),
alignment: Alignment.centerLeft,
child: TabBar(
labelColor: Colors.white,
unselectedLabelColor: Colors.black,
labelPadding: EdgeInsets.all(5.0),
indicator: getIndicator(),
isScrollable: true,
tabs: _groups.map<Tab>((group) {
return Tab(
child: Container(
height: 30.0,
padding: EdgeInsets.symmetric(
horizontal: 10.0),
padding:
EdgeInsets.symmetric(horizontal: 10.0),
alignment: Alignment.center,
decoration: BoxDecoration(
color: Theme.of(context).brightness ==
Brightness.light
? Theme.of(context).primaryColorDark
: Colors.grey[800],
borderRadius: BorderRadius.all(
Radius.circular(15)),
color: group.getColor(),
// Theme.of(context).brightness ==
// Brightness.light
// ? Theme.of(context).primaryColorDark
// : Colors.grey[800],
borderRadius:
BorderRadius.all(Radius.circular(15)),
),
child: Text(
group.name,
)),
);
}).toList(),
),
),
Expanded(
child: Container(
child: TabBarView(
children: _groups.map<Widget>((group) {
return Container(
key: ObjectKey(group),
child: PodcastGroupList(group: group));
}).toList(),
),
),
Expanded(
child: Container(
child: TabBarView(
children: _groups.map<Widget>((group) {
return Container(
key: ObjectKey(group),
child: PodcastGroupList(group: group));
}).toList(),
),
),
)
],
));
}),
),
)
],
));
}),
),
),
);
@ -214,7 +208,8 @@ class _AddGroupState extends State<AddGroup> {
Navigator.of(context).pop();
}
},
child: Text('DONE',style: TextStyle(color: Theme.of(context).accentColor)),
child: Text('DONE',
style: TextStyle(color: Theme.of(context).accentColor)),
)
],
title: Text('Create new group'),
@ -228,10 +223,12 @@ class _AddGroupState extends State<AddGroup> {
hintStyle: TextStyle(fontSize: 18),
filled: true,
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Theme.of(context).accentColor, width: 2.0),
borderSide: BorderSide(
color: Theme.of(context).accentColor, width: 2.0),
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Theme.of(context).accentColor, width: 2.0),
borderSide: BorderSide(
color: Theme.of(context).accentColor, width: 2.0),
),
),
cursorRadius: Radius.circular(2),

View File

@ -1,481 +0,0 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
archive:
dependency: transitive
description:
name: archive
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.11"
args:
dependency: transitive
description:
name: args
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.5.2"
async:
dependency: transitive
description:
name: async
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.0"
audiofileplayer:
dependency: "direct dev"
description:
name: audiofileplayer
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.1"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.5"
cached_network_image:
dependency: "direct dev"
description:
name: cached_network_image
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.2"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.14.11"
color_thief_flutter:
dependency: "direct dev"
description:
name: color_thief_flutter
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.2"
convert:
dependency: transitive
description:
name: convert
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.1"
crypto:
dependency: transitive
description:
name: crypto
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.3"
csslib:
dependency: transitive
description:
name: csslib
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.16.1"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.1.3"
dio:
dependency: "direct dev"
description:
name: dio
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.9"
file_picker:
dependency: "direct dev"
description:
name: file_picker
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.4.3+2"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_cache_manager:
dependency: transitive
description:
name: flutter_cache_manager
url: "https://pub.flutter-io.cn"
source: hosted
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:
dependency: "direct dev"
description:
name: flutter_downloader
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.4.1"
flutter_html:
dependency: "direct dev"
description:
name: flutter_html
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.11.1"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
fluttertoast:
dependency: "direct dev"
description:
name: fluttertoast
url: "https://pub.flutter-io.cn"
source: hosted
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:
dependency: "direct dev"
description:
name: google_fonts
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.3.9"
html:
dependency: transitive
description:
name: html
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.14.0+3"
http:
dependency: transitive
description:
name: http
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.12.0+4"
http_parser:
dependency: transitive
description:
name: http_parser
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.3"
image:
dependency: "direct dev"
description:
name: image
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.4"
intl:
dependency: "direct dev"
description:
name: intl
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.16.1"
json_annotation:
dependency: "direct dev"
description:
name: json_annotation
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.1"
logging:
dependency: transitive
description:
name: logging
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.11.4"
marquee:
dependency: "direct dev"
description:
name: marquee
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.1"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.12.6"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.8"
nested:
dependency: transitive
description:
name: nested
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.0.4"
path:
dependency: transitive
description:
name: path
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.6.4"
path_provider:
dependency: "direct dev"
description:
name: path_provider
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.6.1"
pedantic:
dependency: transitive
description:
name: pedantic
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.8.0+1"
permission_handler:
dependency: "direct dev"
description:
name: permission_handler
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.2.0+hotfix.3"
petitparser:
dependency: transitive
description:
name: petitparser
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.0"
platform:
dependency: transitive
description:
name: platform
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.2.1"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.2"
provider:
dependency: "direct dev"
description:
name: provider
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.0.4"
quantize_dart:
dependency: transitive
description:
name: quantize_dart
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.3"
quiver:
dependency: transitive
description:
name: quiver
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.5"
shared_preferences:
dependency: "direct dev"
description:
name: shared_preferences
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.5.6+2"
shared_preferences_macos:
dependency: transitive
description:
name: shared_preferences_macos
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.0.1+6"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.3"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.1.2+4"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.5.5"
sqflite:
dependency: "direct dev"
description:
name: sqflite
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.2.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.9.3"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.5"
synchronized:
dependency: transitive
description:
name: synchronized
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.2.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.flutter-io.cn"
source: hosted
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:
dependency: transitive
description:
name: typed_data
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.6"
url_launcher:
dependency: "direct dev"
description:
name: url_launcher
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.4.2"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.0.1+4"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.6"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.1.1+1"
uuid:
dependency: "direct dev"
description:
name: uuid
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.4"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.8"
workmanager:
dependency: "direct dev"
description:
name: workmanager
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.0"
xml:
dependency: "direct dev"
description:
name: xml
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.5.0"
sdks:
dart: ">=2.6.0 <3.0.0"
flutter: ">=1.12.13+hotfix.4 <2.0.0"

View File

@ -32,18 +32,18 @@ dev_dependencies:
flutter_html: ^0.11.1
path_provider: ^1.6.1
color_thief_flutter: ^1.0.1
provider: ^4.0.1
google_fonts: ^0.3.2
provider: ^4.0.4
google_fonts: ^0.3.9
dio: ^3.0.9
file_picker: ^1.2.0
file_picker: ^1.4.3+2
xml: ^3.5.0
marquee: ^1.3.1
audiofileplayer: ^1.1.1
flutter_downloader: ^1.4.1
permission_handler: ^4.2.0+hotfix.3
permission_handler: ^4.3.0
fluttertoast: ^3.1.3
intl: ^0.16.1
url_launcher: ^5.4.1
url_launcher: ^5.4.2
image: ^2.1.4
shared_preferences: ^0.5.6+1
uuid: ^2.0.4
@ -52,6 +52,7 @@ dev_dependencies:
workmanager: ^0.2.0
font_awesome_flutter: ^8.7.0
flutter_colorpicker: ^0.3.2
lazy_loading_list: ^1.0.0+1