Commit 8314af4f authored by Wilko Manger's avatar Wilko Manger

Make states for auth requests more generic

parent b6fdd6f4
...@@ -41,8 +41,7 @@ class UsernamePageState extends State<UsernamePage> { ...@@ -41,8 +41,7 @@ class UsernamePageState extends State<UsernamePage> {
super.initState(); super.initState();
subscription = bloc.isUsernameAvailable.listen((state) { subscription = bloc.isUsernameAvailable.listen((state) {
if (state == UsernameAvailableState.available if (state == RequestState.success) {
|| state == UsernameAvailableState.unavailable) {
Navigator.pushNamed(context, Routes.startPassword); Navigator.pushNamed(context, Routes.startPassword);
} }
}); });
...@@ -116,9 +115,9 @@ class UsernamePageState extends State<UsernamePage> { ...@@ -116,9 +115,9 @@ class UsernamePageState extends State<UsernamePage> {
style: TextStyle(fontSize: 24), style: TextStyle(fontSize: 24),
), ),
SizedBox(height: 16), SizedBox(height: 16),
StreamBuilder<UsernameAvailableState>( StreamBuilder<RequestState>(
stream: bloc.isUsernameAvailable, stream: bloc.isUsernameAvailable,
builder: (BuildContext context, AsyncSnapshot<UsernameAvailableState> snapshot) { builder: (BuildContext context, AsyncSnapshot<RequestState> snapshot) {
String errorText; String errorText;
if (snapshot.hasError) { if (snapshot.hasError) {
...@@ -164,13 +163,13 @@ class UsernamePageState extends State<UsernamePage> { ...@@ -164,13 +163,13 @@ class UsernamePageState extends State<UsernamePage> {
} }
), ),
SizedBox(height: 16), SizedBox(height: 16),
StreamBuilder<UsernameAvailableState>( StreamBuilder<RequestState>(
stream: bloc.isUsernameAvailable, stream: bloc.isUsernameAvailable,
builder: (BuildContext context, AsyncSnapshot<UsernameAvailableState> snapshot) { builder: (BuildContext context, AsyncSnapshot<RequestState> snapshot) {
final state = snapshot.data; final state = snapshot.data;
final enabled = final enabled =
state != UsernameAvailableState.checking state != RequestState.active
&& state != UsernameAvailableState.stillChecking; && state != RequestState.stillActive;
var onPressed; var onPressed;
Widget child = PlatformText(l(context).next); Widget child = PlatformText(l(context).next);
...@@ -182,7 +181,7 @@ class UsernamePageState extends State<UsernamePage> { ...@@ -182,7 +181,7 @@ class UsernamePageState extends State<UsernamePage> {
onPressed = null; onPressed = null;
} }
if (state == UsernameAvailableState.stillChecking) { if (state == RequestState.stillActive) {
child = SizedBox( child = SizedBox(
width: 18, width: 18,
height: 18, height: 18,
......
...@@ -35,8 +35,12 @@ class PasswordPageState extends State<PasswordPage> { ...@@ -35,8 +35,12 @@ class PasswordPageState extends State<PasswordPage> {
password = null; password = null;
subscription = bloc.loginStream.listen((state) { subscription = bloc.loginStream.listen((state) {
if (state == LoginState.succeeded) { if (state == RequestState.success) {
Navigator.pushNamedAndRemoveUntil(context, Routes.chats, (route) => false); Navigator.pushNamedAndRemoveUntil(
context,
Routes.chats,
(route) => false
);
} }
}); });
} }
...@@ -76,9 +80,12 @@ class PasswordPageState extends State<PasswordPage> { ...@@ -76,9 +80,12 @@ class PasswordPageState extends State<PasswordPage> {
style: TextStyle(fontSize: 24), style: TextStyle(fontSize: 24),
), ),
SizedBox(height: 16), SizedBox(height: 16),
StreamBuilder<LoginState>( StreamBuilder<RequestState>(
stream: bloc.loginStream, stream: bloc.loginStream,
builder: (BuildContext context, AsyncSnapshot<LoginState> snapshot) { builder: (
BuildContext context,
AsyncSnapshot<RequestState> snapshot) {
String errorText; String errorText;
if (snapshot.hasError) { if (snapshot.hasError) {
...@@ -116,12 +123,12 @@ class PasswordPageState extends State<PasswordPage> { ...@@ -116,12 +123,12 @@ class PasswordPageState extends State<PasswordPage> {
} }
), ),
SizedBox(height: 16), SizedBox(height: 16),
StreamBuilder<LoginState>( StreamBuilder<RequestState>(
stream: bloc.loginStream, stream: bloc.loginStream,
builder: (BuildContext context, AsyncSnapshot<LoginState> snapshot) { builder: (BuildContext context, AsyncSnapshot<RequestState> snapshot) {
final state = snapshot.data; final state = snapshot.data;
final isTrying = final isTrying =
state == LoginState.trying || state == LoginState.stillTrying; state == RequestState.active || state == RequestState.stillActive;
var onPressed; var onPressed;
Widget child = PlatformText(l(context).login); Widget child = PlatformText(l(context).login);
...@@ -134,7 +141,7 @@ class PasswordPageState extends State<PasswordPage> { ...@@ -134,7 +141,7 @@ class PasswordPageState extends State<PasswordPage> {
onPressed = null; onPressed = null;
} }
if (state == LoginState.stillTrying) { if (state == RequestState.stillActive) {
child = SizedBox( child = SizedBox(
width: 18, width: 18,
height: 18, height: 18,
......
...@@ -17,13 +17,16 @@ ...@@ -17,13 +17,16 @@
import 'dart:async'; import 'dart:async';
import 'package:async/async.dart';
import 'package:matrix_sdk/matrix_sdk.dart'; import 'package:matrix_sdk/matrix_sdk.dart';
import 'package:meta/meta.dart';
import 'package:pattle/src/di.dart' as di; import 'package:pattle/src/di.dart' as di;
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
final bloc = StartBloc(); final bloc = StartBloc();
typedef Request = void Function(Function addError);
typedef Check = bool Function(Function addError);
class StartBloc { class StartBloc {
StartBloc() { StartBloc() {
...@@ -55,139 +58,170 @@ class StartBloc { ...@@ -55,139 +58,170 @@ class StartBloc {
} }
} }
final _isUsernameAvailableSubj = ReplaySubject<UsernameAvailableState>(maxSize: 1); final _isUsernameAvailableSubj = ReplaySubject<RequestState>(maxSize: 1);
Observable<UsernameAvailableState> get isUsernameAvailable Observable<RequestState> get isUsernameAvailable
=> _isUsernameAvailableSubj.stream.distinct(); => _isUsernameAvailableSubj.stream.distinct();
StreamSubscription stillCheckingSubscription; static bool _defaultValidate(Function addError) => true;
Future<void> checkUsernameAvailability(String username) async { StreamSubscription stillActiveSubscription;
if (username == null) { Future<void> _do({
return; @required Subject<RequestState> subject,
} Function validate = _defaultValidate,
@required Request request}) async {
await stillActiveSubscription?.cancel();
subject.add(RequestState.active);
await stillCheckingSubscription?.cancel(); // If after three seconds it's still active, change state to
_isUsernameAvailableSubj.add(UsernameAvailableState.checking); // 'stillActive'.
// If after three seconds it's still checking, change state to
// 'stillChecking'.
Future.delayed(loadingTime).then((_) async { Future.delayed(loadingTime).then((_) async {
await stillCheckingSubscription?.cancel(); await stillActiveSubscription?.cancel();
stillCheckingSubscription = _isUsernameAvailableSubj.stream.listen((state) { stillActiveSubscription = subject.stream.listen((state) {
if (state == UsernameAvailableState.checking) { if (state == RequestState.active) {
_isUsernameAvailableSubj.add(UsernameAvailableState.stillChecking); subject.add(RequestState.stillActive);
} }
}, },
); );
}); });
final addError = (error) { final addError = (error) {
stillCheckingSubscription?.cancel(); stillActiveSubscription?.cancel();
_isUsernameAvailableSubj.add(UsernameAvailableState.none); subject.add(RequestState.none);
_isUsernameAvailableSubj.addError(error); subject.addError(error);
}; };
var user; final validated = validate(addError);
// Check if there is a ':' in the username, if (!validated) {
// if so, treat it as a full Matrix ID (with or without '@'). return;
// Otherwise use the local part (with or without '@'). }
// So, accept all of these formats:
// @joe:matrix.org request(addError);
// joe:matrix.org }
// joe
// @joe
if (username.contains(':')) { Future<void> checkUsernameAvailability(String username) async {
final split = username.split(':'); var user;
String server = split[1];
try {
final serverUri = Uri.parse("https://$server");
_setHomeserver(serverUri);
// Add an '@' if the username does not have one, to allow
// for this input: 'pit:pattle.im'
if (!username.startsWith('@')) {
username = "@$username";
}
if (!UserId.isValidFullyQualified(username)) { await _do(
addError(InvalidUserIdException()); subject: _isUsernameAvailableSubj,
return; validate: (addError) {
if (username == null) {
return false;
} }
user = UserId(username).username; // Check if there is a ':' in the username,
} on FormatException { // if so, treat it as a full Matrix ID (with or without '@').
addError(InvalidHostnameException()); // Otherwise use the local part (with or without '@').
return; // So, accept all of these formats:
} // @joe:matrix.org
} else { // joe:matrix.org
if (username.startsWith('@')) { // joe
username = username.substring(1).toLowerCase(); // @joe
} if (username.contains(':')) {
final split = username.split(':');
String server = split[1];
try {
final serverUri = Uri.parse("https://$server");
_setHomeserver(serverUri);
// Add an '@' if the username does not have one, to allow
// for this input: 'pit:pattle.im'
if (!username.startsWith('@')) {
username = "@$username";
}
if (!UserId.isValidFullyQualified(username)) {
addError(InvalidUserIdException());
return false;
}
user = UserId(username).username;
} on FormatException {
addError(InvalidHostnameException());
return false;
}
if (!Username.isValid(username)) { return true;
addError(InvalidUsernameException()); } else {
return; if (username.startsWith('@')) {
} username = username.substring(1).toLowerCase();
}
user = Username(username); if (!Username.isValid(username)) {
} addError(InvalidUsernameException());
return false;
}
homeserver.isUsernameAvailable(user).then((available) { user = Username(username);
if (available) {
_isUsernameAvailableSubj.add(UsernameAvailableState.available);
} else {
_isUsernameAvailableSubj.add(UsernameAvailableState.unavailable);
}
_username = user; return true;
}) }
.catchError((error) => _isUsernameAvailableSubj.addError(error)); },
request: (addError) {
homeserver.isUsernameAvailable(user).then((available) {
_isUsernameAvailableSubj.add(RequestSuccessState(
data: available
));
_username = user;
})
.catchError((error) => _isUsernameAvailableSubj.addError(error));
},
);
} }
final _loginSubj = BehaviorSubject<LoginState>(); final _loginSubj = BehaviorSubject<RequestState>();
Observable<LoginState> get loginStream Observable<RequestState> get loginStream => _loginSubj.stream;
=> _loginSubj.stream;
void login(String password) { void login(String password) {
_loginSubj.add(LoginState.trying); _do(
subject: _loginSubj,
// If after three seconds it's still checking, change state to request: (addError) {
// 'stillTrying'. homeserver.login(
Future.delayed(loadingTime).then((_) { _username,
_loginSubj.stream.listen((state) { password,
if (state == LoginState.trying) { store: di.getStore()
_loginSubj.add(LoginState.stillTrying); )
} .then((user) {
}); di.registerLocalUser(user);
}); _loginSubj.add(RequestState.success);
})
homeserver.login( .catchError((error) => _loginSubj.addError(error));
_username, }
password, );
store: di.getStore()
)
.then((user) {
di.registerLocalUser(user);
_loginSubj.add(LoginState.succeeded);
})
.catchError((error) => _loginSubj.addError(error));
} }
} }
enum UsernameAvailableState { class RequestState {
none, final int _value;
checking,
stillChecking, const RequestState(int value) : _value = value;
unavailable,
available static const none = RequestState(0);
static const active = RequestState(1);
static const stillActive = RequestState(2);
static const success = RequestSuccessState();
@override
bool operator ==(other) {
if (other is RequestState) {
return other._value == this._value;
} else {
return false;
}
}
@override
int get hashCode => _value.hashCode;
} }
enum LoginState { class RequestSuccessState<T> extends RequestState {
none, final T data;
trying,
stillTrying, const RequestSuccessState({this.data}) : super(3);
succeeded
} }
class InvalidUserIdException implements Exception { } class InvalidUserIdException implements Exception { }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment