Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] mentions #706

Open
wants to merge 45 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
1ab87c6
first draft for mention
florian4f6c6 Jun 26, 2022
86cfbf0
show mention added
florian4f6c6 Jun 27, 2022
5ff5872
Avatar added for ListView
florian4f6c6 Jun 27, 2022
db46b4d
crossAxisAlignment added
florian4f6c6 Jun 27, 2022
c49d44e
fixed overflow
florian4f6c6 Jun 28, 2022
63c7dfe
moved width to Mention
florian4f6c6 Jun 28, 2022
dc22b30
if a item user is clicked now the textField is updated
florian4f6c6 Jun 28, 2022
d7b1f37
Cursor now moved to end
florian4f6c6 Jul 2, 2022
9b78696
Fix extended profile widget
Jun 25, 2022
daf7998
Update styleguide.md
ereio Jul 1, 2022
c9d1ee0
fixed visibility
florian4f6c6 Jul 2, 2022
d854e7c
fixed bug with mention on hitting backspace
florian4f6c6 Jul 24, 2022
245b280
mention now also possible in message body
florian4f6c6 Jul 24, 2022
d072710
first draft for mention
florian4f6c6 Jun 26, 2022
815f228
show mention added
florian4f6c6 Jun 27, 2022
d63a566
Avatar added for ListView
florian4f6c6 Jun 27, 2022
6f956fb
crossAxisAlignment added
florian4f6c6 Jun 27, 2022
490a401
fixed overflow
florian4f6c6 Jun 28, 2022
f952636
moved width to Mention
florian4f6c6 Jun 28, 2022
a371fd8
if a item user is clicked now the textField is updated
florian4f6c6 Jun 28, 2022
4faf36e
Cursor now moved to end
florian4f6c6 Jul 2, 2022
0284dc1
fixed visibility
florian4f6c6 Jul 2, 2022
f27962c
fixed bug with mention on hitting backspace
florian4f6c6 Jul 24, 2022
35c29c7
mention now also possible in message body
florian4f6c6 Jul 24, 2022
74e73db
Merge branch 'feature/mentions' of https://github.com/Florian-Sabonch…
florian4f6c6 Jul 24, 2022
832cef7
fixed reply position
florian4f6c6 Jul 24, 2022
55c57ad
fixed bug
florian4f6c6 Aug 7, 2022
c1e3ef4
fixed formatting
florian4f6c6 Aug 7, 2022
3cb1cc0
fixed mention bug duplicate @
florian4f6c6 Aug 20, 2022
abfb2e6
fixed bug
florian4f6c6 Aug 20, 2022
2c20ce1
updated mention logic
florian4f6c6 Sep 3, 2022
b815087
updated setState
florian4f6c6 Sep 3, 2022
b31679d
code Style updated
florian4f6c6 Sep 3, 2022
dd094f7
updated state management
florian4f6c6 Sep 3, 2022
b68066b
fixed bug
florian4f6c6 Sep 4, 2022
6068356
replaced mention logic with regex
florian4f6c6 Sep 4, 2022
8e23ec1
code review
florian4f6c6 Sep 4, 2022
2f778da
fixed bug
florian4f6c6 Sep 4, 2022
60ee658
fixed code style
florian4f6c6 Sep 4, 2022
3263579
fixed bug if user was null
florian4f6c6 Sep 10, 2022
23e54d9
add user null check
Sep 10, 2022
701ccc4
linter
Sep 10, 2022
bad307e
Merge pull request #3 from EdGeraghty/fix-null
florian4f6c6 Sep 11, 2022
22ae58c
refactor: cleaned up mentions branch using chore/upgrades-and-cleanup…
ereio Dec 4, 2022
4765a73
Merge pull request #4 from syphon-org/refactor/mentions
florian4f6c6 Dec 9, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
234 changes: 147 additions & 87 deletions lib/views/home/chat/widgets/chat-input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@ import 'package:syphon/store/index.dart';
import 'package:syphon/store/rooms/room/model.dart';
import 'package:syphon/store/rooms/selectors.dart';
import 'package:syphon/store/settings/theme-settings/selectors.dart';
import 'package:syphon/store/user/model.dart';
import 'package:syphon/store/user/selectors.dart';
import 'package:syphon/views/widgets/buttons/button-text.dart';
import 'package:syphon/views/widgets/containers/media-card.dart';
import 'package:syphon/views/widgets/lists/list-local-images.dart';

import 'chat-mention.dart';
florian4f6c6 marked this conversation as resolved.
Show resolved Hide resolved

const DEFAULT_BORDER_RADIUS = 24.0;

