diff --git a/assets/chat.svg b/assets/chat.svg
new file mode 100644
index 00000000..b80f20d6
--- /dev/null
+++ b/assets/chat.svg
@@ -0,0 +1,185 @@
+
+
+
\ No newline at end of file
diff --git a/lib/components/list_items/message.dart b/lib/components/list_items/message.dart
index 8758bd63..6f8bc695 100644
--- a/lib/components/list_items/message.dart
+++ b/lib/components/list_items/message.dart
@@ -156,12 +156,14 @@ class Message extends StatelessWidget {
color: selected
? Theme.of(context).primaryColor.withAlpha(100)
: Theme.of(context).primaryColor.withAlpha(0),
- padding: EdgeInsets.only(
- left: 8.0, right: 8.0, bottom: sameSender ? 4.0 : 8.0),
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.end,
- mainAxisAlignment: rowMainAxisAlignment,
- children: rowChildren,
+ child: Padding(
+ padding: EdgeInsets.only(
+ left: 8.0, right: 8.0, bottom: sameSender ? 4.0 : 8.0),
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.end,
+ mainAxisAlignment: rowMainAxisAlignment,
+ children: rowChildren,
+ ),
),
),
);
diff --git a/lib/main.dart b/lib/main.dart
index cc31647e..f5e20a0c 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -48,6 +48,7 @@ class App extends StatelessWidget {
textTheme: TextTheme(
title: TextStyle(
color: Colors.black,
+ fontSize: 20,
),
),
iconTheme: IconThemeData(color: Colors.black),
diff --git a/lib/views/chat.dart b/lib/views/chat.dart
index 7e53b8f1..d0a76358 100644
--- a/lib/views/chat.dart
+++ b/lib/views/chat.dart
@@ -17,6 +17,7 @@ import 'package:fluffychat/views/chat_encryption_settings.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
+import 'package:flutter_svg/flutter_svg.dart';
import 'package:image_picker/image_picker.dart';
import 'package:toast/toast.dart';
import 'package:pedantic/pedantic.dart';
@@ -75,6 +76,16 @@ class _ChatState extends State<_Chat> {
bool get selectMode => selectedEvents.isNotEmpty;
+ bool _loadingHistory = false;
+
+ final int _loadHistoryCount = 100;
+
+ void requestHistory() async {
+ setState(() => this._loadingHistory = true);
+ await timeline.requestHistory(historyCount: _loadHistoryCount);
+ setState(() => this._loadingHistory = false);
+ }
+
@override
void initState() {
_scrollController.addListener(() async {
@@ -83,7 +94,7 @@ class _ChatState extends State<_Chat> {
timeline.events.isNotEmpty &&
timeline.events[timeline.events.length - 1].type !=
EventTypes.RoomCreate) {
- await timeline.requestHistory(historyCount: 100);
+ requestHistory();
}
if (_scrollController.position.pixels > 0 &&
showScrollDownButton == false) {
@@ -132,6 +143,9 @@ class _ChatState extends State<_Chat> {
if (timeline.events.isNotEmpty) {
unawaited(room.sendReadReceipt(timeline.events.first.eventId));
}
+ if (timeline.events.length < _loadHistoryCount) {
+ this.requestHistory();
+ }
}
updateView();
return true;
@@ -330,13 +344,14 @@ class _ChatState extends State<_Chat> {
? Container()
: Icon(Icons.edit,
color: Theme.of(context).primaryColor,
- size: 10),
+ size: 13),
SizedBox(width: 4),
Text(
typingText,
style: TextStyle(
color: Theme.of(context).primaryColor,
fontStyle: FontStyle.italic,
+ fontSize: 16,
),
),
],
@@ -372,282 +387,325 @@ class _ChatState extends State<_Chat> {
),
)
: null,
- body: SafeArea(
- child: Column(
- children: [
- Expanded(
- child: FutureBuilder(
- future: getTimeline(),
- builder: (BuildContext context, snapshot) {
- if (!snapshot.hasData) {
- return Center(
- child: CircularProgressIndicator(),
- );
- }
-
- if (room.notificationCount != null &&
- room.notificationCount > 0 &&
- timeline != null &&
- timeline.events.isNotEmpty) {
- room.sendReadReceipt(timeline.events.first.eventId);
- }
-
- if (timeline.events.isEmpty) return Container();
-
- return ListView.builder(
- reverse: true,
- itemCount: timeline.events.length + 1,
- controller: _scrollController,
- itemBuilder: (BuildContext context, int i) {
- return i == 0
- ? AnimatedContainer(
- height: seenByText.isEmpty ? 0 : 24,
- duration: seenByText.isEmpty
- ? Duration(milliseconds: 0)
- : Duration(milliseconds: 500),
- alignment: timeline.events.first.senderId ==
- client.userID
- ? Alignment.topRight
- : Alignment.topLeft,
- child: Text(
- seenByText,
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
- style: TextStyle(
- color: Theme.of(context).primaryColor,
- ),
- ),
- padding: EdgeInsets.only(
- left: 8,
- right: 8,
- bottom: 8,
- ),
- )
- : Message(timeline.events[i - 1],
- onSelect: (Event event) => event.redacted
- ? null
- : selectedEvents.contains(event)
- ? setState(
- () => selectedEvents.remove(event))
- : setState(
- () => selectedEvents.add(event)),
- longPressSelect: selectedEvents.isEmpty,
- selected: selectedEvents
- .contains(timeline.events[i - 1]),
- timeline: timeline,
- nextEvent:
- i >= 2 ? timeline.events[i - 2] : null);
- });
- },
- ),
+ body: Stack(
+ children: [
+ if (!kIsWeb)
+ SvgPicture.asset(
+ "assets/chat.svg",
+ height: double.infinity,
+ color: Theme.of(context).primaryColor.withOpacity(0.2),
),
- AnimatedContainer(
- duration: Duration(milliseconds: 300),
- height: replyEvent != null ? 56 : 0,
- child: Material(
- color: Theme.of(context).secondaryHeaderColor,
- child: Row(
- children: [
- IconButton(
- icon: Icon(Icons.close),
- onPressed: () => setState(() => replyEvent = null),
+ SafeArea(
+ child: Column(
+ children: [
+ Material(
+ elevation: 1,
+ color: Theme.of(context).scaffoldBackgroundColor,
+ child: AnimatedContainer(
+ duration: Duration(milliseconds: 300),
+ height: _loadingHistory ? 40 : 0,
+ child: Center(
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Container(
+ height: 20,
+ width: 20,
+ child: CircularProgressIndicator(strokeWidth: 2),
+ ),
+ SizedBox(width: 8),
+ Text(I18n.of(context).loadingPleaseWait),
+ ],
+ ),
),
- Expanded(
- child: ReplyContent(replyEvent),
- ),
- ],
+ ),
),
- ),
- ),
- room.canSendDefaultMessages && room.membership == Membership.join
- ? Container(
- decoration: BoxDecoration(
- color: Theme.of(context).backgroundColor,
- boxShadow: [
- BoxShadow(
- color: Colors.grey.withOpacity(0.2),
- spreadRadius: 1,
- blurRadius: 2,
- offset: Offset(0, -1), // changes position of shadow
+ Expanded(
+ child: FutureBuilder(
+ future: getTimeline(),
+ builder: (BuildContext context, snapshot) {
+ if (!snapshot.hasData) {
+ return Center(
+ child: CircularProgressIndicator(),
+ );
+ }
+
+ if (room.notificationCount != null &&
+ room.notificationCount > 0 &&
+ timeline != null &&
+ timeline.events.isNotEmpty) {
+ room.sendReadReceipt(timeline.events.first.eventId);
+ }
+
+ if (timeline.events.isEmpty) return Container();
+
+ return ListView.builder(
+ reverse: true,
+ itemCount: timeline.events.length + 1,
+ controller: _scrollController,
+ itemBuilder: (BuildContext context, int i) {
+ return i == 0
+ ? AnimatedContainer(
+ height: seenByText.isEmpty ? 0 : 24,
+ duration: seenByText.isEmpty
+ ? Duration(milliseconds: 0)
+ : Duration(milliseconds: 500),
+ alignment: timeline.events.first.senderId ==
+ client.userID
+ ? Alignment.topRight
+ : Alignment.topLeft,
+ child: Text(
+ seenByText,
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ style: TextStyle(
+ color: Theme.of(context).primaryColor,
+ ),
+ ),
+ padding: EdgeInsets.only(
+ left: 8,
+ right: 8,
+ bottom: 8,
+ ),
+ )
+ : Message(timeline.events[i - 1],
+ onSelect: (Event event) => event.redacted
+ ? null
+ : selectedEvents.contains(event)
+ ? setState(() =>
+ selectedEvents.remove(event))
+ : setState(() =>
+ selectedEvents.add(event)),
+ longPressSelect: selectedEvents.isEmpty,
+ selected: selectedEvents
+ .contains(timeline.events[i - 1]),
+ timeline: timeline,
+ nextEvent:
+ i >= 2 ? timeline.events[i - 2] : null);
+ });
+ },
+ ),
+ ),
+ AnimatedContainer(
+ duration: Duration(milliseconds: 300),
+ height: replyEvent != null ? 56 : 0,
+ child: Material(
+ color: Theme.of(context).secondaryHeaderColor,
+ child: Row(
+ children: [
+ IconButton(
+ icon: Icon(Icons.close),
+ onPressed: () => setState(() => replyEvent = null),
+ ),
+ Expanded(
+ child: ReplyContent(replyEvent),
),
],
),
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.center,
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: selectMode
- ? [
- Container(
- height: 56,
- child: FlatButton(
- onPressed: () => forwardEventsAction(context),
- child: Row(
- children: [
- Icon(Icons.keyboard_arrow_left),
- Text(I18n.of(context).forward),
- ],
- ),
- ),
- ),
- selectedEvents.length == 1
- ? selectedEvents.first.status > 0
- ? Container(
- height: 56,
- child: FlatButton(
- onPressed: () => replyAction(),
- child: Row(
- children: [
- Text(I18n.of(context).reply),
- Icon(
- Icons.keyboard_arrow_right),
- ],
- ),
- ),
- )
- : Container(
- height: 56,
- child: FlatButton(
- onPressed: () => sendAgainAction(),
- child: Row(
- children: [
- Text(I18n.of(context)
- .tryToSendAgain),
- SizedBox(width: 4),
- Icon(Icons.send, size: 16),
- ],
- ),
- ),
- )
- : Container(),
- ]
- : [
- kIsWeb
- ? Container()
- : PopupMenuButton(
- icon: Icon(Icons.add),
- onSelected: (String choice) async {
- if (choice == "file") {
- sendFileAction(context);
- } else if (choice == "image") {
- sendImageAction(context);
- }
- if (choice == "camera") {
- openCameraAction(context);
- }
- },
- itemBuilder: (BuildContext context) =>
- >[
- PopupMenuItem(
- value: "file",
- child: ListTile(
- leading: CircleAvatar(
- backgroundColor: Colors.green,
- foregroundColor: Colors.white,
- child: Icon(Icons.attachment),
- ),
- title:
- Text(I18n.of(context).sendFile),
- contentPadding: EdgeInsets.all(0),
- ),
- ),
- PopupMenuItem(
- value: "image",
- child: ListTile(
- leading: CircleAvatar(
- backgroundColor: Colors.blue,
- foregroundColor: Colors.white,
- child: Icon(Icons.image),
- ),
- title: Text(
- I18n.of(context).sendImage),
- contentPadding: EdgeInsets.all(0),
- ),
- ),
- PopupMenuItem(
- value: "camera",
- child: ListTile(
- leading: CircleAvatar(
- backgroundColor: Colors.purple,
- foregroundColor: Colors.white,
- child: Icon(Icons.camera),
- ),
- title: Text(
- I18n.of(context).openCamera),
- contentPadding: EdgeInsets.all(0),
- ),
- ),
- ],
+ ),
+ ),
+ room.canSendDefaultMessages &&
+ room.membership == Membership.join
+ ? Container(
+ decoration: BoxDecoration(
+ color: Theme.of(context).backgroundColor,
+ boxShadow: [
+ BoxShadow(
+ color: Colors.grey.withOpacity(0.2),
+ spreadRadius: 1,
+ blurRadius: 2,
+ offset:
+ Offset(0, -1), // changes position of shadow
+ ),
+ ],
+ ),
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: selectMode
+ ? [
+ Container(
+ height: 56,
+ child: FlatButton(
+ onPressed: () =>
+ forwardEventsAction(context),
+ child: Row(
+ children: [
+ Icon(Icons.keyboard_arrow_left),
+ Text(I18n.of(context).forward),
+ ],
+ ),
),
- SizedBox(width: 8),
- Expanded(
- child: Padding(
- padding:
- const EdgeInsets.symmetric(vertical: 4.0),
- child: TextField(
- minLines: 1,
- maxLines: kIsWeb ? 1 : 8,
- keyboardType: kIsWeb
- ? TextInputType.text
- : TextInputType.multiline,
- onSubmitted: (String text) {
- send();
- FocusScope.of(context)
- .requestFocus(inputFocus);
- },
- focusNode: inputFocus,
- controller: sendController,
- decoration: InputDecoration(
- hintText: I18n.of(context).writeAMessage,
- border: InputBorder.none,
- suffixIcon: sendController.text.isEmpty
- ? InkWell(
- child: Icon(room.encrypted
- ? Icons.lock
- : Icons.lock_open),
- onTap: () =>
- Navigator.of(context).push(
- AppRoute.defaultRoute(
- context,
- ChatEncryptionSettingsView(
- widget.id),
+ ),
+ selectedEvents.length == 1
+ ? selectedEvents.first.status > 0
+ ? Container(
+ height: 56,
+ child: FlatButton(
+ onPressed: () => replyAction(),
+ child: Row(
+ children: [
+ Text(
+ I18n.of(context).reply),
+ Icon(Icons
+ .keyboard_arrow_right),
+ ],
),
),
)
- : null,
+ : Container(
+ height: 56,
+ child: FlatButton(
+ onPressed: () =>
+ sendAgainAction(),
+ child: Row(
+ children: [
+ Text(I18n.of(context)
+ .tryToSendAgain),
+ SizedBox(width: 4),
+ Icon(Icons.send, size: 16),
+ ],
+ ),
+ ),
+ )
+ : Container(),
+ ]
+ : [
+ kIsWeb
+ ? Container()
+ : PopupMenuButton(
+ icon: Icon(Icons.add),
+ onSelected: (String choice) async {
+ if (choice == "file") {
+ sendFileAction(context);
+ } else if (choice == "image") {
+ sendImageAction(context);
+ }
+ if (choice == "camera") {
+ openCameraAction(context);
+ }
+ },
+ itemBuilder: (BuildContext context) =>
+ >[
+ PopupMenuItem(
+ value: "file",
+ child: ListTile(
+ leading: CircleAvatar(
+ backgroundColor: Colors.green,
+ foregroundColor: Colors.white,
+ child: Icon(Icons.attachment),
+ ),
+ title: Text(
+ I18n.of(context).sendFile),
+ contentPadding:
+ EdgeInsets.all(0),
+ ),
+ ),
+ PopupMenuItem(
+ value: "image",
+ child: ListTile(
+ leading: CircleAvatar(
+ backgroundColor: Colors.blue,
+ foregroundColor: Colors.white,
+ child: Icon(Icons.image),
+ ),
+ title: Text(
+ I18n.of(context).sendImage),
+ contentPadding:
+ EdgeInsets.all(0),
+ ),
+ ),
+ PopupMenuItem(
+ value: "camera",
+ child: ListTile(
+ leading: CircleAvatar(
+ backgroundColor:
+ Colors.purple,
+ foregroundColor: Colors.white,
+ child: Icon(Icons.camera),
+ ),
+ title: Text(I18n.of(context)
+ .openCamera),
+ contentPadding:
+ EdgeInsets.all(0),
+ ),
+ ),
+ ],
+ ),
+ Expanded(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(
+ vertical: 4.0),
+ child: TextField(
+ minLines: 1,
+ maxLines: kIsWeb ? 1 : 8,
+ keyboardType: kIsWeb
+ ? TextInputType.text
+ : TextInputType.multiline,
+ onSubmitted: (String text) {
+ send();
+ FocusScope.of(context)
+ .requestFocus(inputFocus);
+ },
+ focusNode: inputFocus,
+ controller: sendController,
+ decoration: InputDecoration(
+ hintText:
+ I18n.of(context).writeAMessage,
+ border: InputBorder.none,
+ prefixIcon:
+ sendController.text.isEmpty
+ ? InkWell(
+ child: Icon(room.encrypted
+ ? Icons.lock
+ : Icons.lock_open),
+ onTap: () =>
+ Navigator.of(context)
+ .push(
+ AppRoute.defaultRoute(
+ context,
+ ChatEncryptionSettingsView(
+ widget.id),
+ ),
+ ),
+ )
+ : null,
+ ),
+ onChanged: (String text) {
+ this.typingCoolDown?.cancel();
+ this.typingCoolDown =
+ Timer(Duration(seconds: 2), () {
+ this.typingCoolDown = null;
+ this.currentlyTyping = false;
+ room.sendTypingInfo(false);
+ });
+ this.typingTimeout ??=
+ Timer(Duration(seconds: 30), () {
+ this.typingTimeout = null;
+ this.currentlyTyping = false;
+ });
+ if (!this.currentlyTyping) {
+ this.currentlyTyping = true;
+ room.sendTypingInfo(true,
+ timeout: Duration(seconds: 30)
+ .inMilliseconds);
+ }
+ },
+ ),
),
- onChanged: (String text) {
- this.typingCoolDown?.cancel();
- this.typingCoolDown =
- Timer(Duration(seconds: 2), () {
- this.typingCoolDown = null;
- this.currentlyTyping = false;
- room.sendTypingInfo(false);
- });
- this.typingTimeout ??=
- Timer(Duration(seconds: 30), () {
- this.typingTimeout = null;
- this.currentlyTyping = false;
- });
- if (!this.currentlyTyping) {
- this.currentlyTyping = true;
- room.sendTypingInfo(true,
- timeout: Duration(seconds: 30)
- .inMilliseconds);
- }
- },
),
- ),
- ),
- IconButton(
- icon: Icon(Icons.send),
- onPressed: () => send(),
- ),
- ],
- ),
- )
- : Container(),
- ],
- ),
+ IconButton(
+ icon: Icon(Icons.send),
+ onPressed: () => send(),
+ ),
+ ],
+ ),
+ )
+ : Container(),
+ ],
+ ),
+ ),
+ ],
),
);
}
diff --git a/lib/views/new_private_chat.dart b/lib/views/new_private_chat.dart
index 001616d5..bcbbfb80 100644
--- a/lib/views/new_private_chat.dart
+++ b/lib/views/new_private_chat.dart
@@ -104,7 +104,6 @@ class _NewPrivateChatState extends State<_NewPrivateChat> {
@override
Widget build(BuildContext context) {
- final String defaultDomain = Matrix.of(context).client.userID.domain;
return Scaffold(
appBar: AppBar(
title: Text(I18n.of(context).newPrivateChat),
@@ -163,8 +162,7 @@ class _NewPrivateChatState extends State<_NewPrivateChat> {
)
: Icon(Icons.account_circle),
prefixText: "@",
- hintText:
- "${I18n.of(context).username.toLowerCase()}:$defaultDomain",
+ hintText: "${I18n.of(context).username.toLowerCase()}",
),
),
),
@@ -190,7 +188,7 @@ class _NewPrivateChatState extends State<_NewPrivateChat> {
),
title: Text(
foundProfile["display_name"] ??
- foundProfile["user_id"].localpart,
+ (foundProfile["user_id"] as String).localpart,
style: TextStyle(),
maxLines: 1,
),
diff --git a/pubspec.lock b/pubspec.lock
index efa0bf89..e5a2ed27 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -117,8 +117,8 @@ packages:
dependency: "direct main"
description:
path: "."
- ref: e2fde3fa924cb9a1bdccf5dd6b63aad8d820d20a
- resolved-ref: e2fde3fa924cb9a1bdccf5dd6b63aad8d820d20a
+ ref: "0f68b60f16db924b10fa8954623e67de6252b35f"
+ resolved-ref: "0f68b60f16db924b10fa8954623e67de6252b35f"
url: "https://gitlab.com/famedly/famedlysdk.git"
source: git
version: "0.0.1"
@@ -181,6 +181,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.5"
+ flutter_svg:
+ dependency: "direct main"
+ description:
+ name: flutter_svg
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.17.1"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -317,6 +324,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.6.4"
+ path_drawing:
+ dependency: transitive
+ description:
+ name: path_drawing
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.4.1"
+ path_parsing:
+ dependency: transitive
+ description:
+ name: path_parsing
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.1.4"
path_provider:
dependency: "direct main"
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 54d67d52..9eda8c9e 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -11,7 +11,7 @@ description: Chat with your friends.
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
-version: 0.7.2+22
+version: 0.7.3+23
environment:
sdk: ">=2.6.0 <3.0.0"
@@ -27,7 +27,7 @@ dependencies:
famedlysdk:
git:
url: https://gitlab.com/famedly/famedlysdk.git
- ref: e2fde3fa924cb9a1bdccf5dd6b63aad8d820d20a
+ ref: 0f68b60f16db924b10fa8954623e67de6252b35f
localstorage: ^3.0.1+4
bubble: ^1.1.9+1
@@ -49,6 +49,7 @@ dependencies:
http: ^0.12.0+4
universal_html: ^1.1.12
uni_links: ^0.2.0
+ flutter_svg: ^0.17.1
intl: ^0.16.0
intl_translation: ^0.17.9
@@ -84,6 +85,7 @@ flutter:
- assets/logo.png
- assets/private_chat_wallpaper.png
- assets/new_group_wallpaper.png
+ - assets/chat.svg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see