Skip to content

Commit 25a49c3

Browse files
committed
Implement some functionality
1 parent 135d022 commit 25a49c3

File tree

13 files changed

+504
-124
lines changed

13 files changed

+504
-124
lines changed

lib/main.dart

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'package:fluro/fluro.dart';
22
import 'package:flutter/material.dart';
33
import 'package:provider/provider.dart';
44
import 'package:qiscus_chat_sample/src/page/login_page.dart';
5+
import 'package:qiscus_chat_sample/src/page/page.dart';
56
import 'package:qiscus_chat_sample/src/state/app_state.dart';
67
import 'package:qiscus_chat_sample/src/state/room_state.dart';
78

@@ -36,6 +37,14 @@ class _MyAppState extends State<MyApp> {
3637
..handle(
3738
'/room/:roomId',
3839
handler: (_, args) => ChatPage(roomId: int.parse(args['roomId'][0])),
40+
)
41+
..handle('/room/:roomId/detail',
42+
handler: (_, args) => RoomDetailPage(
43+
roomId: int.parse(args['roomId'][0]),
44+
))
45+
..handle(
46+
'/room',
47+
handler: (_, __) => RoomListPage(),
3948
);
4049
}
4150

@@ -55,13 +64,22 @@ class _MyAppState extends State<MyApp> {
5564
ChangeNotifierProvider.value(value: messageState),
5665
],
5766
child: Consumer<AppState>(
58-
builder: (_, state, __) => MaterialApp(
59-
title: 'Flutter Demo',
60-
theme: ThemeData(primarySwatch: Colors.blue),
61-
onGenerateRoute: router.generator,
62-
initialRoute: state.isLoggedIn ? '/' : '/login',
63-
),
67+
builder: (_, state, __) =>
68+
MaterialApp(
69+
title: 'Flutter Demo',
70+
theme: ThemeData(primarySwatch: Colors.blue),
71+
onGenerateRoute: router.generator,
72+
initialRoute: '/login',
73+
),
6474
),
6575
);
6676
}
77+
78+
@override
79+
void deactivate() {
80+
messageState.dispose();
81+
roomState.dispose();
82+
appState.dispose();
83+
super.deactivate();
84+
}
6785
}

lib/src/page/chat_page.dart

Lines changed: 164 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -7,35 +7,111 @@ import 'package:provider/provider.dart';
77
import 'package:qiscus_chat_sample/src/state/state.dart';
88
import 'package:qiscus_chat_sample/src/widget/app_bar.dart';
99
import 'package:qiscus_chat_sample/src/widget/chat_bubble.dart';
10+
import 'package:rxdart/rxdart.dart';
1011

1112
class ChatPage extends StatefulWidget {
12-
ChatPage({this.roomId});
13-
1413
final int roomId;
1514

15+
ChatPage({this.roomId});
16+
1617
@override
1718
State<StatefulWidget> createState() => _ChatState();
1819
}
1920

2021
class _ChatState extends State<ChatPage> {
2122
final scrollController = ScrollController();
23+
final scaffoldKey = GlobalKey<ScaffoldState>();
24+
final _debounceTimer = TimerStream(true, const Duration(milliseconds: 500));
25+
final scroll$ = StreamController<ScrollNotification>();
26+
final typing$ = StreamController();
2227

23-
@override
24-
void initState() {
25-
super.initState();
26-
scheduleMicrotask(() {
27-
_getRoom();
28-
_getMessages();
29-
});
30-
}
28+
bool isLoading = false;
29+
MessageState _messageState;
30+
RoomState _roomState;
3131

3232
@override
3333
Widget build(BuildContext ctx) {
3434
return Consumer<RoomState>(
3535
builder: (_, state, __) => Scaffold(
36-
appBar: appBar(
37-
room: state.currentRoom,
38-
onBack: () => Navigator.pushReplacementNamed(context, '/login'),
36+
key: scaffoldKey,
37+
appBar: AppBar(
38+
leading: FlatButton(
39+
onPressed: () => Navigator.pop(context),
40+
child: Icon(
41+
Icons.chevron_left,
42+
color: Colors.white,
43+
size: 34,
44+
),
45+
),
46+
centerTitle: false,
47+
title: Container(
48+
child: Row(
49+
crossAxisAlignment: CrossAxisAlignment.start,
50+
mainAxisAlignment: MainAxisAlignment.start,
51+
children: <Widget>[
52+
CircleAvatar(
53+
backgroundImage: (state.currentRoom != null)
54+
? Image.network(
55+
state.currentRoom.avatarUrl,
56+
fit: BoxFit.fill,
57+
height: 34,
58+
width: 34,
59+
).image
60+
: Image.asset(
61+
'assets/ic-default-room-avatar.png',
62+
fit: BoxFit.fill,
63+
height: 34,
64+
width: 34,
65+
).image,
66+
),
67+
Padding(
68+
padding: const EdgeInsets.only(left: 20.0),
69+
child: Column(
70+
crossAxisAlignment: CrossAxisAlignment.start,
71+
children: <Widget>[
72+
if (state.currentRoom != null)
73+
Text(state.currentRoom.name,
74+
style: TextStyle(fontSize: 18)),
75+
if (state.currentRoom == null) Text('Loading...'),
76+
if (state.currentRoom != null)
77+
Consumer<RoomState>(
78+
builder: (_, state, __) => MultiProvider(
79+
providers: [
80+
StreamProvider.value(value: state.onTyping),
81+
StreamProvider.value(value: state.onPresence),
82+
],
83+
child: AppBarStatus(room: state.currentRoom),
84+
),
85+
),
86+
],
87+
),
88+
),
89+
],
90+
),
91+
),
92+
actions: <Widget>[
93+
PopupMenuButton(
94+
itemBuilder: (ctx) => <PopupMenuEntry>[
95+
PopupMenuItem(
96+
child: GestureDetector(
97+
onTap: () {
98+
Scaffold.of(ctx).showSnackBar(SnackBar(
99+
duration: const Duration(milliseconds: 500),
100+
content:
101+
Text('Show room ${state.currentRoomId} detail'),
102+
action: SnackBarAction(
103+
label: 'Close',
104+
onPressed: () =>
105+
Scaffold.of(ctx).hideCurrentSnackBar(),
106+
),
107+
));
108+
},
109+
child: Text('Detail'),
110+
),
111+
)
112+
],
113+
)
114+
],
39115
),
40116
body: Column(
41117
children: [
@@ -48,12 +124,41 @@ class _ChatState extends State<ChatPage> {
48124
);
49125
}
50126

51-
Widget buildLoading() {
52-
return Expanded(
53-
child: Center(
54-
child: CircularProgressIndicator(),
55-
),
56-
);
127+
@override
128+
void deactivate() {
129+
_roomState.unsubscribe(_roomState.currentRoom);
130+
super.deactivate();
131+
}
132+
133+
@override
134+
void dispose() async {
135+
super.dispose();
136+
scrollController.dispose();
137+
await scroll$.close();
138+
await typing$.close();
139+
}
140+
141+
@override
142+
void initState() {
143+
super.initState();
144+
scheduleMicrotask(() {
145+
_messageState = Provider.of<MessageState>(context, listen: false);
146+
_roomState = Provider.of<RoomState>(context, listen: false);
147+
});
148+
149+
scheduleMicrotask(() {
150+
_getRoom();
151+
_getMessages();
152+
153+
scroll$.stream
154+
.debounce((_) => _debounceTimer)
155+
.where((n) => n.metrics.pixels == n.metrics.maxScrollExtent)
156+
.listen((_) => _loadMore());
157+
158+
typing$.stream
159+
.debounce((_) => _debounceTimer)
160+
.listen((_) => _roomState.publishTyping(widget.roomId));
161+
});
57162
}
58163

59164
Widget _form(BuildContext ctx) {
@@ -62,13 +167,16 @@ class _ChatState extends State<ChatPage> {
62167
final key = GlobalKey<FormState>();
63168
var messageState = Provider.of<MessageState>(context, listen: false);
64169

170+
controller.addListener(() {
171+
print('controller listener');
172+
});
173+
65174
final submit = (String message) {
66175
messageState.submit(
67176
roomId: roomId,
68177
message: message,
69178
);
70179
controller.text = '';
71-
_animateScrollToBottom();
72180
};
73181
return Form(
74182
key: key,
@@ -77,11 +185,9 @@ class _ChatState extends State<ChatPage> {
77185
IconButton(
78186
icon: Icon(Icons.attach_file),
79187
onPressed: () async {
80-
print('get file');
81188
var file = await FilePicker.getFile();
82189
if (file != null) {
83190
await messageState.sendFile(file);
84-
_animateScrollToBottom();
85191
}
86192
},
87193
),
@@ -114,45 +220,12 @@ class _ChatState extends State<ChatPage> {
114220
);
115221
}
116222

117-
bool isLoading = false;
118-
119-
Widget _messageList(BuildContext ctx) {
120-
return Expanded(
121-
child: Consumer<MessageState>(builder: (_, state, __) {
122-
var reversed = state.messages.reversed;
123-
return ListView.separated(
124-
separatorBuilder: (_, __) {
125-
return Divider(color: Colors.grey, height: 1.0);
126-
},
127-
itemCount: state.messages.length,
128-
itemBuilder: (ctx, index) => ChatBubble(
129-
message: reversed.elementAt(index),
130-
),
131-
controller: scrollController,
132-
padding: EdgeInsets.symmetric(horizontal: 10),
133-
);
134-
}),
135-
);
136-
}
137-
138223
Future _getMessages() async {
139224
var roomId = widget.roomId;
140225
if (roomId == null) return;
141226
var messageState = Provider.of<MessageState>(context, listen: false);
142227
await messageState.getAllMessage(roomId);
143-
messageState.subscribeChatRoom(messageReceivedCallback: () {
144-
_animateScrollToBottom();
145-
});
146-
}
147-
148-
void _animateScrollToBottom() {
149-
Timer(const Duration(milliseconds: 400), () {
150-
scrollController.animateTo(
151-
scrollController.position.maxScrollExtent,
152-
duration: const Duration(milliseconds: 1000),
153-
curve: Curves.ease,
154-
);
155-
});
228+
messageState.subscribeChatRoom();
156229
}
157230

158231
Future _getRoom() async {
@@ -163,8 +236,40 @@ class _ChatState extends State<ChatPage> {
163236
roomState.subscribe(room);
164237
}
165238

166-
@override
167-
void dispose() {
168-
super.dispose();
239+
Future<void> _loadMore() async {
240+
setState(() {
241+
isLoading = true;
242+
});
243+
await _messageState.getPreviousMessage(widget.roomId);
244+
setState(() {
245+
isLoading = false;
246+
});
247+
}
248+
249+
Widget _messageList(BuildContext ctx) {
250+
return Expanded(
251+
child: NotificationListener<ScrollNotification>(
252+
onNotification: (notification) {
253+
scroll$.add(notification);
254+
return true;
255+
},
256+
child: Consumer<MessageState>(builder: (_, state, __) {
257+
var reversed = state.messages.reversed;
258+
return ListView.separated(
259+
separatorBuilder: (_, __) {
260+
return Divider(color: Colors.grey, height: 1.0);
261+
},
262+
itemCount: state.messages.length,
263+
itemBuilder: (ctx, index) =>
264+
ChatBubble(
265+
message: reversed.elementAt(index),
266+
),
267+
controller: scrollController,
268+
padding: EdgeInsets.symmetric(horizontal: 10),
269+
reverse: true,
270+
);
271+
}),
272+
),
273+
);
169274
}
170275
}

lib/src/page/login_page.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,11 @@ class _LoginState extends State<LoginPage> {
117117

118118
await appState.setup(appId);
119119
await appState.setUser(userId, userKey);
120-
var room = await roomState.getRoomWithUser(userId: target);
121120

122121
setState(() {
123122
isLoggingIn = false;
124123
});
125-
Navigator.pushReplacementNamed(context, '/room/${room.id}');
124+
Navigator.pushReplacementNamed(context, '/room');
126125
}
127126
}
128127
}

lib/src/page/page.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export 'chat_page.dart';
22
export 'login_page.dart';
3+
export 'room_detail_page.dart';
34
export 'rooms_page.dart';

0 commit comments

Comments
 (0)