Convert providers to MobX stores

This commit is contained in:
Tommy Chow 2021-10-01 00:08:19 -04:00
parent 2c72d41e78
commit b86b989507
7 changed files with 320 additions and 64 deletions

View file

@ -1,3 +0,0 @@
import 'package:flutter/cupertino.dart';
class SettingsProvider extends ChangeNotifier {}

View file

@ -1,3 +1,4 @@
import 'package:mobx/mobx.dart';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:frosty/constants.dart';
@ -6,7 +7,11 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_web_auth/flutter_web_auth.dart';
class AuthenticationProvider extends ChangeNotifier {
part 'auth_store.g.dart';
class AuthStore = AuthBase with _$AuthStore;
abstract class AuthBase with Store {
static String? _token;
static String? get token => _token;
@ -16,7 +21,9 @@ class AuthenticationProvider extends ChangeNotifier {
final _secret = secret;
final _clientId = clientId;
@observable
var _isLoggedIn = false;
bool get isLoggedIn => _isLoggedIn;
var _tokenIsValid = false;
@ -85,7 +92,7 @@ class AuthenticationProvider extends ChangeNotifier {
debugPrint('Token invalidated :(');
_tokenIsValid = false;
notifyListeners();
// notifyListeners();
}
Future<void> _getUserInfo() async {
@ -121,7 +128,7 @@ class AuthenticationProvider extends ChangeNotifier {
await _getUserInfo();
notifyListeners();
// notifyListeners();
} catch (error) {
debugPrint('Login failed due to $error');
}
@ -133,6 +140,6 @@ class AuthenticationProvider extends ChangeNotifier {
await _storage.delete(key: 'USER_TOKEN');
debugPrint('Succesfully logged out');
notifyListeners();
// notifyListeners();
}
}

View file

@ -0,0 +1,33 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'auth_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 _$AuthStore on AuthBase, Store {
final _$_isLoggedInAtom = Atom(name: 'AuthBase._isLoggedIn');
@override
bool get _isLoggedIn {
_$_isLoggedInAtom.reportRead();
return super._isLoggedIn;
}
@override
set _isLoggedIn(bool value) {
_$_isLoggedInAtom.reportWrite(value, super._isLoggedIn, () {
super._isLoggedIn = value;
});
}
@override
String toString() {
return '''
''';
}
}

View file

@ -1,22 +1,35 @@
import 'package:mobx/mobx.dart';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:frosty/constants.dart';
import 'package:frosty/providers/authentication_provider.dart';
import 'package:frosty/stores/auth_store.dart';
import 'package:http/http.dart' as http;
import 'package:frosty/models/channel.dart';
class ChannelListProvider extends ChangeNotifier {
var topChannels = <Channel>[];
var followedChannels = <Channel>[];
var isLoading = false;
part 'channel_list_store.g.dart';
class ChannelListStore = _ChannelListBase with _$ChannelListStore;
abstract class _ChannelListBase with Store {
@observable
ObservableList<Channel> topChannels = ObservableList<Channel>();
@observable
ObservableList<Channel> followedChannels = ObservableList<Channel>();
@observable
bool isLoading = false;
@observable
String? topChannelsCurrentCursor;
@observable
String? followedChannelsCurrentCursor;
final token = AuthenticationProvider.token;
final token = AuthBase.token;
final String? id;
ChannelListProvider({this.id}) {
_ChannelListBase({this.id}) {
if (token != null) {
updateTopChannels();
}
@ -43,6 +56,7 @@ class ChannelListProvider extends ChangeNotifier {
}
}
@action
Future<void> update({required Category category}) async {
switch (category) {
case Category.top:
@ -55,6 +69,7 @@ class ChannelListProvider extends ChangeNotifier {
}
/// Returns the top 10 streamers and a cursor for further requests.
@action
Future<void> updateTopChannels() async {
final url = Uri.parse('https://api.twitch.tv/helix/streams?first=10');
final headers = {'Authorization': 'Bearer $token', 'Client-Id': clientId};
@ -65,26 +80,32 @@ class ChannelListProvider extends ChangeNotifier {
final decoded = jsonDecode(response.body);
final data = decoded['data'] as List;
final result = {'channels': data.map((channel) => Channel.fromJson(channel)).toList(), 'cursor': decoded['pagination']['cursor']};
final result = {
'channels': data.map((channel) => Channel.fromJson(channel)).toList(),
'cursor': decoded['pagination']['cursor']
};
topChannels = result['channels'];
topChannels = ObservableList.of(result['channels']);
topChannelsCurrentCursor = result['cursor'];
} else {
debugPrint('Failed to update top channels');
}
notifyListeners();
// notifyListeners();
}
@action
Future<void> getMoreChannels({required Category category}) async {
isLoading = true;
Uri url;
switch (category) {
case Category.top:
url = Uri.parse('https://api.twitch.tv/helix/streams?first=10&after=$topChannelsCurrentCursor');
url = Uri.parse(
'https://api.twitch.tv/helix/streams?first=10&after=$topChannelsCurrentCursor');
break;
case Category.followed:
url = Uri.parse('https://api.twitch.tv/helix/streams/followed?user_id=$id&first=10&after=$followedChannelsCurrentCursor');
url = Uri.parse(
'https://api.twitch.tv/helix/streams/followed?user_id=$id&first=10&after=$followedChannelsCurrentCursor');
break;
}
@ -95,7 +116,10 @@ class ChannelListProvider extends ChangeNotifier {
final decoded = jsonDecode(response.body);
final data = decoded['data'] as List;
final result = {'channels': data.map((channel) => Channel.fromJson(channel)).toList(), 'cursor': decoded['pagination']['cursor']};
final result = {
'channels': data.map((channel) => Channel.fromJson(channel)).toList(),
'cursor': decoded['pagination']['cursor']
};
switch (category) {
case Category.top:
@ -112,11 +136,13 @@ class ChannelListProvider extends ChangeNotifier {
debugPrint('Failed to get more channels');
}
isLoading = false;
notifyListeners();
// notifyListeners();
}
@action
Future<void> updateFollowedChannels() async {
final url = Uri.parse('https://api.twitch.tv/helix/streams/followed?first=10&user_id=$id');
final url = Uri.parse(
'https://api.twitch.tv/helix/streams/followed?first=10&user_id=$id');
final headers = {'Authorization': 'Bearer $token', 'Client-Id': clientId};
final response = await http.get(url, headers: headers);
@ -125,14 +151,17 @@ class ChannelListProvider extends ChangeNotifier {
final decoded = jsonDecode(response.body);
final data = decoded['data'] as List;
final result = {'channels': data.map((channel) => Channel.fromJson(channel)).toList(), 'cursor': decoded['pagination']['cursor']};
final result = {
'channels': data.map((channel) => Channel.fromJson(channel)).toList(),
'cursor': decoded['pagination']['cursor']
};
followedChannels = result['channels'];
followedChannels = ObservableList.of(result['channels']);
followedChannelsCurrentCursor = result['cursor'];
} else {
debugPrint('Failed to update followed channels');
}
notifyListeners();
// notifyListeners();
}
}

View file

@ -0,0 +1,135 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'channel_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 _$ChannelListStore on _ChannelListBase, Store {
final _$topChannelsAtom = Atom(name: '_ChannelListBase.topChannels');
@override
ObservableList<Channel> get topChannels {
_$topChannelsAtom.reportRead();
return super.topChannels;
}
@override
set topChannels(ObservableList<Channel> value) {
_$topChannelsAtom.reportWrite(value, super.topChannels, () {
super.topChannels = value;
});
}
final _$followedChannelsAtom =
Atom(name: '_ChannelListBase.followedChannels');
@override
ObservableList<Channel> get followedChannels {
_$followedChannelsAtom.reportRead();
return super.followedChannels;
}
@override
set followedChannels(ObservableList<Channel> value) {
_$followedChannelsAtom.reportWrite(value, super.followedChannels, () {
super.followedChannels = value;
});
}
final _$isLoadingAtom = Atom(name: '_ChannelListBase.isLoading');
@override
bool get isLoading {
_$isLoadingAtom.reportRead();
return super.isLoading;
}
@override
set isLoading(bool value) {
_$isLoadingAtom.reportWrite(value, super.isLoading, () {
super.isLoading = value;
});
}
final _$topChannelsCurrentCursorAtom =
Atom(name: '_ChannelListBase.topChannelsCurrentCursor');
@override
String? get topChannelsCurrentCursor {
_$topChannelsCurrentCursorAtom.reportRead();
return super.topChannelsCurrentCursor;
}
@override
set topChannelsCurrentCursor(String? value) {
_$topChannelsCurrentCursorAtom
.reportWrite(value, super.topChannelsCurrentCursor, () {
super.topChannelsCurrentCursor = value;
});
}
final _$followedChannelsCurrentCursorAtom =
Atom(name: '_ChannelListBase.followedChannelsCurrentCursor');
@override
String? get followedChannelsCurrentCursor {
_$followedChannelsCurrentCursorAtom.reportRead();
return super.followedChannelsCurrentCursor;
}
@override
set followedChannelsCurrentCursor(String? value) {
_$followedChannelsCurrentCursorAtom
.reportWrite(value, super.followedChannelsCurrentCursor, () {
super.followedChannelsCurrentCursor = value;
});
}
final _$updateAsyncAction = AsyncAction('_ChannelListBase.update');
@override
Future<void> update({required Category category}) {
return _$updateAsyncAction.run(() => super.update(category: category));
}
final _$updateTopChannelsAsyncAction =
AsyncAction('_ChannelListBase.updateTopChannels');
@override
Future<void> updateTopChannels() {
return _$updateTopChannelsAsyncAction.run(() => super.updateTopChannels());
}
final _$getMoreChannelsAsyncAction =
AsyncAction('_ChannelListBase.getMoreChannels');
@override
Future<void> getMoreChannels({required Category category}) {
return _$getMoreChannelsAsyncAction
.run(() => super.getMoreChannels(category: category));
}
final _$updateFollowedChannelsAsyncAction =
AsyncAction('_ChannelListBase.updateFollowedChannels');
@override
Future<void> updateFollowedChannels() {
return _$updateFollowedChannelsAsyncAction
.run(() => super.updateFollowedChannels());
}
@override
String toString() {
return '''
topChannels: ${topChannels},
followedChannels: ${followedChannels},
isLoading: ${isLoading},
topChannelsCurrentCursor: ${topChannelsCurrentCursor},
followedChannelsCurrentCursor: ${followedChannelsCurrentCursor}
''';
}
}

View file

@ -2,50 +2,30 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart';
import 'package:frosty/models/channel.dart';
import 'package:frosty/providers/authentication_provider.dart';
import 'package:frosty/stores/auth_store.dart';
import 'package:frosty/utility/request.dart';
import 'package:frosty/widgets/chat_message.dart';
import 'package:mobx/mobx.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
class ChatProvider extends ChangeNotifier {
final Channel channelInfo;
part 'chat_store.g.dart';
class ChatStore = _ChatStoreBase with _$ChatStore;
abstract class _ChatStoreBase with Store {
@observable
bool autoScroll = true;
List<String> messages = [];
final channel = WebSocketChannel.connect(Uri.parse('wss://irc-ws.chat.twitch.tv:443'));
final messages = <String>[];
final scrollController = ScrollController();
final _assetToUrl = <String, String>{};
final _emoteIdToWord = <String, String>{};
var autoScroll = true;
final scrollController = ScrollController();
ChatProvider({required this.channelInfo}) {
final commands = [
'PASS oauth:${AuthenticationProvider.token}',
'NICK justinfan888',
'CAP REQ :twitch.tv/tags',
'CAP REQ :twitch.tv/commands',
// 'CAP REQ :twitch.tv/membership',
'CAP END',
'JOIN #${channelInfo.userLogin}',
];
for (final command in commands) {
channel.sink.add(command);
}
scrollController.addListener(() {
if (!scrollController.position.atEdge) {
autoScroll = false;
notifyListeners();
} else if (scrollController.position.atEdge && scrollController.position.pixels != scrollController.position.minScrollExtent) {
autoScroll = true;
notifyListeners();
}
});
}
Future<void> getEmotes() async {
Future<void> start(Channel channelInfo) async {
final assets = [
await Request.getEmotesBTTVGlobal(),
await Request.getEmotesBTTVChannel(id: channelInfo.userId),
@ -64,8 +44,30 @@ class ChatProvider extends ChangeNotifier {
_assetToUrl.addAll(map);
}
}
final commands = [
'PASS oauth:${AuthBase.token}',
'NICK justinfan888',
'CAP REQ :twitch.tv/tags',
'CAP REQ :twitch.tv/commands',
// 'CAP REQ :twitch.tv/membership',
'CAP END',
'JOIN #${channelInfo.userLogin}',
];
for (final command in commands) {
channel.sink.add(command);
}
scrollController.addListener(() {
if (!scrollController.position.atEdge) {
autoScroll = false;
} else if (scrollController.position.atEdge && scrollController.position.pixels != scrollController.position.minScrollExtent) {
autoScroll = true;
}
});
}
@action
void handleWebsocketData(Object? data) {
for (final message in data.toString().split('\r\n')) {
if (message.startsWith('@')) {
@ -223,6 +225,7 @@ class ChatProvider extends ChangeNotifier {
return result;
}
@action
void resumeScroll() {
autoScroll = true;
scrollController.jumpTo(scrollController.position.maxScrollExtent);
@ -230,12 +233,6 @@ class ChatProvider extends ChangeNotifier {
scrollController.jumpTo(scrollController.position.maxScrollExtent);
});
}
@override
void dispose() {
channel.sink.close();
super.dispose();
}
}
// https://stackoverflow.com/questions/50081213/how-do-i-use-hexadecimal-color-strings-in-flutter

View file

@ -0,0 +1,58 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'chat_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 _$ChatStore on _ChatStoreBase, Store {
final _$autoScrollAtom = Atom(name: '_ChatStoreBase.autoScroll');
@override
bool get autoScroll {
_$autoScrollAtom.reportRead();
return super.autoScroll;
}
@override
set autoScroll(bool value) {
_$autoScrollAtom.reportWrite(value, super.autoScroll, () {
super.autoScroll = value;
});
}
final _$_ChatStoreBaseActionController =
ActionController(name: '_ChatStoreBase');
@override
void handleWebsocketData(Object? data) {
final _$actionInfo = _$_ChatStoreBaseActionController.startAction(
name: '_ChatStoreBase.handleWebsocketData');
try {
return super.handleWebsocketData(data);
} finally {
_$_ChatStoreBaseActionController.endAction(_$actionInfo);
}
}
@override
void resumeScroll() {
final _$actionInfo = _$_ChatStoreBaseActionController.startAction(
name: '_ChatStoreBase.resumeScroll');
try {
return super.resumeScroll();
} finally {
_$_ChatStoreBaseActionController.endAction(_$actionInfo);
}
}
@override
String toString() {
return '''
autoScroll: ${autoScroll}
''';
}
}