Refactor home & streams/categories list

This commit is contained in:
Tommy Chow 2022-01-04 02:58:02 -05:00
parent 569d6959d3
commit 8eb7f36ab1
38 changed files with 334 additions and 616 deletions

View file

@ -4,12 +4,10 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:frosty/core/auth/auth_store.dart';
import 'package:frosty/core/settings/settings_store.dart';
import 'package:frosty/screens/followed_streams/followed_streams_store.dart';
import 'package:frosty/screens/home/home.dart';
import 'package:frosty/screens/search/search_store.dart';
import 'package:frosty/screens/top/categories/categories_store.dart';
import 'package:frosty/screens/top/streams/top_streams_store.dart';
import 'package:frosty/screens/home/search/stores/search_store.dart';
import 'package:frosty/screens/home/stores/list_store.dart';
import 'package:frosty/screens/settings/stores/settings_store.dart';
import 'package:mobx/mobx.dart';
import 'package:provider/provider.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
@ -94,9 +92,20 @@ class MyApp extends StatelessWidget {
title: 'Frosty',
theme: settingsStore.useOledTheme ? oledTheme : defaultTheme,
home: Home(
topStreamsStore: TopStreamsStore(authStore: authStore),
followedStreamsStore: FollowedStreamsStore(authStore: authStore),
categoriesStore: CategoriesStore(authStore: authStore),
topStreamsStore: ListStore(
authStore: authStore,
listType: StreamListType.top,
),
categoriesStore: ListStore(
authStore: authStore,
listType: StreamListType.categories,
),
followedStreamsStore: authStore.isLoggedIn
? ListStore(
authStore: authStore,
listType: StreamListType.followed,
)
: null,
searchStore: SearchStore(authStore: authStore),
),
);

View file

