Change player widget UI

Change homepage to nested scroll
This commit is contained in:
stonegate 2020-03-25 23:33:48 +08:00
parent be5de73ddc
commit 97ec6a59e4
18 changed files with 1036 additions and 739 deletions

View File

@ -9,7 +9,8 @@ jobs:
steps:
- checkout
- run: flutter upgrade
- run: cd /home/cirrus/sdks/flutter && git remote add origin https://github.com/flutter/flutter
- run: flutter upgrade && cd /home/cirrus/project/tsacdop
- run:
name: Run Flutter doctor
command: flutter doctor

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-10/"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="bin/default"/>
</classpath>

View File

@ -155,11 +155,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
notifyListeners();
}
// set setStopOnComplete(bool boo) {
// _stopOnComplete = boo;
//}
set autoPlaySwitch(bool boo) {
set autoPlaySwitch(bool boo) {
_autoPlay = boo;
notifyListeners();
}
@ -168,6 +164,9 @@ class AudioPlayerNotifier extends ChangeNotifier {
void addListener(VoidCallback listener) async {
super.addListener(listener);
await AudioService.connect();
if(await AudioService.running){
AudioService.stop();
}
}
loadPlaylist() async {
@ -515,7 +514,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
_skipState = BasicPlaybackState.skippingToNext;
await _audioPlayer.setUrl(mediaItem.id);
print(mediaItem.id);
Duration duration = await _audioPlayer.durationFuture;
Duration duration = await _audioPlayer.durationFuture ?? 0;
AudioServiceBackground.setMediaItem(
mediaItem.copyWith(duration: duration.inMilliseconds));
_skipState = null;

View File

@ -12,6 +12,7 @@ class PodcastLocal {
final String description;
final int upateCount;
final int episodeCount;
PodcastLocal(
this.title,
this.imageUrl,
@ -25,6 +26,7 @@ class PodcastLocal {
{
this.description ='',
this.upateCount = 0,
this.episodeCount = 0
}
);
}

View File

@ -197,13 +197,13 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
padding: EdgeInsets.only(top: 5.0),
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
physics: AlwaysScrollableScrollPhysics(),
//physics: AlwaysScrollableScrollPhysics(),
controller: _controller,
child: _loaddes
? (_description.contains('<'))
? Html(
padding:
EdgeInsets.symmetric(horizontal: 20.0),
EdgeInsets.only(left: 20.0, right: 20, bottom: 10),
defaultTextStyle: TextStyle(height: 1.8),
data: _description,
linkStyle: TextStyle(
@ -217,7 +217,7 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
)
: Container(
padding:
EdgeInsets.symmetric(horizontal: 20.0),
EdgeInsets.only(left: 20.0, right: 20.0, bottom: 10.0),
alignment: Alignment.topLeft,
child: SelectableLinkify(
onOpen: (link) {

View File

@ -72,7 +72,7 @@ class AboutApp extends StatelessWidget {
image: AssetImage('assets/logo.png'),
height: 80,
),
Text('Version: 0.1.4'),
Text('Version: 0.1.5'),
],
),
),

View File

@ -18,10 +18,10 @@ import 'package:tsacdop/class/podcast_group.dart';
import 'package:tsacdop/class/searchpodcast.dart';
import 'package:tsacdop/class/podcastlocal.dart';
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
import 'package:tsacdop/home/home.dart';
import 'package:tsacdop/home/appbar/popupmenu.dart';
import 'package:tsacdop/webfeed/webfeed.dart';
import 'package:tsacdop/.env.dart';
import '../nested_home.dart';
class MyHomePage extends StatefulWidget {
@override

View File

@ -5,9 +5,9 @@ import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:marquee/marquee.dart';
import 'package:line_icons/line_icons.dart';
import 'package:tuple/tuple.dart';
import 'package:audio_service/audio_service.dart';
import 'package:flutter_neumorphic/flutter_neumorphic.dart';
import 'package:tsacdop/class/episodebrief.dart';
import 'package:tsacdop/class/audiostate.dart';
@ -17,7 +17,6 @@ import 'package:tsacdop/util/pageroute.dart';
import 'package:tsacdop/util/colorize.dart';
import 'package:tsacdop/util/day_night_switch.dart';
//Custom slider
class MyRectangularTrackShape extends RectangularSliderTrackShape {
Rect getPreferredRect({
@required RenderBox parentBox,
@ -83,14 +82,28 @@ class MyRoundSliderThumpShape extends SliderComponentShape {
..style = PaintingStyle.fill
..strokeWidth = 2,
);
// Path _path = Path();
// _path.moveTo(center.dx - 12, center.dy + 10);
// _path.lineTo(center.dx - 12, center.dy - 12);
// _path.lineTo(center.dx -12, center.dy - 12);
// canvas.drawShadow(_path, Colors.black, 4, false);
// Path _pathLight = Path();
// _pathLight.moveTo(center.dx + 12, center.dy - 12);
// _pathLight.lineTo(center.dx + 12, center.dy + 10);
//// _pathLight.lineTo(center.dx - 12, center.dy + 10);
// canvas.drawShadow(_pathLight, Colors.black, 4, true);
canvas.drawRect(
Rect.fromLTRB(
center.dx - 10, center.dy + 10, center.dx + 10, center.dy - 10),
Paint()
..color = colorTween.evaluate(enableAnimation)
..color = Colors.white
..style = PaintingStyle.fill
..strokeWidth = 10,
);
canvas.drawLine(
Offset(center.dx - 5, center.dy - 2),
Offset(center.dx + 5, center.dy + 2),
@ -113,6 +126,22 @@ class _PlayerWidgetState extends State<PlayerWidget> {
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
}
List<BoxShadow> _customShadow = [
BoxShadow(blurRadius: 26, offset: Offset(-6, -6), color: Colors.white),
BoxShadow(
blurRadius: 8,
offset: Offset(2, 2),
color: Colors.grey[600].withOpacity(0.4))
];
List<BoxShadow> _customShadowNight = [
BoxShadow(
blurRadius: 6,
offset: Offset(-1, -1),
color: Colors.grey[100].withOpacity(0.3)),
BoxShadow(blurRadius: 8, offset: Offset(2, 2), color: Colors.black)
];
List minsToSelect = [1, 5, 10, 15, 20, 25, 30, 45, 60, 70, 80, 90, 99];
int _minSelected;
@ -137,7 +166,7 @@ class _PlayerWidgetState extends State<PlayerWidget> {
height: 300,
color: data.item3 > 0
? Colors.black.withOpacity(data.item3)
: Theme.of(context).scaffoldBackgroundColor,
: Theme.of(context).primaryColor,
child: Stack(
children: <Widget>[
Column(
@ -149,54 +178,60 @@ class _PlayerWidgetState extends State<PlayerWidget> {
),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: minsToSelect
.map((e) => InkWell(
onTap: () => setState(() => _minSelected = e),
child: Stack(
alignment: Alignment.center,
children: <Widget>[
AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.elasticOut,
margin: EdgeInsets.symmetric(
horizontal: 10.0),
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.grey[800],
blurRadius: 0,
offset: Offset(0, 0)),
],
color: (e == _minSelected)
? Theme.of(context).accentColor
: Theme.of(context).primaryColorDark,
shape: BoxShape.circle,
child: Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: minsToSelect
.map((e) => InkWell(
onTap: () => setState(() => _minSelected = e),
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Container(
margin: EdgeInsets.symmetric(
horizontal: 10.0),
decoration: BoxDecoration(
boxShadow: !(e == _minSelected ||
data.item3 > 0)
? (Theme.of(context).brightness ==
Brightness.dark)
? _customShadowNight
: _customShadow
: [
BoxShadow(
color: Colors.black,
blurRadius: 0,
offset: Offset(0, 0)),
],
color: (e == _minSelected)
? Theme.of(context).accentColor
: Theme.of(context).primaryColor,
shape: BoxShape.circle,
),
alignment: Alignment.center,
height: 30,
width: 30,
child: Text(e.toString(),
style: TextStyle(
fontWeight: FontWeight.bold,
color: (e == _minSelected)
? Colors.white
: null)),
),
alignment: Alignment.center,
height: (e == _minSelected) ? 40 : 30,
width: (e == _minSelected) ? 40 : 30,
child: Text(e.toString(),
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white)),
),
Container(
height: (e == _minSelected)
? 40 * data.item3
: 30 * data.item3,
width: (e == _minSelected)
? 40 * data.item3
: 30 * data.item3,
decoration: BoxDecoration(
color: Colors.black,
shape: BoxShape.circle),
),
],
),
))
.toList(),
Container(
height: 30 * data.item3,
width: 30 * data.item3,
decoration: BoxDecoration(
color: Colors.black
.withOpacity(data.item3),
shape: BoxShape.circle),
),
],
),
))
.toList(),
),
),
),
Container(
@ -209,7 +244,7 @@ class _PlayerWidgetState extends State<PlayerWidget> {
value: data.item1,
sunColor: Colors.yellow[700],
moonColor: Colors.grey[600],
dayColor: Colors.grey[300],
dayColor: Theme.of(context).primaryColorDark,
nightColor: Colors.black,
onDrag: (value) => audio.setSwitchValue = value,
onChanged: (value) {
@ -231,15 +266,13 @@ class _PlayerWidgetState extends State<PlayerWidget> {
alignment: Alignment.center,
height: 25,
width: 100,
decoration: BoxDecoration(
// color: Theme.of(context).accentColor,
),
decoration: BoxDecoration(),
child: Text(_stringForSeconds(data.item2.toDouble()),
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20,
color: Colors.white,
)),
fontWeight: FontWeight.bold,
fontSize: 20,
color:
(data.item3 > 0) ? Colors.white : null)),
),
],
),
@ -247,7 +280,7 @@ class _PlayerWidgetState extends State<PlayerWidget> {
],
),
Positioned(
bottom: 60 + 20 * data.item3,
bottom: 50 + 20 * data.item3,
left: MediaQuery.of(context).size.width / 2 - 100,
width: 200,
child: Container(
@ -302,7 +335,10 @@ class _PlayerWidgetState extends State<PlayerWidget> {
padding: EdgeInsets.only(top: 10, left: 10, right: 10),
child: SliderTheme(
data: SliderTheme.of(context).copyWith(
activeTrackColor: Colors.grey[400],
activeTrackColor:
Theme.of(context).brightness == Brightness.dark
? Colors.black38
: Colors.grey[400],
inactiveTrackColor:
Theme.of(context).primaryColorDark,
trackHeight: 20.0,
@ -315,7 +351,7 @@ class _PlayerWidgetState extends State<PlayerWidget> {
overlayColor:
Theme.of(context).accentColor.withAlpha(32),
overlayShape:
RoundSliderOverlayShape(overlayRadius: 14.0),
RoundSliderOverlayShape(overlayRadius: 4.0),
),
child: Slider(
value: data.seekSliderValue,
@ -387,32 +423,68 @@ class _PlayerWidgetState extends State<PlayerWidget> {
iconSize: 32.0,
icon: Icon(Icons.replay_10),
color: Colors.grey[500]),
backplay == BasicPlaybackState.playing
? IconButton(
padding: EdgeInsets.symmetric(horizontal: 30.0),
onPressed:
backplay == BasicPlaybackState.playing
? () {
audio.pauseAduio();
}
: null,
iconSize: 60.0,
icon: Icon(
LineIcons.pause_circle,
size: 40,
Container(
margin: EdgeInsets.symmetric(horizontal: 30),
height: 60,
width: 60,
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
shape: BoxShape.circle,
border: Border.all(
color: Theme.of(context).brightness ==
Brightness.dark
? Colors.black12
: Colors.white10,
width: 1),
boxShadow: Theme.of(context).brightness ==
Brightness.dark
? _customShadowNight
: _customShadow),
child: backplay == BasicPlaybackState.playing
? Material(
color: Colors.transparent,
child: InkWell(
borderRadius:
BorderRadius.all(Radius.circular(30)),
onTap:
backplay == BasicPlaybackState.playing
? () {
audio.pauseAduio();
}
: null,
child: SizedBox(
height: 60,
width: 60,
child: Icon(
Icons.pause,
size: 40,
),
),
),
)
: Material(
color: Colors.transparent,
child: InkWell(
borderRadius:
BorderRadius.all(Radius.circular(30)),
onTap:
backplay == BasicPlaybackState.playing
? null
: () {
audio.resumeAudio();
},
child: SizedBox(
height: 60,
width: 60,
child: Icon(
Icons.play_arrow,
size: 40,
color: Theme.of(context).accentColor,
),
),
),
),
color: Colors.grey[500])
: IconButton(
padding: EdgeInsets.symmetric(horizontal: 30.0),
onPressed:
backplay == BasicPlaybackState.playing
? null
: () {
audio.resumeAudio();
},
iconSize: 60.0,
icon: Icon(LineIcons.play_circle, size: 40),
color: Colors.grey[500]),
),
IconButton(
padding: EdgeInsets.symmetric(horizontal: 30.0),
onPressed: backplay == BasicPlaybackState.playing
@ -497,6 +569,11 @@ class _PlayerWidgetState extends State<PlayerWidget> {
FileImage(File("${data.item1.imagePath}")),
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 5.0),
width: 200,
child: Text(data.item1.feedTitle, maxLines: 1, overflow: TextOverflow.fade,),
),
Spacer(),
IconButton(
onPressed: () => Navigator.push(
@ -530,105 +607,127 @@ class _PlayerWidgetState extends State<PlayerWidget> {
//padding: EdgeInsets.only(bottom: 10.0),
decoration: BoxDecoration(
// borderRadius: BorderRadius.all(Radius.circular(10.0)),
color: Theme.of(context).scaffoldBackgroundColor,
color: Theme.of(context).primaryColor,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SingleChildScrollView(
child: Selector<AudioPlayerNotifier, List<EpisodeBrief>>(
selector: (_, audio) => audio.queue.playlist,
builder: (_, playlist, __) {
return ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.vertical,
child: Container(
height: 50,
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
'NEXT TO PLAY',
style: TextStyle(
color: Theme.of(context).accentColor,
fontWeight: FontWeight.bold),
itemCount: playlist.length,
itemBuilder: (BuildContext context, int index) {
print(playlist.length);
if (index == 0) {
return Container(
height: 60,
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
'Playlist',
style: TextStyle(
color: Theme.of(context).accentColor,
fontWeight: FontWeight.bold,
fontSize: 16),
),
Spacer(),
Container(
alignment: Alignment.center,
child: Container(
alignment: Alignment.center,
height: 40,
width: 80,
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
borderRadius:
BorderRadius.all(Radius.circular(20)),
border: Border.all(
color: Theme.of(context).brightness ==
Brightness.dark
? Colors.black12
: Colors.white10,
width: 1),
boxShadow: Theme.of(context).brightness ==
Brightness.dark
? _customShadowNight
: _customShadow),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius:
BorderRadius.all(Radius.circular(20)),
onTap: () => audio.playNext(),
child: SizedBox(
height: 40,
width: 80,
child: Icon(
Icons.skip_next,
size: 30,
),
),
),
),
),
),
],
),
Spacer(),
],
),
),
),
Expanded(
child: Selector<AudioPlayerNotifier, List<EpisodeBrief>>(
selector: (_, audio) => audio.queue.playlist,
builder: (_, playlist, __) {
return ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.vertical,
itemCount: playlist.length,
itemBuilder: (BuildContext context, int index) {
print(playlist.length);
return index == 0
? Center()
: Column(
);
} else {
return Column(
children: <Widget>[
Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
audio.episodeLoad(playlist[index]);
},
child: Container(
height: 60,
padding: EdgeInsets.symmetric(horizontal: 10),
alignment: Alignment.centerLeft,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
audio.episodeLoad(playlist[index]);
},
Container(
padding: EdgeInsets.all(10.0),
child: ClipRRect(
borderRadius:
BorderRadius.all(Radius.circular(15.0)),
child: Container(
height: 60,
padding:
EdgeInsets.symmetric(horizontal: 10),
alignment: Alignment.centerLeft,
// decoration: BoxDecoration(
// color: Theme.of(context)
// .scaffoldBackgroundColor,
// ),
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
padding: EdgeInsets.all(10.0),
child: ClipRRect(
borderRadius: BorderRadius.all(
Radius.circular(15.0)),
child: Container(
height: 30.0,
width: 30.0,
child: Image.file(File(
"${playlist[index].imagePath}"))),
),
),
Expanded(
child: Container(
alignment: Alignment.centerLeft,
child: Text(
playlist[index].title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
),
],
),
height: 30.0,
width: 30.0,
child: Image.file(File(
"${playlist[index].imagePath}"))),
),
),
Expanded(
child: Container(
alignment: Alignment.centerLeft,
child: Text(
playlist[index].title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
),
Divider(height: 2),
],
);
},
),
),
),
),
Divider(height: 2),
],
);
},
),
),
],
}
},
);
},
),
),
);

