|
@ -2,7 +2,7 @@ version: 2
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
docker:
|
docker:
|
||||||
- image: cirrusci/flutter:v1.14.6
|
- image: cirrusci/flutter:v1.15.17
|
||||||
|
|
||||||
branches:
|
branches:
|
||||||
only: master
|
only: master
|
||||||
|
|
|
@ -79,7 +79,7 @@ flutter {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.13'
|
||||||
androidTestImplementation 'androidx.test:runner:1.1.1'
|
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,24 @@ package com.stonegate.tsacdop
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
|
import io.flutter.view.FlutterNativeView
|
||||||
import io.flutter.plugins.GeneratedPluginRegistrant
|
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() {
|
class MainActivity: FlutterActivity() {
|
||||||
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
||||||
GeneratedPluginRegistrant.registerWith(flutterEngine);
|
GeneratedPluginRegistrant.registerWith(flutterEngine);
|
||||||
|
|
||||||
|
MethodChannel(flutterEngine.dartExecutor, "android_app_retain").apply {
|
||||||
|
setMethodCallHandler { method, result ->
|
||||||
|
if (method.method == "sendToBackground") {
|
||||||
|
moveTaskToBack(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Before Width: | Height: | Size: 725 B After Width: | Height: | Size: 633 B |
Before Width: | Height: | Size: 427 B After Width: | Height: | Size: 390 B |
Before Width: | Height: | Size: 822 B After Width: | Height: | Size: 758 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.6 KiB |
|
@ -1,12 +1,12 @@
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.3.50'
|
ext.kotlin_version = '1.3.70'
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
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"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#Fri Jun 23 08:50:38 CEST 2017
|
#Fri Mar 20 23:46:20 CST 2020
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
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
|
||||||
|
|
|
@ -9,7 +9,6 @@ import 'package:tsacdop/local_storage/key_value_storage.dart';
|
||||||
void callbackDispatcher() {
|
void callbackDispatcher() {
|
||||||
Workmanager.executeTask((task, inputData) async {
|
Workmanager.executeTask((task, inputData) async {
|
||||||
var dbHelper = DBHelper();
|
var dbHelper = DBHelper();
|
||||||
print('Start task');
|
|
||||||
List<PodcastLocal> podcastList = await dbHelper.getPodcastLocalAll();
|
List<PodcastLocal> podcastList = await dbHelper.getPodcastLocalAll();
|
||||||
int i = 0;
|
int i = 0;
|
||||||
await Future.forEach(podcastList, (podcastLocal) async {
|
await Future.forEach(podcastList, (podcastLocal) async {
|
||||||
|
@ -135,7 +134,7 @@ class SettingState extends ChangeNotifier {
|
||||||
|
|
||||||
Future _getUpdateInterval() async {
|
Future _getUpdateInterval() async {
|
||||||
_initUpdateTag = await intervalstorage.getInt();
|
_initUpdateTag = await intervalstorage.getInt();
|
||||||
_updateInterval = _initUpdateTag == 0 ? 24 : _initUpdateTag;
|
_updateInterval = _initUpdateTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _saveUpdateInterval() async {
|
Future _saveUpdateInterval() async {
|
||||||
|
|
|
@ -11,6 +11,8 @@ import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
import 'package:audio_service/audio_service.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/audiostate.dart';
|
||||||
import 'package:tsacdop/class/episodebrief.dart';
|
import 'package:tsacdop/class/episodebrief.dart';
|
||||||
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
|
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
|
||||||
|
@ -31,10 +33,11 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
||||||
bool _loaddes;
|
bool _loaddes;
|
||||||
bool _showMenu;
|
bool _showMenu;
|
||||||
String path;
|
String path;
|
||||||
|
String _description;
|
||||||
Future getSDescription(String url) async {
|
Future getSDescription(String url) async {
|
||||||
var dbHelper = DBHelper();
|
var dbHelper = DBHelper();
|
||||||
widget.episodeItem.description = (await dbHelper.getDescription(url))
|
_description = (await dbHelper.getDescription(url))
|
||||||
.replaceAll(RegExp(r'\s?<p>(<br>)?</p>\s?'), '');
|
.replaceAll(RegExp(r'\s?<p>(<br>)?</p>\s?'), '').replaceAll('\r', '');
|
||||||
if (mounted)
|
if (mounted)
|
||||||
setState(() {
|
setState(() {
|
||||||
_loaddes = true;
|
_loaddes = true;
|
||||||
|
@ -85,12 +88,11 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
||||||
systemNavigationBarColor: Theme.of(context).primaryColor,
|
systemNavigationBarColor: Theme.of(context).primaryColor,
|
||||||
systemNavigationBarIconBrightness:
|
systemNavigationBarIconBrightness:
|
||||||
Theme.of(context).accentColorBrightness,
|
Theme.of(context).accentColorBrightness,
|
||||||
// statusBarColor: Theme.of(context).primaryColor,
|
|
||||||
),
|
),
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
backgroundColor: Theme.of(context).primaryColor,
|
backgroundColor: Theme.of(context).primaryColor,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(widget.episodeItem.feedTitle),
|
// title: Text(widget.episodeItem.feedTitle),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
),
|
),
|
||||||
body: Stack(
|
body: Stack(
|
||||||
|
@ -162,7 +164,8 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
||||||
style: textstyle),
|
style: textstyle),
|
||||||
)
|
)
|
||||||
: Center(),
|
: Center(),
|
||||||
widget.episodeItem.enclosureLength != null
|
widget.episodeItem.enclosureLength != null &&
|
||||||
|
widget.episodeItem.enclosureLength != 0
|
||||||
? Container(
|
? Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.lightBlue[300],
|
color: Colors.lightBlue[300],
|
||||||
|
@ -193,15 +196,15 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
||||||
padding: EdgeInsets.only(top: 5.0),
|
padding: EdgeInsets.only(top: 5.0),
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
scrollDirection: Axis.vertical,
|
scrollDirection: Axis.vertical,
|
||||||
//physics: const AlwaysScrollableScrollPhysics(),
|
physics: AlwaysScrollableScrollPhysics(),
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
child: _loaddes
|
child: _loaddes
|
||||||
? (widget.episodeItem.description.contains('<'))
|
? (_description.contains('<'))
|
||||||
? Html(
|
? Html(
|
||||||
padding:
|
padding:
|
||||||
EdgeInsets.symmetric(horizontal: 20.0),
|
EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
defaultTextStyle: TextStyle(height: 1.8),
|
defaultTextStyle: TextStyle(height: 1.8),
|
||||||
data: widget.episodeItem.description,
|
data: _description,
|
||||||
linkStyle: TextStyle(
|
linkStyle: TextStyle(
|
||||||
color: Theme.of(context).accentColor,
|
color: Theme.of(context).accentColor,
|
||||||
decoration: TextDecoration.underline,
|
decoration: TextDecoration.underline,
|
||||||
|
@ -215,12 +218,20 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
||||||
padding:
|
padding:
|
||||||
EdgeInsets.symmetric(horizontal: 20.0),
|
EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
alignment: Alignment.topLeft,
|
alignment: Alignment.topLeft,
|
||||||
child: Text(
|
child: SelectableLinkify(
|
||||||
widget.episodeItem.description,
|
onOpen: (link) {
|
||||||
|
_launchUrl(link.url);
|
||||||
|
},
|
||||||
|
text: _description,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
height: 1.8,
|
height: 1.8,
|
||||||
),
|
),
|
||||||
))
|
linkStyle: TextStyle(
|
||||||
|
color: Theme.of(context).accentColor,
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
: Center(),
|
: Center(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -32,6 +32,8 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
final _MyHomePageDelegate _delegate = _MyHomePageDelegate();
|
final _MyHomePageDelegate _delegate = _MyHomePageDelegate();
|
||||||
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
|
|
||||||
|
var _androidAppRetain = MethodChannel("android_app_retain");
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
@ -71,7 +73,16 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
PopupMenu(),
|
PopupMenu(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: Home(),
|
body: WillPopScope(
|
||||||
|
onWillPop: () async {
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
_androidAppRetain.invokeMethod('sendToBackground');
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Home()),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'hometab.dart';
|
import 'hometab.dart';
|
||||||
import 'package:tsacdop/home/appbar/importompl.dart';
|
import 'package:tsacdop/home/appbar/importompl.dart';
|
||||||
import 'package:tsacdop/home/audioplayer.dart';
|
import 'package:tsacdop/home/audioplayer.dart';
|
||||||
import 'homescroll.dart';
|
import 'home_groups.dart';
|
||||||
|
|
||||||
class Home extends StatelessWidget {
|
class Home extends StatelessWidget {
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
|
||||||
bool isLoading = groupList.isLoading;
|
bool isLoading = groupList.isLoading;
|
||||||
return isLoading
|
return isLoading
|
||||||
? Container(
|
? Container(
|
||||||
height: (_width - 20) / 3 + 110,
|
height: (_width - 20) / 3 + 140,
|
||||||
)
|
)
|
||||||
: groups[_groupIndex].podcastList.length == 0
|
: groups[_groupIndex].podcastList.length == 0
|
||||||
? Column(
|
? Column(
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
@ -6,6 +7,7 @@ import 'package:provider/provider.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:tsacdop/episodes/episodedetail.dart';
|
import 'package:tsacdop/episodes/episodedetail.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:line_icons/line_icons.dart';
|
import 'package:line_icons/line_icons.dart';
|
||||||
import 'package:tsacdop/class/audiostate.dart';
|
import 'package:tsacdop/class/audiostate.dart';
|
||||||
import 'package:tsacdop/class/episodebrief.dart';
|
import 'package:tsacdop/class/episodebrief.dart';
|
||||||
|
@ -19,7 +21,8 @@ class PlaylistPage extends StatefulWidget {
|
||||||
class _PlaylistPageState extends State<PlaylistPage> {
|
class _PlaylistPageState extends State<PlaylistPage> {
|
||||||
final GlobalKey<AnimatedListState> _playlistKey = GlobalKey();
|
final GlobalKey<AnimatedListState> _playlistKey = GlobalKey();
|
||||||
final textstyle = TextStyle(fontSize: 15.0, color: Colors.black);
|
final textstyle = TextStyle(fontSize: 15.0, color: Colors.black);
|
||||||
Widget episodeTag(String text, Color color) {
|
|
||||||
|
Widget _episodeTag(String text, Color color) {
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: color, borderRadius: BorderRadius.all(Radius.circular(15.0))),
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
|
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
|
||||||
|
@ -42,8 +79,9 @@ class _PlaylistPageState extends State<PlaylistPage> {
|
||||||
systemNavigationBarColor: Theme.of(context).primaryColor,
|
systemNavigationBarColor: Theme.of(context).primaryColor,
|
||||||
),
|
),
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
|
backgroundColor: Theme.of(context).primaryColor,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('Playlist'),
|
title: _topHeight == 0 ? Text('Playlist') : Center(),
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
backgroundColor: Theme.of(context).primaryColor,
|
backgroundColor: Theme.of(context).primaryColor,
|
||||||
),
|
),
|
||||||
|
@ -53,137 +91,225 @@ class _PlaylistPageState extends State<PlaylistPage> {
|
||||||
selector: (_, audio) =>
|
selector: (_, audio) =>
|
||||||
Tuple2(audio.queue.playlist, audio.playerRunning),
|
Tuple2(audio.queue.playlist, audio.playerRunning),
|
||||||
builder: (_, data, __) {
|
builder: (_, data, __) {
|
||||||
return AnimatedList(
|
return Column(
|
||||||
key: _playlistKey,
|
mainAxisSize: MainAxisSize.min,
|
||||||
shrinkWrap: true,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
scrollDirection: Axis.vertical,
|
children: <Widget>[
|
||||||
initialItemCount: data.item1.length,
|
Transform.scale(
|
||||||
itemBuilder: (context, index, animation) {
|
alignment: Alignment.topLeft,
|
||||||
Color _c =
|
scale: _topHeight / 100,
|
||||||
(Theme.of(context).brightness == Brightness.light)
|
child: Container(
|
||||||
? data.item1[index].primaryColor.colorizedark()
|
height: _topHeight,
|
||||||
: data.item1[index].primaryColor.colorizeLight();
|
padding: EdgeInsets.only(
|
||||||
return ScaleTransition(
|
bottom: (_topHeight - 60) > 0 ? _topHeight - 60 : 0,
|
||||||
alignment: Alignment.centerLeft,
|
left: 60),
|
||||||
scale: animation,
|
alignment: Alignment.bottomLeft,
|
||||||
child: Dismissible(
|
child: RichText(
|
||||||
background: Container(
|
text: TextSpan(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 20.0),
|
text: 'Total ',
|
||||||
child: Row(
|
style: TextStyle(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
color: Theme.of(context).accentColor,
|
||||||
children: <Widget>[
|
fontSize: 20,
|
||||||
Icon(
|
|
||||||
Icons.delete,
|
|
||||||
color: Theme.of(context).accentColor,
|
|
||||||
),
|
|
||||||
Icon(
|
|
||||||
Icons.delete,
|
|
||||||
color: Theme.of(context).accentColor,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
height: 50,
|
children: <TextSpan>[
|
||||||
color: Colors.grey[500],
|
TextSpan(
|
||||||
),
|
text: data.item1.length.toString(),
|
||||||
key: Key(data.item1[index].enclosureUrl),
|
style: TextStyle(
|
||||||
onDismissed: (direction) async {
|
color: Theme.of(context).accentColor,
|
||||||
await audio.delFromPlaylist(data.item1[index]);
|
fontSize: 40,
|
||||||
_playlistKey.currentState.removeItem(
|
)),
|
||||||
index, (context, animation) => Center());
|
TextSpan(
|
||||||
Fluttertoast.showToast(
|
text: data.item1.length < 2
|
||||||
msg: 'Removed From Playlist',
|
? ' episode'
|
||||||
gravity: ToastGravity.BOTTOM,
|
: ' episodes ',
|
||||||
);
|
style: TextStyle(
|
||||||
},
|
color: Theme.of(context).accentColor,
|
||||||
child: Column(
|
fontSize: 20,
|
||||||
children: <Widget>[
|
)),
|
||||||
ListTile(
|
TextSpan(
|
||||||
title: Text(
|
text: _sumPlaylistLength(data.item1).toString(),
|
||||||
data.item1[index].title,
|
style: GoogleFonts.teko(
|
||||||
maxLines: 1,
|
textStyle: TextStyle(
|
||||||
overflow: TextOverflow.ellipsis,
|
color: Theme.of(context).accentColor,
|
||||||
),
|
fontSize: 60,
|
||||||
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,
|
|
||||||
),
|
),
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -417,7 +417,7 @@ class DBHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
final title = feed.items[i].itunes.title ?? feed.items[i].title;
|
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 pubDate = feed.items[i].pubDate;
|
||||||
final date = _parsePubDate(pubDate);
|
final date = _parsePubDate(pubDate);
|
||||||
final milliseconds = date.millisecondsSinceEpoch;
|
final milliseconds = date.millisecondsSinceEpoch;
|
||||||
|
|
|
@ -2,28 +2,12 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_downloader/flutter_downloader.dart';
|
import 'package:flutter_downloader/flutter_downloader.dart';
|
||||||
import 'package:workmanager/workmanager.dart';
|
|
||||||
|
|
||||||
import 'package:tsacdop/class/podcastlocal.dart';
|
|
||||||
import 'package:tsacdop/class/podcast_group.dart';
|
import 'package:tsacdop/class/podcast_group.dart';
|
||||||
import 'package:tsacdop/home/appbar/addpodcast.dart';
|
import 'package:tsacdop/home/appbar/addpodcast.dart';
|
||||||
import 'package:tsacdop/class/audiostate.dart';
|
import 'package:tsacdop/class/audiostate.dart';
|
||||||
import 'package:tsacdop/class/importompl.dart';
|
import 'package:tsacdop/class/importompl.dart';
|
||||||
import 'package:tsacdop/class/settingstate.dart';
|
import 'package:tsacdop/class/settingstate.dart';
|
||||||
import '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();
|
final SettingState themeSetting = SettingState();
|
||||||
Future main() async {
|
Future main() async {
|
||||||
|
|
|
@ -6,8 +6,8 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:html/parser.dart';
|
import 'package:html/parser.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
|
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||||
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:tsacdop/class/podcastlocal.dart';
|
import 'package:tsacdop/class/podcastlocal.dart';
|
||||||
|
@ -389,6 +389,14 @@ class _AboutPodcastState extends State<AboutPodcast> {
|
||||||
setState(() => _load = true);
|
setState(() => _load = true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_launchUrl(String url) async {
|
||||||
|
if (await canLaunch(url)) {
|
||||||
|
await launch(url);
|
||||||
|
} else {
|
||||||
|
throw 'Could not launch $url';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
@ -419,8 +427,15 @@ class _AboutPodcastState extends State<AboutPodcast> {
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Linkify(
|
||||||
_description,
|
onOpen: (link) {
|
||||||
|
_launchUrl(link.url);
|
||||||
|
},
|
||||||
|
text: _description,
|
||||||
|
linkStyle: TextStyle(
|
||||||
|
color: Theme.of(context).accentColor,
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
textBaseline: TextBaseline.ideographic),
|
||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
overflow: TextOverflow.ellipsis,
|
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 {
|
} else {
|
||||||
return SelectableText(
|
return Linkify(
|
||||||
_description,
|
text: _description,
|
||||||
toolbarOptions: ToolbarOptions(copy: true),
|
onOpen: (link) {
|
||||||
|
_launchUrl(link.url);
|
||||||
|
},
|
||||||
|
linkStyle: TextStyle(
|
||||||
|
color: Theme.of(context).accentColor,
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
textBaseline: TextBaseline.ideographic),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -301,50 +301,47 @@ class _PodcastCardState extends State<PodcastCard> {
|
||||||
Brightness.light
|
Brightness.light
|
||||||
? Color.fromRGBO(113, 113, 113, 1)
|
? Color.fromRGBO(113, 113, 113, 1)
|
||||||
: Color.fromRGBO(15, 15, 15, 1),
|
: Color.fromRGBO(15, 15, 15, 1),
|
||||||
statusBarColor:
|
// statusBarColor:
|
||||||
Theme.of(context).brightness ==
|
// Theme.of(context).brightness ==
|
||||||
Brightness.light
|
// Brightness.light
|
||||||
? Color.fromRGBO(113, 113, 113, 1)
|
// ? Color.fromRGBO(113, 113, 113, 1)
|
||||||
: Color.fromRGBO(5, 5, 5, 1),
|
// : Color.fromRGBO(5, 5, 5, 1),
|
||||||
),
|
),
|
||||||
child: SafeArea(
|
child: AlertDialog(
|
||||||
child: AlertDialog(
|
elevation: 1,
|
||||||
elevation: 1,
|
shape: RoundedRectangleBorder(
|
||||||
shape: RoundedRectangleBorder(
|
borderRadius: BorderRadius.all(
|
||||||
borderRadius: BorderRadius.all(
|
Radius.circular(10.0))),
|
||||||
Radius.circular(10.0))),
|
titlePadding: EdgeInsets.only(
|
||||||
titlePadding: EdgeInsets.only(
|
top: 20,
|
||||||
top: 20,
|
left: 20,
|
||||||
left: 20,
|
right: 200,
|
||||||
right: 200,
|
bottom: 20),
|
||||||
bottom: 20),
|
title: Text('Remove confirm'),
|
||||||
title: Text('Remove confirm'),
|
content: Text(
|
||||||
content: Text(
|
'Are you sure you want to unsubscribe?'),
|
||||||
'Are you sure you want to unsubscribe?'),
|
actions: <Widget>[
|
||||||
actions: <Widget>[
|
FlatButton(
|
||||||
FlatButton(
|
onPressed: () =>
|
||||||
onPressed: () =>
|
Navigator.of(context).pop(),
|
||||||
Navigator.of(context).pop(),
|
child: Text(
|
||||||
child: Text(
|
'CANCEL',
|
||||||
'CANCEL',
|
style: TextStyle(
|
||||||
style: TextStyle(
|
color: Colors.grey[600]),
|
||||||
color: Colors.grey[600]),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
FlatButton(
|
),
|
||||||
onPressed: () {
|
FlatButton(
|
||||||
_groupList.removePodcast(
|
onPressed: () {
|
||||||
widget.podcastLocal.id);
|
_groupList.removePodcast(
|
||||||
Navigator.of(context).pop();
|
widget.podcastLocal.id);
|
||||||
},
|
Navigator.of(context).pop();
|
||||||
child: Text(
|
},
|
||||||
'CONFIRM',
|
child: Text(
|
||||||
style:
|
'CONFIRM',
|
||||||
TextStyle(color: Colors.red),
|
style: TextStyle(color: Colors.red),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -136,9 +136,9 @@ class _DownloadsManageState extends State<DownloadsManage> {
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: _fileNum.toString(),
|
text: _fileNum.toString(),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).accentColor,
|
color: Theme.of(context).accentColor,
|
||||||
fontSize: 40,
|
fontSize: 40,
|
||||||
fontWeight: FontWeight.bold)),
|
)),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: _fileNum < 2 ? ' episode' : ' episodes ',
|
text: _fileNum < 2 ? ' episode' : ' episodes ',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
@ -148,9 +148,9 @@ class _DownloadsManageState extends State<DownloadsManage> {
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: (_size ~/ 1000000).toString(),
|
text: (_size ~/ 1000000).toString(),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).accentColor,
|
color: Theme.of(context).accentColor,
|
||||||
fontSize: 60,
|
fontSize: 60,
|
||||||
fontWeight: FontWeight.bold)),
|
)),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: ' Mb',
|
text: ' Mb',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
|
|
@ -59,8 +59,8 @@ class Settings extends StatelessWidget {
|
||||||
backgroundColor: Theme.of(context).primaryColor,
|
backgroundColor: Theme.of(context).primaryColor,
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
//physics: const AlwaysScrollableScrollPhysics(),
|
||||||
scrollDirection: Axis.vertical,
|
scrollDirection: Axis.vertical,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
@ -93,14 +93,16 @@ class Settings extends StatelessWidget {
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => ThemeSetting())),
|
builder: (context) => ThemeSetting())),
|
||||||
contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
|
contentPadding:
|
||||||
|
EdgeInsets.symmetric(horizontal: 25.0),
|
||||||
leading: Icon(LineIcons.adjust_solid),
|
leading: Icon(LineIcons.adjust_solid),
|
||||||
title: Text('Appearance'),
|
title: Text('Appearance'),
|
||||||
subtitle: Text('Colors and themes'),
|
subtitle: Text('Colors and themes'),
|
||||||
),
|
),
|
||||||
Divider(height: 2),
|
Divider(height: 2),
|
||||||
ListTile(
|
ListTile(
|
||||||
contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
|
contentPadding:
|
||||||
|
EdgeInsets.symmetric(horizontal: 25.0),
|
||||||
leading: Icon(LineIcons.play_circle),
|
leading: Icon(LineIcons.play_circle),
|
||||||
title: Text('AutoPlay'),
|
title: Text('AutoPlay'),
|
||||||
subtitle: Text('Autoplay next episode in playlist'),
|
subtitle: Text('Autoplay next episode in playlist'),
|
||||||
|
@ -117,7 +119,8 @@ class Settings extends StatelessWidget {
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => SyncingSetting())),
|
builder: (context) => SyncingSetting())),
|
||||||
contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
|
contentPadding:
|
||||||
|
EdgeInsets.symmetric(horizontal: 25.0),
|
||||||
leading: Icon(LineIcons.cloud_download_alt_solid),
|
leading: Icon(LineIcons.cloud_download_alt_solid),
|
||||||
title: Text('Syncing'),
|
title: Text('Syncing'),
|
||||||
subtitle: Text('Refresh podcasts in the background'),
|
subtitle: Text('Refresh podcasts in the background'),
|
||||||
|
@ -128,7 +131,8 @@ class Settings extends StatelessWidget {
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => StorageSetting())),
|
builder: (context) => StorageSetting())),
|
||||||
contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
|
contentPadding:
|
||||||
|
EdgeInsets.symmetric(horizontal: 25.0),
|
||||||
leading: Icon(LineIcons.save),
|
leading: Icon(LineIcons.save),
|
||||||
title: Text('Storage'),
|
title: Text('Storage'),
|
||||||
subtitle: Text('Manage cache and download storage'),
|
subtitle: Text('Manage cache and download storage'),
|
||||||
|
@ -139,7 +143,8 @@ class Settings extends StatelessWidget {
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => PlayedHistory())),
|
builder: (context) => PlayedHistory())),
|
||||||
contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
|
contentPadding:
|
||||||
|
EdgeInsets.symmetric(horizontal: 25.0),
|
||||||
leading: Icon(Icons.update),
|
leading: Icon(Icons.update),
|
||||||
title: Text('History'),
|
title: Text('History'),
|
||||||
subtitle: Text('Listen data'),
|
subtitle: Text('Listen data'),
|
||||||
|
@ -149,7 +154,8 @@ class Settings extends StatelessWidget {
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_exportOmpl();
|
_exportOmpl();
|
||||||
},
|
},
|
||||||
contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
|
contentPadding:
|
||||||
|
EdgeInsets.symmetric(horizontal: 25.0),
|
||||||
leading: Icon(LineIcons.file_code_solid),
|
leading: Icon(LineIcons.file_code_solid),
|
||||||
title: Text('Export'),
|
title: Text('Export'),
|
||||||
subtitle: Text('Export ompl file'),
|
subtitle: Text('Export ompl file'),
|
||||||
|
@ -184,25 +190,31 @@ class Settings extends StatelessWidget {
|
||||||
ListTile(
|
ListTile(
|
||||||
onTap: () => _launchUrl(
|
onTap: () => _launchUrl(
|
||||||
'https://github.com/stonega/tsacdop/releases'),
|
'https://github.com/stonega/tsacdop/releases'),
|
||||||
contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
|
contentPadding:
|
||||||
|
EdgeInsets.symmetric(horizontal: 25.0),
|
||||||
leading: Icon(LineIcons.map_signs_solid),
|
leading: Icon(LineIcons.map_signs_solid),
|
||||||
title: Text('Changelog'),
|
title: Text('Changelog'),
|
||||||
subtitle: Text('List of chagnes'),
|
subtitle: Text('List of chagnes'),
|
||||||
),
|
),
|
||||||
Divider(height: 2),
|
Divider(height: 2),
|
||||||
ListTile(
|
ListTile(
|
||||||
onTap: () => Navigator.push(context,
|
onTap: () => Navigator.push(
|
||||||
MaterialPageRoute(builder: (context) => Libries())),
|
context,
|
||||||
contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
|
MaterialPageRoute(
|
||||||
|
builder: (context) => Libries())),
|
||||||
|
contentPadding:
|
||||||
|
EdgeInsets.symmetric(horizontal: 25.0),
|
||||||
leading: Icon(LineIcons.book_open_solid),
|
leading: Icon(LineIcons.book_open_solid),
|
||||||
title: Text('Libraries'),
|
title: Text('Libraries'),
|
||||||
subtitle: Text('Open source libraries in application'),
|
subtitle:
|
||||||
|
Text('Open source libraries in application'),
|
||||||
),
|
),
|
||||||
Divider(height: 2),
|
Divider(height: 2),
|
||||||
ListTile(
|
ListTile(
|
||||||
onTap: () => _launchUrl(
|
onTap: () => _launchUrl(
|
||||||
'mailto:<xijieyin@gmail.com>?subject=Tsacdop Feedback'),
|
'mailto:<xijieyin@gmail.com>?subject=Tsacdop Feedback'),
|
||||||
contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
|
contentPadding:
|
||||||
|
EdgeInsets.symmetric(horizontal: 25.0),
|
||||||
leading: Icon(LineIcons.bug_solid),
|
leading: Icon(LineIcons.bug_solid),
|
||||||
title: Text('Feedback'),
|
title: Text('Feedback'),
|
||||||
subtitle: Text('Bugs and feature requests'),
|
subtitle: Text('Bugs and feature requests'),
|
||||||
|
|
|
@ -42,6 +42,7 @@ class StorageSetting extends StatelessWidget {
|
||||||
.copyWith(color: Theme.of(context).accentColor)),
|
.copyWith(color: Theme.of(context).accentColor)),
|
||||||
),
|
),
|
||||||
ListView(
|
ListView(
|
||||||
|
physics: const BouncingScrollPhysics(),
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
scrollDirection: Axis.vertical,
|
scrollDirection: Axis.vertical,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
|
|
|
@ -48,6 +48,7 @@ class SyncingSetting extends StatelessWidget {
|
||||||
.copyWith(color: Theme.of(context).accentColor)),
|
.copyWith(color: Theme.of(context).accentColor)),
|
||||||
),
|
),
|
||||||
ListView(
|
ListView(
|
||||||
|
physics: const BouncingScrollPhysics(),
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
scrollDirection: Axis.vertical,
|
scrollDirection: Axis.vertical,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
|
|
|
@ -43,6 +43,7 @@ class ThemeSetting extends StatelessWidget {
|
||||||
.copyWith(color: Theme.of(context).accentColor)),
|
.copyWith(color: Theme.of(context).accentColor)),
|
||||||
),
|
),
|
||||||
ListView(
|
ListView(
|
||||||
|
physics: const BouncingScrollPhysics(),
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
scrollDirection: Axis.vertical,
|
scrollDirection: Axis.vertical,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
|
|
|
@ -31,7 +31,7 @@ class EpisodeGrid extends StatelessWidget {
|
||||||
this.heroTag,
|
this.heroTag,
|
||||||
this.updateCount = 0})
|
this.updateCount = 0})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
double _width = MediaQuery.of(context).size.width;
|
double _width = MediaQuery.of(context).size.width;
|
||||||
|
@ -46,7 +46,6 @@ class EpisodeGrid extends StatelessWidget {
|
||||||
borderRadius: BorderRadius.all(Radius.circular(10))),
|
borderRadius: BorderRadius.all(Radius.circular(10))),
|
||||||
context: context,
|
context: context,
|
||||||
position: RelativeRect.fromLTRB(left, top, _width - left, 0),
|
position: RelativeRect.fromLTRB(left, top, _width - left, 0),
|
||||||
|
|
||||||
items: <PopupMenuEntry<int>>[
|
items: <PopupMenuEntry<int>>[
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: 0,
|
value: 0,
|
||||||
|
@ -85,7 +84,7 @@ class EpisodeGrid extends StatelessWidget {
|
||||||
if (value == 0) {
|
if (value == 0) {
|
||||||
if (!isPlaying) audio.episodeLoad(episode);
|
if (!isPlaying) audio.episodeLoad(episode);
|
||||||
} else if (value == 1) {
|
} else if (value == 1) {
|
||||||
if (!isInPlaylist) {
|
if (!isInPlaylist) {
|
||||||
audio.addToPlaylist(episode);
|
audio.addToPlaylist(episode);
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
msg: 'Added to playlist',
|
msg: 'Added to playlist',
|
||||||
|
@ -97,7 +96,7 @@ class EpisodeGrid extends StatelessWidget {
|
||||||
msg: 'Removed from playlist',
|
msg: 'Removed from playlist',
|
||||||
gravity: ToastGravity.BOTTOM,
|
gravity: ToastGravity.BOTTOM,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -187,7 +186,15 @@ class EpisodeGrid extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Spacer(),
|
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
|
showNumber
|
||||||
? Container(
|
? Container(
|
||||||
alignment: Alignment.topRight,
|
alignment: Alignment.topRight,
|
||||||
|
@ -371,7 +378,7 @@ class _DownloadIconState extends State<DownloadIcon> {
|
||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(
|
||||||
backgroundColor: Colors.grey[200],
|
backgroundColor: Colors.grey[200],
|
||||||
strokeWidth: 1,
|
strokeWidth: 1,
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
|
valueColor: AlwaysStoppedAnimation<Color>(Theme.of(context).accentColor),
|
||||||
value: task.progress / 100,
|
value: task.progress / 100,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -391,7 +398,7 @@ class _DownloadIconState extends State<DownloadIcon> {
|
||||||
data: IconThemeData(size: 15),
|
data: IconThemeData(size: 15),
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.done_all,
|
Icons.done_all,
|
||||||
color: Colors.blue,
|
color: Theme.of(context).accentColor,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else if (task.status == DownloadTaskStatus.failed) {
|
} else if (task.status == DownloadTaskStatus.failed) {
|
||||||
|
|
|
@ -347,6 +347,8 @@ Future<T> _showMenu<T>({
|
||||||
break;
|
break;
|
||||||
case TargetPlatform.android:
|
case TargetPlatform.android:
|
||||||
case TargetPlatform.fuchsia:
|
case TargetPlatform.fuchsia:
|
||||||
|
case TargetPlatform.linux:
|
||||||
|
case TargetPlatform.windows:
|
||||||
label = semanticLabel ?? MaterialLocalizations.of(context)?.popupMenuLabel;
|
label = semanticLabel ?? MaterialLocalizations.of(context)?.popupMenuLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -469,6 +471,8 @@ class MyPopupMenuButtonState<T> extends State<MyPopupMenuButton<T>> {
|
||||||
return const Icon(Icons.more_vert);
|
return const Icon(Icons.more_vert);
|
||||||
case TargetPlatform.iOS:
|
case TargetPlatform.iOS:
|
||||||
case TargetPlatform.macOS:
|
case TargetPlatform.macOS:
|
||||||
|
case TargetPlatform.linux:
|
||||||
|
case TargetPlatform.windows:
|
||||||
return const Icon(Icons.more_horiz);
|
return const Icon(Icons.more_horiz);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -49,8 +49,7 @@ class RssItem {
|
||||||
}
|
}
|
||||||
return RssItem(
|
return RssItem(
|
||||||
title: findElementOrNull(element, "title")?.text,
|
title: findElementOrNull(element, "title")?.text,
|
||||||
description: findElementOrNull(element, "description")?.text?.trim()
|
description: findElementOrNull(element, "description")?.text?.trim(),
|
||||||
,
|
|
||||||
link: findElementOrNull(element, "link")?.text?.trim(),
|
link: findElementOrNull(element, "link")?.text?.trim(),
|
||||||
categories: element.findElements("category").map((element) {
|
categories: element.findElements("category").map((element) {
|
||||||
return RssCategory.parse(element);
|
return RssCategory.parse(element);
|
||||||
|
@ -60,7 +59,7 @@ class RssItem {
|
||||||
author: findElementOrNull(element, "author")?.text?.trim(),
|
author: findElementOrNull(element, "author")?.text?.trim(),
|
||||||
// comments: findElementOrNull(element, "comments")?.text,
|
// comments: findElementOrNull(element, "comments")?.text,
|
||||||
// source: RssSource.parse(findElementOrNull(element, "source")),
|
// source: RssSource.parse(findElementOrNull(element, "source")),
|
||||||
content: RssContent.parse(findElementOrNull(element, "content:encoded")),
|
content: RssContent.parse(findElementOrNull(element, "content:encoded")),
|
||||||
// media: Media.parse(element),
|
// media: Media.parse(element),
|
||||||
enclosure: RssEnclosure.parse(findElementOrNull(element, "enclosure")),
|
enclosure: RssEnclosure.parse(findElementOrNull(element, "enclosure")),
|
||||||
//dc: DublinCore.parse(element),
|
//dc: DublinCore.parse(element),
|
||||||
|
|
|
@ -28,7 +28,7 @@ dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
json_annotation: ^3.0.1
|
json_annotation: ^3.0.1
|
||||||
sqflite: ^1.2.2+1
|
sqflite: ^1.3.0
|
||||||
flutter_html: ^0.11.1
|
flutter_html: ^0.11.1
|
||||||
path_provider: ^1.6.1
|
path_provider: ^1.6.1
|
||||||
color_thief_flutter: ^1.0.1
|
color_thief_flutter: ^1.0.1
|
||||||
|
@ -59,6 +59,7 @@ dev_dependencies:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/galonsos/line_icons.git
|
url: https://github.com/galonsos/line_icons.git
|
||||||
flutter_file_dialog: ^0.0.5
|
flutter_file_dialog: ^0.0.5
|
||||||
|
flutter_linkify: ^3.1.0
|
||||||
|
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
|
|