From 0bbf7f80d45fcdf3213a6fd2bd8e48149f051025 Mon Sep 17 00:00:00 2001 From: tommyxchow Date: Sun, 14 Nov 2021 04:21:39 -0500 Subject: [PATCH] Add search results & search store --- lib/screens/search.dart | 120 ++++++++++++++++++++++----------- lib/stores/search_store.dart | 62 +++++++++++++++++ lib/stores/search_store.g.dart | 33 +++++++++ 3 files changed, 177 insertions(+), 38 deletions(-) create mode 100644 lib/stores/search_store.dart create mode 100644 lib/stores/search_store.g.dart diff --git a/lib/screens/search.dart b/lib/screens/search.dart index 068b6d6e..a6724a0c 100644 --- a/lib/screens/search.dart +++ b/lib/screens/search.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:frosty/api/twitch_api.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:frosty/screens/video_chat.dart'; import 'package:frosty/stores/auth_store.dart'; +import 'package:frosty/stores/search_store.dart'; import 'package:provider/provider.dart'; class Search extends StatefulWidget { @@ -12,50 +13,93 @@ class Search extends StatefulWidget { } class _SearchState extends State { - final _textController = TextEditingController(); + late final SearchStore searchStore; + + @override + void initState() { + super.initState(); + searchStore = SearchStore(authStore: context.read()); + } @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(10.0), - child: TextField( - controller: _textController, - autocorrect: false, - decoration: const InputDecoration( - isDense: true, - contentPadding: EdgeInsets.all(8.0), - border: OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(15.0)), + return Column( + children: [ + Padding( + padding: const EdgeInsets.all(15.0), + child: TextField( + controller: searchStore.textController, + autocorrect: false, + decoration: const InputDecoration( + isDense: true, + contentPadding: EdgeInsets.all(8.0), + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(15.0)), + ), + hintText: 'Search for a channel', + ), + onSubmitted: searchStore.handleQuery, ), - hintText: 'Search for a channel', ), - onSubmitted: (string) async { - final user = await Twitch.getUser(userLogin: string, headers: context.read().headersTwitch); - if (user != null) { - final channelInfo = await Twitch.getChannel(userId: user.id, headers: context.read().headersTwitch); - if (channelInfo != null) { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) { - return VideoChat( - userLogin: channelInfo.broadcasterLogin, - userName: channelInfo.broadcasterName, + Expanded( + child: Observer( + builder: (_) { + return ListView.builder( + itemCount: searchStore.searchResults.length + 1, + itemBuilder: (context, index) { + if (index == searchStore.searchResults.length) { + if (searchStore.textController.text.isEmpty) { + return const SizedBox(); + } + return ListTile( + title: Text('Go to ${searchStore.textController.text}'), + onTap: () => searchStore.handleSearch(searchStore.textController.text, context), ); - }, - ), + } + + final channel = searchStore.searchResults[index]; + return ListTile( + title: Text(channel.displayName), + trailing: channel.isLive + ? ClipRRect( + borderRadius: BorderRadius.circular(5.0), + child: Container( + color: Colors.red, + padding: const EdgeInsets.all(8.0), + child: const Text( + 'LIVE', + style: TextStyle(fontWeight: FontWeight.bold), + ), + ), + ) + : null, + subtitle: channel.isLive ? Text('Uptime: ${DateTime.now().difference(DateTime.parse(channel.startedAt)).toString().split('.')[0]}') : null, + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) { + return VideoChat( + userLogin: channel.broadcasterLogin, + userName: channel.displayName, + ); + }, + ), + ); + }, + ); + }, ); - } else { - const snackBar = SnackBar(content: Text('Failed to get channel info :(')); - ScaffoldMessenger.of(context).showSnackBar(snackBar); - } - } else { - const snackBar = SnackBar(content: Text('User does not exist :(')); - ScaffoldMessenger.of(context).showSnackBar(snackBar); - } - _textController.clear(); - }, - ), + }, + ), + ), + ], ); } + + @override + void dispose() { + searchStore.dispose(); + super.dispose(); + } } diff --git a/lib/stores/search_store.dart b/lib/stores/search_store.dart new file mode 100644 index 00000000..d0b8fb98 --- /dev/null +++ b/lib/stores/search_store.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:frosty/api/twitch_api.dart'; +import 'package:frosty/models/channel.dart'; +import 'package:frosty/screens/video_chat.dart'; +import 'package:frosty/stores/auth_store.dart'; +import 'package:mobx/mobx.dart'; +import 'package:provider/provider.dart'; + +part 'search_store.g.dart'; + +class SearchStore = _SearchStoreBase with _$SearchStore; + +abstract class _SearchStoreBase with Store { + final textController = TextEditingController(); + + final AuthStore authStore; + + @observable + var _searchResults = ObservableList(); + ObservableList get searchResults => _searchResults; + + _SearchStoreBase({required this.authStore}); + + @action + Future handleQuery(String query) async { + final results = await Twitch.searchChannels(query: query, headers: authStore.headersTwitch); + results.sort((c1, c2) => c2.isLive ? 1 : -1); + + _searchResults = ObservableList.of(results); + } + + Future handleSearch(String search, BuildContext context) async { + final user = await Twitch.getUser(userLogin: search, headers: authStore.headersTwitch); + if (user != null) { + final channelInfo = await Twitch.getChannel(userId: user.id, headers: context.read().headersTwitch); + if (channelInfo != null) { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) { + return VideoChat( + userLogin: channelInfo.broadcasterLogin, + userName: channelInfo.broadcasterName, + ); + }, + ), + ); + } else { + const snackBar = SnackBar(content: Text('Failed to get channel info :(')); + ScaffoldMessenger.of(context).showSnackBar(snackBar); + } + } else { + const snackBar = SnackBar(content: Text('User does not exist :(')); + ScaffoldMessenger.of(context).showSnackBar(snackBar); + } + textController.clear(); + } + + void dispose() { + textController.dispose(); + } +} diff --git a/lib/stores/search_store.g.dart b/lib/stores/search_store.g.dart new file mode 100644 index 00000000..155f1e68 --- /dev/null +++ b/lib/stores/search_store.g.dart @@ -0,0 +1,33 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'search_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 _$SearchStore on _SearchStoreBase, Store { + final _$_searchResultsAtom = Atom(name: '_SearchStoreBase._searchResults'); + + @override + ObservableList get _searchResults { + _$_searchResultsAtom.reportRead(); + return super._searchResults; + } + + @override + set _searchResults(ObservableList value) { + _$_searchResultsAtom.reportWrite(value, super._searchResults, () { + super._searchResults = value; + }); + } + + @override + String toString() { + return ''' + + '''; + } +}