ui: Migrate [AddContactPage] to Bloc

This commit is contained in:
2022-03-26 18:01:15 +01:00
parent 7a999d40d8
commit 489ef364c1
13 changed files with 221 additions and 80 deletions

View File

@@ -0,0 +1,66 @@
import "package:moxxyv2/shared/commands.dart";
import "package:moxxyv2/shared/events.dart";
import "package:moxxyv2/shared/helpers.dart";
import "package:moxxyv2/shared/backgroundsender.dart";
import "package:moxxyv2/ui/bloc/conversations_bloc.dart";
import "package:moxxyv2/ui/bloc/conversation_bloc.dart";
import "package:get_it/get_it.dart";
import "package:bloc/bloc.dart";
import "package:freezed_annotation/freezed_annotation.dart";
part "addcontact_state.dart";
part "addcontact_event.dart";
part "addcontact_bloc.freezed.dart";
class AddContactBloc extends Bloc<AddContactEvent, AddContactState> {
AddContactBloc() : super(AddContactState()) {
on<AddedContactEvent>(_onContactAdded);
on<JidChangedEvent>(_onJidChanged);
}
Future<void> _onContactAdded(AddedContactEvent event, Emitter<AddContactState> emit) async {
// TODO: Remove once we can disable the custom buttom
if (state.working) return;
final validation = validateJidString(state.jid);
if (validation != null) {
emit(state.copyWith(jidError: validation));
return;
}
emit(
state.copyWith(
working: true,
jidError: null
)
);
final result = await GetIt.I.get<BackgroundServiceDataSender>().sendData(
AddContactCommand(
jid: state.jid
)
) as AddContactResultEvent;
if (result.conversation != null) {
if (result.added) {
GetIt.I.get<ConversationsBloc>().add(ConversationsAddedEvent(result.conversation!));
} else {
GetIt.I.get<ConversationsBloc>().add(ConversationsUpdatedEvent(result.conversation!));
}
}
GetIt.I.get<ConversationBloc>().add(
RequestedConversationEvent(
result.conversation!.jid,
result.conversation!.title,
result.conversation!.avatarUrl,
removeUntilConversations: true
)
);
}
Future<void> _onJidChanged(JidChangedEvent event, Emitter<AddContactState> emit) async {
emit(state.copyWith(jid: event.jid));
}
}

View File

@@ -0,0 +1,13 @@
part of "addcontact_bloc.dart";
abstract class AddContactEvent {}
/// Triggered when a new contact has been added by the UI
class AddedContactEvent extends AddContactEvent {}
/// Triggered by the UI when the JID input field is changed
class JidChangedEvent extends AddContactEvent {
final String jid;
JidChangedEvent(this.jid);
}

View File

@@ -0,0 +1,10 @@
part of "addcontact_bloc.dart";
@freezed
class AddContactState with _$AddContactState {
factory AddContactState({
@Default("") String jid,
@Default(null) String? jidError,
@Default(false) bool working
}) = _AddContactState;
}

View File

@@ -11,6 +11,7 @@ import "package:moxxyv2/ui/bloc/conversations_bloc.dart";
import "package:get_it/get_it.dart";
import "package:bloc/bloc.dart";
import "package:freezed_annotation/freezed_annotation.dart";
import "package:flutter/widgets.dart";
part "conversation_state.dart";
part "conversation_event.dart";
@@ -47,11 +48,18 @@ class ConversationBloc extends Bloc<ConversationEvent, ConversationState> {
)
);
GetIt.I.get<NavigationBloc>().add(
final navEvent = event.removeUntilConversations ? (
PushedNamedAndRemoveUntilEvent(
const NavigationDestination(conversationRoute),
ModalRoute.withName(conversationsRoute)
)
) : (
PushedNamedEvent(
const NavigationDestination(conversationRoute)
)
);
GetIt.I.get<NavigationBloc>().add(navEvent);
final result = await GetIt.I.get<BackgroundServiceDataSender>().sendData(
GetMessagesForJidCommand(

View File

@@ -28,8 +28,16 @@ class RequestedConversationEvent extends ConversationEvent {
final String jid;
final String title;
final String avatarUrl;
final bool removeUntilConversations;
RequestedConversationEvent(this.jid, this.title, this.avatarUrl);
RequestedConversationEvent(
this.jid,
this.title,
this.avatarUrl,
{
this.removeUntilConversations = false
}
);
}
/// Triggered by the UI when a message is quoted

View File

@@ -1,5 +1,3 @@
import "package:moxxyv2/shared/events.dart";
import "package:moxxyv2/shared/backgroundsender.dart";
import "package:moxxyv2/shared/models/conversation.dart";
import "package:bloc/bloc.dart";

View File

@@ -14,20 +14,6 @@ part "login_state.dart";
part "login_event.dart";
part "login_bloc.freezed.dart";
/// Returns an error string if [jid] is not a valid JID. Returns null if everything
/// appears okay.
String? _validateJid(String jid) {
switch (validateJid(jid)) {
case JidFormatError.empty: return "XMPP-Address cannot be empty";
case JidFormatError.noSeparator:
case JidFormatError.tooManySeparators: return "XMPP-Address must contain exactly one @";
// TODO: Find a better text
case JidFormatError.noDomain: return "A domain must follow the @";
case JidFormatError.noLocalpart: return "Your username must preceed the @";
case JidFormatError.none: return null;
}
}
class LoginBloc extends Bloc<LoginEvent, LoginState> {
LoginBloc() : super(LoginState()) {
on<LoginJidChangedEvent>(_onJidChanged);
@@ -49,7 +35,7 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> {
}
Future<void> _onSubmitted(LoginSubmittedEvent event, Emitter<LoginState> emit) async {
final jidValidity = _validateJid(state.jid);
final jidValidity = validateJidString(state.jid);
if (jidValidity != null) {
return emit(
state.copyWith(

View File

@@ -4,14 +4,12 @@ import "package:moxxyv2/shared/helpers.dart";
import "package:moxxyv2/shared/models/roster.dart";
import "package:moxxyv2/shared/models/conversation.dart";
import "package:moxxyv2/shared/backgroundsender.dart";
import "package:moxxyv2/ui/constants.dart";
import "package:moxxyv2/ui/bloc/conversations_bloc.dart";
import "package:moxxyv2/ui/bloc/navigation_bloc.dart";
import "package:moxxyv2/ui/bloc/conversation_bloc.dart";
import "package:bloc/bloc.dart";
import "package:freezed_annotation/freezed_annotation.dart";
import "package:get_it/get_it.dart";
import "package:flutter/widgets.dart";
part "newconversation_state.dart";
part "newconversation_event.dart";
@@ -38,14 +36,12 @@ class NewConversationBloc extends Bloc<NewConversationEvent, NewConversationStat
// Guard against an unneccessary roundtrip
if (listContains(conversations.state.conversations, (Conversation c) => c.jid == event.jid)) {
// TODO: Use the [ConversationBloc]
GetIt.I.get<NavigationBloc>().add(
PushedNamedAndRemoveUntilEvent(
NavigationDestination(
conversationRoute,
//arguments: ConversationPageArguments(event.jid)
),
ModalRoute.withName(conversationsRoute)
GetIt.I.get<ConversationBloc>().add(
RequestedConversationEvent(
event.jid,
event.title,
event.avatarUrl,
removeUntilConversations: true
)
);
return;
@@ -68,20 +64,17 @@ class NewConversationBloc extends Bloc<NewConversationEvent, NewConversationStat
conversations.add(ConversationsAddedEvent(result.conversation));
}
// TODO: Use the [ConversationBloc]
GetIt.I.get<NavigationBloc>().add(
PushedNamedAndRemoveUntilEvent(
NavigationDestination(
conversationRoute,
//arguments: ConversationPageArguments(event.jid)
),
ModalRoute.withName(conversationsRoute)
GetIt.I.get<ConversationBloc>().add(
RequestedConversationEvent(
event.jid,
event.title,
event.avatarUrl,
removeUntilConversations: true
)
);
}
Future<void> _onRosterItemRemoved(NewConversationRosterItemRemovedEvent event, Emitter<NewConversationState> emit) async {
// TODO
return emit(
state.copyWith(
roster: state.roster.where(

View File

@@ -1,52 +1,25 @@
/*
import "package:moxxyv2/ui/constants.dart";
import "package:moxxyv2/ui/helpers.dart";
import "package:moxxyv2/ui/widgets/topbar.dart";
import "package:moxxyv2/ui/widgets/textfield.dart";
import "package:moxxyv2/ui/widgets/button.dart";
import "package:moxxyv2/ui/constants.dart";
import "package:moxxyv2/ui/helpers.dart";
import "package:moxxyv2/ui/redux/state.dart";
import "package:moxxyv2/ui/redux/addcontact/actions.dart";
import "package:moxxyv2/ui/bloc/addcontact_bloc.dart";
import "package:flutter/material.dart";
import "package:flutter_redux/flutter_redux.dart";
import "package:flutter_bloc/flutter_bloc.dart";
class _AddContactPageViewModel {
final bool doingWork;
final String? errorText;
final void Function(String jid) addContact;
final void Function() resetErrors;
const _AddContactPageViewModel({ required this.addContact, required this.doingWork, required this.resetErrors, this.errorText });
}
// TODO: Reset the errorText using WillPopScope
class AddContactPage extends StatelessWidget {
final TextEditingController _controller;
AddContactPage({ Key? key }) : _controller = TextEditingController(), super(key: key);
void _addToRoster(BuildContext context, _AddContactPageViewModel viewModel) {
if (_controller.text.isEmpty) return;
viewModel.resetErrors();
viewModel.addContact(_controller.text);
}
const AddContactPage({ Key? key }) : super(key: key);
@override
Widget build(BuildContext context) {
return StoreConnector<MoxxyState, _AddContactPageViewModel>(
converter: (store) => _AddContactPageViewModel(
doingWork: store.state.globalState.doingWork,
errorText: store.state.addContactErrorText,
addContact: (jid) => store.dispatch(AddContactAction(jid: jid)),
resetErrors: () => store.dispatch(AddContactSetErrorLogin())
),
builder: (context, viewModel) => Scaffold(
return BlocBuilder<AddContactBloc, AddContactState>(
builder: (context, state) => Scaffold(
appBar: BorderlessTopbar.simple(title: "Add new contact"),
body: Column(
children: [
Visibility(
visible: viewModel.doingWork,
visible: state.working,
child: const LinearProgressIndicator(value: null)
),
@@ -54,11 +27,14 @@ class AddContactPage extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: paddingVeryLarge).add(const EdgeInsets.only(top: 8.0)),
child: CustomTextField(
maxLines: 1,
controller: _controller,
labelText: "XMPP-Address",
onChanged: (value) => context.read<AddContactBloc>().add(
JidChangedEvent(value)
),
enabled: !state.working,
cornerRadius: textfieldRadiusRegular,
contentPadding: const EdgeInsets.only(top: 4.0, bottom: 4.0, left: 8.0, right: 8.0),
errorText: viewModel.errorText,
errorText: state.jidError,
suffixIcon: IconButton(
icon: const Icon(Icons.qr_code),
onPressed: () {
@@ -84,7 +60,7 @@ class AddContactPage extends StatelessWidget {
color: Colors.purple,
child: const Text("Add to contacts"),
cornerRadius: 32.0,
onTap: () => _addToRoster(context, viewModel)
onTap: () => context.read<AddContactBloc>().add(AddedContactEvent())
)
)
]
@@ -96,4 +72,3 @@ class AddContactPage extends StatelessWidget {
);
}
}
*/