Skip to content

Commit

Permalink
feat: chat-mode via overlay (#538)
Browse files Browse the repository at this point in the history
* feat: chat-mode via dialog

* feat: use overlay

* chore: resolve warning

* fix: logic fix and allows startswith filtering

* chore: code tidy

* chore: bump sdk to at least 2.17.0 for enhance enum feature

* chore: use enum instead of class
  • Loading branch information
chungwwei authored Jul 7, 2022
1 parent 0055d9e commit 3022e35
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 1 deletion.
96 changes: 96 additions & 0 deletions lib/components/message_input.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
import 'package:provider/provider.dart';
import 'package:rtchat/components/emote_picker.dart';
import 'package:rtchat/models/adapters/actions.dart';
import 'package:rtchat/models/channels.dart';
import 'package:rtchat/models/chat_mode.dart';
import 'package:rtchat/models/commands.dart';

class MessageInputWidget extends StatefulWidget {
Expand All @@ -20,6 +23,89 @@ class _MessageInputWidgetState extends State<MessageInputWidget> {
final _chatInputFocusNode = FocusNode();
var _isEmotePickerVisible = false;

OverlayEntry? entry;

@override
void initState() {
super.initState();
}

bool startsWithPossibleCommands(String text) {
if (text == "" || text.isEmpty) {
return false;
}
for (final mode in ChatMode.values) {
if (mode.title.startsWith(text)) {
return true;
}
}
return false;
}

void hideOverlay() {
entry?.remove();
entry = null;
}

void showOverlay(String text) {
// remove existing overlay, bc user can contiunously type a prefix string that matches a command
hideOverlay();

final overlay = Overlay.of(context)!;

// the renderbox of this widget
final renderBox = context.findRenderObject() as RenderBox;
final size = renderBox.size;

final offset = renderBox.localToGlobal(Offset.zero);
final lst =
ChatMode.values.where((element) => element.title.startsWith(text));

// None to show
if (lst.isEmpty) {
hideOverlay();
return;
}

final lstSize = lst.length;
const listTileSize = 75; // the roughly size of a listTile
final shiftUp = min(lstSize * listTileSize, 300);

entry = OverlayEntry(builder: (context) {
return Positioned(
left: offset.dx,
top: offset.dy - shiftUp,
width: size.width,
child: Material(
child: SizedBox(
height: shiftUp.toDouble(),
child: ListView(
padding: EdgeInsets.zero,
shrinkWrap: true,
primary: false,
children: lst.map((e) {
return ListTile(
title: Text(e.title),
subtitle: Text(e.subtitle),
onTap: () {
_textEditingController.text = e.title;
hideOverlay();
// move cursor position
_textEditingController.selection =
TextSelection.fromPosition(TextPosition(
offset: _textEditingController.text.length));
},
);
}).toList(),
),
),
),
);
});

overlay.insert(entry!);
}

void sendMessage(String value) async {
value = value.trim();
if (value.isEmpty) {
Expand Down Expand Up @@ -83,6 +169,11 @@ class _MessageInputWidgetState extends State<MessageInputWidget> {

@override
Widget build(BuildContext context) {
// remove overlay if keyboard is not visible
if (MediaQuery.of(context).viewInsets.bottom == 0) {
hideOverlay();
}

return Material(
child: Column(children: [
Padding(
Expand Down Expand Up @@ -134,6 +225,11 @@ class _MessageInputWidgetState extends State<MessageInputWidget> {
border: InputBorder.none,
hintText: "Send a message..."),
onChanged: (text) {
if (startsWithPossibleCommands(text)) {
showOverlay(text);
} else {
hideOverlay();
}
final filtered = text.replaceAll('\n', ' ');
if (filtered == text) {
return;
Expand Down
30 changes: 30 additions & 0 deletions lib/models/chat_mode.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
enum ChatMode {
followers(
title: "/followers",
subtitle:
"Restrict the chat to followers-only mode; optionally, specify a time duration (e.g., 30 minutes, 1 week)"),
followersoff(title: "/followersoff", subtitle: "Disable followers-only mode"),
subscribers(title: "/subscribers", subtitle: "Restrict Chat to subscribers"),
subscribersoff(
title: "/subscribersoff", subtitle: "Turn off subscribers-only mode"),
uniquechat(
title: "/uniquechat",
subtitle: "Prevent users from sending duplicate messages in Chat"),
uniquechatoff(title: "/uniquechatoff", subtitle: "Turn off unique-chat mode"),
emoteonly(
title: "/emoteonly",
subtitle: "Users can only send emotes in their messages"),
emoteonlyoff(title: "/emoteonlyoff", subtitle: "Disable emotes only mode"),
slow(
title: "/slow",
subtitle: "Limit the rate at which users can send messages"),
slowoff(title: "/slowoff", subtitle: "Disable slow mode");

const ChatMode({
required this.title,
required this.subtitle,
});

final String title;
final String subtitle;
}
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ publish_to: "none"
version: 1.0.0+4

environment:
sdk: ">=2.16.0 <3.0.0"
sdk: ">=2.17.0 <3.0.0"

dependencies:
flutter:
Expand Down

0 comments on commit 3022e35

Please sign in to comment.