View File

@ -1,28 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'hometab.dart';
import 'package:tsacdop/home/appbar/importompl.dart';
import 'package:tsacdop/home/audioplayer.dart';
import 'home_groups.dart';
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Stack(children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Import(),
Container(child: ScrollPodcasts()),
Expanded(
child: MainTab(),
),
],
),
Container(child: PlayerWidget()),
]);
}
}

View File

@ -43,100 +43,105 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
height: (_width - 20) / 3 + 140,
)
: groups[_groupIndex].podcastList.length == 0
? Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
GestureDetector(
onVerticalDragEnd: (event) {
if (event.primaryVelocity > 200) {
if (groups.length == 1) {
Fluttertoast.showToast(
msg: 'Add some groups',
gravity: ToastGravity.BOTTOM,
);
} else {
if (mounted)
? Container(
height: (_width - 20) / 3 + 140,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
GestureDetector(
onVerticalDragEnd: (event) {
if (event.primaryVelocity > 200) {
if (groups.length == 1) {
Fluttertoast.showToast(
msg: 'Add some groups',
gravity: ToastGravity.BOTTOM,
);
} else {
if (mounted)
setState(() {
(_groupIndex != 0)
? _groupIndex--
: _groupIndex = groups.length - 1;
});
}
} else if (event.primaryVelocity < -200) {
if (groups.length == 1) {
Fluttertoast.showToast(
msg: 'Add some groups',
gravity: ToastGravity.BOTTOM,
);
} else {
setState(() {
(_groupIndex != 0)
? _groupIndex--
: _groupIndex = groups.length - 1;
(_groupIndex < groups.length - 1)
? _groupIndex++
: _groupIndex = 0;
});
}
}
} else if (event.primaryVelocity < -200) {
if (groups.length == 1) {
Fluttertoast.showToast(
msg: 'Add some groups',
gravity: ToastGravity.BOTTOM,
);
} else {
setState(() {
(_groupIndex < groups.length - 1)
? _groupIndex++
: _groupIndex = 0;
});
}
}
},
child: Column(
children: <Widget>[
Container(
child: Row(
children: <Widget>[
Container(
padding: EdgeInsets.symmetric(
horizontal: 15.0),
child: Text(
groups[_groupIndex].name,
style: Theme.of(context)
.textTheme
.bodyText1
.copyWith(
color: Theme.of(context)
.accentColor),
)),
Spacer(),
Container(
height: 30,
padding:
EdgeInsets.symmetric(horizontal: 15),
alignment: Alignment.bottomRight,
child: InkWell(
onTap: () {
Navigator.push(
context,
SlideLeftRoute(page: PodcastManage()),
);
},
child: Container(
height: 30,
padding: EdgeInsets.all(5.0),
child: Text(
'See All',
style: Theme.of(context)
.textTheme
.bodyText1
.copyWith(
color: Theme.of(context)
.accentColor),
)),
},
child: Column(
children: <Widget>[
Container(
child: Row(
children: <Widget>[
Container(
padding: EdgeInsets.symmetric(
horizontal: 15.0),
child: Text(
groups[_groupIndex].name,
style: Theme.of(context)
.textTheme
.bodyText1
.copyWith(
color: Theme.of(context)
.accentColor),
)),
Spacer(),
Container(
height: 30,
padding:
EdgeInsets.symmetric(horizontal: 15),
alignment: Alignment.bottomRight,
child: InkWell(
onTap: () {
Navigator.push(
context,
SlideLeftRoute(
page: PodcastManage()),
);
},
child: Container(
height: 30,
padding: EdgeInsets.all(5.0),
child: Text(
'See All',
style: Theme.of(context)
.textTheme
.bodyText1
.copyWith(
color: Theme.of(context)
.accentColor),
)),
),
),
),
],
],
),
),
),
Container(
height: 70,
color: Theme.of(context).scaffoldBackgroundColor,
),
],
)),
Container(
height: (_width - 20) / 3 + 40,
color: Theme.of(context).primaryColor,
margin: EdgeInsets.symmetric(horizontal: 15),
),
],
Container(
height: 70,
color:
Theme.of(context).scaffoldBackgroundColor,
),
],
)),
Container(
height: (_width - 20) / 3 + 40,
color: Theme.of(context).primaryColor,
margin: EdgeInsets.symmetric(horizontal: 15),
),
],
),
)
: DefaultTabController(
length: groups[_groupIndex].podcastList.length,
@ -194,7 +199,7 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
)),
Spacer(),
Container(
height: 30,
height: 30.0,
padding:
EdgeInsets.symmetric(horizontal: 15),
alignment: Alignment.bottomRight,
@ -223,7 +228,6 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
),
),
Container(
// color: Colors.white10,
height: 70,
width: _width,
alignment: Alignment.centerLeft,

View File

@ -1,33 +1,31 @@
import 'dart:io';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart' hide NestedScrollView;
import 'package:provider/provider.dart';
import 'package:tsacdop/local_storage/key_value_storage.dart';
import 'package:tsacdop/home/playlist.dart';
import 'package:tuple/tuple.dart';
import 'package:tsacdop/class/audiostate.dart';
import 'package:tsacdop/class/episodebrief.dart';
import 'package:tsacdop/home/playlist.dart';
import 'package:tsacdop/local_storage/key_value_storage.dart';
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
import 'package:tsacdop/util/episodegrid.dart';
import 'package:tsacdop/util/mypopupmenu.dart';
class MainTab extends StatefulWidget {
import 'package:tsacdop/home/appbar/importompl.dart';
import 'package:tsacdop/home/audioplayer.dart';
import 'home_groups.dart';
class Home extends StatefulWidget {
@override
_MainTabState createState() => _MainTabState();
_HomeState createState() => _HomeState();
}
class _MainTabState extends State<MainTab> with TickerProviderStateMixin {
class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
TabController _controller;
bool _loadPlay;
static String _stringForSeconds(int seconds) {
if (seconds == null) return null;
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
}
Decoration getIndicator(BuildContext context) {
Decoration _getIndicator(BuildContext context) {
return UnderlineTabIndicator(
borderSide: BorderSide(color: Theme.of(context).accentColor, width: 2),
borderSide: BorderSide(color: Theme.of(context).accentColor, width: 3),
insets: EdgeInsets.only(
left: 10.0,
right: 10.0,
@ -35,6 +33,147 @@ class _MainTabState extends State<MainTab> with TickerProviderStateMixin {
));
}
@override
void initState() {
super.initState();
_controller = TabController(length: 3, vsync: this);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
double top = 0;
@override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
double height = (width - 20) / 3 + 140;
return SafeArea(
child: Stack(
children: <Widget>[
Column(
children: <Widget>[
Import(),
Expanded(
child: NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxScrolled) {
return <Widget>[
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return SizedBox(
height: height,
width: width,
child: ScrollPodcasts(),
);
},
childCount: 1,
),
),
SliverPersistentHeader(
delegate: _SliverAppBarDelegate(
TabBar(
indicator: _getIndicator(context),
isScrollable: true,
indicatorSize: TabBarIndicatorSize.tab,
controller: _controller,
tabs: <Widget>[
Tab(
child: Text('Recent Update'),
),
Tab(
child: Text('Favorite'),
),
Tab(
child: Text('Download'),
)
],
),
),
pinned: true,
),
];
},
body: TabBarView(
controller: _controller,
children: <Widget>[
_RecentUpdate(),
_MyFavorite(),
_MyDownload(),
],
),
),
),
Selector<AudioPlayerNotifier, bool>(
selector: (_, audio) => audio.playerRunning,
builder: (_, data, __) {
return Padding(
padding: EdgeInsets.only(bottom: data ? 60.0 : 0),
);
}),
],
),
Container(child: PlayerWidget()),
],
),
);
}
}
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
_SliverAppBarDelegate(this._tabBar);
final TabBar _tabBar;
@override
double get minExtent => _tabBar.preferredSize.height + 2;
@override
double get maxExtent => _tabBar.preferredSize.height + 2;
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return Container(
color: Theme.of(context).scaffoldBackgroundColor,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
_tabBar,
Spacer(),
PlaylistButton(),
],
),
Container(height: 2, color: Theme.of(context).primaryColorDark),
],
),
);
}
@override
bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
return true;
}
}
class PlaylistButton extends StatefulWidget {
PlaylistButton({Key key}) : super(key: key);
@override
PlaylistButtonState createState() => PlaylistButtonState();
}
class PlaylistButtonState extends State<PlaylistButton> {
bool _loadPlay;
static String _stringForSeconds(int seconds) {
if (seconds == null) return null;
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
}
_getPlaylist() async {
await Provider.of<AudioPlayerNotifier>(context, listen: false)
.loadPlaylist();
@ -43,7 +182,15 @@ class _MainTabState extends State<MainTab> with TickerProviderStateMixin {
});
}
Widget playlist(BuildContext context) {
@override
void initState() {
super.initState();
_loadPlay = false;
_getPlaylist();
}
@override
Widget build(BuildContext context) {
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
return MyPopupMenuButton<int>(
shape: RoundedRectangleBorder(
@ -163,86 +310,14 @@ class _MainTabState extends State<MainTab> with TickerProviderStateMixin {
},
);
}
@override
void initState() {
super.initState();
_controller = TabController(length: 3, vsync: this);
_loadPlay = false;
_getPlaylist();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Row(
children: <Widget>[
Container(
padding: EdgeInsets.symmetric(horizontal: 10.0),
height: 50,
alignment: Alignment.bottomLeft,
child: TabBar(
indicatorSize: TabBarIndicatorSize.tab,
isScrollable: true,
labelPadding: EdgeInsets.all(10.0),
controller: _controller,
indicator: getIndicator(context),
tabs: <Widget>[
Text(
'Recent Update',
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(
'Favorites',
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(
'Downloads',
style: TextStyle(fontWeight: FontWeight.bold),
),
],
),
),
Spacer(),
playlist(context),
],
),
Container(height: 2, color: Theme.of(context).primaryColorDark),
Expanded(
child: Container(
child: TabBarView(
controller: _controller,
children: <Widget>[
Container(
child: RecentUpdate()),
Container(
child: MyFavorite()),
Container(
child: MyDownload()),
],
),
),
),
],
);
}
}
class RecentUpdate extends StatefulWidget {
class _RecentUpdate extends StatefulWidget {
@override
_RecentUpdateState createState() => _RecentUpdateState();
}
class _RecentUpdateState extends State<RecentUpdate> {
class _RecentUpdateState extends State<_RecentUpdate> {
int _updateCount = 0;
Future<List<EpisodeBrief>> _getRssItem(int top) async {
var dbHelper = DBHelper();
@ -252,34 +327,24 @@ class _RecentUpdateState extends State<RecentUpdate> {
return episodes;
}
ScrollController _controller;
_loadMoreEpisode() async {
if (mounted) setState(() => _loadMore = true);
await Future.delayed(Duration(seconds: 3));
if (mounted)
setState(() {
_top = _top + 33;
_loadMore = false;
});
}
int _top;
bool _loadMore;
_scrollListener() async {
if (_controller.offset == _controller.position.maxScrollExtent) {
if (mounted) setState(() => _loadMore = true);
await Future.delayed(Duration(seconds: 3));
if (mounted)
setState(() {
_top = _top + 33;
_loadMore = false;
});
}
}
@override
void initState() {
super.initState();
_loadMore = false;
_top = 33;
_controller = ScrollController();
_controller.addListener(_scrollListener);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
@ -289,14 +354,92 @@ class _RecentUpdateState extends State<RecentUpdate> {
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
return (snapshot.hasData)
? CustomScrollView(
controller: _controller,
physics: const AlwaysScrollableScrollPhysics(),
primary: false,
slivers: <Widget>[
? NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
if (scrollInfo.metrics.pixels ==
scrollInfo.metrics.maxScrollExtent &&
snapshot.data.length == _top) _loadMoreEpisode();
return true;
},
child: CustomScrollView(
key: PageStorageKey<String>('update'),
physics: const AlwaysScrollableScrollPhysics(),
slivers: <Widget>[
EpisodeGrid(
episodes: snapshot.data,
updateCount: _updateCount,
),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return _loadMore
? Container(
height: 2, child: LinearProgressIndicator())
: Center();
},
childCount: 1,
),
),
]),
)
: Center(child: CircularProgressIndicator());
},
);
}
}
class _MyFavorite extends StatefulWidget {
@override
_MyFavoriteState createState() => _MyFavoriteState();
}
class _MyFavoriteState extends State<_MyFavorite> {
Future<List<EpisodeBrief>> _getLikedRssItem(_top) async {
var dbHelper = DBHelper();
List<EpisodeBrief> episodes = await dbHelper.getLikedRssItem(_top);
return episodes;
}
_loadMoreEpisode() async {
if (mounted) setState(() => _loadMore = true);
await Future.delayed(Duration(seconds: 3));
if (mounted)
setState(() {
_top = _top + 33;
_loadMore = false;
});
}
int _top;
bool _loadMore;
@override
void initState() {
super.initState();
_loadMore = false;
_top = 33;
}
@override
Widget build(BuildContext context) {
return FutureBuilder<List<EpisodeBrief>>(
future: _getLikedRssItem(_top),
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
return (snapshot.hasData)
? NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
if (scrollInfo.metrics.pixels ==
scrollInfo.metrics.maxScrollExtent &&
snapshot.data.length == _top) _loadMoreEpisode();
return true;
},
child: CustomScrollView(
key: PageStorageKey<String>('favorite'),
physics: const AlwaysScrollableScrollPhysics(),
slivers: <Widget>[
EpisodeGrid(
podcast: snapshot.data,
updateCount: _updateCount,
episodes: snapshot.data,
),
SliverList(
delegate: SliverChildBuilderDelegate(
@ -309,40 +452,8 @@ class _RecentUpdateState extends State<RecentUpdate> {
childCount: 1,
),
),
])
: Center(child: CircularProgressIndicator());
},
);
}
}
class MyFavorite extends StatefulWidget {
@override
_MyFavoriteState createState() => _MyFavoriteState();
}
class _MyFavoriteState extends State<MyFavorite> {
Future<List<EpisodeBrief>> _getLikedRssItem() async {
var dbHelper = DBHelper();
List<EpisodeBrief> episodes = await dbHelper.getLikedRssItem();
return episodes;
}
@override
Widget build(BuildContext context) {
return FutureBuilder<List<EpisodeBrief>>(
future: _getLikedRssItem(),
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
return (snapshot.hasData)
? CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
primary: false,
slivers: <Widget>[
EpisodeGrid(
podcast: snapshot.data,
)
],
],
),
)
: Center(child: CircularProgressIndicator());
},
@ -350,12 +461,12 @@ class _MyFavoriteState extends State<MyFavorite> {
}
}
class MyDownload extends StatefulWidget {
class _MyDownload extends StatefulWidget {
@override
_MyDownloadState createState() => _MyDownloadState();
}
class _MyDownloadState extends State<MyDownload> {
class _MyDownloadState extends State<_MyDownload> {
Future<List<EpisodeBrief>> _getDownloadedRssItem() async {
var dbHelper = DBHelper();
List<EpisodeBrief> episodes = await dbHelper.getDownloadedRssItem();
@ -374,7 +485,7 @@ class _MyDownloadState extends State<MyDownload> {
primary: false,
slivers: <Widget>[
EpisodeGrid(
podcast: snapshot.data,
episodes: snapshot.data,
showDownload: true,
)
],

View File

@ -49,7 +49,7 @@ class _PlaylistPageState extends State<PlaylistPage> {
ScrollController _controller;
_scrollListener() {
double value = _controller.offset;
setState(() => _topHeight = (100 - value) > 0 ? 100 - value : 0);
setState(() => _topHeight = (100 - value) > 60 ? 100 - value : 60);
}
double _topHeight;
@ -58,12 +58,12 @@ class _PlaylistPageState extends State<PlaylistPage> {
void initState() {
super.initState();
_topHeight = 100;
_controller = ScrollController();
_controller.addListener(_scrollListener);
_controller = ScrollController()..addListener(_scrollListener);
}
@override
void dispose() {
_controller.removeListener(_scrollListener);
_controller.dispose();
super.dispose();
}
@ -81,7 +81,7 @@ class _PlaylistPageState extends State<PlaylistPage> {
child: Scaffold(
backgroundColor: Theme.of(context).primaryColor,
appBar: AppBar(
title: _topHeight == 0 ? Text('Playlist') : Center(),
title: _topHeight == 60 ? Text('Playlist') : Center(),
elevation: 0,
backgroundColor: Theme.of(context).primaryColor,
),
@ -95,56 +95,89 @@ class _PlaylistPageState extends State<PlaylistPage> {
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,
Container(
height: _topHeight,
child: Row(
children: <Widget>[
Container(
height: _topHeight,
padding: EdgeInsets.only(
left: 70,
),
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,
)),
alignment: Alignment.centerLeft,
child: RichText(
text: TextSpan(
text: _topHeight > 90 ? 'Playlist\n' : '',
style: TextStyle(
color:
Theme.of(context).textTheme.bodyText1.color,
fontSize: 30,
),
children: <TextSpan>[
TextSpan(
text: data.item1.length.toString(),
style: GoogleFonts.cairo(
textStyle: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 30,
),
),
),
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.cairo(
textStyle: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 30,
)),
),
TextSpan(
text: ' mins',
style: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 20,
)),
],
),
TextSpan(
text: ' mins',
style: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 20,
)),
],
),
),
),
Spacer(),
_topHeight > 65
? Center()
: Container(
padding: EdgeInsets.only(
right: 20, bottom: 80 - _topHeight),
child: data.item2
? Padding(
padding: EdgeInsets.only(right: 15),
child: SizedBox(
width: 20,
height: 15,
child: WaveLoader()),
)
: IconButton(
icon: Icon(Icons.play_circle_filled,
size: 40,
color:
Theme.of(context).accentColor),
onPressed: () => audio.playlistLoad(),
),
),
],
),
),
Divider(
height: 3,
),
Expanded(
child: AnimatedList(
controller: _controller,
@ -224,15 +257,19 @@ class _PlaylistPageState extends State<PlaylistPage> {
trailing: index == 0
? data.item2
? Padding(
padding: const EdgeInsets.only(
right: 12.0),
padding: EdgeInsets.only(
right: 15, top: 20),
child: SizedBox(
width: 20,
height: 15,
child: WaveLoader()),
)
: IconButton(
icon: Icon(Icons.play_arrow),
icon: Icon(
Icons.play_arrow,
color: Theme.of(context)
.accentColor,
),
onPressed: () =>
audio.playlistLoad())
: Transform.rotate(

View File

@ -54,6 +54,8 @@ class DBHelper {
list = await dbClient.rawQuery(
'SELECT id, title, imageUrl, rssUrl, primaryColor, author, imagePath , provider, link ,update_count FROM PodcastLocal WHERE id = ?',
[s]);
int count = Sqflite.firstIntValue(await dbClient.rawQuery(
'SELECT COUNT(*) FROM Episodes WHERE feed_id = ?', [s]));
podcastLocal.add(PodcastLocal(
list.first['title'],
list.first['imageUrl'],
@ -64,7 +66,8 @@ class DBHelper {
list.first['imagePath'],
list.first['provider'],
list.first['link'],
upateCount: list.first['update_count']));
upateCount: list.first['update_count'],
episodeCount: count));
});
return podcastLocal;
}
@ -398,13 +401,6 @@ class DBHelper {
} else {
for (int i = 0; i < (result - count); i++) {
print(feed.items[i].title);
// if (feed.items[i].itunes.summary != null) {
// feed.items[i].itunes.summary.contains('<')
// ? description = feed.items[i].itunes.summary
// : description = feed.items[i].description;
// } else {
// description = feed.items[i].description;
// }
description = _getDescription(
feed.items[i].content.value ?? '',
feed.items[i].description ?? '',
@ -444,11 +440,11 @@ class DBHelper {
});
}
}
return result - count;
return (result - count) < 0 ? 0 : (result - count);
}
}
Future<List<EpisodeBrief>> getRssItem(String id) async {
Future<List<EpisodeBrief>> getRssItem(String id, int i) async {
var dbClient = await database;
List<EpisodeBrief> episodes = [];
List<Map> list = await dbClient
@ -456,7 +452,7 @@ class DBHelper {
E.milliseconds, P.imagePath, P.title as feedTitle, E.duration, E.explicit, E.liked,
E.downloaded, P.primaryColor , E.media_id
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
WHERE P.id = ? ORDER BY E.milliseconds DESC""", [id]);
WHERE P.id = ? ORDER BY E.milliseconds DESC LIMIT ?""", [id, i]);
for (int x = 0; x < list.length; x++) {
episodes.add(EpisodeBrief(
list[x]['title'],
@ -557,14 +553,14 @@ class DBHelper {
return episodes;
}
Future<List<EpisodeBrief>> getLikedRssItem() async {
Future<List<EpisodeBrief>> getLikedRssItem(int i) async {
var dbClient = await database;
List<EpisodeBrief> episodes = List();
List<Map> list = await dbClient.rawQuery(
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath,
P.title as feed_title, E.duration, E.explicit, E.liked, E.downloaded,
P.primaryColor, E.media_id FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
WHERE E.liked = 1 ORDER BY E.milliseconds DESC LIMIT 99""");
WHERE E.liked = 1 ORDER BY E.milliseconds DESC LIMIT ?""",[i]);
for (int x = 0; x < list.length; x++) {
episodes.add(EpisodeBrief(
list[x]['title'],

View File

@ -5,9 +5,11 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:html/parser.dart';
import 'package:tsacdop/class/audiostate.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:flutter_linkify/flutter_linkify.dart';
import 'package:provider/provider.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:tsacdop/class/podcastlocal.dart';
@ -45,9 +47,10 @@ class _PodcastDetailState extends State<PodcastDetail> {
if (mounted) setState(() {});
}
Future<List<EpisodeBrief>> _getRssItem(PodcastLocal podcastLocal) async {
Future<List<EpisodeBrief>> _getRssItem(
PodcastLocal podcastLocal, int i) async {
var dbHelper = DBHelper();
List<EpisodeBrief> episodes = await dbHelper.getRssItem(podcastLocal.id);
List<EpisodeBrief> episodes = await dbHelper.getRssItem(podcastLocal.id, i);
if (podcastLocal.provider.contains('fireside')) {
FiresideData data = FiresideData(podcastLocal.id, podcastLocal.link);
await data.getData();
@ -156,7 +159,25 @@ class _PodcastDetailState extends State<PodcastDetail> {
);
}
double top = 0;
double _topHeight = 0;
ScrollController _controller;
int _top;
bool _loadMore;
@override
void initState() {
super.initState();
_loadMore = false;
_top = 33;
_controller = ScrollController();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
@ -178,177 +199,239 @@ class _PodcastDetailState extends State<PodcastDetail> {
onRefresh: () => _updateRssItem(widget.podcastLocal),
child: Stack(
children: <Widget>[
FutureBuilder<List<EpisodeBrief>>(
future: _getRssItem(widget.podcastLocal),
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
return (snapshot.hasData)
? CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
primary: true,
slivers: <Widget>[
SliverAppBar(
brightness: Brightness.dark,
actions: <Widget>[
PopupMenuButton<String>(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10))),
elevation: 2,
tooltip: 'Menu',
itemBuilder: (context) => [
widget.podcastLocal.link != null
? PopupMenuItem(
value: widget.podcastLocal.link,
Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(
child: FutureBuilder<List<EpisodeBrief>>(
future: _getRssItem(widget.podcastLocal, _top),
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
return (snapshot.hasData)
? CustomScrollView(
controller: _controller
..addListener(() async {
if (_controller.offset ==
_controller
.position.maxScrollExtent &&
snapshot.data.length == _top) {
if (mounted)
setState(() => _loadMore = true);
await Future.delayed(
Duration(seconds: 3));
if (mounted)
setState(() {
_top = _top + 33;
_loadMore = false;
});
}
}),
physics: const ClampingScrollPhysics(),
//primary: true,
slivers: <Widget>[
SliverAppBar(
brightness: Brightness.dark,
actions: <Widget>[
PopupMenuButton<String>(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10))),
elevation: 2,
tooltip: 'Menu',
itemBuilder: (context) => [
widget.podcastLocal.link != null
? PopupMenuItem(
value: widget
.podcastLocal.link,
child: Container(
padding: EdgeInsets.only(
left: 10),
child: Row(
children: <Widget>[
Icon(Icons.link,
color: Theme.of(
context)
.tabBarTheme
.labelColor),
Padding(
padding: EdgeInsets
.symmetric(
horizontal:
5.0),
),
Text('Visit Site'),
],
),
),
)
: Center(),
PopupMenuItem(
value: widget.podcastLocal.rssUrl,
child: Container(
padding:
EdgeInsets.only(left: 10),
child: Row(
children: <Widget>[
Icon(Icons.link,
color: Theme.of(context)
.tabBarTheme
.labelColor),
Icon(
Icons.rss_feed,
color: Theme.of(context)
.tabBarTheme
.labelColor,
),
Padding(
padding:
EdgeInsets.symmetric(
horizontal: 5.0),
),
Text('Visit Site'),
Text('View Rss Feed'),
],
),
),
)
: Center(),
PopupMenuItem(
value: widget.podcastLocal.rssUrl,
child: Container(
padding: EdgeInsets.only(left: 10),
child: Row(
),
],
onSelected: (url) {
_launchUrl(url);
},
)
],
elevation: 0,
iconTheme: IconThemeData(
color: Colors.white,
),
expandedHeight: 150 +
MediaQuery.of(context).padding.top,
backgroundColor: _color,
floating: true,
pinned: true,
flexibleSpace: LayoutBuilder(builder:
(BuildContext context,
BoxConstraints constraints) {
_topHeight = constraints.biggest.height;
return FlexibleSpaceBar(
background: Stack(
children: <Widget>[
Icon(
Icons.rss_feed,
color: Theme.of(context)
.tabBarTheme
.labelColor,
Container(
margin: EdgeInsets.only(
top: 120 +
MediaQuery.of(context)
.padding
.top),
padding: EdgeInsets.only(
left: 80, right: 120),
color: Colors.white10,
alignment: Alignment.centerLeft,
child: Column(
mainAxisAlignment:
MainAxisAlignment.start,
mainAxisSize:
MainAxisSize.min,
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
Text(
widget.podcastLocal
.author ??
'',
maxLines: 1,
overflow: TextOverflow
.ellipsis,
style: TextStyle(
color:
Colors.white)),
widget.podcastLocal.provider
.isNotEmpty
? Text(
'Hosted on ' +
widget
.podcastLocal
.provider,
maxLines: 1,
style: TextStyle(
color: Colors
.white),
)
: Center(),
],
),
),
Padding(
padding: EdgeInsets.symmetric(
horizontal: 5.0),
Container(
alignment:
Alignment.centerRight,
padding:
EdgeInsets.only(right: 10),
child: SizedBox(
height: 120,
child: Image.file(File(
"${widget.podcastLocal.imagePath}")),
),
),
Container(
alignment: Alignment.center,
child: podcastInfo(context),
),
Text('View Rss Feed'),
],
),
),
),
],
onSelected: (url) {
_launchUrl(url);
},
)
],
elevation: 0,
iconTheme: IconThemeData(
color: Colors.white,
),
expandedHeight:
150 + MediaQuery.of(context).padding.top,
backgroundColor: _color,
floating: true,
pinned: true,
flexibleSpace: LayoutBuilder(builder:
(BuildContext context,
BoxConstraints constraints) {
top = constraints.biggest.height;
return FlexibleSpaceBar(
background: Stack(
children: <Widget>[
Container(
margin: EdgeInsets.only(
top: 120 +
MediaQuery.of(context)
.padding
.top),
padding: EdgeInsets.only(
left: 80, right: 120),
color: Colors.white10,
alignment: Alignment.centerLeft,
child: Column(
mainAxisAlignment:
MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
Text(
widget.podcastLocal.author ??
'',
title: _topHeight <
70 +
MediaQuery.of(context)
.padding
.top
? Text(widget.podcastLocal.title,
maxLines: 1,
overflow:
TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white)),
widget.podcastLocal.provider
.isNotEmpty
? Text(
'Hosted on ' +
widget.podcastLocal
.provider,
maxLines: 1,
style: TextStyle(
color: Colors.white),
)
: Center(),
],
),
),
Container(
alignment: Alignment.centerRight,
padding: EdgeInsets.only(right: 10),
child: SizedBox(
height: 120,
child: Image.file(File(
"${widget.podcastLocal.imagePath}")),
),
),
Container(
alignment: Alignment.center,
child: podcastInfo(context),
),
],
color: Colors.white))
: Center(),
);
}),
),
title: top <
70 +
MediaQuery.of(context)
.padding
.top
? Text(widget.podcastLocal.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style:
TextStyle(color: Colors.white))
: Center(),
);
}),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return hostsList(context, hosts);
},
childCount: 1,
),
),
EpisodeGrid(
podcast: snapshot.data,
showFavorite: true,
showNumber: true,
updateCount: widget.podcastLocal.upateCount,
),
],
)
: Center(child: CircularProgressIndicator());
},
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return hostsList(context, hosts);
},
childCount: 1,
),
),
EpisodeGrid(
episodes: snapshot.data,
showFavorite: true,
showNumber: true,
updateCount:
widget.podcastLocal.upateCount,
episodeCount:
widget.podcastLocal.episodeCount,
),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return _loadMore
? Container(
height: 2,
child:
LinearProgressIndicator())
: Center();
},
childCount: 1,
),
),
],
)
: Center(child: CircularProgressIndicator());
},
),
),
Selector<AudioPlayerNotifier, bool>(
selector: (_, audio) => audio.playerRunning,
builder: (_, data, __) {
return Padding(
padding: EdgeInsets.only(bottom: data ? 60.0 : 0),
);
}),
],
),
Container(child: PlayerWidget()),
],

