Add chat settings screen and typing notifications

parent af3f641b
......@@ -22,6 +22,7 @@ import 'package:matrix_sdk/matrix_sdk.dart';
import 'package:pattle/src/ui/initial/initial_page.dart';
import 'package:pattle/src/ui/main/chat/chat_page.dart';
import 'package:pattle/src/ui/main/chat/image/image_page.dart';
import 'package:pattle/src/ui/main/chat/settings/chat_settings_page.dart';
import 'package:pattle/src/ui/main/overview/chat_overview_page.dart';
import 'package:pattle/src/ui/resources/localizations.dart';
import 'package:pattle/src/ui/resources/theme.dart';
......@@ -47,6 +48,10 @@ final routes = {
}
}
),
Routes.chatsSettings: (Object arguments) => platformPageRoute(
settings: RouteSettings(name: Routes.chatsSettings),
builder: (context) => ChatSettingsPage(arguments)
),
Routes.chatsNew: (Object arguments) => platformPageRoute(
settings: RouteSettings(name: Routes.chatsNew),
builder: (context) => CreateGroupMembersPage()
......@@ -83,6 +88,7 @@ class Routes {
static const root = '/';
static const chats = '/chats';
static const chatsSettings = '/chats/settings';
static const image = '/image';
static const start = '/start';
......
......@@ -26,9 +26,13 @@ import 'package:pattle/src/di.dart' as di;
class ChatBloc {
Room room;
final Room room;
int _eventCount = 20;
StreamSubscription syncSub;
ChatBloc(this.room) {
syncSub = syncBloc.stream.listen((_) => _shouldRefreshSubj.add(true));
}
List<Type> get ignoredEvents => ignoredEventsOf(room, isOverview: false);
......@@ -148,4 +152,50 @@ class ChatBloc {
}
}
}
bool _notifying = false;
bool _typing = false;
final _stopwatch = Stopwatch();
Timer _notTypingTimer;
String _lastInput;
Future<void> notifyInputChanged(String input) async {
final room = this.room as JoinedRoom;
if (!_notifying) {
// Ignore null -> to '' input, triggers when clicking
// on the textfield
if (_lastInput == null && input.isEmpty) return;
_lastInput = input;
_notifying = true;
_notTypingTimer?.cancel();
if (_stopwatch.elapsed >= const Duration(seconds: 4) ||
!_stopwatch.isRunning) {
_typing = true;
await room.setIsTyping(true, timeout: const Duration(seconds: 7));
_stopwatch.reset();
_stopwatch.start();
}
_notifying = false;
}
_notTypingTimer = Timer(const Duration(seconds: 5), () async {
if (_typing) {
_typing = false;
await room.setIsTyping(false);
}
});
}
void cleanUp() {
syncSub.cancel();
}
}
\ No newline at end of file
......@@ -20,12 +20,15 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:matrix_sdk/matrix_sdk.dart';
import 'package:pattle/src/app.dart';
import 'package:pattle/src/ui/main/chat/chat_bloc.dart';
import 'package:pattle/src/ui/main/chat/util/typing_span.dart';
import 'package:pattle/src/ui/main/chat/widgets/date_header.dart';
import 'package:pattle/src/ui/main/chat/widgets/loading_bubble.dart';
import 'package:pattle/src/ui/main/models/chat_item.dart';
import 'package:pattle/src/ui/main/widgets/chat_name.dart';
import 'package:pattle/src/ui/main/widgets/error.dart';
import 'package:pattle/src/ui/main/widgets/title_with_sub.dart';
import 'package:pattle/src/ui/resources/localizations.dart';
import 'package:pattle/src/ui/resources/theme.dart';
import 'package:pattle/src/ui/util/future_or_builder.dart';
......@@ -39,7 +42,7 @@ import 'widgets/bubble.dart';
class ChatPageState extends State<ChatPage> {
final me = di.getLocalUser();
final ChatBloc bloc = ChatBloc();
final ChatBloc bloc;
final Room room;
ScrollController scrollController = ScrollController();
......@@ -49,13 +52,17 @@ class ChatPageState extends State<ChatPage> {
int maxPageCount;
ChatPageState(this.room) {
bloc.room = room;
ChatPageState(this.room) : bloc = ChatBloc(room) {
textController.addListener(() {
bloc.notifyInputChanged(textController.text);
});
}
@override
void dispose() {
super.dispose();
bloc.cleanUp();
textController.dispose();
}
@override
......@@ -97,29 +104,50 @@ class ChatPageState extends State<ChatPage> {
);
}
final settingsGestureDetector = ({Widget child}) {
return GestureDetector(
onTap: () => Navigator.of(context)
.pushNamed(Routes.chatsSettings, arguments: room),
child: child,
);
};
final title = room.isSomeoneElseTyping
? TitleWithSub(
title: ChatName(room: room),
subtitle: RichText(
text: TextSpan(
children: typingSpan(context, room)
),
),
)
: ChatName(room: room);
return PlatformScaffold(
backgroundColor: LightColors.red[50],
appBar: PlatformAppBar(
automaticallyImplyLeading: false,
android: (context) => MaterialAppBarData(
titleSpacing: 0,
title: Row(
children: [
IconButton(
icon: Icon(Icons.arrow_back),
padding: EdgeInsets.all(0),
onPressed: () {
Navigator.pop(context);
},
),
avatar,
SizedBox(
width: 16,
),
Flexible(
child: ChatName(room: room),
)
],
title: settingsGestureDetector(
child: Row(
children: [
IconButton(
icon: Icon(Icons.arrow_back),
padding: EdgeInsets.all(0),
onPressed: () {
Navigator.pop(context);
},
),
avatar,
SizedBox(
width: 16,
),
Flexible(
child: title,
)
],
)
),
),
ios: (context) => CupertinoNavigationBarData(
......@@ -134,10 +162,12 @@ class ChatPageState extends State<ChatPage> {
child: avatar,
),
),
title: ChatName(
room: room,
style: TextStyle(
color: Colors.white
title: settingsGestureDetector(
child: ChatName(
room: room,
style: TextStyle(
color: Colors.white
)
)
)
)
......
......@@ -18,6 +18,7 @@
import 'package:flutter/material.dart';
import 'package:matrix_sdk/matrix_sdk.dart';
import 'package:pattle/src/ui/main/chat/widgets/state/creation_bubble.dart';
import 'package:pattle/src/ui/main/chat/widgets/state/topic_bubble.dart';
import 'package:pattle/src/ui/main/chat/widgets/state/upgrade_bubble.dart';
import 'package:pattle/src/ui/main/models/chat_item.dart';
......@@ -102,6 +103,13 @@ abstract class Bubble extends Item {
nextItem: nextItem,
isMine: isMine,
);
} else if (item.event is TopicChangeEvent) {
return TopicBubble(
item: item,
previousItem: previousItem,
nextItem: nextItem,
isMine: isMine,
);
} else {
return null;
}
......
......@@ -43,6 +43,8 @@ abstract class StateBubble extends Bubble {
isMine: isMine
);
final void Function(BuildContext) onTap = (context) { };
TextStyle defaultTextStyle(BuildContext context)
=> Theme.of(context).textTheme.body1;
......@@ -89,7 +91,7 @@ abstract class StateBubble extends Bubble {
customBorder: RoundedRectangleBorder(
borderRadius: borderRadius
),
onTap: () { },
onTap: () => onTap(context),
child: Padding(
padding: Bubble.padding,
child: Column(
......
......@@ -190,7 +190,7 @@ class ChatOverviewPageState extends State<ChatOverviewPage> {
},
leading: avatar,
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
subtitle: Subtitle.fromEvent(chat.latestEvent)
subtitle: Subtitle.forChat(chat)
);
}
}
......
......@@ -18,12 +18,15 @@
import 'package:flutter/material.dart';
import 'package:matrix_sdk/matrix_sdk.dart';
import 'package:pattle/src/di.dart' as di;
import 'package:pattle/src/ui/main/overview/models/chat_overview.dart';
import 'package:pattle/src/ui/main/overview/widgets/typing_subtitle.dart';
import 'package:pattle/src/ui/util/user.dart';
import 'image_subtitle.dart';
import 'member_subtitle.dart';
import 'redacted_subtitle.dart';
import 'text_subtitle.dart';
import 'topic_subtitle.dart';
import 'unsupported_subtitle.dart';
abstract class Subtitle extends StatelessWidget {
......@@ -44,22 +47,30 @@ abstract class Subtitle extends StatelessWidget {
? '${displayNameOf(event.sender)}: '
: '';
factory Subtitle.fromEvent(Event event) {
if (event == null) {
return UnsupportedSubtitle(event);
}
factory Subtitle.forChat(ChatOverview chat) {
if (event is TextMessageEvent) {
return TextSubtitle(event);
} else if (event is ImageMessageEvent) {
return ImageSubtitle(event);
} else if (event is MemberChangeEvent) {
return MemberSubtitle(event);
} else if (event is RedactedEvent) {
return RedactedSubtitle(event);
}
if (chat.room.isSomeoneElseTyping) {
return TypingSubtitle(chat.room);
} else {
final event = chat.latestEvent;
if (event == null) {
return UnsupportedSubtitle(event);
}
return UnsupportedSubtitle(event);
if (event is TextMessageEvent) {
return TextSubtitle(event);
} else if (event is ImageMessageEvent) {
return ImageSubtitle(event);
} else if (event is MemberChangeEvent) {
return MemberSubtitle(event);
} else if (event is RedactedEvent) {
return RedactedSubtitle(event);
} else if (event is TopicChangeEvent) {
return TopicSubtitle(event);
}
return UnsupportedSubtitle(event);
}
}
TextStyle textStyle(BuildContext context) =>
......
......@@ -90,6 +90,13 @@ class Strings {
List<TextSpan> upgradedThisGroup(TextSpan name)
=> [name, TextSpan(text: ' upgraded this group')];
// Topic change event
List<TextSpan> changedDescriptionTapToView(TextSpan name)
=> [name, TextSpan(text: ' changed the description of this group. Tap to view')];
List<TextSpan> changedDescription(TextSpan name)
=> [name, TextSpan(text: ' changed the description of this group')];
// Member change events
List<TextSpan> changedTheirNameTo(TextSpan oldName, TextSpan newName)
=> [oldName, TextSpan(text: ' changed their name to '), newName];
......@@ -113,6 +120,28 @@ class Strings {
TextSpan(text: ' was invited by '),
inviter
];
// Typing events
List<TextSpan> get typing=> [TextSpan(text: 'typing...')];
List<TextSpan> isTyping(TextSpan name) => [
name,
TextSpan(text: ' is typing...'),
];
List<TextSpan> areTyping(TextSpan first, TextSpan second) => [
first,
TextSpan(text: ' and '),
second,
TextSpan(text: ' are typing...')
];
List<TextSpan> andMoreAreTyping(TextSpan first, TextSpan second) => [
first,
TextSpan(text: ' and '),
second,
TextSpan(text: ' and more typing...')
];
}
class AppLocalizations {
......
......@@ -213,14 +213,14 @@ packages:
name: matrix_sdk
url: "https://pub.dartlang.org"
source: hosted
version: "0.19.1"
version: "0.19.6"
matrix_sdk_sqflite:
dependency: "direct main"
description:
name: matrix_sdk_sqflite
url: "https://pub.dartlang.org"
source: hosted
version: "0.15.0"
version: "0.15.2"
meta:
dependency: transitive
description:
......
......@@ -12,8 +12,8 @@ dependencies:
injector: ^1.0.8
matrix_sdk: ^0.19.1
matrix_sdk_sqflite: ^0.15.0
matrix_sdk: ^0.19.6
matrix_sdk_sqflite: ^0.15.1
rxdart: ^0.22.0
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment