ui: Migrate the login to Redux + ConversationRepo -> RosterRepo

This commit is contained in:
PapaTutuWawa 2021-12-24 11:57:40 +01:00
parent f83b465834
commit f73b2e8558
13 changed files with 401 additions and 357 deletions

View File

@ -3,11 +3,11 @@ import 'ui/pages/conversation.dart';
import 'ui/pages/conversations.dart';
import 'ui/pages/profile.dart';
import 'ui/pages/newconversation.dart';
import 'ui/pages/login.dart';
import 'ui/pages/login/login.dart';
import 'ui/pages/register.dart';
import 'ui/pages/intro.dart';
import 'ui/pages/addcontact.dart';
import 'repositories/conversations.dart';
import 'repositories/roster.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
@ -17,7 +17,7 @@ import "redux/state.dart";
import 'package:get_it/get_it.dart';
void main() {
GetIt.I.registerSingleton<ConversationRepository>(ConversationRepository());
GetIt.I.registerSingleton<RosterRepository>(RosterRepository());
runApp(MyApp());
}

7
lib/models/roster.dart Normal file
View File

@ -0,0 +1,7 @@
class RosterItem {
final String avatarUrl;
final String jid;
final String title;
RosterItem({ required this.avatarUrl, required this.jid, required this.title });
}

View File

@ -0,0 +1,7 @@
// TODO: "Send" the login data to perform the actual login
class PerformLoginAction {}
// TODO:
class LoginSuccessfulAction {}
class TogglePasswordVisibilityAction {}

View File

@ -0,0 +1,12 @@
import "package:moxxyv2/redux/login/actions.dart";
import "package:moxxyv2/ui/pages/login/state.dart";
LoginPageState loginReducer(LoginPageState state, dynamic action) {
if (action is PerformLoginAction) {
return state.copyWith(doingWork: true);
} else if (action is TogglePasswordVisibilityAction) {
return state.copyWith(showPassword: !state.showPassword);
}
return state;
}

View File

@ -1,20 +1,24 @@
import "dart:collection";
import "package:moxxyv2/redux/conversation/reducers.dart";
import "package:moxxyv2/redux/conversations/reducers.dart";
import "package:moxxyv2/redux/login/reducers.dart";
import "package:moxxyv2/ui/pages/login/state.dart";
import "package:moxxyv2/models/message.dart";
import "package:moxxyv2/models/conversation.dart";
MoxxyState moxxyReducer(MoxxyState state, dynamic action) {
return MoxxyState(
messages: messageReducer(state.messages, action),
conversations: conversationReducer(state.conversations, action)
conversations: conversationReducer(state.conversations, action),
loginPageState: loginReducer(state.loginPageState, action)
);
}
class MoxxyState {
final HashMap<String, List<Message>> messages;
final List<Conversation> conversations;
final LoginPageState loginPageState;
const MoxxyState({ required this.messages, required this.conversations });
MoxxyState.initialState() : messages = HashMap(), conversations = List.empty(growable: true);
const MoxxyState({ required this.messages, required this.conversations, required this.loginPageState });
MoxxyState.initialState() : messages = HashMap(), conversations = List.empty(growable: true), loginPageState = LoginPageState(doingWork: false, showPassword: false);
}

View File

@ -1,36 +0,0 @@
import "dart:collection";
import "package:moxxyv2/models/conversation.dart";
class ConversationRepository {
HashMap<String, Conversation> _conversations = HashMap.from({
// TODO: Remove
"houshou.marine@hololive.tv": Conversation(
title: "Houshou Marine",
lastMessageBody: "",
avatarUrl: "https://vignette.wikia.nocookie.net/virtualyoutuber/images/4/4e/Houshou_Marine_-_Portrait.png/revision/latest?cb=20190821035347",
jid: "houshou.marine@hololive.tv",
unreadCounter: 0
),
"nakiri.ayame@hololive.tv": Conversation(
title: "Nakiri Ayame",
lastMessageBody: "",
avatarUrl: "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fi.pinimg.com%2Foriginals%2F2a%2F77%2F0a%2F2a770a77b0d873331583dfb88b05829f.jpg&f=1&nofb=1",
jid: "nakiri.ayame@hololive.tv",
unreadCounter: 0
)
});
ConversationRepository();
bool hasJid(String jid) => this._conversations.containsKey(jid);
Conversation? getConversation(String jid) => this._conversations[jid];
void setConversation(Conversation conversation) {
this._conversations[conversation.jid] = conversation;
}
List<Conversation> getAllConversations() {
return this._conversations.values.toList();
}
}

View File

@ -0,0 +1,37 @@
import "dart:collection";
import "package:moxxyv2/models/roster.dart";
class RosterRepository {
HashMap<String, RosterItem> _rosterItems = HashMap.from({
// TODO: Remove
"houshou.marine@hololive.tv": RosterItem(
title: "Houshou Marine",
avatarUrl: "https://vignette.wikia.nocookie.net/virtualyoutuber/images/4/4e/Houshou_Marine_-_Portrait.png/revision/latest?cb=20190821035347",
jid: "houshou.marine@hololive.tv",
),
"nakiri.ayame@hololive.tv": RosterItem(
title: "Nakiri Ayame",
avatarUrl: "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fi.pinimg.com%2Foriginals%2F2a%2F77%2F0a%2F2a770a77b0d873331583dfb88b05829f.jpg&f=1&nofb=1",
jid: "nakiri.ayame@hololive.tv",
),
"momosuzu.nene@hololive.tv": RosterItem(
title: "Momosuzu Nene",
avatarUrl: "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fstatic.miraheze.org%2Fhololivewiki%2Fthumb%2F3%2F36%2FMomosuzu_Nene_-_Portrait_01-1.png%2F580px-Momosuzu_Nene_-_Portrait_01-1.png&f=1&nofb=1",
jid: "momosuzu.nene@hololive.tv",
)
});
RosterRepository();
bool hasJid(String jid) => this._rosterItems.containsKey(jid);
RosterItem? getRosterItem(String jid) => this._rosterItems[jid];
void setRosterItem(RosterItem item) {
this._rosterItems[item.jid] = item;
}
List<RosterItem> getAllRosterItems() {
return this._rosterItems.values.toList();
}
}

View File

@ -5,7 +5,6 @@ import "package:moxxyv2/models/message.dart";
import "package:moxxyv2/models/conversation.dart";
import "package:moxxyv2/redux/state.dart";
import "package:moxxyv2/redux/conversation/actions.dart";
import "package:moxxyv2/repositories/conversations.dart";
import "package:moxxyv2/ui/pages/profile.dart";
import "package:moxxyv2/ui/constants.dart";
@ -17,7 +16,6 @@ import 'package:get_it/get_it.dart';
typedef SendMessageFunction = void Function(String body);
// TODO: Maybe use a PageView to combine ConversationsPage and ConversationPage
// TODO: Move to a separate file
class ConversationPageArguments {
final String jid;
@ -27,9 +25,10 @@ class ConversationPageArguments {
class _MessageListViewModel {
final List<Message> messages;
final Conversation conversation;
final SendMessageFunction sendMessage;
_MessageListViewModel({ required this.messages, required this.sendMessage });
_MessageListViewModel({ required this.conversation, required this.messages, required this.sendMessage });
}
class _ConversationPageState extends State<ConversationPage> {
@ -86,14 +85,12 @@ class _ConversationPageState extends State<ConversationPage> {
@override
Widget build(BuildContext context) {
var args = ModalRoute.of(context)!.settings.arguments as ConversationPageArguments;
Conversation conversation = GetIt.I.get<ConversationRepository>().getConversation(args.jid)!;
String jid = conversation.jid;
String jid = args.jid;
return StoreConnector<MoxxyState, _MessageListViewModel>(
converter: (store) => _MessageListViewModel(
// TODO
messages: store.state.messages.containsKey(jid) ? store.state.messages[jid]! : [],
conversation: store.state.conversations.firstWhere((item) => item.jid == jid),
sendMessage: (body) => store.dispatch(
// TODO
AddMessageAction(
@ -104,145 +101,147 @@ class _ConversationPageState extends State<ConversationPage> {
)
)
),
builder: (context, viewModel) => Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(60),
child: BorderlessTopbar(
boxShadow: true,
builder: (context, viewModel) {
return Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(60),
child: BorderlessTopbar(
boxShadow: true,
children: [
Center(
child: InkWell(
onTap: () {
Navigator.pop(context);
},
child: Icon(Icons.arrow_back)
)
),
Center(
child: InkWell(
child: Row(
children: [
Padding(
padding: EdgeInsets.only(left: 16.0),
child: CircleAvatar(
// TODO
backgroundImage: NetworkImage(viewModel.conversation.avatarUrl),
radius: 25.0
)
),
Padding(
padding: EdgeInsets.only(left: 2.0),
child: Text(
viewModel.conversation.title,
style: TextStyle(
fontSize: 20
)
)
)
]
),
onTap: () {
Navigator.pushNamed(context, "/conversation/profile", arguments: ProfilePageArguments(conversation: viewModel.conversation));
}
)
)
]
)
),
body: Column(
children: [
Center(
child: InkWell(
onTap: () {
Navigator.pop(context);
},
child: Icon(Icons.arrow_back)
Expanded(
child: ListView.builder(
itemCount: viewModel.messages.length,
itemBuilder: (context, index) => this._renderBubble(viewModel.messages, index)
)
),
Center(
child: InkWell(
child: Row(
children: [
Padding(
padding: EdgeInsets.only(left: 16.0),
child: CircleAvatar(
// TODO
backgroundImage: NetworkImage(conversation.avatarUrl),
radius: 25.0
)
),
Padding(
padding: EdgeInsets.only(left: 2.0),
child: Text(
conversation.title,
style: TextStyle(
fontSize: 20
Padding(
padding: EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(
width: 1,
color: BUBBLE_COLOR_SENT
)
),
// TODO: Fix the TextField being too tall
child: TextField(
maxLines: 5,
minLines: 1,
controller: this.controller,
onChanged: this._onMessageTextChanged,
decoration: InputDecoration(
hintText: "Send a message...",
border: InputBorder.none,
contentPadding: EdgeInsets.all(5)
)
)
)
]
),
onTap: () {
Navigator.pushNamed(context, "/conversation/profile", arguments: ProfilePageArguments(conversation: conversation));
}
),
Padding(
padding: EdgeInsets.only(left: 8.0),
// NOTE: https://stackoverflow.com/a/52786741
// Thank you kind sir
child: Container(
height: 45.0,
width: 45.0,
child: FittedBox(
child: SpeedDial(
icon: this._showSendButton ? Icons.send : Icons.add,
visible: true,
curve: Curves.bounceInOut,
backgroundColor: BUBBLE_COLOR_SENT,
// TODO: Theme dependent?
foregroundColor: Colors.white,
openCloseDial: this._isSpeedDialOpen,
onPress: () {
if (this._showSendButton) {
this._onSendButtonPressed(viewModel);
} else {
this._isSpeedDialOpen.value = true;
}
},
children: [
SpeedDialChild(
child: Icon(Icons.image),
onTap: () {},
backgroundColor: BUBBLE_COLOR_SENT,
// TODO: Theme dependent?
foregroundColor: Colors.white,
label: "Add Image"
),
SpeedDialChild(
child: Icon(Icons.photo_camera),
onTap: () {},
backgroundColor: BUBBLE_COLOR_SENT,
// TODO: Theme dependent?
foregroundColor: Colors.white,
label: "Take photo"
),
SpeedDialChild(
child: Icon(Icons.attach_file),
onTap: () {},
backgroundColor: BUBBLE_COLOR_SENT,
// TODO: Theme dependent?
foregroundColor: Colors.white,
label: "Add file"
),
]
)
)
)
)
]
)
)
]
)
),
body: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: viewModel.messages.length,
itemBuilder: (context, index) => this._renderBubble(viewModel.messages, index)
)
),
Padding(
padding: EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(
width: 1,
color: BUBBLE_COLOR_SENT
)
),
// TODO: Fix the TextField being too tall
child: TextField(
maxLines: 5,
minLines: 1,
controller: this.controller,
onChanged: this._onMessageTextChanged,
decoration: InputDecoration(
hintText: "Send a message...",
border: InputBorder.none,
contentPadding: EdgeInsets.all(5)
)
)
)
),
Padding(
padding: EdgeInsets.only(left: 8.0),
// NOTE: https://stackoverflow.com/a/52786741
// Thank you kind sir
child: Container(
height: 45.0,
width: 45.0,
child: FittedBox(
child: SpeedDial(
icon: this._showSendButton ? Icons.send : Icons.add,
visible: true,
curve: Curves.bounceInOut,
backgroundColor: BUBBLE_COLOR_SENT,
// TODO: Theme dependent?
foregroundColor: Colors.white,
openCloseDial: this._isSpeedDialOpen,
onPress: () {
if (this._showSendButton) {
this._onSendButtonPressed(viewModel);
} else {
this._isSpeedDialOpen.value = true;
}
},
children: [
SpeedDialChild(
child: Icon(Icons.image),
onTap: () {},
backgroundColor: BUBBLE_COLOR_SENT,
// TODO: Theme dependent?
foregroundColor: Colors.white,
label: "Add Image"
),
SpeedDialChild(
child: Icon(Icons.photo_camera),
onTap: () {},
backgroundColor: BUBBLE_COLOR_SENT,
// TODO: Theme dependent?
foregroundColor: Colors.white,
label: "Take photo"
),
SpeedDialChild(
child: Icon(Icons.attach_file),
onTap: () {},
backgroundColor: BUBBLE_COLOR_SENT,
// TODO: Theme dependent?
foregroundColor: Colors.white,
label: "Add file"
),
]
)
)
)
)
]
)
)
]
)
)
);
}
);
}
}

View File

@ -62,7 +62,7 @@ class IntroPage extends StatelessWidget {
Expanded(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 64.0).add(EdgeInsets.only(bottom: 64.0)),
child: ElevatedButton(
child: TextButton(
child: Text("Register"),
onPressed: () => Navigator.pushNamed(context, "/register")
)

View File

@ -1,153 +0,0 @@
import 'package:flutter/material.dart';
import 'package:moxxyv2/ui/widgets/topbar.dart';
import 'package:moxxyv2/ui/constants.dart';
class _LoginPageState extends State<LoginPage> {
bool _doingWork = false;
bool _showPassword = false;
void _navigateToConversations(BuildContext context) {
Navigator.pushNamedAndRemoveUntil(
context,
"/conversations",
(route) => false
);
}
void _togglePasswordVisibility() {
setState(() {
this._showPassword = !this._showPassword;
});
}
void _performLogin(BuildContext context) {
// TODO: Stub
setState(() {
this._doingWork = true;
});
Future.delayed(Duration(seconds: 3), () => this._navigateToConversations(context));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(60),
child: BorderlessTopbar(
children: [
BackButton(),
Text(
"Login",
style: TextStyle(
fontSize: 19
)
)
]
)
),
// TODO: The TextFields look a bit too smal
// TODO: Hide the LinearProgressIndicator if we're not doing anything
// TODO: Disable the inputs and the BackButton if we're working on loggin in
body: Column(
children: [
Visibility(
visible: this._doingWork,
child: LinearProgressIndicator(value: null)
),
Padding(
padding: EdgeInsets.symmetric(horizontal: PADDING_VERY_LARGE).add(EdgeInsets.only(top: 8.0)),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
border: Border.all(
width: 1,
color: Colors.purple
)
),
child: TextField(
maxLines: 1,
enabled: !this._doingWork,
decoration: InputDecoration(
labelText: "XMPP-Address",
border: InputBorder.none,
contentPadding: EdgeInsets.only(top: 4.0, bottom: 4.0, left: 8.0, right: 8.0)
)
)
)
),
Padding(
padding: EdgeInsets.symmetric(horizontal: PADDING_VERY_LARGE).add(EdgeInsets.only(top: 8.0)),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
border: Border.all(
width: 1,
color: Colors.purple
)
),
child: TextField(
maxLines: 1,
obscureText: this._showPassword,
enabled: !this._doingWork,
decoration: InputDecoration(
labelText: "Password",
border: InputBorder.none,
contentPadding: EdgeInsets.only(top: 4.0, bottom: 4.0, left: 8.0, right: 8.0),
suffixIcon: Padding(
padding: EdgeInsetsDirectional.only(end: 8.0),
// TODO: Switch this icon depending on the state
child: InkWell(
onTap: () => this._togglePasswordVisibility(),
child: Icon(
this._showPassword ? Icons.visibility : Icons.visibility_off
)
)
)
)
)
)
),
Padding(
padding: EdgeInsets.symmetric(horizontal: PADDING_VERY_LARGE).add(EdgeInsets.only(top: 8.0)),
child: ExpansionTile(
title: Text("Advanced options"),
children: [
Column(
children: [
SwitchListTile(
title: Text("Create account on server"),
value: false,
// TODO
onChanged: this._doingWork ? null : (value) {}
)
]
)
]
)
),
Row(
children: [
Expanded(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: PADDING_VERY_LARGE).add(EdgeInsets.only(top: 8.0)),
child: ElevatedButton(
child: Text("Login"),
onPressed: this._doingWork ? null : () => this._performLogin(context)
)
)
)
]
)
]
)
);
}
}
class LoginPage extends StatefulWidget {
const LoginPage({ Key? key }) : super(key: key);
@override
_LoginPageState createState() => _LoginPageState();
}

View File

@ -0,0 +1,156 @@
import 'package:flutter/material.dart';
import 'package:moxxyv2/ui/widgets/topbar.dart';
import 'package:moxxyv2/ui/constants.dart';
import "package:moxxyv2/redux/state.dart";
import "package:moxxyv2/redux/login/actions.dart";
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
class _LoginPageViewModel {
final void Function() togglePasswordVisibility;
final void Function() performLogin;
final bool doingWork;
final bool showPassword;
_LoginPageViewModel({ required this.togglePasswordVisibility, required this.performLogin, required this.doingWork, required this.showPassword });
}
class LoginPage extends StatelessWidget {
void _navigateToConversations(BuildContext context) {
Navigator.pushNamedAndRemoveUntil(
context,
"/conversations",
(route) => false
);
}
void _performLogin(BuildContext context, _LoginPageViewModel viewModel) {
// TODO: Remove
Future.delayed(Duration(seconds: 3), () => this._navigateToConversations(context));
viewModel.performLogin();
}
@override
Widget build(BuildContext context) {
return StoreConnector<MoxxyState, _LoginPageViewModel>(
converter: (store) => _LoginPageViewModel(
togglePasswordVisibility: () => store.dispatch(TogglePasswordVisibilityAction()),
performLogin: () => store.dispatch(PerformLoginAction()),
doingWork: store.state.loginPageState.doingWork,
showPassword: store.state.loginPageState.showPassword
),
builder: (context, viewModel) => Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(60),
child: BorderlessTopbar(
children: [
BackButton(),
Text(
"Login",
style: TextStyle(
fontSize: 19
)
)
]
)
),
// TODO: The TextFields look a bit too smal
// TODO: Hide the LinearProgressIndicator if we're not doing anything
// TODO: Disable the inputs and the BackButton if we're working on loggin in
body: Column(
children: [
Visibility(
visible: viewModel.doingWork,
child: LinearProgressIndicator(value: null)
),
Padding(
padding: EdgeInsets.symmetric(horizontal: PADDING_VERY_LARGE).add(EdgeInsets.only(top: 8.0)),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
border: Border.all(
width: 1,
color: Colors.purple
)
),
child: TextField(
maxLines: 1,
enabled: !viewModel.doingWork,
decoration: InputDecoration(
labelText: "XMPP-Address",
border: InputBorder.none,
contentPadding: EdgeInsets.only(top: 4.0, bottom: 4.0, left: 8.0, right: 8.0)
)
)
)
),
Padding(
padding: EdgeInsets.symmetric(horizontal: PADDING_VERY_LARGE).add(EdgeInsets.only(top: 8.0)),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
border: Border.all(
width: 1,
color: Colors.purple
)
),
child: TextField(
maxLines: 1,
obscureText: !viewModel.showPassword,
enabled: !viewModel.doingWork,
decoration: InputDecoration(
labelText: "Password",
border: InputBorder.none,
contentPadding: EdgeInsets.only(top: 4.0, bottom: 4.0, left: 8.0, right: 8.0),
suffixIcon: Padding(
padding: EdgeInsetsDirectional.only(end: 8.0),
child: InkWell(
onTap: () => viewModel.togglePasswordVisibility(),
child: Icon(
viewModel.showPassword ? Icons.visibility : Icons.visibility_off
)
)
)
)
)
)
),
Padding(
padding: EdgeInsets.symmetric(horizontal: PADDING_VERY_LARGE).add(EdgeInsets.only(top: 8.0)),
child: ExpansionTile(
title: Text("Advanced options"),
children: [
Column(
children: [
SwitchListTile(
title: Text("Create account on server"),
value: false,
// TODO
onChanged: viewModel.doingWork ? null : (value) {}
)
]
)
]
)
),
Row(
children: [
Expanded(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: PADDING_VERY_LARGE).add(EdgeInsets.only(top: 8.0)),
child: ElevatedButton(
child: Text("Login"),
onPressed: viewModel.doingWork ? null : () => this._performLogin(context, viewModel)
)
)
)
]
)
]
)
)
);
}
}

View File

@ -0,0 +1,13 @@
class LoginPageState {
final bool doingWork;
final bool showPassword;
LoginPageState({ required this.doingWork, required this.showPassword });
LoginPageState copyWith({ bool? doingWork, bool? showPassword }) {
return LoginPageState(
doingWork: doingWork ?? this.doingWork,
showPassword: showPassword ?? this.showPassword
);
}
}

View File

@ -1,11 +1,12 @@
import 'package:flutter/material.dart';
import 'package:moxxyv2/ui/widgets/topbar.dart';
import 'package:moxxyv2/ui/widgets/conversation.dart';
import 'package:moxxyv2/models/roster.dart';
import 'package:moxxyv2/models/conversation.dart';
import 'package:moxxyv2/redux/state.dart';
import 'package:moxxyv2/redux/conversations/actions.dart';
import 'package:moxxyv2/ui/pages/conversation.dart';
import 'package:moxxyv2/repositories/conversations.dart';
import 'package:moxxyv2/repositories/roster.dart';
import 'package:moxxyv2/ui/helpers.dart';
import 'package:flutter_redux/flutter_redux.dart';
@ -17,31 +18,27 @@ typedef AddConversationFunction = void Function(Conversation conversation);
class _NewConversationViewModel {
final AddConversationFunction addConversation;
final List<Conversation> conversations;
final List<RosterItem> roster;
_NewConversationViewModel({ required this.addConversation, required this.conversations });
_NewConversationViewModel({ required this.conversations, required this.roster, required this.addConversation });
}
class NewConversationPage extends StatelessWidget {
void _addNewContact(_NewConversationViewModel viewModel, BuildContext context, String jid) {
bool hasConversation = viewModel.conversations.length > 0 && viewModel.conversations.firstWhere((item) => item.jid == jid, orElse: null) != null;
void _addNewContact(_NewConversationViewModel viewModel, BuildContext context, RosterItem rosterItem) {
bool hasConversation = viewModel.conversations.length > 0 && viewModel.conversations.firstWhere((item) => item.jid == rosterItem.jid, orElse: null) != null;
// Prevent adding the same conversation twice to the list of open conversations
if (!hasConversation) {
Conversation? conversation = GetIt.I.get<ConversationRepository>().getConversation(jid);
if (conversation == null) {
// TODO
// TODO: Install a middleware to make sure that the conversation gets added to the
// repository. Also handle updates
conversation = Conversation(
title: jid,
jid: jid,
lastMessageBody: "",
avatarUrl: "",
unreadCounter: 0
);
GetIt.I.get<ConversationRepository>().setConversation(conversation);
}
// TODO
// TODO: Install a middleware to make sure that the conversation gets added to the
// repository. Also handle updates
Conversation conversation = Conversation(
title: rosterItem.title,
jid: rosterItem.jid,
lastMessageBody: "",
avatarUrl: rosterItem.avatarUrl,
unreadCounter: 0
);
viewModel.addConversation(conversation);
}
@ -50,12 +47,12 @@ class NewConversationPage extends StatelessWidget {
context,
"/conversation",
ModalRoute.withName("/conversations"),
arguments: ConversationPageArguments(jid: jid));
arguments: ConversationPageArguments(jid: rosterItem.jid));
}
@override
Widget build(BuildContext context) {
var conversations = GetIt.I.get<ConversationRepository>().getAllConversations();
var roster = GetIt.I.get<RosterRepository>().getAllRosterItems();
return Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(60),
@ -81,10 +78,11 @@ class NewConversationPage extends StatelessWidget {
jid: c.jid
)
),
conversations: store.state.conversations
conversations: store.state.conversations,
roster: GetIt.I.get<RosterRepository>().getAllRosterItems()
),
builder: (context, viewModel) => ListView.builder(
itemCount: conversations.length + 2,
itemCount: viewModel.roster.length + 2,
itemBuilder: (context, index) {
switch(index) {
case 0: {
@ -142,9 +140,9 @@ class NewConversationPage extends StatelessWidget {
}
break;
default: {
Conversation item = conversations[index - 2];
RosterItem item = viewModel.roster[index - 2];
return InkWell(
onTap: () => this._addNewContact(viewModel, context, item.jid),
onTap: () => this._addNewContact(viewModel, context, item),
child: ConversationsListRow(item.avatarUrl, item.title, item.jid, 0)
);
}