_empty({
Expand Down Expand Up @@ -79,6 +83,9 @@ class ChatInput extends StatefulWidget {
class ChatInputState extends State<ChatInput> {
ChatInputState() : super();

bool mention = false;
List<User?> users = [];

bool sendable = false;
bool showAttachments = false;

Expand Down Expand Up @@ -141,6 +148,37 @@ class ChatInputState extends State<ChatInput> {
sendable = text.trim().isNotEmpty;
});


// mention-dialog
setState((){
mention = false;
users = props!.users;

if (text.startsWith('@') || text.contains(' @')) {
EdGeraghty marked this conversation as resolved.
Show resolved Hide resolved
mention = true;
users = users.where((user) {
if (user != null){
final String searchText = text.toLowerCase()
.substring(text.indexOf('@') + 1, text.length);

if(user.userId != null){
return user.userId!.toLowerCase().contains(searchText);
}
else {
return user.displayName!.toLowerCase().contains(searchText);
}
}
else{
return false;
}
}).toList();
}
else{
florian4f6c6 marked this conversation as resolved.
Show resolved Hide resolved
users = [];
}
});


// start an interval for updating typing status
if (widget.focusNode.hasFocus && typingNotifier == null) {
props!.onSendTyping(typing: true, roomId: props.room.id);
Expand Down Expand Up @@ -265,6 +303,9 @@ class ChatInputState extends State<ChatInput> {
// account for if editing
widget.editing && (widget.editorController?.text.isNotEmpty ?? false);

final bool showMention = mention;


Color sendButtonColor = const Color(AppColors.blueBubbly);

if (widget.mediumType == MediumType.plaintext) {
Expand Down Expand Up @@ -373,7 +414,18 @@ class ChatInputState extends State<ChatInput> {
}

return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//////// MENTION DIALOG ////////
Container(padding: EdgeInsets.symmetric(horizontal: 15, vertical: 5),
child: Mention(
visible: showMention,
width: MediaQuery.of(context).size.width - Dimensions.buttonSendSize * 2.1, // HACK: fix the width of the dialog
EdGeraghty marked this conversation as resolved.
Show resolved Hide resolved
users: users,
controller: widget.controller,
)
),
Visibility(
visible: replying,
maintainSize: false,
Expand Down Expand Up @@ -450,101 +502,106 @@ class ChatInputState extends State<ChatInput> {
),
),
//////// TEXT FIELD ////////
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
Container(
constraints: BoxConstraints(
maxHeight: maxInputHeight,
maxWidth: messageInputWidth,
),
child: Stack(
children: [
Visibility(
visible: widget.editing,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ButtonText(
text: Strings.buttonSaveMessageEdit,
size: 18.0,
disabled: widget.sending || !isSendable,
onPressed: () => onSubmit(),
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
Container(
constraints: BoxConstraints(
maxHeight: maxInputHeight,
maxWidth: messageInputWidth,
),
Visibility(
visible: !widget.editing,
child: TextField(
maxLines: null,
autocorrect: props.autocorrectEnabled,
enableSuggestions: props.suggestionsEnabled,
textCapitalization: props.textCapitalization,
keyboardType: TextInputType.multiline,
textInputAction:
widget.enterSend ? TextInputAction.send : TextInputAction.newline,
cursorColor: props.inputCursorColor,
focusNode: widget.focusNode,
controller: widget.controller,
onChanged: (text) => onUpdate(text, props: props),
onSubmitted: !isSendable ? null : (text) => onSubmit(),
style: TextStyle(
height: 1.5,
color: props.inputTextColor,
),
decoration: InputDecoration(
filled: true,
hintText: hintText,
suffixIcon: Visibility(
visible: isSendable,
child: IconButton(
color: Theme.of(context).iconTheme.color,
onPressed: () => onToggleMediaOptions(),
icon: Icon(
Icons.add,
size: Dimensions.iconSizeLarge,
child: Stack(
children: [
Visibility(
visible: widget.editing,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ButtonText(
text: Strings.buttonSaveMessageEdit,
size: 18.0,
disabled: widget.sending || !isSendable,
onPressed: () => onSubmit(),
),
),
],
),
fillColor: props.inputColorBackground,
contentPadding: Dimensions.inputContentPadding,
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.secondary,
width: 1,
),
Visibility(
visible: !widget.editing,
child: TextField(
maxLines: null,
autocorrect: props.autocorrectEnabled,
enableSuggestions: props.suggestionsEnabled,
textCapitalization: props.textCapitalization,
keyboardType: TextInputType.multiline,
textInputAction:
widget.enterSend ? TextInputAction.send : TextInputAction.newline,
cursorColor: props.inputCursorColor,
focusNode: widget.focusNode,
controller: widget.controller,
onChanged: (text) => onUpdate(text, props: props),
onSubmitted: !isSendable ? null : (text) => onSubmit(),
style: TextStyle(
height: 1.5,
color: props.inputTextColor,
),
decoration: InputDecoration(
filled: true,
hintText: hintText,
suffixIcon: Visibility(
visible: isSendable,
child: IconButton(
color: Theme.of(context).iconTheme.color,
onPressed: () => onToggleMediaOptions(),
icon: Icon(
Icons.add,
size: Dimensions.iconSizeLarge,
),
),
),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(!replying ? DEFAULT_BORDER_RADIUS : 0),
topRight:
fillColor: props.inputColorBackground,
contentPadding: Dimensions.inputContentPadding,
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.secondary,
width: 1,
),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(!replying ? DEFAULT_BORDER_RADIUS : 0),
topRight:
Radius.circular(!replying ? DEFAULT_BORDER_RADIUS : 0),
bottomLeft: Radius.circular(DEFAULT_BORDER_RADIUS),
bottomRight: Radius.circular(DEFAULT_BORDER_RADIUS),
)),
border: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.secondary,
width: 1,
),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(!replying ? DEFAULT_BORDER_RADIUS : 0),
topRight:
bottomLeft: Radius.circular(DEFAULT_BORDER_RADIUS),
bottomRight: Radius.circular(DEFAULT_BORDER_RADIUS),
)),
border: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.secondary,
width: 1,
),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(!replying ? DEFAULT_BORDER_RADIUS : 0),
topRight:
Radius.circular(!replying ? DEFAULT_BORDER_RADIUS : 0),
bottomLeft: Radius.circular(DEFAULT_BORDER_RADIUS),
bottomRight: Radius.circular(DEFAULT_BORDER_RADIUS),
)),
bottomLeft: Radius.circular(DEFAULT_BORDER_RADIUS),
bottomRight: Radius.circular(DEFAULT_BORDER_RADIUS),
)),
),
),
),
),
],
),
],
),
),
Container(
width: Dimensions.buttonSendSize,
padding: EdgeInsets.symmetric(vertical: 4),
child: sendButton,
),
Container(
width: Dimensions.buttonSendSize,
padding: EdgeInsets.symmetric(vertical: 4),
child: sendButton,
),
],
),
],
),
Expand Down Expand Up @@ -633,6 +690,7 @@ class _Props extends Equatable {
final bool autocorrectEnabled;
final bool suggestionsEnabled;
final TextCapitalization textCapitalization;
final List<User?> users;

final Function onSendTyping;

Expand All @@ -646,6 +704,7 @@ class _Props extends Equatable {
required this.suggestionsEnabled,
required this.textCapitalization,
required this.onSendTyping,
required this.users
});