View File

@ -5,6 +5,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:path_provider/path_provider.dart';
import 'package:line_icons/line_icons.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:tsacdop/class/episodebrief.dart';
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
@ -123,22 +124,24 @@ class _DownloadsManageState extends State<DownloadsManage> {
),
Container(
height: 100.0,
padding: EdgeInsets.only(bottom: 40, left: 60),
padding: EdgeInsets.only(bottom: 20, left: 60),
alignment: Alignment.centerLeft,
child: RichText(
text: TextSpan(
text: 'Total ',
text: 'Total ',
style: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 20,
),
children: <TextSpan>[
TextSpan(
text: _fileNum.toString(),
style: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 40,
)),
text: _fileNum.toString(),
style: GoogleFonts.cairo(
textStyle: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 40,
)),
),
TextSpan(
text: _fileNum < 2 ? ' episode' : ' episodes ',
style: TextStyle(
@ -146,11 +149,13 @@ class _DownloadsManageState extends State<DownloadsManage> {
fontSize: 20,
)),
TextSpan(
text: (_size ~/ 1000000).toString(),
style: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 60,
)),
text: (_size ~/ 1000000).toString(),
style: GoogleFonts.cairo(
textStyle: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 50,
)),
),
TextSpan(
text: ' Mb',
style: TextStyle(

View File

@ -196,7 +196,7 @@ class _RenderSwitch extends RenderToggleable {
..onEnd = _handleDragEnd
..dragStartBehavior = dragStartBehavior;
}
ImageProvider get activeThumbImage => _activeThumbImage;
ImageProvider _activeThumbImage;
set activeThumbImage(ImageProvider value) {
@ -285,7 +285,9 @@ class _RenderSwitch extends RenderToggleable {
positionController.value += delta;
break;
}
positionController.addListener(() {onDrag(positionController.value);});
positionController.addListener(() {
onDrag(positionController.value);
});
}
}
@ -297,8 +299,6 @@ class _RenderSwitch extends RenderToggleable {
reactionController.reverse();
}
@override
void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
assert(debugHandleEvent(event, entry));
@ -309,7 +309,7 @@ class _RenderSwitch extends RenderToggleable {
Color _cachedThumbColor;
ImageProvider _cachedThumbImage;
BoxPainter _cachedThumbPainter;
BoxDecoration _createDefaultThumbDecoration(
Color color, ImageProvider image) {
return BoxDecoration(
@ -335,9 +335,7 @@ class _RenderSwitch extends RenderToggleable {
super.describeSemanticsConfiguration(config);
config.isToggled = value == true;
}
@override
void paint(PaintingContext context, Offset offset) {
final Canvas canvas = context.canvas;
@ -413,7 +411,7 @@ class _RenderSwitch extends RenderToggleable {
);
var starPaint = Paint()
..strokeWidth = 4 + (6 * (1 - currentValue))
..strokeWidth = 2 + (6 * (1 - currentValue))
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke
..color = Color.fromARGB((255 * currentValue).floor(), 255, 255, 255);
@ -450,14 +448,14 @@ class _RenderSwitch extends RenderToggleable {
_isPainting = false;
}
canvas.drawLine(
Offset(offset.dx + _kSwitchWidth * 0.3, offset.dy + _kSwitchHeight * 0.5),
Offset(
offset.dx +
(_kSwitchWidth * 0.3) +
(_kSwitchWidth / 2 * (1 - currentValue)),
offset.dy + _kSwitchHeight * 0.5),
linePaint,
);
canvas.drawLine(
Offset(offset.dx + _kSwitchWidth * 0.3, offset.dy + _kSwitchHeight * 0.5),
Offset(
offset.dx +
(_kSwitchWidth * 0.3) +
(_kSwitchWidth / 2 * (1 - currentValue)),
offset.dy + _kSwitchHeight * 0.5),
linePaint,
);
}
}
}

View File

@ -17,20 +17,22 @@ import 'package:tsacdop/episodes/episodedetail.dart';
import 'package:tsacdop/util/colorize.dart';
class EpisodeGrid extends StatelessWidget {
final List<EpisodeBrief> podcast;
final List<EpisodeBrief> episodes;
final bool showFavorite;
final bool showDownload;
final bool showNumber;
final int updateCount;
final String heroTag;
final int episodeCount;
EpisodeGrid(
{Key key,
@required this.podcast,
@required this.episodes,
this.heroTag = '',
this.showDownload = false,
this.showFavorite = false,
this.showNumber = false,
this.updateCount = 0})
this.updateCount = 0,
this.episodeCount = 0})
: super(key: key);
@override
@ -115,14 +117,14 @@ class EpisodeGrid extends StatelessWidget {
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
Color _c = (Theme.of(context).brightness == Brightness.light)
? podcast[index].primaryColor.colorizedark()
: podcast[index].primaryColor.colorizeLight();
? episodes[index].primaryColor.colorizedark()
: episodes[index].primaryColor.colorizeLight();
return Selector<AudioPlayerNotifier,
Tuple2<EpisodeBrief, List<String>>>(
selector: (_, audio) => Tuple2(audio?.episode,
audio.queue.playlist.map((e) => e.enclosureUrl).toList()),
builder: (_, data, __) => OpenContainerWrapper(
episode: podcast[index],
episode: episodes[index],
closedBuilder: (context, action, boo) => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(5.0)),
@ -143,21 +145,11 @@ class EpisodeGrid extends StatelessWidget {
details.globalPosition.dx, details.globalPosition.dy),
onLongPress: () => _showPopupMenu(
_offset,
podcast[index],
episodes[index],
context,
data.item1 == podcast[index],
data.item2.contains(podcast[index].enclosureUrl)),
data.item1 == episodes[index],
data.item2.contains(episodes[index].enclosureUrl)),
onTap: action,
// {
// Navigator.push(
// context,
// ScaleRoute(
// page: EpisodeDetail(
// episodeItem: podcast[index],
// heroTag: heroTag,
// )),
// )
// },
child: Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
@ -187,7 +179,7 @@ class EpisodeGrid extends StatelessWidget {
backgroundColor:
_c.withOpacity(0.5),
backgroundImage: FileImage(File(
"${podcast[index].imagePath}")),
"${episodes[index].imagePath}")),
),
),
Spacer(),
@ -205,7 +197,7 @@ class EpisodeGrid extends StatelessWidget {
? Container(
alignment: Alignment.topRight,
child: Text(
(podcast.length - index).toString(),
(episodeCount- index).toString(),
style: GoogleFonts.teko(
textStyle: TextStyle(
fontSize: _width / 24,
@ -224,7 +216,7 @@ class EpisodeGrid extends StatelessWidget {
alignment: Alignment.topLeft,
padding: EdgeInsets.only(top: 2.0),
child: Text(
podcast[index].title,
episodes[index].title,
style: TextStyle(
fontSize: _width / 32,
),
@ -240,7 +232,7 @@ class EpisodeGrid extends StatelessWidget {
Align(
alignment: Alignment.bottomLeft,
child: Text(
podcast[index].dateToString(),
episodes[index].dateToString(),
style: TextStyle(
fontSize: _width / 35,
color: _c,
@ -250,7 +242,7 @@ class EpisodeGrid extends StatelessWidget {
Spacer(),
showDownload
? DownloadIcon(
episodeBrief: podcast[index])
episodeBrief: episodes[index])
: Center(),
Padding(
padding: EdgeInsets.all(1),
@ -258,7 +250,7 @@ class EpisodeGrid extends StatelessWidget {
showFavorite
? Container(
alignment: Alignment.bottomRight,
child: (podcast[index].liked == 0)
child: (episodes[index].liked == 0)
? Center()
: IconTheme(
data: IconThemeData(size: 15),
@ -281,7 +273,7 @@ class EpisodeGrid extends StatelessWidget {
),
);
},
childCount: podcast.length,
childCount: episodes.length,
),
),
);

View File

@ -11,7 +11,7 @@ description: An easy-use podacasts player.
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.1.4
version: 0.1.5
environment:
sdk: ">=2.6.0 <3.0.0"
@ -51,7 +51,7 @@ dev_dependencies:
workmanager: ^0.2.2
flutter_colorpicker: ^0.3.2
app_settings: ^3.0.1
fl_chart: ^0.8.3
fl_chart: ^0.8.7
audio_service: ^0.6.2
just_audio: ^0.1.3
rxdart: ^0.23.1
@ -60,8 +60,6 @@ dev_dependencies:
url: https://github.com/galonsos/line_icons.git
flutter_file_dialog: ^0.0.5
flutter_linkify: ^3.1.0
animations: ^1.0.0+5
# For information on the generic Dart part of this file, see the
# following page: https://www.dartlang.org/tools/pub/pubspec