@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:frosty/core/settings/settings_store.dart';
import 'package:frosty/models/irc_message.dart';
import 'package:frosty/screens/channel/chat/chat_assets_store.dart';
import 'package:frosty/screens/settings/stores/settings_store.dart';
class ChatMessage extends StatelessWidget {
final IRCMessage ircMessage;

View file

@ -3,10 +3,10 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:frosty/core/auth/auth_store.dart';
import 'package:frosty/core/settings/settings_store.dart';
import 'package:frosty/models/irc_message.dart';
import 'package:frosty/screens/channel/chat/chat_assets_store.dart';
import 'package:frosty/screens/channel/chat/details/chat_details_store.dart';
import 'package:frosty/screens/settings/stores/settings_store.dart';
import 'package:mobx/mobx.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
@ -140,6 +140,7 @@ abstract class _ChatStoreBase with Store {
// The IRC data can contain more than one message separated by CRLF.
// To account for this, split by CRLF, then loop and process each message.
for (final message in data.trimRight().split('\r\n')) {
debugPrint(message);
if (message.startsWith('@')) {
final parsedIRCMessage = IRCMessage.fromString(message, username: auth.user.details?.login);

View file

@ -73,7 +73,10 @@ class _ChattersListState extends State<ChattersList> {
hintText: 'Filter chatters',
contentPadding: const EdgeInsets.all(10.0),
suffixIcon: IconButton(
onPressed: () => setState(() => _textController.clear()),
onPressed: () => setState(() {
_textController.clear();
FocusManager.instance.primaryFocus?.unfocus();
}),
icon: const Icon(Icons.clear),
),
border: const OutlineInputBorder(

View file

@ -2,8 +2,8 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:frosty/core/settings/settings.dart';
import 'package:frosty/screens/channel/video/video_store.dart';
import 'package:frosty/screens/settings/settings.dart';
import 'package:intl/intl.dart';
class VideoOverlay extends StatelessWidget {

View file

@ -2,8 +2,8 @@ import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:frosty/api/twitch_api.dart';
import 'package:frosty/core/auth/auth_store.dart';
import 'package:frosty/core/settings/settings_store.dart';
import 'package:frosty/models/stream.dart';
import 'package:frosty/screens/settings/stores/settings_store.dart';
import 'package:mobx/mobx.dart';
import 'package:webview_flutter/webview_flutter.dart';

View file

@ -2,12 +2,12 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:frosty/core/auth/auth_store.dart';
import 'package:frosty/core/settings/settings.dart';
import 'package:frosty/core/settings/settings_store.dart';
import 'package:frosty/screens/channel/chat/chat.dart';
import 'package:frosty/screens/channel/chat/chat_store.dart';
import 'package:frosty/screens/channel/video/video.dart';
import 'package:frosty/screens/channel/video/video_store.dart';
import 'package:frosty/screens/settings/settings.dart';
import 'package:frosty/screens/settings/stores/settings_store.dart';
import 'package:provider/provider.dart';
class VideoChat extends StatelessWidget {

View file

@ -1,43 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:frosty/core/settings/settings_store.dart';
import 'package:frosty/screens/followed_streams/followed_streams_store.dart';
import 'package:frosty/widgets/loading_indicator.dart';
import 'package:frosty/widgets/stream_card.dart';
import 'package:provider/provider.dart';
class FollowedStreams extends StatelessWidget {
final FollowedStreamsStore store;
const FollowedStreams({Key? key, required this.store}) : super(key: key);
@override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: store.refresh,
child: Observer(
builder: (_) {
if (store.followedStreams.isEmpty && store.isLoading) {
return const LoadingIndicator(
subtitle: Text('Loading followed streams...'),
);
}
return ListView.builder(
itemCount: store.followedStreams.length,
itemBuilder: (context, index) {
if (index > store.followedStreams.length / 2 && store.hasMore) {
store.getFollowedStreams();
}
return Observer(
builder: (context) => StreamCard(
streamInfo: store.followedStreams[index],
showUptime: context.read<SettingsStore>().showThumbnailUptime,
),
);
},
);
},
),
);
}
}

View file

@ -1,62 +0,0 @@
import 'package:frosty/api/twitch_api.dart';
import 'package:frosty/core/auth/auth_store.dart';
import 'package:frosty/models/stream.dart';
import 'package:mobx/mobx.dart';
part 'followed_streams_store.g.dart';
class FollowedStreamsStore = _FollowedStreamsStoreBase with _$FollowedStreamsStore;
abstract class _FollowedStreamsStoreBase with Store {
/// The list of the fetched followed streams.
@readonly
var _followedStreams = ObservableList<StreamTwitch>();
/// The loading status for pagination.
@readonly
bool _isLoading = false;
/// The pagination cursor for followed streams.
String? _followedStreamsCurrentCursor;
/// The authentication store.
final AuthStore authStore;
/// Returns whether or not there are more streams and loading status for pagination.
bool get hasMore => _isLoading == false && _followedStreamsCurrentCursor != null;
_FollowedStreamsStoreBase({required this.authStore}) {
if (authStore.isLoggedIn) getFollowedStreams();
}
/// Fetches the followed streams based on the current cursor.
@action
Future<void> getFollowedStreams() async {
_isLoading = true;
final newFollowedStreams = await Twitch.getFollowedStreams(
id: authStore.user.details!.id,
headers: authStore.headersTwitch,
cursor: _followedStreamsCurrentCursor,
);
if (newFollowedStreams != null) {
if (_followedStreamsCurrentCursor == null) {
_followedStreams = newFollowedStreams.data.asObservable();
} else {
_followedStreams.addAll(newFollowedStreams.data);
}
_followedStreamsCurrentCursor = newFollowedStreams.pagination['cursor'];
}
_isLoading = false;
}
/// Resets the cursor and then fetches the followed streams.
@action
Future<void> refresh() {
_followedStreamsCurrentCursor = null;
return getFollowedStreams();
}
}

View file

@ -1,76 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'followed_streams_store.dart';
// **************************************************************************
// StoreGenerator
// **************************************************************************
// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic
mixin _$FollowedStreamsStore on _FollowedStreamsStoreBase, Store {
final _$_followedStreamsAtom =
Atom(name: '_FollowedStreamsStoreBase._followedStreams');
ObservableList<StreamTwitch> get followedStreams {
_$_followedStreamsAtom.reportRead();
return super._followedStreams;
}
@override
ObservableList<StreamTwitch> get _followedStreams => followedStreams;
@override
set _followedStreams(ObservableList<StreamTwitch> value) {
_$_followedStreamsAtom.reportWrite(value, super._followedStreams, () {
super._followedStreams = value;
});
}
final _$_isLoadingAtom = Atom(name: '_FollowedStreamsStoreBase._isLoading');
bool get isLoading {
_$_isLoadingAtom.reportRead();
return super._isLoading;
}
@override
bool get _isLoading => isLoading;
@override
set _isLoading(bool value) {
_$_isLoadingAtom.reportWrite(value, super._isLoading, () {
super._isLoading = value;
});
}
final _$getFollowedStreamsAsyncAction =
AsyncAction('_FollowedStreamsStoreBase.getFollowedStreams');
@override
Future<void> getFollowedStreams() {
return _$getFollowedStreamsAsyncAction
.run(() => super.getFollowedStreams());
}
final _$_FollowedStreamsStoreBaseActionController =
ActionController(name: '_FollowedStreamsStoreBase');
@override
Future<void> refresh() {
final _$actionInfo = _$_FollowedStreamsStoreBaseActionController
.startAction(name: '_FollowedStreamsStoreBase.refresh');
try {
return super.refresh();
} finally {
_$_FollowedStreamsStoreBaseActionController.endAction(_$actionInfo);
}
}
@override
String toString() {
return '''
''';
}
}

View file

@ -1,29 +1,27 @@
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:frosty/core/auth/auth_store.dart';
import 'package:frosty/core/settings/settings.dart';
import 'package:frosty/core/settings/settings_store.dart';
import 'package:frosty/screens/followed_streams/followed_streams.dart';
import 'package:frosty/screens/followed_streams/followed_streams_store.dart';
import 'package:frosty/screens/search/search.dart';
import 'package:frosty/screens/search/search_store.dart';
import 'package:frosty/screens/top/categories/categories_store.dart';
import 'package:frosty/screens/top/streams/top_streams_store.dart';
import 'package:frosty/screens/top/top_section.dart';
import 'package:frosty/screens/home/search/search.dart';
import 'package:frosty/screens/home/search/stores/search_store.dart';
import 'package:frosty/screens/home/stores/list_store.dart';
import 'package:frosty/screens/home/streams_list.dart';
import 'package:frosty/screens/home/top/top_section.dart';
import 'package:frosty/screens/settings/settings.dart';
import 'package:frosty/screens/settings/stores/settings_store.dart';
import 'package:provider/provider.dart';
class Home extends StatefulWidget {
final TopStreamsStore topStreamsStore;
final FollowedStreamsStore followedStreamsStore;
final CategoriesStore categoriesStore;
final ListStore topStreamsStore;
final ListStore categoriesStore;
final SearchStore searchStore;
final ListStore? followedStreamsStore;
const Home({
Key? key,
required this.topStreamsStore,
required this.followedStreamsStore,
required this.categoriesStore,
required this.searchStore,
required this.followedStreamsStore,
}) : super(key: key);
@override
@ -78,8 +76,11 @@ class _HomeState extends State<Home> {
return IndexedStack(
index: _selectedIndex,
children: [
if (authStore.isLoggedIn) FollowedStreams(store: widget.followedStreamsStore),
TopSection(topStreamsStore: widget.topStreamsStore, categoriesStore: widget.categoriesStore),
if (authStore.isLoggedIn) StreamsList(store: widget.followedStreamsStore!),
TopSection(
topStreamsStore: widget.topStreamsStore,
categoriesStore: widget.categoriesStore,
),
Search(searchStore: widget.searchStore),
],
);

View file

@ -1,9 +1,9 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:frosty/screens/search/search_results_categories.dart';
import 'package:frosty/screens/search/search_results_channels.dart';
import 'package:frosty/screens/search/search_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/search/stores/search_store.dart';
import 'package:frosty/widgets/section_header.dart';
class Search extends StatefulWidget {

View file

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:frosty/models/category.dart';
import 'package:frosty/screens/top/categories/category_card.dart';
import 'package:frosty/screens/home/top/categories/category_card.dart';
class SearchResultsCategories extends StatelessWidget {
final List<CategoryTwitch> categories;

View file

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:frosty/screens/channel/video_chat.dart';
import 'package:frosty/screens/search/search_store.dart';
import 'package:frosty/screens/home/search/stores/search_store.dart';
import 'package:frosty/widgets/profile_picture.dart';
class SearchResultsChannels extends StatelessWidget {

View file

@ -0,0 +1,130 @@
import 'package:frosty/api/twitch_api.dart';
import 'package:frosty/core/auth/auth_store.dart';
import 'package:frosty/models/category.dart';
import 'package:frosty/models/stream.dart';
import 'package:mobx/mobx.dart';
part 'list_store.g.dart';
class ListStore = _ListStoreBase with _$ListStore;
abstract class _ListStoreBase with Store {
/// The list of the fetched streams.
@readonly
var _streams = ObservableList<StreamTwitch>();
@readonly
var _categories = ObservableList<CategoryTwitch>();
/// The loading status for pagination.
@readonly
bool _isLoading = false;
/// The pagination cursor for the streams.
String? _currentCursor;
StreamListType listType;
final CategoryTwitch? categoryInfo;
/// The authentication store.
final AuthStore authStore;
/// Returns whether or not there are more streams and loading status for pagination.
bool get hasMore => _isLoading == false && _currentCursor != null;
_ListStoreBase({
required this.authStore,
required this.listType,
this.categoryInfo,
}) {
switch (listType) {
case StreamListType.followed:
if (listType == StreamListType.followed && authStore.isLoggedIn) {
getData();
}
break;
case StreamListType.top:
case StreamListType.category:
getData();
break;
case StreamListType.categories:
getGames();
break;
}
}
/// Fetches the streams based on the type and current cursor.
@action
Future<void> getData() async {
_isLoading = true;
final StreamsTwitch? newStreams;
switch (listType) {
case StreamListType.followed:
newStreams = await Twitch.getFollowedStreams(
id: authStore.user.details!.id,
headers: authStore.headersTwitch,
cursor: _currentCursor,
);
break;
case StreamListType.top:
newStreams = await Twitch.getTopStreams(
headers: authStore.headersTwitch,
cursor: _currentCursor,
);
break;
case StreamListType.category:
newStreams = await Twitch.getStreamsUnderGame(
gameId: categoryInfo!.id,
headers: authStore.headersTwitch,
cursor: _currentCursor,
);
break;
case StreamListType.categories:
return getGames();
}
if (newStreams != null) {
if (_currentCursor == null) {
_streams = newStreams.data.asObservable();
} else {
_streams.addAll(newStreams.data);
}
_currentCursor = newStreams.pagination['cursor'];
}
_isLoading = false;
}
// Fetches the top categories based on the current cursor.
@action
Future<void> getGames() async {
final result = await Twitch.getTopGames(headers: authStore.headersTwitch, cursor: _currentCursor);
if (result != null) {
if (_currentCursor == null) {
_categories = result.data.asObservable();
} else {
_categories.addAll(result.data);
}
_currentCursor = result.pagination['cursor'];
}
_isLoading = false;
}
/// Resets the cursor and then fetches the streams.
@action
Future<void> refresh() {
_currentCursor = null;
return getData();
}
}
enum StreamListType {
followed,
top,
category,
categories,
}

View file

@ -0,0 +1,97 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'list_store.dart';
// **************************************************************************
// StoreGenerator
// **************************************************************************
// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic
mixin _$ListStore on _ListStoreBase, Store {
final _$_streamsAtom = Atom(name: '_ListStoreBase._streams');
ObservableList<StreamTwitch> get streams {
_$_streamsAtom.reportRead();
return super._streams;
}
@override
ObservableList<StreamTwitch> get _streams => streams;
@override
set _streams(ObservableList<StreamTwitch> value) {
_$_streamsAtom.reportWrite(value, super._streams, () {
super._streams = value;
});
}
final _$_categoriesAtom = Atom(name: '_ListStoreBase._categories');
ObservableList<CategoryTwitch> get categories {
_$_categoriesAtom.reportRead();
return super._categories;
}
@override
ObservableList<CategoryTwitch> get _categories => categories;
@override
set _categories(ObservableList<CategoryTwitch> value) {
_$_categoriesAtom.reportWrite(value, super._categories, () {
super._categories = value;
});
}
final _$_isLoadingAtom = Atom(name: '_ListStoreBase._isLoading');
bool get isLoading {
_$_isLoadingAtom.reportRead();
return super._isLoading;
}
@override
bool get _isLoading => isLoading;
@override
set _isLoading(bool value) {
_$_isLoadingAtom.reportWrite(value, super._isLoading, () {
super._isLoading = value;
});
}
final _$getDataAsyncAction = AsyncAction('_ListStoreBase.getData');
@override
Future<void> getData() {
return _$getDataAsyncAction.run(() => super.getData());
}
final _$getGamesAsyncAction = AsyncAction('_ListStoreBase.getGames');
@override
Future<void> getGames() {
return _$getGamesAsyncAction.run(() => super.getGames());
}
final _$_ListStoreBaseActionController =
ActionController(name: '_ListStoreBase');
@override
Future<void> refresh() {
final _$actionInfo = _$_ListStoreBaseActionController.startAction(
name: '_ListStoreBase.refresh');
try {
return super.refresh();
} finally {
_$_ListStoreBaseActionController.endAction(_$actionInfo);
}
}
@override
String toString() {
return '''
''';
}
}

View file

@ -1,15 +1,15 @@
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:frosty/core/settings/settings_store.dart';
import 'package:frosty/screens/top/streams/top_streams_store.dart';
import 'package:frosty/screens/home/stores/list_store.dart';
import 'package:frosty/screens/settings/stores/settings_store.dart';
import 'package:frosty/widgets/loading_indicator.dart';
import 'package:frosty/widgets/stream_card.dart';
import 'package:provider/provider.dart';
class TopStreams extends StatelessWidget {
final TopStreamsStore store;
class StreamsList extends StatelessWidget {
final ListStore store;
const TopStreams({Key? key, required this.store}) : super(key: key);
const StreamsList({Key? key, required this.store}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -17,18 +17,18 @@ class TopStreams extends StatelessWidget {
onRefresh: store.refresh,
child: Observer(
builder: (_) {
if (store.topStreams.isEmpty && store.isLoading) {
return const LoadingIndicator(subtitle: Text('Loading top streams...'));
if (store.streams.isEmpty && store.isLoading) {
return const LoadingIndicator(subtitle: Text('Loading streams...'));
}
return ListView.builder(
itemCount: store.topStreams.length,
itemCount: store.streams.length,
itemBuilder: (context, index) {
if (index > store.topStreams.length / 2 && store.hasMore) {
store.getTopStreams();
if (index > store.streams.length / 2 && store.hasMore) {
store.getData();
}
return Observer(
builder: (context) => StreamCard(
streamInfo: store.topStreams[index],
streamInfo: store.streams[index],
showUptime: context.read<SettingsStore>().showThumbnailUptime,
),
);

View file

@ -1,10 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:frosty/screens/top/categories/categories_store.dart';
import 'package:frosty/screens/top/categories/category_card.dart';
import 'package:frosty/screens/home/stores/list_store.dart';
import 'package:frosty/screens/home/top/categories/category_card.dart';
class Categories extends StatelessWidget {
final CategoriesStore store;
final ListStore store;
const Categories({
Key? key,

View file

@ -2,8 +2,8 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:frosty/core/auth/auth_store.dart';
import 'package:frosty/models/category.dart';
import 'package:frosty/screens/top/categories/category_streams_list/category_streams.dart';
import 'package:frosty/screens/top/categories/category_streams_list/category_streams_store.dart';
import 'package:frosty/screens/home/stores/list_store.dart';
import 'package:frosty/screens/home/top/categories/category_streams.dart';
import 'package:provider/provider.dart';
/// A tappable card widget that displays a category's box art and name.
@ -19,9 +19,10 @@ class CategoryCard extends StatelessWidget {
context,
MaterialPageRoute(
builder: (context) => CategoryStreams(
store: CategoryStreamsStore(
categoryInfo: category,
store: ListStore(
authStore: context.read<AuthStore>(),
listType: StreamListType.category,
categoryInfo: category,
),
),
),

View file

@ -1,13 +1,13 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:frosty/core/settings/settings_store.dart';
import 'package:frosty/screens/top/categories/category_streams_list/category_streams_store.dart';
import 'package:frosty/screens/home/stores/list_store.dart';
import 'package:frosty/screens/settings/stores/settings_store.dart';
import 'package:frosty/widgets/stream_card.dart';
import 'package:provider/provider.dart';
class CategoryStreams extends StatelessWidget {
final CategoryStreamsStore store;
final ListStore store;
const CategoryStreams({Key? key, required this.store}) : super(key: key);
@ -27,13 +27,13 @@ class CategoryStreams extends StatelessWidget {
expandedHeight: MediaQuery.of(context).size.height / 3,
flexibleSpace: FlexibleSpaceBar(
title: Text(
store.categoryInfo.name,
store.categoryInfo!.name,
style: Theme.of(context).textTheme.headline6?.copyWith(fontWeight: FontWeight.bold),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
background: CachedNetworkImage(
imageUrl: store.categoryInfo.boxArtUrl.replaceRange(store.categoryInfo.boxArtUrl.lastIndexOf('-') + 1, null, '300x400.jpg'),
imageUrl: store.categoryInfo!.boxArtUrl.replaceRange(store.categoryInfo!.boxArtUrl.lastIndexOf('-') + 1, null, '300x400.jpg'),
color: const Color.fromRGBO(255, 255, 255, 0.5),
colorBlendMode: BlendMode.modulate,
fit: BoxFit.cover,
@ -44,7 +44,7 @@ class CategoryStreams extends StatelessWidget {
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index > store.streams.length / 2 && store.hasMore) {
store.getStreams();
store.getData();
}
return Observer(
builder: (context) => StreamCard(

View file

@ -1,12 +1,11 @@
import 'package:flutter/material.dart';
import 'package:frosty/screens/top/categories/categories.dart';
import 'package:frosty/screens/top/categories/categories_store.dart';
import 'package:frosty/screens/top/streams/top_streams.dart';
import 'package:frosty/screens/top/streams/top_streams_store.dart';
import 'package:frosty/screens/home/stores/list_store.dart';
import 'package:frosty/screens/home/streams_list.dart';
import 'package:frosty/screens/home/top/categories/categories.dart';
class TopSection extends StatelessWidget {
final TopStreamsStore topStreamsStore;
final CategoriesStore categoriesStore;
final ListStore topStreamsStore;
final ListStore categoriesStore;
const TopSection({
Key? key,
@ -29,7 +28,7 @@ class TopSection extends StatelessWidget {
Expanded(
child: TabBarView(
children: [
TopStreams(store: topStreamsStore),
StreamsList(store: topStreamsStore),
Categories(store: categoriesStore),
],
),

View file

@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:frosty/core/auth/auth_store.dart';
import 'package:frosty/core/settings/account/blocked_users.dart';
import 'package:frosty/core/settings/account/profile_card.dart';
import 'package:frosty/core/settings/settings_store.dart';
import 'package:frosty/screens/settings/account/blocked_users.dart';
import 'package:frosty/screens/settings/account/profile_card.dart';
import 'package:frosty/screens/settings/stores/settings_store.dart';
import 'package:frosty/widgets/section_header.dart';
class AccountSettings extends StatelessWidget {
@ -20,7 +20,10 @@ class AccountSettings extends StatelessWidget {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SectionHeader('Account'),
const SectionHeader(
'Account',
padding: EdgeInsets.all(10.0),
),
ProfileCard(authStore: authStore),
if (authStore.isLoggedIn)
ListTile(

View file

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:frosty/core/settings/settings_store.dart';
import 'package:frosty/screens/settings/stores/settings_store.dart';
import 'package:frosty/widgets/section_header.dart';
class ChatSettings extends StatelessWidget {

View file

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:frosty/core/settings/settings_store.dart';
import 'package:frosty/screens/settings/stores/settings_store.dart';
import 'package:frosty/widgets/section_header.dart';
class GeneralSettings extends StatelessWidget {

View file

@ -1,10 +1,11 @@
import 'package:flutter/material.dart';
import 'package:frosty/core/auth/auth_store.dart';
import 'package:frosty/core/settings/account/account_settings.dart';
import 'package:frosty/core/settings/chat/chat_settings.dart';
import 'package:frosty/core/settings/general/general_settings.dart';
import 'package:frosty/core/settings/settings_store.dart';
import 'package:frosty/core/settings/video/video_settings.dart';
import 'package:frosty/screens/settings/account/account_settings.dart';
import 'package:frosty/screens/settings/chat_settings.dart';
import 'package:frosty/screens/settings/general_settings.dart';
import 'package:frosty/screens/settings/stores/settings_store.dart';
import 'package:frosty/screens/settings/video_settings.dart';
import 'package:frosty/widgets/section_header.dart';
import 'package:provider/provider.dart';
class Settings extends StatelessWidget {
@ -25,6 +26,17 @@ class Settings extends StatelessWidget {
GeneralSettings(settingsStore: settingsStore),
VideoSettings(settingsStore: settingsStore),
ChatSettings(settingsStore: settingsStore),
const SectionHeader('Misc'),
ListTile(
leading: const Icon(Icons.info),
title: const Text('About'),
onTap: () => showAboutDialog(
context: context,
applicationName: 'Frosty for Twitch',
applicationVersion: '1.0.0',
applicationLegalese: '\u{a9} 2021 Tommy Chow',
),
)
],
),
),

View file

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:frosty/core/settings/settings_store.dart';
import 'package:frosty/screens/settings/stores/settings_store.dart';
import 'package:frosty/widgets/section_header.dart';
class VideoSettings extends StatelessWidget {

View file

@ -1,49 +0,0 @@
import 'package:frosty/api/twitch_api.dart';
import 'package:frosty/core/auth/auth_store.dart';
import 'package:frosty/models/category.dart';
import 'package:mobx/mobx.dart';
part 'categories_store.g.dart';
class CategoriesStore = _CategoriesStoreBase with _$CategoriesStore;
abstract class _CategoriesStoreBase with Store {
final AuthStore authStore;
@readonly
var _categories = ObservableList<CategoryTwitch>();
String? _currentCursor;
var _isLoading = false;
bool get hasMore => _isLoading == false && _currentCursor != null;
_CategoriesStoreBase({required this.authStore}) {
getGames();
}
@action
Future<void> getGames() async {
_isLoading = true;
final result = await Twitch.getTopGames(headers: authStore.headersTwitch, cursor: _currentCursor);
if (result != null) {
if (_currentCursor == null) {
_categories = result.data.asObservable();
} else {
_categories.addAll(result.data);
}
_currentCursor = result.pagination['cursor'];
}
_isLoading = false;
}
@action
Future<void> refresh() async {
_currentCursor = null;
return getGames();
}
}

View file

@ -1,49 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'categories_store.dart';
// **************************************************************************
// StoreGenerator
// **************************************************************************
// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic
mixin _$CategoriesStore on _CategoriesStoreBase, Store {
final _$_categoriesAtom = Atom(name: '_CategoriesStoreBase._categories');
ObservableList<CategoryTwitch> get categories {
_$_categoriesAtom.reportRead();
return super._categories;
}
@override
ObservableList<CategoryTwitch> get _categories => categories;
@override
set _categories(ObservableList<CategoryTwitch> value) {
_$_categoriesAtom.reportWrite(value, super._categories, () {
super._categories = value;
});
}
final _$getGamesAsyncAction = AsyncAction('_CategoriesStoreBase.getGames');
@override
Future<void> getGames() {
return _$getGamesAsyncAction.run(() => super.getGames());
}
final _$refreshAsyncAction = AsyncAction('_CategoriesStoreBase.refresh');
@override
Future<void> refresh() {
return _$refreshAsyncAction.run(() => super.refresh());
}
@override
String toString() {
return '''
''';
}
}

View file

@ -1,67 +0,0 @@
import 'package:frosty/api/twitch_api.dart';
import 'package:frosty/core/auth/auth_store.dart';
import 'package:frosty/models/category.dart';
import 'package:frosty/models/stream.dart';
import 'package:mobx/mobx.dart';
part 'category_streams_store.g.dart';
class CategoryStreamsStore = _CategoryStreamsStoreBase with _$CategoryStreamsStore;
abstract class _CategoryStreamsStoreBase with Store {
final CategoryTwitch categoryInfo;
/// The list of the fetched streams under the category.
@readonly
var _streams = ObservableList<StreamTwitch>();
/// The loading status for pagination.
bool _isLoading = false;
/// The pagination cursor for streams under the category.
String? streamsCurrentCursor;
/// The authentication store used for obtaining the request headers.
final AuthStore authStore;
/// Returns whether or not there are more streams and loading status for pagination.
bool get hasMore => _isLoading == false && streamsCurrentCursor != null;
_CategoryStreamsStoreBase({
required this.categoryInfo,
required this.authStore,
}) {
getStreams();
}
/// Fetches the streams under the category based on the current cursor.
@action
Future<void> getStreams() async {
_isLoading = true;
final newStreams = await Twitch.getStreamsUnderGame(
gameId: categoryInfo.id,
headers: authStore.headersTwitch,
cursor: streamsCurrentCursor,
);
if (newStreams != null) {
if (streamsCurrentCursor == null) {
_streams = ObservableList.of(newStreams.data);
} else {
_streams.addAll(newStreams.data);
}
streamsCurrentCursor = newStreams.pagination['cursor'];
}
_isLoading = false;
}
/// Resets the cursor and then fetches the streams under the category.
@action
Future<void> refresh() {
streamsCurrentCursor = null;
return getStreams();
}
}

View file

@ -1,57 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'category_streams_store.dart';
// **************************************************************************
// StoreGenerator
// **************************************************************************
// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic
mixin _$CategoryStreamsStore on _CategoryStreamsStoreBase, Store {
final _$_streamsAtom = Atom(name: '_CategoryStreamsStoreBase._streams');
ObservableList<StreamTwitch> get streams {
_$_streamsAtom.reportRead();
return super._streams;
}
@override
ObservableList<StreamTwitch> get _streams => streams;
@override
set _streams(ObservableList<StreamTwitch> value) {
_$_streamsAtom.reportWrite(value, super._streams, () {
super._streams = value;
});
}
final _$getStreamsAsyncAction =
AsyncAction('_CategoryStreamsStoreBase.getStreams');
@override
Future<void> getStreams() {
return _$getStreamsAsyncAction.run(() => super.getStreams());
}
final _$_CategoryStreamsStoreBaseActionController =
ActionController(name: '_CategoryStreamsStoreBase');
@override
Future<void> refresh() {
final _$actionInfo = _$_CategoryStreamsStoreBaseActionController
.startAction(name: '_CategoryStreamsStoreBase.refresh');
try {
return super.refresh();
} finally {
_$_CategoryStreamsStoreBaseActionController.endAction(_$actionInfo);
}
}
@override
String toString() {
return '''
''';
}
}

View file

@ -1,61 +0,0 @@
import 'package:frosty/api/twitch_api.dart';
import 'package:frosty/core/auth/auth_store.dart';
import 'package:frosty/models/stream.dart';
import 'package:mobx/mobx.dart';
part 'top_streams_store.g.dart';
class TopStreamsStore = _TopStreamsStoreBase with _$TopStreamsStore;
abstract class _TopStreamsStoreBase with Store {
/// The list of the fetched top streams.
@readonly
var _topStreams = ObservableList<StreamTwitch>();
/// The loading status for pagination.
@readonly
bool _isLoading = false;
/// The pagination cursor for top streams.
String? _topStreamsCurrentCursor;
/// The authentication store.
final AuthStore authStore;
/// Returns whether or not there are more streams and loading status for pagination.
bool get hasMore => _isLoading == false && _topStreamsCurrentCursor != null;
_TopStreamsStoreBase({required this.authStore}) {
getTopStreams();
}
/// Fetches the top streams based on the current cursor.
@action
Future<void> getTopStreams() async {
_isLoading = true;
final newTopStreams = await Twitch.getTopStreams(
headers: authStore.headersTwitch,
cursor: _topStreamsCurrentCursor,
);
if (newTopStreams != null) {
if (_topStreamsCurrentCursor == null) {
_topStreams = newTopStreams.data.asObservable();
} else {
_topStreams.addAll(newTopStreams.data);
}
_topStreamsCurrentCursor = newTopStreams.pagination['cursor'];
}
_isLoading = false;
}
/// Resets the cursor and then fetches the top streams.
@action
Future<void> refresh() {
_topStreamsCurrentCursor = null;
return getTopStreams();
}
}

View file

@ -1,74 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'top_streams_store.dart';
// **************************************************************************
// StoreGenerator
// **************************************************************************
// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic
mixin _$TopStreamsStore on _TopStreamsStoreBase, Store {
final _$_topStreamsAtom = Atom(name: '_TopStreamsStoreBase._topStreams');
ObservableList<StreamTwitch> get topStreams {
_$_topStreamsAtom.reportRead();
return super._topStreams;
}
@override
ObservableList<StreamTwitch> get _topStreams => topStreams;
@override
set _topStreams(ObservableList<StreamTwitch> value) {
_$_topStreamsAtom.reportWrite(value, super._topStreams, () {
super._topStreams = value;
});
}
final _$_isLoadingAtom = Atom(name: '_TopStreamsStoreBase._isLoading');
bool get isLoading {
_$_isLoadingAtom.reportRead();
return super._isLoading;
}
@override
bool get _isLoading => isLoading;
@override
set _isLoading(bool value) {
_$_isLoadingAtom.reportWrite(value, super._isLoading, () {
super._isLoading = value;
});
}
final _$getTopStreamsAsyncAction =
AsyncAction('_TopStreamsStoreBase.getTopStreams');
@override
Future<void> getTopStreams() {
return _$getTopStreamsAsyncAction.run(() => super.getTopStreams());
}
final _$_TopStreamsStoreBaseActionController =
ActionController(name: '_TopStreamsStoreBase');
@override
Future<void> refresh() {
final _$actionInfo = _$_TopStreamsStoreBaseActionController.startAction(
name: '_TopStreamsStoreBase.refresh');
try {
return super.refresh();
} finally {
_$_TopStreamsStoreBaseActionController.endAction(_$actionInfo);
}
}
@override
String toString() {
return '''
''';
}
}