@override
Expand All @@ -656,6 +715,7 @@ class _Props extends Equatable {

static _Props mapStateToProps(Store<AppState> store, String roomId) => _Props(
room: selectRoom(id: roomId, state: store.state),
users: roomUsers(store.state, roomId),
inputTextColor: selectInputTextColor(store.state.settingsStore.themeSettings.themeType),
inputCursorColor: selectCursorColor(store.state.settingsStore.themeSettings.themeType),
inputColorBackground:
Expand Down
77 changes: 77 additions & 0 deletions lib/views/home/chat/widgets/chat-mention.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import 'package:flutter/material.dart';
import 'package:syphon/global/colors.dart';
import 'package:syphon/global/dimensions.dart';
import 'package:syphon/store/user/model.dart';
import 'package:syphon/store/user/selectors.dart';
import 'package:syphon/views/widgets/avatars/avatar.dart';


class Mention extends StatefulWidget{

Mention({
Key? key,
required this.users,
required this.width,
required this.controller,
this.visible = false,
this.height = 200
}) : super(key: key);

final List<dynamic> users;
final double width;
final double height;
final TextEditingController controller;
bool visible;


@override
State<StatefulWidget> createState() => MentionState();
}

class MentionState extends State<Mention>{


@override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: BoxConstraints(
maxHeight: widget.height,
maxWidth: widget.width
),
child: ListView.builder(itemBuilder: (buildContext, index){
final String userName = formatUsername(widget.users[index] as User);
return Visibility(
visible: widget.visible,
child: Card(
child: ListTile(
onTap: () => onTab(widget.users[index]),
leading: Avatar(
uri: widget.users[index]?.avatarUri,
alt: userName,
size: Dimensions.avatarSizeMin,
background: AppColors.hashedColor(
userName,
),),
title: Text(userName),
subtitle: Text(widget.users[index]?.userId),
),
));
},
itemCount: widget.users.length,
shrinkWrap: true,
scrollDirection: Axis.vertical,
physics: ClampingScrollPhysics(),
padding: EdgeInsets.symmetric(vertical: 5),
));
}

onTab(User user){
setState((){
widget.controller.text = user.userId!;
florian4f6c6 marked this conversation as resolved.
Show resolved Hide resolved
widget.controller.selection = TextSelection.fromPosition(
TextPosition(offset: widget.controller.text.length));
widget.visible = false;
});
}
}