Back to saty in background

Playlist UI change
This commit is contained in:
stonegate 2020-03-22 00:14:10 +08:00
parent c5fcfd239b
commit 64dee98523
29 changed files with 447 additions and 249 deletions

View File

@ -2,7 +2,7 @@ version: 2
jobs:
build:
docker:
- image: cirrusci/flutter:v1.14.6
- image: cirrusci/flutter:v1.15.17
branches:
only: master

View File

@ -79,7 +79,7 @@ flutter {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

View File

@ -3,10 +3,24 @@ package com.stonegate.tsacdop
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.view.FlutterNativeView
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.embedding.engine.dart.DartExecutor
import io.flutter.embedding.engine.dart.DartExecutor.DartCallback
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
MethodChannel(flutterEngine.dartExecutor, "android_app_retain").apply {
setMethodCallHandler { method, result ->
if (method.method == "sendToBackground") {
moveTaskToBack(true)
}
}
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 725 B

After

Width:  |  Height:  |  Size: 633 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 427 B

After

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 822 B

After

Width:  |  Height:  |  Size: 758 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1,12 +1,12 @@
buildscript {
ext.kotlin_version = '1.3.50'
ext.kotlin_version = '1.3.70'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.3'
classpath 'com.android.tools.build:gradle:3.6.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

View File

@ -1,6 +1,6 @@
#Fri Jun 23 08:50:38 CEST 2017
#Fri Mar 20 23:46:20 CST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip

View File

@ -9,7 +9,6 @@ import 'package:tsacdop/local_storage/key_value_storage.dart';
void callbackDispatcher() {
Workmanager.executeTask((task, inputData) async {
var dbHelper = DBHelper();
print('Start task');
List<PodcastLocal> podcastList = await dbHelper.getPodcastLocalAll();
int i = 0;
await Future.forEach(podcastList, (podcastLocal) async {
@ -135,7 +134,7 @@ class SettingState extends ChangeNotifier {
Future _getUpdateInterval() async {
_initUpdateTag = await intervalstorage.getInt();
_updateInterval = _initUpdateTag == 0 ? 24 : _initUpdateTag;
_updateInterval = _initUpdateTag;
}
Future _saveUpdateInterval() async {

View File

@ -11,6 +11,8 @@ import 'package:fluttertoast/fluttertoast.dart';
import 'package:intl/intl.dart';
import 'package:tuple/tuple.dart';
import 'package:audio_service/audio_service.dart';
import 'package:flutter_linkify/flutter_linkify.dart';
import 'package:tsacdop/class/audiostate.dart';
import 'package:tsacdop/class/episodebrief.dart';
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
@ -31,10 +33,11 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
bool _loaddes;
bool _showMenu;
String path;
String _description;
Future getSDescription(String url) async {
var dbHelper = DBHelper();
widget.episodeItem.description = (await dbHelper.getDescription(url))
.replaceAll(RegExp(r'\s?<p>(<br>)?</p>\s?'), '');
_description = (await dbHelper.getDescription(url))
.replaceAll(RegExp(r'\s?<p>(<br>)?</p>\s?'), '').replaceAll('\r', '');
if (mounted)
setState(() {
_loaddes = true;
@ -85,12 +88,11 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
systemNavigationBarColor: Theme.of(context).primaryColor,
systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness,
// statusBarColor: Theme.of(context).primaryColor,
),
child: Scaffold(
backgroundColor: Theme.of(context).primaryColor,
appBar: AppBar(
title: Text(widget.episodeItem.feedTitle),
// title: Text(widget.episodeItem.feedTitle),
centerTitle: true,
),
body: Stack(
@ -162,7 +164,8 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
style: textstyle),
)
: Center(),
widget.episodeItem.enclosureLength != null
widget.episodeItem.enclosureLength != null &&
widget.episodeItem.enclosureLength != 0
? Container(
decoration: BoxDecoration(
color: Colors.lightBlue[300],
@ -193,15 +196,15 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
padding: EdgeInsets.only(top: 5.0),
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
//physics: const AlwaysScrollableScrollPhysics(),
physics: AlwaysScrollableScrollPhysics(),
controller: _controller,
child: _loaddes
? (widget.episodeItem.description.contains('<'))
? (_description.contains('<'))
? Html(
padding:
EdgeInsets.symmetric(horizontal: 20.0),
defaultTextStyle: TextStyle(height: 1.8),
data: widget.episodeItem.description,
data: _description,
linkStyle: TextStyle(
color: Theme.of(context).accentColor,
decoration: TextDecoration.underline,
@ -215,12 +218,20 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
padding:
EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment.topLeft,
child: Text(
widget.episodeItem.description,
child: SelectableLinkify(
onOpen: (link) {
_launchUrl(link.url);
},
text: _description,
style: TextStyle(
height: 1.8,
),
))
linkStyle: TextStyle(
color: Theme.of(context).accentColor,
decoration: TextDecoration.underline,
),
),
)
: Center(),
),
),

View File

@ -32,6 +32,8 @@ class _MyHomePageState extends State<MyHomePage> {
final _MyHomePageDelegate _delegate = _MyHomePageDelegate();
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
var _androidAppRetain = MethodChannel("android_app_retain");
@override
void initState() {
super.initState();
@ -71,7 +73,16 @@ class _MyHomePageState extends State<MyHomePage> {
PopupMenu(),
],
),
body: Home(),
body: WillPopScope(
onWillPop: () async {
if (Platform.isAndroid) {
_androidAppRetain.invokeMethod('sendToBackground');
return false;
} else {
return true;
}
},
child: Home()),
),
);
}

View File

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

View File

@ -40,7 +40,7 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
bool isLoading = groupList.isLoading;
return isLoading
? Container(
height: (_width - 20) / 3 + 110,
height: (_width - 20) / 3 + 140,
)
: groups[_groupIndex].podcastList.length == 0
? Column(

View File

@ -1,4 +1,5 @@
import 'dart:io';
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -6,6 +7,7 @@ import 'package:provider/provider.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:tsacdop/episodes/episodedetail.dart';
import 'package:tuple/tuple.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:line_icons/line_icons.dart';
import 'package:tsacdop/class/audiostate.dart';
import 'package:tsacdop/class/episodebrief.dart';
@ -19,7 +21,8 @@ class PlaylistPage extends StatefulWidget {
class _PlaylistPageState extends State<PlaylistPage> {
final GlobalKey<AnimatedListState> _playlistKey = GlobalKey();
final textstyle = TextStyle(fontSize: 15.0, color: Colors.black);
Widget episodeTag(String text, Color color) {
Widget _episodeTag(String text, Color color) {
return Container(
decoration: BoxDecoration(
color: color, borderRadius: BorderRadius.all(Radius.circular(15.0))),
@ -31,6 +34,40 @@ class _PlaylistPageState extends State<PlaylistPage> {
);
}
int _sumPlaylistLength(List<EpisodeBrief> episodes) {
int sum = 0;
if (episodes.length == 0) {
return sum;
} else {
episodes.forEach((episode) {
sum += episode.duration;
});
return sum;
}
}
ScrollController _controller;
_scrollListener() {
double value = _controller.offset;
setState(() => _topHeight = (100 - value) > 0 ? 100 - value : 0);
}
double _topHeight;
@override
void initState() {
super.initState();
_topHeight = 100;
_controller = ScrollController();
_controller.addListener(_scrollListener);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
@ -42,8 +79,9 @@ class _PlaylistPageState extends State<PlaylistPage> {
systemNavigationBarColor: Theme.of(context).primaryColor,
),
child: Scaffold(
backgroundColor: Theme.of(context).primaryColor,
appBar: AppBar(
title: Text('Playlist'),
title: _topHeight == 0 ? Text('Playlist') : Center(),
elevation: 0,
backgroundColor: Theme.of(context).primaryColor,
),
@ -53,137 +91,225 @@ class _PlaylistPageState extends State<PlaylistPage> {
selector: (_, audio) =>
Tuple2(audio.queue.playlist, audio.playerRunning),
builder: (_, data, __) {
return AnimatedList(
key: _playlistKey,
shrinkWrap: true,
scrollDirection: Axis.vertical,
initialItemCount: data.item1.length,
itemBuilder: (context, index, animation) {
Color _c =
(Theme.of(context).brightness == Brightness.light)
? data.item1[index].primaryColor.colorizedark()
: data.item1[index].primaryColor.colorizeLight();
return ScaleTransition(
alignment: Alignment.centerLeft,
scale: animation,
child: Dismissible(
background: Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Icon(
Icons.delete,
color: Theme.of(context).accentColor,
),
Icon(
Icons.delete,
color: Theme.of(context).accentColor,
),
],
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Transform.scale(
alignment: Alignment.topLeft,
scale: _topHeight / 100,
child: Container(
height: _topHeight,
padding: EdgeInsets.only(
bottom: (_topHeight - 60) > 0 ? _topHeight - 60 : 0,
left: 60),
alignment: Alignment.bottomLeft,
child: RichText(
text: TextSpan(
text: 'Total ',
style: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 20,
),
height: 50,
color: Colors.grey[500],
),
key: Key(data.item1[index].enclosureUrl),
onDismissed: (direction) async {
await audio.delFromPlaylist(data.item1[index]);
_playlistKey.currentState.removeItem(
index, (context, animation) => Center());
Fluttertoast.showToast(
msg: 'Removed From Playlist',
gravity: ToastGravity.BOTTOM,
);
},
child: Column(
children: <Widget>[
ListTile(
title: Text(
data.item1[index].title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
leading: CircleAvatar(
backgroundColor: _c.withOpacity(0.5),
backgroundImage: FileImage(
File("${data.item1[index].imagePath}")),
),
trailing: index == 0
? data.item2
? Padding(
padding: const EdgeInsets.only(
right: 12.0),
child: SizedBox(
width: 20,
height: 15,
child: WaveLoader()),
)
: IconButton(
icon: Icon(Icons.play_arrow),
onPressed: () => audio.playlistLoad())
: IconButton(
tooltip: 'Move to Top',
icon:
Icon(LineIcons.arrow_circle_up_solid),
onPressed: () async {
await audio
.moveToTop(data.item1[index]);
_playlistKey.currentState.removeItem(
index,
(context, animation) =>
Container());
data.item2
? _playlistKey.currentState
.insertItem(1)
: _playlistKey.currentState
.insertItem(0);
}),
subtitle: Container(
padding: EdgeInsets.symmetric(vertical: 5),
child: Row(
children: <Widget>[
(data.item1[index].explicit == 1)
? Container(
decoration: BoxDecoration(
color: Colors.red[800],
shape: BoxShape.circle),
height: 20.0,
width: 20.0,
margin:
EdgeInsets.only(right: 10.0),
alignment: Alignment.center,
child: Text('E',
style: TextStyle(
color: Colors.white)))
: Center(),
data.item1[index].duration != 0
? episodeTag(
(data.item1[index].duration)
.toString() +
'mins',
Colors.cyan[300])
: Center(),
data.item1[index].enclosureLength != null
? episodeTag(
((data.item1[index]
.enclosureLength) ~/
1000000)
.toString() +
'MB',
Colors.lightBlue[300])
: Center(),
],
),
),
),
Divider(
height: 2,
children: <TextSpan>[
TextSpan(
text: data.item1.length.toString(),
style: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 40,
)),
TextSpan(
text: data.item1.length < 2
? ' episode'
: ' episodes ',
style: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 20,
)),
TextSpan(
text: _sumPlaylistLength(data.item1).toString(),
style: GoogleFonts.teko(
textStyle: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 60,
)),
),
TextSpan(
text: ' mins',
style: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 20,
)),
],
),
),
);
});
),
),
Expanded(
child: AnimatedList(
controller: _controller,
key: _playlistKey,
shrinkWrap: true,
scrollDirection: Axis.vertical,
initialItemCount: data.item1.length,
itemBuilder: (context, index, animation) {
Color _c = (Theme.of(context).brightness ==
Brightness.light)
? data.item1[index].primaryColor.colorizedark()
: data.item1[index].primaryColor.colorizeLight();
return ScaleTransition(
alignment: Alignment.centerLeft,
scale: animation,
child: Dismissible(
background: Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: <Widget>[
Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.red),
padding: EdgeInsets.all(5),
child: Icon(
LineIcons.trash_alt_solid,
color: Colors.white,
size: 15,
),
),
Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.red),
padding: EdgeInsets.all(5),
child: Icon(
LineIcons.trash_alt_solid,
color: Colors.white,
size: 15,
),
),
],
),
height: 50,
color: Theme.of(context).accentColor,
),
key: Key(data.item1[index].enclosureUrl),
onDismissed: (direction) async {
await audio.delFromPlaylist(data.item1[index]);
_playlistKey.currentState.removeItem(
index, (context, animation) => Center());
Fluttertoast.showToast(
msg: 'Removed From Playlist',
gravity: ToastGravity.BOTTOM,
);
},
child: Column(
children: <Widget>[
ListTile(
title: Container(
padding: EdgeInsets.only(
top: 10.0, bottom: 5.0),
child: Text(
data.item1[index].title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
leading: CircleAvatar(
backgroundColor: _c.withOpacity(0.5),
backgroundImage: FileImage(File(
"${data.item1[index].imagePath}")),
),
trailing: index == 0
? data.item2
? Padding(
padding: const EdgeInsets.only(
right: 12.0),
child: SizedBox(
width: 20,
height: 15,
child: WaveLoader()),
)
: IconButton(
icon: Icon(Icons.play_arrow),
onPressed: () =>
audio.playlistLoad())
: Transform.rotate(
angle: math.pi,
child: IconButton(
tooltip: 'Move to Top',
icon: Icon(
LineIcons.download_solid),
onPressed: () async {
await audio.moveToTop(
data.item1[index]);
_playlistKey.currentState
.removeItem(
index,
(context,
animation) =>
Container());
data.item2
? _playlistKey
.currentState
.insertItem(1)
: _playlistKey
.currentState
.insertItem(0);
}),
),
subtitle: Container(
padding:
EdgeInsets.only(top: 5, bottom: 10),
child: Row(
children: <Widget>[
(data.item1[index].explicit == 1)
? Container(
decoration: BoxDecoration(
color: Colors.red[800],
shape: BoxShape.circle),
height: 20.0,
width: 20.0,
margin: EdgeInsets.only(
right: 10.0),
alignment: Alignment.center,
child: Text('E',
style: TextStyle(
color: Colors.white)))
: Center(),
data.item1[index].duration != 0
? _episodeTag(
(data.item1[index].duration)
.toString() +
'mins',
Colors.cyan[300])
: Center(),
data.item1[index].enclosureLength !=
null
? _episodeTag(
((data.item1[index]
.enclosureLength) ~/
1000000)
.toString() +
'MB',
Colors.lightBlue[300])
: Center(),
],
),
),
),
Divider(
height: 2,
),
],
),
),
);
}),
),
],
);
},
),
),

View File

@ -417,7 +417,7 @@ class DBHelper {
}
final title = feed.items[i].itunes.title ?? feed.items[i].title;
final length = feed.items[i]?.enclosure?.length;
final length = feed.items[i]?.enclosure?.length ?? 0;
final pubDate = feed.items[i].pubDate;
final date = _parsePubDate(pubDate);
final milliseconds = date.millisecondsSinceEpoch;

View File

@ -2,28 +2,12 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter/services.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:workmanager/workmanager.dart';
import 'package:tsacdop/class/podcastlocal.dart';
import 'package:tsacdop/class/podcast_group.dart';
import 'package:tsacdop/home/appbar/addpodcast.dart';
import 'package:tsacdop/class/audiostate.dart';
import 'package:tsacdop/class/importompl.dart';
import 'package:tsacdop/class/settingstate.dart';
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
void callbackDispatcher() {
Workmanager.executeTask((task, inputData) async {
var dbHelper = DBHelper();
print('Start task');
List<PodcastLocal> podcastList = await dbHelper.getPodcastLocalAll();
await Future.forEach(podcastList, (podcastLocal) async {
await dbHelper.updatePodcastRss(podcastLocal);
print('Refresh ' + podcastLocal.title);
});
return Future.value(true);
});
}
final SettingState themeSetting = SettingState();
Future main() async {

View File

@ -6,8 +6,8 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:html/parser.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:flutter_linkify/flutter_linkify.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:tsacdop/class/podcastlocal.dart';
@ -389,6 +389,14 @@ class _AboutPodcastState extends State<AboutPodcast> {
setState(() => _load = true);
}
_launchUrl(String url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
@override
void initState() {
super.initState();
@ -419,8 +427,15 @@ class _AboutPodcastState extends State<AboutPodcast> {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
_description,
Linkify(
onOpen: (link) {
_launchUrl(link.url);
},
text: _description,
linkStyle: TextStyle(
color: Theme.of(context).accentColor,
decoration: TextDecoration.underline,
textBaseline: TextBaseline.ideographic),
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
@ -432,12 +447,27 @@ class _AboutPodcastState extends State<AboutPodcast> {
),
],
)
: Text(_description),
: Linkify(
onOpen: (link) {
_launchUrl(link.url);
},
text: _description,
linkStyle: TextStyle(
color: Theme.of(context).accentColor,
decoration: TextDecoration.underline,
textBaseline: TextBaseline.ideographic),
),
);
} else {
return SelectableText(
_description,
toolbarOptions: ToolbarOptions(copy: true),
return Linkify(
text: _description,
onOpen: (link) {
_launchUrl(link.url);
},
linkStyle: TextStyle(
color: Theme.of(context).accentColor,
decoration: TextDecoration.underline,
textBaseline: TextBaseline.ideographic),
);
}
},

View File

@ -301,50 +301,47 @@ class _PodcastCardState extends State<PodcastCard> {
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),
// 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('Remove confirm'),
content: Text(
'Are you sure you want to unsubscribe?'),
actions: <Widget>[
FlatButton(
onPressed: () =>
Navigator.of(context).pop(),
child: Text(
'CANCEL',
style: TextStyle(
color: Colors.grey[600]),
),
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('Remove confirm'),
content: Text(
'Are you sure you want to unsubscribe?'),
actions: <Widget>[
FlatButton(
onPressed: () =>
Navigator.of(context).pop(),
child: Text(
'CANCEL',
style: TextStyle(
color: Colors.grey[600]),
),
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

@ -136,9 +136,9 @@ class _DownloadsManageState extends State<DownloadsManage> {
TextSpan(
text: _fileNum.toString(),
style: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 40,
fontWeight: FontWeight.bold)),
color: Theme.of(context).accentColor,
fontSize: 40,
)),
TextSpan(
text: _fileNum < 2 ? ' episode' : ' episodes ',
style: TextStyle(
@ -148,9 +148,9 @@ class _DownloadsManageState extends State<DownloadsManage> {
TextSpan(
text: (_size ~/ 1000000).toString(),
style: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 60,
fontWeight: FontWeight.bold)),
color: Theme.of(context).accentColor,
fontSize: 60,
)),
TextSpan(
text: ' Mb',
style: TextStyle(

View File

@ -59,8 +59,8 @@ class Settings extends StatelessWidget {
backgroundColor: Theme.of(context).primaryColor,
),
body: SafeArea(
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: SingleChildScrollView(
//physics: const AlwaysScrollableScrollPhysics(),
scrollDirection: Axis.vertical,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
@ -93,14 +93,16 @@ class Settings extends StatelessWidget {
context,
MaterialPageRoute(
builder: (context) => ThemeSetting())),
contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
contentPadding:
EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(LineIcons.adjust_solid),
title: Text('Appearance'),
subtitle: Text('Colors and themes'),
),
Divider(height: 2),
ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
contentPadding:
EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(LineIcons.play_circle),
title: Text('AutoPlay'),
subtitle: Text('Autoplay next episode in playlist'),
@ -117,7 +119,8 @@ class Settings extends StatelessWidget {
context,
MaterialPageRoute(
builder: (context) => SyncingSetting())),
contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
contentPadding:
EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(LineIcons.cloud_download_alt_solid),
title: Text('Syncing'),
subtitle: Text('Refresh podcasts in the background'),
@ -128,7 +131,8 @@ class Settings extends StatelessWidget {
context,
MaterialPageRoute(
builder: (context) => StorageSetting())),
contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
contentPadding:
EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(LineIcons.save),
title: Text('Storage'),
subtitle: Text('Manage cache and download storage'),
@ -139,7 +143,8 @@ class Settings extends StatelessWidget {
context,
MaterialPageRoute(
builder: (context) => PlayedHistory())),
contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
contentPadding:
EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(Icons.update),
title: Text('History'),
subtitle: Text('Listen data'),
@ -149,7 +154,8 @@ class Settings extends StatelessWidget {
onTap: () {
_exportOmpl();
},
contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
contentPadding:
EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(LineIcons.file_code_solid),
title: Text('Export'),
subtitle: Text('Export ompl file'),
@ -184,25 +190,31 @@ class Settings extends StatelessWidget {
ListTile(
onTap: () => _launchUrl(
'https://github.com/stonega/tsacdop/releases'),
contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
contentPadding:
EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(LineIcons.map_signs_solid),
title: Text('Changelog'),
subtitle: Text('List of chagnes'),
),
Divider(height: 2),
ListTile(
onTap: () => Navigator.push(context,
MaterialPageRoute(builder: (context) => Libries())),
contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Libries())),
contentPadding:
EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(LineIcons.book_open_solid),
title: Text('Libraries'),
subtitle: Text('Open source libraries in application'),
subtitle:
Text('Open source libraries in application'),
),
Divider(height: 2),
ListTile(
onTap: () => _launchUrl(
'mailto:<xijieyin@gmail.com>?subject=Tsacdop Feedback'),
contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
contentPadding:
EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(LineIcons.bug_solid),
title: Text('Feedback'),
subtitle: Text('Bugs and feature requests'),

View File

@ -42,6 +42,7 @@ class StorageSetting extends StatelessWidget {
.copyWith(color: Theme.of(context).accentColor)),
),
ListView(
physics: const BouncingScrollPhysics(),
shrinkWrap: true,
scrollDirection: Axis.vertical,
children: <Widget>[

View File

@ -48,6 +48,7 @@ class SyncingSetting extends StatelessWidget {
.copyWith(color: Theme.of(context).accentColor)),
),
ListView(
physics: const BouncingScrollPhysics(),
shrinkWrap: true,
scrollDirection: Axis.vertical,
children: <Widget>[

View File

@ -43,6 +43,7 @@ class ThemeSetting extends StatelessWidget {
.copyWith(color: Theme.of(context).accentColor)),
),
ListView(
physics: const BouncingScrollPhysics(),
shrinkWrap: true,
scrollDirection: Axis.vertical,
children: <Widget>[

View File

@ -31,7 +31,7 @@ class EpisodeGrid extends StatelessWidget {
this.heroTag,
this.updateCount = 0})
: super(key: key);
@override
Widget build(BuildContext context) {
double _width = MediaQuery.of(context).size.width;
@ -46,7 +46,6 @@ class EpisodeGrid extends StatelessWidget {
borderRadius: BorderRadius.all(Radius.circular(10))),
context: context,
position: RelativeRect.fromLTRB(left, top, _width - left, 0),
items: <PopupMenuEntry<int>>[
PopupMenuItem(
value: 0,
@ -85,7 +84,7 @@ class EpisodeGrid extends StatelessWidget {
if (value == 0) {
if (!isPlaying) audio.episodeLoad(episode);
} else if (value == 1) {
if (!isInPlaylist) {
if (!isInPlaylist) {
audio.addToPlaylist(episode);
Fluttertoast.showToast(
msg: 'Added to playlist',
@ -97,7 +96,7 @@ class EpisodeGrid extends StatelessWidget {
msg: 'Removed from playlist',
gravity: ToastGravity.BOTTOM,
);
}
}
}
});
}
@ -187,7 +186,15 @@ class EpisodeGrid extends StatelessWidget {
),
),
Spacer(),
index < updateCount ? Text('New', style: TextStyle(color: Colors.red, fontStyle: FontStyle.italic)) : Center(),
index < updateCount
? Text('New',
style: TextStyle(
color: Colors.red,
fontStyle: FontStyle.italic))
: Center(),
Padding(
padding: EdgeInsets.symmetric(horizontal: 2),
),
showNumber
? Container(
alignment: Alignment.topRight,
@ -371,7 +378,7 @@ class _DownloadIconState extends State<DownloadIcon> {
child: CircularProgressIndicator(
backgroundColor: Colors.grey[200],
strokeWidth: 1,
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
valueColor: AlwaysStoppedAnimation<Color>(Theme.of(context).accentColor),
value: task.progress / 100,
),
);
@ -391,7 +398,7 @@ class _DownloadIconState extends State<DownloadIcon> {
data: IconThemeData(size: 15),
child: Icon(
Icons.done_all,
color: Colors.blue,
color: Theme.of(context).accentColor,
),
);
} else if (task.status == DownloadTaskStatus.failed) {

View File

@ -347,6 +347,8 @@ Future<T> _showMenu<T>({
break;
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
label = semanticLabel ?? MaterialLocalizations.of(context)?.popupMenuLabel;
}
@ -469,6 +471,8 @@ class MyPopupMenuButtonState<T> extends State<MyPopupMenuButton<T>> {
return const Icon(Icons.more_vert);
case TargetPlatform.iOS:
case TargetPlatform.macOS:
case TargetPlatform.linux:
case TargetPlatform.windows:
return const Icon(Icons.more_horiz);
}
return null;

View File

@ -49,8 +49,7 @@ class RssItem {
}
return RssItem(
title: findElementOrNull(element, "title")?.text,
description: findElementOrNull(element, "description")?.text?.trim()
,
description: findElementOrNull(element, "description")?.text?.trim(),
link: findElementOrNull(element, "link")?.text?.trim(),
categories: element.findElements("category").map((element) {
return RssCategory.parse(element);
@ -60,7 +59,7 @@ class RssItem {
author: findElementOrNull(element, "author")?.text?.trim(),
// comments: findElementOrNull(element, "comments")?.text,
// source: RssSource.parse(findElementOrNull(element, "source")),
content: RssContent.parse(findElementOrNull(element, "content:encoded")),
content: RssContent.parse(findElementOrNull(element, "content:encoded")),
// media: Media.parse(element),
enclosure: RssEnclosure.parse(findElementOrNull(element, "enclosure")),
//dc: DublinCore.parse(element),

View File

@ -28,7 +28,7 @@ dev_dependencies:
flutter_test:
sdk: flutter
json_annotation: ^3.0.1
sqflite: ^1.2.2+1
sqflite: ^1.3.0
flutter_html: ^0.11.1
path_provider: ^1.6.1
color_thief_flutter: ^1.0.1
@ -59,6 +59,7 @@ dev_dependencies:
git:
url: https://github.com/galonsos/line_icons.git
flutter_file_dialog: ^0.0.5
flutter_linkify: ^3.1.0
# For information on the generic Dart part of this file, see the