Add search results & search store
This commit is contained in:
parent
6b40f9e76f
commit
0bbf7f80d4
3 changed files with 177 additions and 38 deletions
|
|
@ -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<Search> {
|
||||
final _textController = TextEditingController();
|
||||
late final SearchStore searchStore;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
searchStore = SearchStore(authStore: context.read<AuthStore>());
|
||||
}
|
||||
|
||||
@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<AuthStore>().headersTwitch);
|
||||
if (user != null) {
|
||||
final channelInfo = await Twitch.getChannel(userId: user.id, headers: context.read<AuthStore>().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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
62
lib/stores/search_store.dart
Normal file
62
lib/stores/search_store.dart
Normal file
|
|
@ -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<ChannelQuery>();
|
||||
ObservableList<ChannelQuery> get searchResults => _searchResults;
|
||||
|
||||
_SearchStoreBase({required this.authStore});
|
||||
|
||||
@action
|
||||
Future<void> 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<void> 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<AuthStore>().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();
|
||||
}
|
||||
}
|
||||
33
lib/stores/search_store.g.dart
Normal file
33
lib/stores/search_store.g.dart
Normal file
|
|
@ -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<ChannelQuery> get _searchResults {
|
||||
_$_searchResultsAtom.reportRead();
|
||||
return super._searchResults;
|
||||
}
|
||||
|
||||
@override
|
||||
set _searchResults(ObservableList<ChannelQuery> value) {
|
||||
_$_searchResultsAtom.reportWrite(value, super._searchResults, () {
|
||||
super._searchResults = value;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '''
|
||||
|
||||
''';
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue