Skip to content
This repository has been archived by the owner on Jul 31, 2021. It is now read-only.

Commit

Permalink
(clean) display user status or typing indicator under name in chat page
Browse files Browse the repository at this point in the history
  • Loading branch information
mkofdwu committed Dec 25, 2020
1 parent ad8fafa commit 922bc93
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 6 deletions.
4 changes: 2 additions & 2 deletions admin/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { generateTestAccounts } from './test-accounts';
import { migrateChatTyping } from './migrations';

generateTestAccounts();
migrateChatTyping().then(() => console.log('DONE'));
15 changes: 14 additions & 1 deletion admin/src/migrations.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import admin = require('firebase-admin');
import { usersAlgorithmDataRef, usersPrivateInfoRef } from './constants';
import {
chatsRef,
usersAlgorithmDataRef,
usersPrivateInfoRef,
} from './constants';

export const migrateUserChatLastRead = async () => {
const privateInfoDocs = (await usersPrivateInfoRef.get()).docs;
Expand Down Expand Up @@ -29,3 +33,12 @@ export const migrateSuggestionsGoneThrough = async () => {
});
}
};

export const migrateChatTyping = async () => {
const chatDocs = (await chatsRef.get()).docs;
for (const chatDoc of chatDocs) {
await chatDoc.ref.update({
typing: [],
});
}
};
1 change: 1 addition & 0 deletions firebase_schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ const user_statuses = Collection({
const chats = Collection({
chatId: {
participants: ['userId'],
typing: ['userId'],
messages: [
{
senderUid: 'userId',
Expand Down
1 change: 1 addition & 0 deletions functions/src/functions/chats-http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export const startConversation = functions.https.onCall(
// create chat with participants & messages (add the first message)
const chatDoc = await chatsRef.add({
participants: [uid, otherUid],
typing: [],
});
await chatDoc.collection('messages').add({
senderUid: uid,
Expand Down
1 change: 1 addition & 0 deletions functions/src/functions/suggestions-http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ export const matchWith = functions.https.onCall(async (data, context) => {
// save matches - create chats
const chatDoc = await chatsRef.add({
participants: [uid, otherUid],
typing: [],
});
await usersPrivateInfoRef.doc(uid).collection('chats').doc(chatDoc.id).set({
uid: otherUid,
Expand Down
52 changes: 49 additions & 3 deletions lib/pages/chat/chat.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import 'package:tundr/enums/chat_type.dart';
import 'package:tundr/models/chat.dart';
import 'package:tundr/models/media.dart';
import 'package:tundr/models/message.dart';
import 'package:tundr/models/user_status.dart';
import 'package:tundr/pages/chat/widgets/message_field.dart';
import 'package:tundr/pages/chat/widgets/popup_menu.dart';

import 'package:tundr/repositories/user.dart';
import 'package:tundr/services/chats_service.dart';
import 'package:tundr/services/storage_service.dart';
import 'package:tundr/services/users_service.dart';
import 'package:tundr/utils/format_date.dart';
import 'package:tundr/utils/from_theme.dart';
import 'package:tundr/utils/get_network_image.dart';
import 'package:tundr/utils/show_info_dialog.dart';
Expand Down Expand Up @@ -162,6 +164,18 @@ class _ChatPageState extends State<ChatPage> {
}
}

Widget _buildUserStatusText() => StreamBuilder<UserStatus>(
stream: UsersService.getUserStatusStream(widget.chat.otherProfile.uid),
builder: (context, snapshot) {
if (!snapshot.hasData) return SizedBox.shrink();
final status = snapshot.data;
return Text(
status.online ? 'online' : formatDate(status.lastSeen),
style: TextStyle(fontSize: 12, color: MyPalette.white),
);
},
);

Widget _buildTopBar() => SizedBox(
height: 50,
child: Row(
Expand All @@ -174,9 +188,31 @@ class _ChatPageState extends State<ChatPage> {
SizedBox(width: 10),
Expanded(
child: GestureDetector(
child: Text(
widget.chat.otherProfile.name,
style: TextStyle(fontSize: 20, color: MyPalette.white),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.chat.otherProfile.name,
style: TextStyle(fontSize: 20, color: MyPalette.white),
),
StreamBuilder<bool>(
stream: ChatsService.otherUserIsTypingStream(widget.chat),
builder: (context, snapshot) {
if (!snapshot.hasData) return SizedBox.shrink();
if (snapshot.data) {
return Text(
'typing',
style: TextStyle(
fontSize: 12,
color: MyPalette.white,
),
);
}
return _buildUserStatusText();
},
),
],
),
onTap: () async => Navigator.pushNamed(
context,
Expand Down Expand Up @@ -379,6 +415,16 @@ class _ChatPageState extends State<ChatPage> {
setState(() => _media = newMedia);
},
onSendMessage: _sendMessage,
onStartTyping: () => ChatsService.toggleTyping(
widget.chat.id,
Provider.of<User>(context, listen: false).profile.uid,
true,
),
onStopTyping: () => ChatsService.toggleTyping(
widget.chat.id,
Provider.of<User>(context, listen: false).profile.uid,
false,
),
);
}),
),
Expand Down
36 changes: 36 additions & 0 deletions lib/pages/chat/widgets/message_field.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:async';

import 'package:feature_discovery/feature_discovery.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
Expand All @@ -22,6 +24,8 @@ class MessageField extends StatefulWidget {
final Function onRemoveReferencedMessage;
final Function(Media) onChangeMedia;
final Function(String) onSendMessage;
final Function onStartTyping;
final Function onStopTyping;

MessageField({
@required this.media,
Expand All @@ -30,6 +34,8 @@ class MessageField extends StatefulWidget {
@required this.onRemoveReferencedMessage,
@required this.onChangeMedia,
@required this.onSendMessage,
@required this.onStartTyping,
@required this.onStopTyping,
});

@override
Expand All @@ -38,13 +44,26 @@ class MessageField extends StatefulWidget {

class _MessageFieldState extends State<MessageField> {
final TextEditingController _textController = TextEditingController();
bool _isTyping = false;
Timer _stopTypingTimer;

@override
void initState() {
super.initState();
_textController.addListener(() => setState(() {}));
}

@override
void dispose() {
_textController.dispose();
_stopTypingTimer?.cancel();
if (_isTyping) {
widget.onStopTyping();
_isTyping = false;
}
super.dispose();
}

void _selectMedia(MediaType type) async {
final source = await showOptionsDialog(
context: context,
Expand Down Expand Up @@ -196,6 +215,23 @@ class _MessageFieldState extends State<MessageField> {
hintText: 'Say something',
hintTextColor: MyPalette.white,
color: MyPalette.white,
onChanged: (_) {
if (!_isTyping) {
widget.onStartTyping();
_isTyping = true;
}

_stopTypingTimer?.cancel();
_stopTypingTimer = Timer(
Duration(
seconds: 3,
), // if not typing for 3 seconds the user is considered to have stopped typing
() {
widget.onStopTyping();
_isTyping = false;
},
);
},
),
),
SizedBox(width: 10),
Expand Down
13 changes: 13 additions & 0 deletions lib/services/chats_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,17 @@ class ChatsService {
);
}
}

static Future<void> toggleTyping(String chatId, String uid, bool isTyping) =>
chatsRef.doc(chatId).update({
'typing': isTyping
? FieldValue.arrayUnion([uid])
: FieldValue.arrayRemove([uid]),
});

static Stream<bool> otherUserIsTypingStream(Chat chat) {
return chatsRef.doc(chat.id).snapshots().map<bool>((chatDoc) {
return chatDoc.data()['typing'].contains(chat.otherProfile.uid);
});
}
}
3 changes: 3 additions & 0 deletions lib/widgets/textfields/plain.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class PlainTextField extends StatelessWidget {
final Color color;
final double fontSize;
final String fontFamily;
final Function(String) onChanged;

PlainTextField({
Key key,
Expand All @@ -16,6 +17,7 @@ class PlainTextField extends StatelessWidget {
this.color,
this.fontSize = 20,
this.fontFamily,
this.onChanged,
}) : super(key: key);

@override
Expand All @@ -36,6 +38,7 @@ class PlainTextField extends StatelessWidget {
),
border: InputBorder.none,
),
onChanged: onChanged,
);
}
}

0 comments on commit 922bc93

Please sign in to comment.