Layout setting, supprt one raw view

This commit is contained in:
stonegate 2020-06-03 20:39:15 +08:00
parent e9ba82d5db
commit b45c7e3a5b
16 changed files with 818 additions and 513 deletions

View File

@ -9,6 +9,7 @@ import 'package:dio/dio.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:cached_network_image/cached_network_image.dart';
import '../type/searchpodcast.dart';
import '../state/subscribe_podcast.dart';
@ -391,26 +392,26 @@ class _SearchResultState extends State<SearchResult>
},
leading: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(20.0)),
child: Image.network(
widget.onlinePodcast.image,
child: CachedNetworkImage(
height: 40.0,
width: 40.0,
fit: BoxFit.fitWidth,
alignment: Alignment.center,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Container(
alignment: Alignment.center,
height: 40,
width: 40,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: context.primaryColorDark),
child: SizedBox(
width: 20, height: 2, child: LinearProgressIndicator()),
);
},
errorBuilder: (context, error, stackTrace) => Container(
imageUrl: widget.onlinePodcast.image,
progressIndicatorBuilder: (context, url, downloadProgress) =>
Container(
height: 40,
width: 40,
alignment: Alignment.center,
color: context.primaryColorDark,
child: SizedBox(
width: 20,
height: 2,
child: LinearProgressIndicator(
value: downloadProgress.progress),
),
),
errorWidget: (context, url, error) => Container(
width: 40,
height: 40,
alignment: Alignment.center,

View File

@ -12,6 +12,7 @@ import 'package:fluttertoast/fluttertoast.dart';
import '../state/audiostate.dart';
import '../type/episodebrief.dart';
import '../local_storage/sqflite_localpodcast.dart';
import '../local_storage/key_value_storage.dart';
import '../util/episodegrid.dart';
import '../util/mypopupmenu.dart';
import '../util/context_extension.dart';
@ -401,6 +402,10 @@ class _RecentUpdate extends StatefulWidget {
class _RecentUpdateState extends State<_RecentUpdate>
with AutomaticKeepAliveClientMixin, SingleTickerProviderStateMixin {
Future<List<EpisodeBrief>> _getRssItem(int top, List<String> group) async {
KeyValueStorage storage = KeyValueStorage(recentLayoutKey);
int index = await storage.getInt();
if (_layout == null) _layout = Layout.values[index];
var dbHelper = DBHelper();
List<EpisodeBrief> episodes;
if (group.first == 'All')
@ -441,7 +446,6 @@ class _RecentUpdateState extends State<_RecentUpdate>
_loadMore = false;
_groupName = 'All';
_group = ['All'];
_layout = Layout.three;
}
@override
@ -641,11 +645,16 @@ class _RecentUpdateState extends State<_RecentUpdate>
onPressed: () {
if (_layout == Layout.three)
setState(() {
_layout = Layout.two;
_layout = Layout.one;
});
else if (_layout ==
Layout.two)
setState(() {
_layout = Layout.three;
});
else
setState(() {
_layout = Layout.three;
_layout = Layout.two;
});
},
icon: _layout == Layout.three
@ -661,18 +670,33 @@ class _RecentUpdateState extends State<_RecentUpdate>
.color),
),
)
: SizedBox(
height: 10,
width: 30,
child: CustomPaint(
painter: LayoutPainter(
1,
context
.textTheme
.bodyText1
.color),
),
),
: _layout == Layout.two
? SizedBox(
height: 10,
width: 30,
child:
CustomPaint(
painter: LayoutPainter(
1,
context
.textTheme
.bodyText1
.color),
),
)
: SizedBox(
height: 10,
width: 30,
child:
CustomPaint(
painter: LayoutPainter(
4,
context
.textTheme
.bodyText1
.color),
),
),
)),
],
),
@ -714,6 +738,9 @@ class _MyFavorite extends StatefulWidget {
class _MyFavoriteState extends State<_MyFavorite>
with AutomaticKeepAliveClientMixin {
Future<List<EpisodeBrief>> _getLikedRssItem(int top, int sortBy) async {
KeyValueStorage storage = KeyValueStorage(favLayoutKey);
int index = await storage.getInt();
if (_layout == null) _layout = Layout.values[index];
var dbHelper = DBHelper();
List<EpisodeBrief> episodes = await dbHelper.getLikedRssItem(top, sortBy);
return episodes;
@ -737,7 +764,6 @@ class _MyFavoriteState extends State<_MyFavorite>
void initState() {
super.initState();
_loadMore = false;
_layout = Layout.three;
_sortBy = 0;
}
@ -832,39 +858,53 @@ class _MyFavoriteState extends State<_MyFavorite>
Material(
color: Colors.transparent,
child: IconButton(
padding: EdgeInsets.zero,
onPressed: () {
if (_layout == Layout.three)
setState(() {
_layout = Layout.two;
});
else
setState(() {
_layout = Layout.three;
});
},
icon: _layout == Layout.three
? SizedBox(
height: 10,
width: 30,
child: CustomPaint(
painter: LayoutPainter(
0,
context.textTheme.bodyText1
.color),
),
)
: SizedBox(
height: 10,
width: 30,
child: CustomPaint(
painter: LayoutPainter(
1,
context.textTheme.bodyText1
.color),
),
),
),
padding: EdgeInsets.zero,
onPressed: () {
if (_layout == Layout.three)
setState(() {
_layout = Layout.one;
});
else if (_layout == Layout.two)
setState(() {
_layout = Layout.three;
});
else
setState(() {
_layout = Layout.two;
});
},
icon: _layout == Layout.three
? SizedBox(
height: 10,
width: 30,
child: CustomPaint(
painter: LayoutPainter(
0,
context.textTheme
.bodyText1.color),
),
)
: _layout == Layout.two
? SizedBox(
height: 10,
width: 30,
child: CustomPaint(
painter: LayoutPainter(
1,
context.textTheme
.bodyText1.color),
),
)
: SizedBox(
height: 10,
width: 30,
child: CustomPaint(
painter: LayoutPainter(
4,
context.textTheme
.bodyText1.color),
),
)),
),
],
)),
@ -906,10 +946,19 @@ class _MyDownload extends StatefulWidget {
class _MyDownloadState extends State<_MyDownload>
with AutomaticKeepAliveClientMixin {
Layout _layout;
_getLayout() async {
KeyValueStorage keyValueStorage = KeyValueStorage(downloadLayoutKey);
int layout = await keyValueStorage.getInt();
if (_layout == null)
setState(() {
_layout = Layout.values[layout];
});
}
@override
void initState() {
super.initState();
_layout = Layout.three;
_getLayout();
}
@override
@ -920,50 +969,65 @@ class _MyDownloadState extends State<_MyDownload>
slivers: <Widget>[
DownloadList(),
SliverToBoxAdapter(
child: Container(
height: 40,
color: context.primaryColor,
child: Row(
children: <Widget>[
Container(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Text('Downloaded')),
Spacer(),
Material(
color: Colors.transparent,
child: IconButton(
padding: EdgeInsets.zero,
onPressed: () {
if (_layout == Layout.three)
setState(() {
_layout = Layout.two;
});
else
setState(() {
_layout = Layout.three;
});
},
icon: _layout == Layout.three
? SizedBox(
height: 10,
width: 30,
child: CustomPaint(
painter: LayoutPainter(
0, context.textTheme.bodyText1.color),
),
)
: SizedBox(
height: 10,
width: 30,
child: CustomPaint(
painter: LayoutPainter(
1, context.textTheme.bodyText1.color),
),
),
),
),
],
)),
child: _layout == null
? Center()
: Container(
height: 40,
color: context.primaryColor,
child: Row(
children: <Widget>[
Container(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Text('Downloaded')),
Spacer(),
Material(
color: Colors.transparent,
child: IconButton(
padding: EdgeInsets.zero,
onPressed: () {
if (_layout == Layout.three)
setState(() {
_layout = Layout.one;
});
else if (_layout == Layout.two)
setState(() {
_layout = Layout.three;
});
else
setState(() {
_layout = Layout.two;
});
},
icon: _layout == Layout.three
? SizedBox(
height: 10,
width: 30,
child: CustomPaint(
painter: LayoutPainter(
0, context.textTheme.bodyText1.color),
),
)
: _layout == Layout.two
? SizedBox(
height: 10,
width: 30,
child: CustomPaint(
painter: LayoutPainter(1,
context.textTheme.bodyText1.color),
),
)
: SizedBox(
height: 10,
width: 30,
child: CustomPaint(
painter: LayoutPainter(4,
context.textTheme.bodyText1.color),
),
),
),
),
],
)),
),
Consumer<DownloadState>(
builder: (_, downloader, __) {

View File

@ -10,6 +10,7 @@ import 'package:line_icons/line_icons.dart';
import '../type/episodebrief.dart';
import '../state/podcast_group.dart';
import '../state/subscribe_podcast.dart';
import '../type/podcastlocal.dart';
import '../state/audiostate.dart';
import '../util/custompaint.dart';
@ -394,29 +395,31 @@ class _PodcastPreviewState extends State<PodcastPreview> {
return Column(
children: <Widget>[
Expanded(
child: Container(
child: FutureBuilder<List<EpisodeBrief>>(
future: _getRssItemTop(widget.podcastLocal),
builder: (context, snapshot) {
if (snapshot.hasError) {
print(snapshot.error);
Center();
}
return (snapshot.hasData)
? ShowEpisode(
episodes: snapshot.data,
podcastLocal: widget.podcastLocal,
)
: Container(
padding: EdgeInsets.all(5.0),
);
},
),
),
child: Selector<SubscribeWorker, bool>(
selector: (_, worker) => worker.created,
builder: (context, created, child) {
return FutureBuilder<List<EpisodeBrief>>(
future: _getRssItemTop(widget.podcastLocal),
builder: (context, snapshot) {
if (snapshot.hasError) {
print(snapshot.error);
Center();
}
return (snapshot.hasData)
? ShowEpisode(
episodes: snapshot.data,
podcastLocal: widget.podcastLocal,
)
: Container(
padding: EdgeInsets.all(5.0),
);
},
);
}),
),
Container(
height: 40,
padding: EdgeInsets.symmetric(horizontal: 10.0),
padding: EdgeInsets.only(left: 10.0),
alignment: Alignment.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
@ -434,18 +437,25 @@ class _PodcastPreviewState extends State<PodcastPreview> {
alignment: Alignment.centerRight,
child: Material(
color: Colors.transparent,
child: IconButton(
icon: Icon(Icons.arrow_forward),
tooltip: 'See All',
onPressed: () {
Navigator.push(
context,
SlideLeftHideRoute(
page: PodcastDetail(
podcastLocal: widget.podcastLocal,
)),
);
},
child: Selector<AudioPlayerNotifier, bool>(
selector: (_, audio) => audio.playerRunning,
builder: (_, playerRunning, __) => IconButton(
icon: Icon(Icons.arrow_forward),
tooltip: 'See All',
onPressed: () {
Navigator.push(
context,
SlideLeftHideRoute(
transitionPage: PodcastDetail(
podcastLocal: widget.podcastLocal,
hide: playerRunning,
),
page: PodcastDetail(
podcastLocal: widget.podcastLocal,
)),
);
},
),
),
),
),

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flare_flutter/flare_actor.dart';
import '../util/context_extension.dart';
class FirstPage extends StatefulWidget {
FirstPage({Key key}) : super(key: key);
@ -12,7 +13,7 @@ class _FirstPageState extends State<FirstPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
body: Container(
color: Color.fromRGBO(35, 204, 198, 1),
child: Center(
child: Column(
@ -20,10 +21,10 @@ class _FirstPageState extends State<FirstPage> {
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.all(100),
padding: EdgeInsets.symmetric(vertical: 100),
),
Container(
height: 400,
height: context.width * 3 / 4,
// color: Colors.red,
child: FlareActor(
'assets/splash.flr',

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flare_flutter/flare_actor.dart';
import '../util/context_extension.dart';
class FourthPage extends StatefulWidget {
FourthPage({Key key}) : super(key: key);
@ -28,7 +29,7 @@ class _FourthPageState extends State<FourthPage> {
),),
),
Container(
height: 400,
height: context.width*3/4,
// color: Colors.red,
child: FlareActor(
'assets/longtap.flr',

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flare_flutter/flare_actor.dart';
import '../util/context_extension.dart';
class SecondPage extends StatefulWidget {
SecondPage({Key key}) : super(key: key);
@ -21,14 +22,15 @@ class _SecondPageState extends State<SecondPage> {
Container(
height: 200,
alignment: Alignment.center,
padding: EdgeInsets.all(40),
padding:
EdgeInsets.only(top: 20, bottom: 20, left: 40, right: 40),
child: Text(
'Subscribe podcast via search or import OMPL file.',
style: TextStyle(fontSize: 30, color: Colors.white),
),
),
Container(
height: 400,
height: context.width * 3 / 4,
// color: Colors.red,
child: FlareActor(
'assets/add.flr',

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flare_flutter/flare_actor.dart';
import '../util/context_extension.dart';
class ThirdPage extends StatefulWidget {
ThirdPage({Key key}) : super(key: key);
@ -22,13 +23,13 @@ class _ThirdPageState extends State<ThirdPage> {
height: 200,
alignment: Alignment.center,
padding: EdgeInsets.all(40),
child: Text('Swipe on podcast list to change group.', style: TextStyle(
fontSize: 30,
color: Colors.white
),),
child: Text(
'Swipe on podcast list to change group.',
style: TextStyle(fontSize: 30, color: Colors.white),
),
),
Container(
height: 400,
height: context.width * 3 / 4,
// color: Colors.red,
child: FlareActor(
'assets/swipe.flr',

View File

@ -1,8 +1,10 @@
import 'dart:async';
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
import '../state/podcast_group.dart';
import '../util/episodegrid.dart';
const String autoPlayKey = 'autoPlay';
const String autoAddKey = 'autoAdd';
@ -17,6 +19,10 @@ const String downloadUsingDataKey = 'downloadUsingData';
const String introKey = 'intro';
const String realDarkKey = 'realDark';
const String cacheMaxKey = 'cacheMax';
const String podcastLayoutKey = 'podcastLayoutKey';
const String recentLayoutKey = 'recentLayoutKey';
const String favLayoutKey = 'favLayoutKey';
const String downloadLayoutKey = 'downloadLayoutKey';
class KeyValueStorage {
final String key;

View File

@ -15,6 +15,7 @@ import 'package:cached_network_image/cached_network_image.dart';
import '../type/podcastlocal.dart';
import '../type/episodebrief.dart';
import '../local_storage/sqflite_localpodcast.dart';
import '../local_storage/key_value_storage.dart';
import '../util/episodegrid.dart';
import '../home/audioplayer.dart';
import '../type/fireside_data.dart';
@ -25,8 +26,10 @@ import '../state/audiostate.dart';
import '../state/podcast_group.dart';
class PodcastDetail extends StatefulWidget {
PodcastDetail({Key key, this.podcastLocal}) : super(key: key);
PodcastDetail({Key key, @required this.podcastLocal, this.hide = false})
: super(key: key);
final PodcastLocal podcastLocal;
final bool hide;
@override
_PodcastDetailState createState() => _PodcastDetailState();
}
@ -34,8 +37,10 @@ class PodcastDetail extends StatefulWidget {
class _PodcastDetailState extends State<PodcastDetail> {
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
GlobalKey<RefreshIndicatorState>();
String backgroundImage;
List<PodcastHost> hosts;
String _backgroundImage;
List<PodcastHost> _hosts;
int _episodeCount;
Layout _layout;
Future _updateRssItem(PodcastLocal podcastLocal) async {
var dbHelper = DBHelper();
@ -50,8 +55,8 @@ class _PodcastDetailState extends State<PodcastDetail> {
msg: 'Updated $result Episodes',
gravity: ToastGravity.TOP,
);
Provider.of<GroupList>(context, listen: false)
.updatePodcast(podcastLocal.id);
// Provider.of<GroupList>(context, listen: false)
// .updatePodcast(podcastLocal.id);
} else {
Fluttertoast.showToast(
msg: 'Update failed, network error',
@ -64,13 +69,17 @@ class _PodcastDetailState extends State<PodcastDetail> {
Future<List<EpisodeBrief>> _getRssItem(
PodcastLocal podcastLocal, int i, bool reverse) async {
var dbHelper = DBHelper();
_episodeCount = await dbHelper.getPodcastCounts(podcastLocal.id);
KeyValueStorage storage = KeyValueStorage(podcastLayoutKey);
int index = await storage.getInt();
if (_layout == null) _layout = Layout.values[index];
List<EpisodeBrief> episodes =
await dbHelper.getRssItem(podcastLocal.id, i, reverse);
if (podcastLocal.provider.contains('fireside')) {
FiresideData data = FiresideData(podcastLocal.id, podcastLocal.link);
await data.getData();
backgroundImage = data.background;
hosts = data.hosts;
_backgroundImage = data.background;
_hosts = data.hosts;
}
return episodes;
}
@ -114,7 +123,7 @@ class _PodcastDetailState extends State<PodcastDetail> {
image: DecorationImage(
// colorFilter: ColorFilter.mode(_color, BlendMode.color),
image: CachedNetworkImageProvider(
backgroundImage,
_backgroundImage,
),
fit: BoxFit.cover)),
alignment: Alignment.centerRight,
@ -179,14 +188,12 @@ class _PodcastDetailState extends State<PodcastDetail> {
ScrollController _controller;
int _top;
bool _loadMore;
Layout _layout;
bool _reverse;
@override
void initState() {
super.initState();
_loadMore = false;
_top = 99;
_layout = Layout.three;
_reverse = false;
_controller = ScrollController();
}
@ -211,6 +218,7 @@ class _PodcastDetailState extends State<PodcastDetail> {
child: Scaffold(
body: SafeArea(
top: false,
minimum: widget.hide ? EdgeInsets.only(bottom: 50) : EdgeInsets.zero,
child: RefreshIndicator(
key: _refreshIndicatorKey,
color: Theme.of(context).accentColor,
@ -412,7 +420,7 @@ class _PodcastDetailState extends State<PodcastDetail> {
}),
),
SliverToBoxAdapter(
child: hostsList(context, hosts),
child: hostsList(context, _hosts),
),
SliverToBoxAdapter(
child: Container(
@ -485,11 +493,16 @@ class _PodcastDetailState extends State<PodcastDetail> {
onPressed: () {
if (_layout == Layout.three)
setState(() {
_layout = Layout.two;
_layout = Layout.one;
});
else if (_layout ==
Layout.two)
setState(() {
_layout = Layout.three;
});
else
setState(() {
_layout = Layout.three;
_layout = Layout.two;
});
},
icon: _layout == Layout.three
@ -505,18 +518,33 @@ class _PodcastDetailState extends State<PodcastDetail> {
.color),
),
)
: SizedBox(
height: 10,
width: 30,
child: CustomPaint(
painter: LayoutPainter(
1,
context
.textTheme
.bodyText1
.color),
),
),
: _layout == Layout.two
? SizedBox(
height: 10,
width: 30,
child:
CustomPaint(
painter: LayoutPainter(
1,
context
.textTheme
.bodyText1
.color),
),
)
: SizedBox(
height: 10,
width: 30,
child:
CustomPaint(
painter: LayoutPainter(
4,
context
.textTheme
.bodyText1
.color),
),
),
),
),
],
@ -528,8 +556,7 @@ class _PodcastDetailState extends State<PodcastDetail> {
showNumber: true,
layout: _layout,
reverse: _reverse,
episodeCount:
widget.podcastLocal.episodeCount,
episodeCount: _episodeCount,
),
SliverList(
delegate: SliverChildBuilderDelegate(
@ -590,7 +617,7 @@ class _AboutPodcastState extends State<AboutPodcast> {
var doc = parse(description);
_description = parse(doc.body.text).documentElement.text;
}
if(mounted) setState(() => _load = true);
if (mounted) setState(() => _load = true);
}
_launchUrl(String url) async {

165
lib/settings/layouts.dart Normal file
View File

@ -0,0 +1,165 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../util/context_extension.dart';
import '../util/episodegrid.dart';
import '../util/custompaint.dart';
import '../local_storage/key_value_storage.dart';
class LayoutSetting extends StatefulWidget {
const LayoutSetting({Key key}) : super(key: key);
@override
_LayoutSettingState createState() => _LayoutSettingState();
}
class _LayoutSettingState extends State<LayoutSetting> {
Future<Layout> _getLayout(String key) async {
KeyValueStorage keyValueStorage = KeyValueStorage(key);
int layout = await keyValueStorage.getInt();
return Layout.values[layout];
}
Widget _gridOptions(BuildContext context,
{String key, Layout layout, Layout option, double scale}) =>
Padding(
padding: const EdgeInsets.only(top: 10.0, bottom: 10.0, left: 20.0),
child: InkWell(
onTap: () async {
KeyValueStorage storage = KeyValueStorage(key);
await storage.saveInt(option.index);
print(option.index);
setState(() {});
},
child: Container(
height: 30,
width: 50,
color: layout == option ? context.accentColor : Colors.transparent,
alignment: Alignment.center,
child: SizedBox(
height: 10,
width: 30,
child: CustomPaint(
painter: LayoutPainter(
scale,
layout == option
? Colors.white
: context.textTheme.bodyText1.color),
),
),
),
),
);
Widget _setDefaultGrid(BuildContext context, {String key}) {
return FutureBuilder<Layout>(
future: _getLayout(key),
builder: (context, snapshot) {
return snapshot.hasData
? Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
_gridOptions(context,
key: key,
layout: snapshot.data,
option: Layout.one,
scale: 4),
_gridOptions(context,
key: key,
layout: snapshot.data,
option: Layout.two,
scale: 1),
_gridOptions(context,
key: key,
layout: snapshot.data,
option: Layout.three,
scale: 0),
],
)
: Center();
});
}
@override
Widget build(BuildContext context) {
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: context.primaryColor,
systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness,
),
child: Scaffold(
appBar: AppBar(
title: Text('Layout'),
elevation: 0,
backgroundColor: context.primaryColor,
),
body: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.all(10.0),
),
Container(
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 80),
alignment: Alignment.centerLeft,
child: Text('Default grid view',
style: Theme.of(context)
.textTheme
.bodyText1
.copyWith(color: Theme.of(context).accentColor)),
),
ListView(
physics: const BouncingScrollPhysics(),
shrinkWrap: true,
scrollDirection: Axis.vertical,
children: <Widget>[
ListTile(
contentPadding:
EdgeInsets.only(left: 80.0, right: 20, bottom: 10),
// leading: Icon(Icons.colorize),
title: Text(
'Podcast page',
),
subtitle:
_setDefaultGrid(context, key: podcastLayoutKey),
),
ListTile(
contentPadding:
EdgeInsets.only(left: 80.0, right: 20, bottom: 10),
// leading: Icon(Icons.colorize),
title: Text(
'Recent tab in homepage',
),
subtitle:
_setDefaultGrid(context, key: recentLayoutKey),
),
ListTile(
contentPadding:
EdgeInsets.only(left: 80.0, right: 20, bottom: 10),
// leading: Icon(Icons.colorize),
title: Text(
'Favorite tab in homepage',
),
subtitle: _setDefaultGrid(context, key: favLayoutKey),
),
ListTile(
contentPadding:
EdgeInsets.only(left: 80.0, right: 20, bottom: 10),
// leading: Icon(Icons.colorize),
title: Text(
'Download tab in homepage',
),
subtitle: _setDefaultGrid(context, key: downloadLayoutKey),
),
Divider(height: 2),
]),
],
),
)),
);
}
}

View File

@ -15,6 +15,7 @@ import '../intro_slider/app_intro.dart';
import '../type/podcastlocal.dart';
import '../local_storage/sqflite_localpodcast.dart';
import 'theme.dart';
import 'layouts.dart';
import 'storage.dart';
import 'history.dart';
import 'syncing.dart';
@ -152,6 +153,18 @@ class _SettingsState extends State<Settings>
subtitle: Text('Colors and themes'),
),
Divider(height: 2),
ListTile(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LayoutSetting())),
contentPadding:
EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(LineIcons.stop_circle_solid),
title: Text('Layout'),
subtitle: Text('App layout'),
),
Divider(height: 2),
ListTile(
onTap: () => Navigator.push(
context,
@ -294,6 +307,7 @@ class _SettingsState extends State<Settings>
MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox(width: 75),
_feedbackItem(
LineIcons.github,
'Submit issue',

View File

@ -26,175 +26,169 @@ class ThemeSetting extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
Padding(
padding: EdgeInsets.all(10.0),
),
Container(
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 80),
alignment: Alignment.centerLeft,
child: Text('Interface',
style: Theme.of(context)
.textTheme
.bodyText1
.copyWith(color: Theme.of(context).accentColor)),
),
ListView(
physics: const BouncingScrollPhysics(),
shrinkWrap: true,
scrollDirection: Axis.vertical,
children: <Widget>[
Padding(
padding: EdgeInsets.all(10.0),
),
Container(
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 80),
alignment: Alignment.centerLeft,
child: Text('Interface',
style: Theme.of(context)
.textTheme
.bodyText1
.copyWith(color: Theme.of(context).accentColor)),
),
ListView(
physics: const BouncingScrollPhysics(),
shrinkWrap: true,
scrollDirection: Axis.vertical,
children: <Widget>[
ListTile(
onTap: () => showGeneralDialog(
context: context,
barrierDismissible: true,
barrierLabel: MaterialLocalizations.of(context)
.modalBarrierDismissLabel,
barrierColor: Colors.black54,
transitionDuration: const Duration(milliseconds: 200),
pageBuilder: (BuildContext context,
Animation animaiton,
Animation secondaryAnimation) =>
AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Brightness.light,
systemNavigationBarColor:
Theme.of(context).brightness ==
Brightness.light
? Color.fromRGBO(113, 113, 113, 1)
: Color.fromRGBO(15, 15, 15, 1),
ListTile(
onTap: () => showGeneralDialog(
context: context,
barrierDismissible: true,
barrierLabel: MaterialLocalizations.of(context)
.modalBarrierDismissLabel,
barrierColor: Colors.black54,
transitionDuration: const Duration(milliseconds: 200),
pageBuilder: (BuildContext context,
Animation animaiton,
Animation secondaryAnimation) =>
AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Brightness.light,
systemNavigationBarColor:
Theme.of(context).brightness ==
Brightness.light
? Color.fromRGBO(113, 113, 113, 1)
: Color.fromRGBO(15, 15, 15, 1),
),
child: AlertDialog(
titlePadding: EdgeInsets.only(
top: 20,
left: 40,
right: context.width / 3,
),
elevation: 1,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0))),
title: Text('Theme'),
content: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment:
MainAxisAlignment.start,
children: <Widget>[
RadioListTile(
title: Text('System default'),
value: ThemeMode.system,
groupValue: settings.theme,
onChanged: (value) {
settings.setTheme = value;
Navigator.of(context).pop();
}),
RadioListTile(
title: Text('Dark mode'),
value: ThemeMode.dark,
groupValue: settings.theme,
onChanged: (value) {
settings.setTheme = value;
Navigator.of(context).pop();
}),
RadioListTile(
title: Text('Light mode'),
value: ThemeMode.light,
groupValue: settings.theme,
onChanged: (value) {
settings.setTheme = value;
Navigator.of(context).pop();
}),
],
),
child: AlertDialog(
titlePadding: EdgeInsets.only(
),
),
)),
contentPadding: EdgeInsets.symmetric(horizontal: 80.0),
// leading: Icon(Icons.colorize),
title: Text('Theme'),
subtitle: Text('System default'),
),
ListTile(
contentPadding:
EdgeInsets.only(left: 80.0, right: 20, bottom: 10),
// leading: Icon(Icons.colorize),
title: Text(
'Real Dark',
),
subtitle: Text(
'Turn on if you think the night is not dark enough'),
trailing: Selector<SettingState, bool>(
selector: (_, setting) => setting.realDark,
builder: (_, data, __) => Switch(
value: data,
onChanged: (boo) async {
settings.setRealDark = boo;
}),
),
),
Divider(height: 2),
ListTile(
onTap: () => showGeneralDialog(
context: context,
barrierDismissible: true,
barrierLabel: MaterialLocalizations.of(context)
.modalBarrierDismissLabel,
barrierColor: Colors.black54,
transitionDuration: const Duration(milliseconds: 200),
pageBuilder: (BuildContext context,
Animation animaiton,
Animation secondaryAnimation) =>
AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Brightness.light,
systemNavigationBarColor:
Theme.of(context).brightness ==
Brightness.light
? Color.fromRGBO(113, 113, 113, 1)
: Color.fromRGBO(15, 15, 15, 1),
),
child: AlertDialog(
elevation: 1,
titlePadding: EdgeInsets.only(
top: 20,
left: 40,
right: context.width / 3,
),
elevation: 1,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0))),
title: Text('Theme'),
content: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment:
MainAxisAlignment.start,
children: <Widget>[
RadioListTile(
title: Text('System default'),
value: ThemeMode.system,
groupValue: settings.theme,
onChanged: (value) {
settings.setTheme = value;
Navigator.of(context).pop();
}),
RadioListTile(
title: Text('Dark mode'),
value: ThemeMode.dark,
groupValue: settings.theme,
onChanged: (value) {
settings.setTheme = value;
Navigator.of(context).pop();
}),
RadioListTile(
title: Text('Light mode'),
value: ThemeMode.light,
groupValue: settings.theme,
onChanged: (value) {
settings.setTheme = value;
Navigator.of(context).pop();
}),
],
),
),
right: 200,
bottom: 0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0))),
title: Text.rich(
TextSpan(text: 'Choose a ', children: [
TextSpan(
text: 'color',
style: TextStyle(
fontWeight: FontWeight.bold,
color: context.accentColor))
])),
content: ColorPicker(
onColorChanged: (value) =>
settings.setAccentColor = value,
),
)),
contentPadding: EdgeInsets.symmetric(horizontal: 80.0),
// leading: Icon(Icons.colorize),
title: Text('Theme'),
subtitle: Text('System default'),
),
ListTile(
contentPadding:
EdgeInsets.only(left: 80.0, right: 20, bottom: 10),
// leading: Icon(Icons.colorize),
title: Text(
'Real Dark',
),
subtitle: Text(
'Turn on if you think the night is not dark enough'),
trailing: Selector<SettingState, bool>(
selector: (_, setting) => setting.realDark,
builder: (_, data, __) => Switch(
value: data,
onChanged: (boo) async {
settings.setRealDark = boo;
}),
),
),
Divider(height: 2),
ListTile(
onTap: () => showGeneralDialog(
context: context,
barrierDismissible: true,
barrierLabel: MaterialLocalizations.of(context)
.modalBarrierDismissLabel,
barrierColor: Colors.black54,
transitionDuration: const Duration(milliseconds: 200),
pageBuilder: (BuildContext context,
Animation animaiton,
Animation secondaryAnimation) =>
AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Brightness.light,
systemNavigationBarColor:
Theme.of(context).brightness ==
Brightness.light
? Color.fromRGBO(113, 113, 113, 1)
: Color.fromRGBO(15, 15, 15, 1),
),
child: AlertDialog(
elevation: 1,
titlePadding: EdgeInsets.only(
top: 20,
left: 40,
right: 200,
bottom: 0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0))),
title: Text.rich(
TextSpan(text: 'Choose a ', children: [
TextSpan(
text: 'color',
style: TextStyle(
fontWeight: FontWeight.bold,
color: context.accentColor))
])),
content: ColorPicker(
onColorChanged: (value) =>
settings.setAccentColor = value,
),
))),
contentPadding: EdgeInsets.only(left: 80.0, right: 25),
title: Text('Accent color'),
subtitle: Text('Include the overlay color'),
trailing: Container(
height: 25,
width: 25,
decoration: BoxDecoration(
shape: BoxShape.circle, color: context.accentColor),
),
),
Divider(height: 2),
],
))),
contentPadding: EdgeInsets.only(left: 80.0, right: 25),
title: Text('Accent color'),
subtitle: Text('Include the overlay color'),
trailing: Container(
height: 25,
width: 25,
decoration: BoxDecoration(
shape: BoxShape.circle, color: context.accentColor),
),
),
Divider(height: 2),
],
),
],

View File

@ -140,11 +140,11 @@ Future<void> subIsolateEntryPoint(SendPort sendPort) async {
String realUrl =
response.redirects.isEmpty ? rss : response.realUri.toString();
print(realUrl);
bool checkUrl = await dbHelper.checkPodcast(realUrl);
String imageUrl;
if (checkUrl) {
img.Image thumbnail;
String imageUrl;
try {
Response<List<int>> imageResponse = await Dio().get<List<int>>(
p.itunes.image.href,
@ -153,7 +153,6 @@ Future<void> subIsolateEntryPoint(SendPort sendPort) async {
img.Image image = img.decodeImage(imageResponse.data);
thumbnail = img.copyResize(image, width: 300);
} catch (e) {
print(e);
try {
Response<List<int>> imageResponse = await Dio().get<List<int>>(
item.imgUrl,

View File

@ -17,10 +17,12 @@ class LayoutPainter extends CustomPainter {
..strokeCap = StrokeCap.round;
canvas.drawRect(Rect.fromLTRB(0, 0, 10 + 5 * scale, 10), _paint);
canvas.drawRect(
Rect.fromLTRB(10 + 5 * scale, 0, 20 + 10 * scale, 10), _paint);
canvas.drawRect(
Rect.fromLTRB(20 + 5 * scale, 0, 30, 10 - 10 * scale), _paint);
if (scale < 4) {
canvas.drawRect(
Rect.fromLTRB(10 + 5 * scale, 0, 20 + 10 * scale, 10), _paint);
canvas.drawRect(
Rect.fromLTRB(20 + 5 * scale, 0, 30, 10 - 10 * scale), _paint);
}
}
@override
@ -664,7 +666,10 @@ class _HeartOpenState extends State<HeartOpen>
left: widget.width * position,
bottom: widget.height * _value * scale,
child: Icon(Icons.favorite,
color: _value > 0.5 ? Colors.red.withOpacity(2 - _value*2) : Colors.red, size: 20 * _value * scale),
color: _value > 0.5
? Colors.red.withOpacity(2 - _value * 2)
: Colors.red,
size: 20 * _value * scale),
);
}

View File

@ -18,7 +18,7 @@ import 'colorize.dart';
import 'context_extension.dart';
import 'custompaint.dart';
enum Layout { two, three }
enum Layout { three, two, one }
class EpisodeGrid extends StatelessWidget {
final List<EpisodeBrief> episodes;
@ -56,6 +56,117 @@ class EpisodeGrid extends StatelessWidget {
this.reverse,
}) : super(key: key);
Widget _title(EpisodeBrief episode) => Container(
alignment:
layout == Layout.one ? Alignment.centerLeft : Alignment.topLeft,
padding: EdgeInsets.only(top: 2.0),
child: Text(
episode.title,
maxLines: layout == Layout.one ? 1 : 4,
overflow:
layout == Layout.one ? TextOverflow.ellipsis : TextOverflow.fade,
),
);
Widget _circleImage(BuildContext context,
{EpisodeBrief episode, Color color, bool boo}) =>
Container(
height: context.width / 16,
width: context.width / 16,
child: boo
? Center()
: CircleAvatar(
backgroundColor: color.withOpacity(0.5),
backgroundImage: FileImage(File("${episode.imagePath}")),
),
);
Widget _listenIndicater(BuildContext context,
{EpisodeBrief episode, int isListened}) =>
Selector<AudioPlayerNotifier, Tuple2<EpisodeBrief, bool>>(
selector: (_, audio) => Tuple2(audio.episode, audio.playerRunning),
builder: (_, data, __) {
return (episode.enclosureUrl == data.item1?.enclosureUrl &&
data.item2)
? Container(
height: 20,
width: 20,
margin: EdgeInsets.symmetric(horizontal: 2),
decoration: BoxDecoration(
shape: BoxShape.circle,
),
child: WaveLoader(color: context.accentColor))
: layout == Layout.two && isListened > 0
? Container(
height: 20,
width: 20,
margin: EdgeInsets.symmetric(horizontal: 2),
decoration: BoxDecoration(
color: context.accentColor,
shape: BoxShape.circle,
),
child: CustomPaint(
painter: ListenedAllPainter(
Colors.white,
)),
)
: Center();
});
Widget _downloadIndicater(BuildContext context, {EpisodeBrief episode}) =>
showDownload || layout != Layout.three
? Container(
child: (episode.enclosureUrl != episode.mediaId)
? Container(
height: 20,
width: 20,
margin: EdgeInsets.symmetric(horizontal: 5),
padding: EdgeInsets.symmetric(horizontal: 2),
decoration: BoxDecoration(
color: context.accentColor,
shape: BoxShape.circle,
),
child: Icon(
Icons.done_all,
size: 15,
color: Colors.white,
),
)
: Center(),
)
: Center();
Widget _isNewIndicator(EpisodeBrief episode) => episode.isNew == 1
? Container(
padding: EdgeInsets.symmetric(horizontal: 2),
child: Text('New',
style: TextStyle(color: Colors.red, fontStyle: FontStyle.italic)),
)
: Center();
Widget _numberIndicater(BuildContext context, {int index, Color color}) =>
showNumber
? Container(
alignment: Alignment.topRight,
child: Text(
reverse
? (index + 1).toString()
: (episodeCount - index).toString(),
style: GoogleFonts.teko(
textStyle: TextStyle(
fontSize: context.width / 24,
color: color,
),
),
),
)
: Center();
Widget _pubDate(BuildContext context, {EpisodeBrief episode, Color color}) =>
Text(
episode.dateToString(),
style: TextStyle(
fontSize: context.width / 35,
color: color,
fontStyle: FontStyle.italic),
);
@override
Widget build(BuildContext context) {
double _width = MediaQuery.of(context).size.width;
@ -139,8 +250,10 @@ class EpisodeGrid extends StatelessWidget {
options: options,
itemCount: episodes.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: layout == Layout.three ? 1 : 1.5,
crossAxisCount: layout == Layout.three ? 3 : 2,
childAspectRatio:
layout == Layout.three ? 1 : layout == Layout.two ? 1.5 : 4,
crossAxisCount:
layout == Layout.three ? 3 : layout == Layout.two ? 2 : 1,
mainAxisSpacing: 6.0,
crossAxisSpacing: 6.0,
),
@ -211,166 +324,71 @@ class EpisodeGrid extends StatelessWidget {
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
flex: 2,
flex: layout == Layout.one ? 1 : 2,
child: Row(
mainAxisAlignment:
MainAxisAlignment.start,
children: <Widget>[
Container(
height: _width / 16,
width: _width / 16,
child: boo
? Center()
: CircleAvatar(
backgroundColor:
_c.withOpacity(0.5),
backgroundImage: FileImage(File(
"${episodes[index].imagePath}")),
),
),
layout != Layout.one
? _circleImage(context,
episode: episodes[index],
color: _c,
boo: boo)
: _pubDate(context,
episode: episodes[index],
color: _c),
Spacer(),
Selector<AudioPlayerNotifier,
Tuple2<EpisodeBrief, bool>>(
selector: (_, audio) => Tuple2(
audio.episode,
audio.playerRunning),
builder: (_, data, __) {
return (episodes[index]
.enclosureUrl ==
data.item1
?.enclosureUrl &&
data.item2)
? Container(
height: 20,
width: 20,
margin:
EdgeInsets.symmetric(
horizontal: 2),
decoration: BoxDecoration(
shape: BoxShape.circle,
),
child: WaveLoader(
color: context
.accentColor))
: layout == Layout.two &&
snapshot.data > 0
? Container(
height: 20,
width: 20,
margin: EdgeInsets
.symmetric(
horizontal:
2),
decoration:
BoxDecoration(
color: context
.accentColor,
shape:
BoxShape.circle,
),
child: CustomPaint(
painter:
ListenedAllPainter(
Colors.white,
)),
)
: Center();
}),
showDownload || layout == Layout.two
? Container(
child: (episodes[index]
.enclosureUrl !=
episodes[index].mediaId)
? Container(
height: 20,
width: 20,
margin: EdgeInsets
.symmetric(
horizontal: 5),
padding: EdgeInsets
.symmetric(
horizontal: 2),
decoration:
BoxDecoration(
color: context
.accentColor,
shape:
BoxShape.circle,
),
child: Icon(
Icons.done_all,
size: 15,
color: Colors.white,
),
)
: Center(),
)
: Center(),
episodes[index].isNew == 1
? Container(
padding: EdgeInsets.symmetric(
horizontal: 2),
child: Text('New',
style: TextStyle(
color: Colors.red,
fontStyle:
FontStyle.italic)),
)
: Center(),
showNumber
? Container(
alignment: Alignment.topRight,
child: Text(
reverse
? (index + 1).toString()
: (episodeCount - index)
.toString(),
style: GoogleFonts.teko(
textStyle: TextStyle(
fontSize: _width / 24,
color: _c,
),
),
),
)
: Center(),
_listenIndicater(context,
episode: episodes[index],
isListened: snapshot.data),
_downloadIndicater(context,
episode: episodes[index]),
_isNewIndicator(episodes[index]),
_numberIndicater(context,
index: index, color: _c)
],
),
),
Expanded(
flex: 5,
child: Container(
alignment: Alignment.topLeft,
padding: EdgeInsets.only(top: 2.0),
child: Text(
episodes[index].title,
style: TextStyle(
// fontSize: _width / 32,
),
maxLines: 4,
overflow: TextOverflow.fade,
),
),
flex: layout == Layout.one ? 3 : 5,
child: layout != Layout.one
? _title(episodes[index])
: Row(
crossAxisAlignment:
CrossAxisAlignment.center,
children: [
_circleImage(context,
episode: episodes[index],
color: _c,
boo: boo),
SizedBox(
width: 5,
),
Expanded(
child:
_title(episodes[index]))
],
),
),
Expanded(
flex: 1,
child: Row(
crossAxisAlignment:
CrossAxisAlignment.center,
children: <Widget>[
Align(
alignment: Alignment.bottomLeft,
child: Text(
episodes[index].dateToString(),
style: TextStyle(
fontSize: _width / 35,
color: _c,
fontStyle: FontStyle.italic),
),
),
layout != Layout.one
? Align(
alignment: Alignment.bottomLeft,
child: _pubDate(context,
episode: episodes[index],
color: _c),
)
: SizedBox(width: 1),
Spacer(),
layout == Layout.two &&
layout != Layout.three &&
episodes[index].duration != 0
? Container(
alignment: Alignment.center,
@ -401,7 +419,7 @@ class EpisodeGrid extends StatelessWidget {
// fontStyle: FontStyle.italic,
),
),
layout == Layout.two &&
layout != Layout.three &&
episodes[index]
.enclosureLength !=
null &&
@ -424,19 +442,19 @@ class EpisodeGrid extends StatelessWidget {
Padding(
padding: EdgeInsets.all(1),
),
showFavorite || layout == Layout.two
showFavorite || layout != Layout.three
? FutureBuilder<bool>(
future:
_isLiked(episodes[index]),
initialData: false,
builder: (context, snapshot) =>
Container(
alignment:
Alignment.bottomRight,
alignment: Alignment.center,
child: (snapshot.data)
? IconTheme(
data: IconThemeData(
size: 15),
size:
_width / 35),
child: Icon(
Icons.favorite,
color: Colors.red,

View File

@ -39,22 +39,19 @@ class SlideLeftHideRoute extends PageRouteBuilder {
Animation<double> secondaryAnimation,
) =>
page,
transitionDuration: Duration(seconds: 2),
transitionDuration: Duration(milliseconds: 500),
transitionsBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
if (animation.isCompleted)
return child;
else
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(1, 0),
end: Offset.zero,
).animate(animation),
child: transitionPage);
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(1, 0),
end: Offset.zero,
).animate(animation),
child: child);
},
);
}