Add categories screen, store, & card (#42)
This commit is contained in:
parent
051b8678e5
commit
d1bd25eebf
5 changed files with 141 additions and 0 deletions
28
lib/screens/categories.dart
Normal file
28
lib/screens/categories.dart
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:frosty/stores/auth_store.dart';
|
||||
import 'package:frosty/stores/categories_store.dart';
|
||||
import 'package:frosty/widgets/category_card.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class Categories extends StatelessWidget {
|
||||
const Categories({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final categoriesStore = CategoriesStore(authStore: context.read<AuthStore>());
|
||||
return Observer(
|
||||
builder: (_) {
|
||||
return ListView.builder(
|
||||
itemCount: categoriesStore.categories.length,
|
||||
itemBuilder: (context, index) {
|
||||
if (index > categoriesStore.categories.length / 2 && categoriesStore.hasMore) {
|
||||
categoriesStore.getGames();
|
||||
}
|
||||
return CategoryCard(category: categoriesStore.categories[index]);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:frosty/screens/categories.dart';
|
||||
import 'package:frosty/screens/search.dart';
|
||||
import 'package:frosty/screens/settings.dart';
|
||||
import 'package:frosty/stores/auth_store.dart';
|
||||
|
|
@ -38,6 +39,7 @@ class _HomeState extends State<Home> {
|
|||
final titles = [
|
||||
if (context.read<AuthStore>().isLoggedIn) 'Followed Streams',
|
||||
'Top Streams',
|
||||
'Categories',
|
||||
'Search',
|
||||
];
|
||||
return Text(
|
||||
|
|
@ -75,6 +77,7 @@ class _HomeState extends State<Home> {
|
|||
category: StreamCategory.top,
|
||||
streamListStore: widget.streamListStore,
|
||||
),
|
||||
const Categories(),
|
||||
const Search(),
|
||||
],
|
||||
);
|
||||
|
|
@ -94,6 +97,10 @@ class _HomeState extends State<Home> {
|
|||
icon: Icon(Icons.arrow_upward),
|
||||
label: 'Top',
|
||||
),
|
||||
const BottomNavigationBarItem(
|
||||
icon: Icon(Icons.games),
|
||||
label: 'Categories',
|
||||
),
|
||||
const BottomNavigationBarItem(
|
||||
icon: Icon(Icons.search),
|
||||
label: 'Search',
|
||||
|
|
|
|||
37
lib/stores/categories_store.dart
Normal file
37
lib/stores/categories_store.dart
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import 'package:frosty/api/twitch_api.dart';
|
||||
import 'package:frosty/models/category.dart';
|
||||
import 'package:frosty/stores/auth_store.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'categories_store.g.dart';
|
||||
|
||||
class CategoriesStore = _CategoriesStoreBase with _$CategoriesStore;
|
||||
|
||||
abstract class _CategoriesStoreBase with Store {
|
||||
final categories = ObservableList<CategoryTwitch>();
|
||||
|
||||
String? currentCursor;
|
||||
|
||||
var _isLoading = false;
|
||||
|
||||
bool get hasMore => _isLoading == false && currentCursor != null;
|
||||
|
||||
final AuthStore authStore;
|
||||
|
||||
_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) {
|
||||
categories.addAll(result.data);
|
||||
currentCursor = result.cursor;
|
||||
}
|
||||
|
||||
_isLoading = false;
|
||||
}
|
||||
}
|
||||
25
lib/stores/categories_store.g.dart
Normal file
25
lib/stores/categories_store.g.dart
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// 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 _$getGamesAsyncAction = AsyncAction('_CategoriesStoreBase.getGames');
|
||||
|
||||
@override
|
||||
Future<void> getGames() {
|
||||
return _$getGamesAsyncAction.run(() => super.getGames());
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '''
|
||||
|
||||
''';
|
||||
}
|
||||
}
|
||||
44
lib/widgets/category_card.dart
Normal file
44
lib/widgets/category_card.dart
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:frosty/models/category.dart';
|
||||
|
||||
/// A tappable card widget that displays a category's box art and name.
|
||||
class CategoryCard extends StatelessWidget {
|
||||
final CategoryTwitch category;
|
||||
|
||||
const CategoryCard({Key? key, required this.category}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: () {},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 15.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: category.boxArtUrl.replaceFirst('-{width}x{height}', '-138x184'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Flexible(
|
||||
flex: 3,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
category.name,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const Text('0 Viewers')
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue