Refactor home
This commit is contained in:
parent
26d35a31fe
commit
cac7e06791
6 changed files with 76 additions and 109 deletions
|
|
@ -11,10 +11,6 @@ import 'package:frosty/api/twitch_api.dart';
|
|||
import 'package:frosty/constants/constants.dart';
|
||||
import 'package:frosty/core/auth/auth_store.dart';
|
||||
import 'package:frosty/screens/home/home.dart';
|
||||
import 'package:frosty/screens/home/stores/categories_store.dart';
|
||||
import 'package:frosty/screens/home/stores/home_store.dart';
|
||||
import 'package:frosty/screens/home/stores/list_store.dart';
|
||||
import 'package:frosty/screens/home/stores/search_store.dart';
|
||||
import 'package:frosty/screens/settings/stores/settings_store.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
|
@ -154,9 +150,7 @@ class MyApp extends StatelessWidget {
|
|||
|
||||
return Observer(
|
||||
builder: (context) {
|
||||
final authStore = context.read<AuthStore>();
|
||||
final settingsStore = context.read<SettingsStore>();
|
||||
final twitchApi = context.read<TwitchApi>();
|
||||
|
||||
return MaterialApp(
|
||||
title: 'Frosty',
|
||||
|
|
@ -167,29 +161,7 @@ class MyApp extends StatelessWidget {
|
|||
: settingsStore.themeType == ThemeType.light
|
||||
? ThemeMode.light
|
||||
: ThemeMode.dark,
|
||||
home: Home(
|
||||
homeStore: HomeStore(),
|
||||
followedStreamsStore: authStore.isLoggedIn
|
||||
? ListStore(
|
||||
authStore: authStore,
|
||||
twitchApi: twitchApi,
|
||||
listType: ListType.followed,
|
||||
)
|
||||
: null,
|
||||
topSectionStore: ListStore(
|
||||
authStore: authStore,
|
||||
twitchApi: twitchApi,
|
||||
listType: ListType.top,
|
||||
),
|
||||
categoriesSectionStore: CategoriesStore(
|
||||
authStore: authStore,
|
||||
twitchApi: twitchApi,
|
||||
),
|
||||
searchStore: SearchStore(
|
||||
authStore: authStore,
|
||||
twitchApi: twitchApi,
|
||||
),
|
||||
),
|
||||
home: const Home(),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,10 +2,8 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:frosty/core/auth/auth_store.dart';
|
||||
import 'package:frosty/screens/home/search/search.dart';
|
||||
import 'package:frosty/screens/home/stores/categories_store.dart';
|
||||
import 'package:frosty/screens/home/stores/home_store.dart';
|
||||
import 'package:frosty/screens/home/stores/list_store.dart';
|
||||
import 'package:frosty/screens/home/stores/search_store.dart';
|
||||
import 'package:frosty/screens/home/top/top_section.dart';
|
||||
import 'package:frosty/screens/home/widgets/streams_list.dart';
|
||||
import 'package:frosty/screens/settings/settings.dart';
|
||||
|
|
@ -13,23 +11,11 @@ import 'package:frosty/screens/settings/stores/settings_store.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
|
||||
class Home extends StatelessWidget {
|
||||
final HomeStore homeStore;
|
||||
final ListStore? followedStreamsStore;
|
||||
final ListStore topSectionStore;
|
||||
final CategoriesStore categoriesSectionStore;
|
||||
final SearchStore searchStore;
|
||||
|
||||
const Home({
|
||||
Key? key,
|
||||
required this.homeStore,
|
||||
required this.followedStreamsStore,
|
||||
required this.topSectionStore,
|
||||
required this.categoriesSectionStore,
|
||||
required this.searchStore,
|
||||
}) : super(key: key);
|
||||
const Home({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final homeStore = HomeStore();
|
||||
final authStore = context.read<AuthStore>();
|
||||
|
||||
return GestureDetector(
|
||||
|
|
@ -66,12 +52,9 @@ class Home extends StatelessWidget {
|
|||
builder: (_) => IndexedStack(
|
||||
index: homeStore.selectedIndex,
|
||||
children: [
|
||||
if (authStore.isLoggedIn) StreamsList(store: followedStreamsStore!),
|
||||
TopSection(
|
||||
topSectionStore: topSectionStore,
|
||||
categoriesSectionStore: categoriesSectionStore,
|
||||
),
|
||||
Search(searchStore: searchStore),
|
||||
if (authStore.isLoggedIn) const StreamsList(listType: ListType.followed),
|
||||
const TopSection(),
|
||||
const Search(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,28 +1,33 @@
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:frosty/api/twitch_api.dart';
|
||||
import 'package:frosty/core/auth/auth_store.dart';
|
||||
import 'package:frosty/screens/home/search/search_results_categories.dart';
|
||||
import 'package:frosty/screens/home/search/search_results_channels.dart';
|
||||
import 'package:frosty/screens/home/stores/search_store.dart';
|
||||
import 'package:frosty/widgets/section_header.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class Search extends StatefulWidget {
|
||||
final SearchStore searchStore;
|
||||
|
||||
const Search({Key? key, required this.searchStore}) : super(key: key);
|
||||
const Search({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_SearchState createState() => _SearchState();
|
||||
}
|
||||
|
||||
class _SearchState extends State<Search> {
|
||||
late final _searchStore = SearchStore(
|
||||
authStore: context.read<AuthStore>(),
|
||||
twitchApi: context.read<TwitchApi>(),
|
||||
);
|
||||
|
||||
final _textEditingController = TextEditingController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const headerPadding = EdgeInsets.fromLTRB(15.0, 20.0, 15.0, 10.0);
|
||||
|
||||
final searchStore = widget.searchStore;
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
|
|
@ -48,17 +53,17 @@ class _SearchState extends State<Search> {
|
|||
icon: const Icon(Icons.clear),
|
||||
),
|
||||
),
|
||||
onSubmitted: searchStore.handleQuery,
|
||||
onSubmitted: _searchStore.handleQuery,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Observer(
|
||||
builder: (context) {
|
||||
if (_textEditingController.text.isEmpty || searchStore.channelFuture == null || searchStore.categoryFuture == null) {
|
||||
if (_textEditingController.text.isEmpty || _searchStore.channelFuture == null || _searchStore.categoryFuture == null) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (searchStore.searchHistory.isNotEmpty)
|
||||
if (_searchStore.searchHistory.isNotEmpty)
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(15.0),
|
||||
child: Text(
|
||||
|
|
@ -68,7 +73,7 @@ class _SearchState extends State<Search> {
|
|||
),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
children: searchStore.searchHistory
|
||||
children: _searchStore.searchHistory
|
||||
.mapIndexed(
|
||||
(index, searchTerm) => ListTile(
|
||||
leading: const Icon(Icons.history),
|
||||
|
|
@ -77,13 +82,13 @@ class _SearchState extends State<Search> {
|
|||
tooltip: 'Remove',
|
||||
icon: const Icon(Icons.cancel),
|
||||
onPressed: () => setState(() {
|
||||
searchStore.searchHistory.removeAt(index);
|
||||
_searchStore.searchHistory.removeAt(index);
|
||||
}),
|
||||
),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_textEditingController.text = searchTerm;
|
||||
searchStore.handleQuery(searchTerm);
|
||||
_searchStore.handleQuery(searchTerm);
|
||||
});
|
||||
},
|
||||
),
|
||||
|
|
@ -103,7 +108,7 @@ class _SearchState extends State<Search> {
|
|||
),
|
||||
),
|
||||
SearchResultsChannels(
|
||||
searchStore: searchStore,
|
||||
searchStore: _searchStore,
|
||||
query: _textEditingController.text,
|
||||
),
|
||||
const SliverToBoxAdapter(
|
||||
|
|
@ -112,7 +117,7 @@ class _SearchState extends State<Search> {
|
|||
padding: headerPadding,
|
||||
),
|
||||
),
|
||||
SearchResultsCategories(searchStore: searchStore),
|
||||
SearchResultsCategories(searchStore: _searchStore),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,28 +1,30 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:frosty/api/twitch_api.dart';
|
||||
import 'package:frosty/core/auth/auth_store.dart';
|
||||
import 'package:frosty/screens/home/stores/categories_store.dart';
|
||||
import 'package:frosty/screens/home/widgets/category_card.dart';
|
||||
import 'package:frosty/widgets/loading_indicator.dart';
|
||||
import 'package:frosty/widgets/scroll_to_top_button.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class Categories extends StatefulWidget {
|
||||
final CategoriesStore store;
|
||||
|
||||
const Categories({
|
||||
Key? key,
|
||||
required this.store,
|
||||
}) : super(key: key);
|
||||
const Categories({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_CategoriesState createState() => _CategoriesState();
|
||||
}
|
||||
|
||||
class _CategoriesState extends State<Categories> with AutomaticKeepAliveClientMixin {
|
||||
late final _categoriesStore = CategoriesStore(
|
||||
authStore: context.read<AuthStore>(),
|
||||
twitchApi: context.read<TwitchApi>(),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
final store = widget.store;
|
||||
|
||||
final size = MediaQuery.of(context).size;
|
||||
final pixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||
|
|
@ -33,11 +35,11 @@ class _CategoriesState extends State<Categories> with AutomaticKeepAliveClientMi
|
|||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
HapticFeedback.lightImpact();
|
||||
await store.refreshCategories();
|
||||
await _categoriesStore.refreshCategories();
|
||||
|
||||
if (store.error != null) {
|
||||
if (_categoriesStore.error != null) {
|
||||
final snackBar = SnackBar(
|
||||
content: Text(store.error!),
|
||||
content: Text(_categoriesStore.error!),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
|
|
@ -45,7 +47,7 @@ class _CategoriesState extends State<Categories> with AutomaticKeepAliveClientMi
|
|||
},
|
||||
child: Observer(
|
||||
builder: (_) {
|
||||
if (store.categories.isEmpty && store.isLoading && store.error == null) {
|
||||
if (_categoriesStore.categories.isEmpty && _categoriesStore.isLoading && _categoriesStore.error == null) {
|
||||
return const LoadingIndicator(subtitle: Text('Loading categories...'));
|
||||
}
|
||||
return Stack(
|
||||
|
|
@ -53,17 +55,17 @@ class _CategoriesState extends State<Categories> with AutomaticKeepAliveClientMi
|
|||
children: [
|
||||
GridView.builder(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
controller: store.scrollController,
|
||||
controller: _categoriesStore.scrollController,
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
),
|
||||
itemCount: store.categories.length,
|
||||
itemCount: _categoriesStore.categories.length,
|
||||
itemBuilder: (context, index) {
|
||||
if (index > store.categories.length / 2 && store.hasMore) {
|
||||
store.getCategories();
|
||||
if (index > _categoriesStore.categories.length / 2 && _categoriesStore.hasMore) {
|
||||
_categoriesStore.getCategories();
|
||||
}
|
||||
return CategoryCard(
|
||||
category: store.categories[index],
|
||||
category: _categoriesStore.categories[index],
|
||||
width: artWidth,
|
||||
height: artHeight,
|
||||
);
|
||||
|
|
@ -72,7 +74,7 @@ class _CategoriesState extends State<Categories> with AutomaticKeepAliveClientMi
|
|||
Observer(
|
||||
builder: (context) => AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: store.showJumpButton ? ScrollToTopButton(scrollController: store.scrollController) : null,
|
||||
child: _categoriesStore.showJumpButton ? ScrollToTopButton(scrollController: _categoriesStore.scrollController) : null,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
@ -87,7 +89,7 @@ class _CategoriesState extends State<Categories> with AutomaticKeepAliveClientMi
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.store.dispose();
|
||||
_categoriesStore.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,18 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:frosty/screens/home/stores/categories_store.dart';
|
||||
import 'package:frosty/screens/home/stores/list_store.dart';
|
||||
import 'package:frosty/screens/home/top/categories.dart';
|
||||
import 'package:frosty/screens/home/widgets/streams_list.dart';
|
||||
|
||||
class TopSection extends StatelessWidget {
|
||||
final ListStore topSectionStore;
|
||||
final CategoriesStore categoriesSectionStore;
|
||||
|
||||
const TopSection({
|
||||
Key? key,
|
||||
required this.topSectionStore,
|
||||
required this.categoriesSectionStore,
|
||||
}) : super(key: key);
|
||||
const TopSection({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DefaultTabController(
|
||||
length: 2,
|
||||
child: Column(
|
||||
children: [
|
||||
const TabBar(
|
||||
children: const [
|
||||
TabBar(
|
||||
indicatorColor: Colors.deepPurpleAccent,
|
||||
tabs: [
|
||||
Tab(text: 'Streams'),
|
||||
|
|
@ -30,8 +22,8 @@ class TopSection extends StatelessWidget {
|
|||
Expanded(
|
||||
child: TabBarView(
|
||||
children: [
|
||||
StreamsList(store: topSectionStore),
|
||||
Categories(store: categoriesSectionStore),
|
||||
StreamsList(listType: ListType.top),
|
||||
Categories(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:frosty/api/twitch_api.dart';
|
||||
import 'package:frosty/core/auth/auth_store.dart';
|
||||
import 'package:frosty/models/category.dart';
|
||||
import 'package:frosty/screens/home/stores/list_store.dart';
|
||||
import 'package:frosty/screens/home/widgets/stream_card.dart';
|
||||
import 'package:frosty/screens/settings/stores/settings_store.dart';
|
||||
|
|
@ -9,20 +12,30 @@ import 'package:frosty/widgets/scroll_to_top_button.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
|
||||
class StreamsList extends StatefulWidget {
|
||||
final ListStore store;
|
||||
final ListType listType;
|
||||
final CategoryTwitch? categoryTwitch;
|
||||
|
||||
const StreamsList({Key? key, required this.store}) : super(key: key);
|
||||
const StreamsList({
|
||||
Key? key,
|
||||
required this.listType,
|
||||
this.categoryTwitch,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_StreamsListState createState() => _StreamsListState();
|
||||
}
|
||||
|
||||
class _StreamsListState extends State<StreamsList> with AutomaticKeepAliveClientMixin {
|
||||
late final _listStore = ListStore(
|
||||
authStore: context.read<AuthStore>(),
|
||||
twitchApi: context.read<TwitchApi>(),
|
||||
listType: widget.listType,
|
||||
categoryInfo: widget.categoryTwitch,
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
final store = widget.store;
|
||||
|
||||
final size = MediaQuery.of(context).size;
|
||||
final pixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||
|
||||
|
|
@ -32,11 +45,11 @@ class _StreamsListState extends State<StreamsList> with AutomaticKeepAliveClient
|
|||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
HapticFeedback.lightImpact();
|
||||
await store.refreshStreams();
|
||||
await _listStore.refreshStreams();
|
||||
|
||||
if (store.error != null) {
|
||||
if (_listStore.error != null) {
|
||||
final snackBar = SnackBar(
|
||||
content: Text(store.error!),
|
||||
content: Text(_listStore.error!),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
|
|
@ -44,7 +57,7 @@ class _StreamsListState extends State<StreamsList> with AutomaticKeepAliveClient
|
|||
},
|
||||
child: Observer(
|
||||
builder: (_) {
|
||||
if (store.streams.isEmpty && store.isLoading && store.error == null) {
|
||||
if (_listStore.streams.isEmpty && _listStore.isLoading && _listStore.error == null) {
|
||||
return const LoadingIndicator(subtitle: Text('Loading streams...'));
|
||||
}
|
||||
return Stack(
|
||||
|
|
@ -52,15 +65,15 @@ class _StreamsListState extends State<StreamsList> with AutomaticKeepAliveClient
|
|||
children: [
|
||||
ListView.builder(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
controller: store.scrollController,
|
||||
itemCount: store.streams.length,
|
||||
controller: _listStore.scrollController,
|
||||
itemCount: _listStore.streams.length,
|
||||
itemBuilder: (context, index) {
|
||||
if (index > store.streams.length / 2 && store.hasMore) {
|
||||
store.getStreams();
|
||||
if (index > _listStore.streams.length / 2 && _listStore.hasMore) {
|
||||
_listStore.getStreams();
|
||||
}
|
||||
return Observer(
|
||||
builder: (context) => StreamCard(
|
||||
streamInfo: store.streams[index],
|
||||
streamInfo: _listStore.streams[index],
|
||||
width: thumbnailWidth,
|
||||
height: thumbnailHeight,
|
||||
showUptime: context.read<SettingsStore>().showThumbnailUptime,
|
||||
|
|
@ -71,7 +84,7 @@ class _StreamsListState extends State<StreamsList> with AutomaticKeepAliveClient
|
|||
Observer(
|
||||
builder: (context) => AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: store.showJumpButton ? ScrollToTopButton(scrollController: store.scrollController) : null,
|
||||
child: _listStore.showJumpButton ? ScrollToTopButton(scrollController: _listStore.scrollController) : null,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
@ -86,7 +99,7 @@ class _StreamsListState extends State<StreamsList> with AutomaticKeepAliveClient
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.store.dispose();
|
||||
_listStore.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue