diff --git a/lib/components/list_items/chat_list_item.dart b/lib/components/list_items/chat_list_item.dart index b1c3fb9..960a5a1 100644 --- a/lib/components/list_items/chat_list_item.dart +++ b/lib/components/list_items/chat_list_item.dart @@ -1,5 +1,6 @@ import 'package:famedlysdk/famedlysdk.dart'; import 'package:fluffychat/components/message_content.dart'; +import 'package:fluffychat/utils/ChatTime.dart'; import 'package:fluffychat/utils/app_route.dart'; import 'package:fluffychat/views/chat.dart'; import 'package:flutter/material.dart'; @@ -38,7 +39,7 @@ class ChatListItem extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.end, children: [ - Text(room.timeCreated.toEventTimeString()), + Text(ChatTime(room.timeCreated).toEventTimeString()), room.notificationCount > 0 ? Container( width: 20, diff --git a/lib/components/list_items/message.dart b/lib/components/list_items/message.dart index f060b07..453e078 100644 --- a/lib/components/list_items/message.dart +++ b/lib/components/list_items/message.dart @@ -2,6 +2,7 @@ import 'package:bubble/bubble.dart'; import 'package:famedlysdk/famedlysdk.dart'; import 'package:fluffychat/components/dialogs/redact_message_dialog.dart'; import 'package:fluffychat/components/message_content.dart'; +import 'package:fluffychat/utils/ChatTime.dart'; import 'package:flutter/material.dart'; import '../avatar.dart'; @@ -96,7 +97,7 @@ class Message extends StatelessWidget { ), SizedBox(width: 4), Text( - event.time.toEventTimeString(), + ChatTime(event.time).toEventTimeString(), style: TextStyle(color: textColor, fontSize: 12), ), ], diff --git a/lib/components/matrix.dart b/lib/components/matrix.dart index d154321..0477792 100644 --- a/lib/components/matrix.dart +++ b/lib/components/matrix.dart @@ -38,12 +38,12 @@ class MatrixState extends State { final credentialsStr = storage.getItem(widget.clientName); if (credentialsStr == null || credentialsStr.isEmpty) { - client.connection.onLoginStateChanged.add(LoginState.loggedOut); + client.onLoginStateChanged.add(LoginState.loggedOut); return; } print("[Matrix] Restoring account credentials"); final Map credentials = json.decode(credentialsStr); - client.connection.connect( + client.connect( newDeviceID: credentials["deviceID"], newDeviceName: credentials["deviceName"], newHomeserver: credentials["homeserver"], diff --git a/lib/main.dart b/lib/main.dart index a3af3b6..1daa2e4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -39,8 +39,7 @@ class MyApp extends StatelessWidget { ), home: Builder( builder: (BuildContext context) => StreamBuilder( - stream: - Matrix.of(context).client.connection.onLoginStateChanged.stream, + stream: Matrix.of(context).client.onLoginStateChanged.stream, builder: (context, snapshot) { if (!snapshot.hasData) return Scaffold( diff --git a/lib/utils/ChatTime.dart b/lib/utils/ChatTime.dart new file mode 100644 index 0000000..96dd4ff --- /dev/null +++ b/lib/utils/ChatTime.dart @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2019 Zender & Kurtz GbR. + * + * Authors: + * Christian Pauly + * Marcel Radzio + * + * This file is part of famedlysdk. + * + * famedlysdk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * famedlysdk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with famedlysdk. If not, see . + */ + +/// Used to localize and present time in a chat application manner. +class ChatTime { + DateTime dateTime = DateTime.now(); + + /// Insert with a timestamp [ts] which represents the milliseconds since + /// the Unix epoch. + ChatTime(DateTime ts) { + if (ts != null) dateTime = ts; + } + + /// Returns a ChatTime object which represents the current time. + ChatTime.now() { + dateTime = DateTime.now(); + } + + /// Returns [toTimeString()] if the ChatTime is today, the name of the week + /// day if the ChatTime is this week and a date string else. + String toString() { + DateTime now = DateTime.now(); + + bool sameYear = now.year == dateTime.year; + + bool sameDay = + sameYear && now.month == dateTime.month && now.day == dateTime.day; + + bool sameWeek = sameYear && + !sameDay && + now.millisecondsSinceEpoch - dateTime.millisecondsSinceEpoch < + 1000 * 60 * 60 * 24 * 7; + + if (sameDay) { + return toTimeString(); + } else if (sameWeek) { + switch (dateTime.weekday) { + case 1: + return "Montag"; + case 2: + return "Dienstag"; + case 3: + return "Mittwoch"; + case 4: + return "Donnerstag"; + case 5: + return "Freitag"; + case 6: + return "Samstag"; + case 7: + return "Sonntag"; + } + } else if (sameYear) { + return "${_z(dateTime.day)}.${_z(dateTime.month)}"; + } + return "${_z(dateTime.day)}.${_z(dateTime.month)}.${_z(dateTime.year)}"; + } + + /// Returns the milliseconds since the Unix epoch. + num toTimeStamp() { + return dateTime.millisecondsSinceEpoch; + } + + operator <(ChatTime other) { + return this.toTimeStamp() < other.toTimeStamp(); + } + + operator >(ChatTime other) { + return this.toTimeStamp() > other.toTimeStamp(); + } + + operator >=(ChatTime other) { + return this.toTimeStamp() >= other.toTimeStamp(); + } + + operator <=(ChatTime other) { + return this.toTimeStamp() <= other.toTimeStamp(); + } + + /// Two message events can belong to the same environment. That means that they + /// don't need to display the time they were sent because they are close + /// enaugh. + static final minutesBetweenEnvironments = 5; + + /// Checks if two ChatTimes are close enough to belong to the same + /// environment. + bool sameEnvironment(ChatTime prevTime) { + return toTimeStamp() - prevTime.toTimeStamp() < + 1000 * 60 * minutesBetweenEnvironments; + } + + /// Returns a simple time String. + String toTimeString() { + return "${_z(dateTime.hour)}:${_z(dateTime.minute)}"; + } + + /// If the ChatTime is today, this returns [toTimeString()], if not it also + /// shows the date. + String toEventTimeString() { + DateTime now = DateTime.now(); + + bool sameYear = now.year == dateTime.year; + + bool sameDay = + sameYear && now.month == dateTime.month && now.day == dateTime.day; + + if (sameDay) return toTimeString(); + return "${toString()}, ${toTimeString()}"; + } + + static String _z(int i) => i < 10 ? "0${i.toString()}" : i.toString(); +} diff --git a/lib/utils/sqflite_store.dart b/lib/utils/sqflite_store.dart index 2c8cfe0..bd7af53 100644 --- a/lib/utils/sqflite_store.dart +++ b/lib/utils/sqflite_store.dart @@ -72,7 +72,7 @@ class Store extends StoreAPI { .rawQuery("SELECT * FROM Clients WHERE client=?", [client.clientName]); if (list.length == 1) { var clientList = list[0]; - client.connection.connect( + client.connect( newToken: clientList["token"], newHomeserver: clientList["homeserver"], newUserID: clientList["matrix_id"], @@ -87,7 +87,7 @@ class Store extends StoreAPI { if (client.debug) print("[Store] Restore client credentials of ${client.userID}"); } else - client.connection.onLoginStateChanged.add(LoginState.loggedOut); + client.onLoginStateChanged.add(LoginState.loggedOut); } Future createTables(Database db) async { @@ -337,7 +337,7 @@ class Store extends StoreAPI { "SELECT * FROM RoomStates WHERE state_key=? AND room_id=?", [matrixID, room.id]); if (res.length != 1) return null; - return RoomState.fromJson(res[0], room).asUser; + return Event.fromJson(res[0], room).asUser; } /// Loads all Users in the database to provide a contact list @@ -348,8 +348,7 @@ class Store extends StoreAPI { [client.userID, exceptRoomID]); List userList = []; for (int i = 0; i < res.length; i++) - userList - .add(RoomState.fromJson(res[i], Room(id: "", client: client)).asUser); + userList.add(Event.fromJson(res[i], Room(id: "", client: client)).asUser); return userList; } @@ -365,7 +364,7 @@ class Store extends StoreAPI { List participants = []; for (num i = 0; i < res.length; i++) { - participants.add(RoomState.fromJson(res[i], room).asUser); + participants.add(Event.fromJson(res[i], room).asUser); } return participants; diff --git a/lib/views/chat_list.dart b/lib/views/chat_list.dart index 6650eea..962c9a5 100644 --- a/lib/views/chat_list.dart +++ b/lib/views/chat_list.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:famedlysdk/famedlysdk.dart'; import 'package:fluffychat/components/adaptive_page_layout.dart'; import 'package:fluffychat/components/dialogs/new_group_dialog.dart'; @@ -33,24 +35,19 @@ class ChatList extends StatefulWidget { } class _ChatListState extends State { - RoomList roomList; + StreamSubscription sub; - Future> getRooms(BuildContext context) async { + Future waitForFirstSync(BuildContext context) async { Client client = Matrix.of(context).client; - if (roomList != null) return roomList.rooms; if (client.prevBatch?.isEmpty ?? true) - await client.connection.onFirstSync.stream.first; - roomList = client.getRoomList(onUpdate: () { - setState(() {}); - }); - return roomList.rooms; + await client.onFirstSync.stream.first; + sub ??= client.onSync.stream.listen((s) => setState(() => null)); + return true; } @override void dispose() { - roomList?.eventSub?.cancel(); - roomList?.firstSyncSub?.cancel(); - roomList?.roomSub?.cancel(); + sub?.cancel(); super.dispose(); } @@ -112,11 +109,11 @@ class _ChatListState extends State { ), ], ), - body: FutureBuilder>( - future: getRooms(context), + body: FutureBuilder( + future: waitForFirstSync(context), builder: (BuildContext context, snapshot) { if (snapshot.hasData) { - List rooms = snapshot.data; + List rooms = Matrix.of(context).client.rooms; return ListView.builder( itemCount: rooms.length, itemBuilder: (BuildContext context, int i) => ChatListItem( diff --git a/lib/views/invitation_selection.dart b/lib/views/invitation_selection.dart index 545d5a2..c0f10ce 100644 --- a/lib/views/invitation_selection.dart +++ b/lib/views/invitation_selection.dart @@ -16,8 +16,8 @@ class InvitationSelection extends StatelessWidget { List participants = await room.requestParticipants(); List contacts = []; Map userMap = {}; - for (int i = 0; i < client.roomList.rooms.length; i++) { - List roomUsers = client.roomList.rooms[i].getParticipants(); + for (int i = 0; i < client.rooms.length; i++) { + List roomUsers = client.rooms[i].getParticipants(); for (int j = 0; j < roomUsers.length; j++) { if (userMap[roomUsers[j].id] != true && participants.indexWhere((u) => u.id == roomUsers[j].id) == -1) diff --git a/lib/views/settings.dart b/lib/views/settings.dart index 344ceab..033aeff 100644 --- a/lib/views/settings.dart +++ b/lib/views/settings.dart @@ -43,7 +43,7 @@ class _SettingsState extends State { final MatrixState matrix = Matrix.of(context); final Map success = await matrix.tryRequestWithLoadingDialog( - matrix.client.connection.jsonRequest( + matrix.client.jsonRequest( type: HTTPType.PUT, action: "/client/r0/profile/${matrix.client.userID}/displayname", data: {"displayname": displayname}, diff --git a/pubspec.lock b/pubspec.lock index 41fdbe0..982c066 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -82,8 +82,8 @@ packages: dependency: "direct main" description: path: "." - ref: "4cbfec1ac4c13e6cc72ee98e8290fd802a96cf89" - resolved-ref: "4cbfec1ac4c13e6cc72ee98e8290fd802a96cf89" + ref: "920d7144ecd32b8d1077abbfec9f7552362e42a8" + resolved-ref: "920d7144ecd32b8d1077abbfec9f7552362e42a8" url: "https://gitlab.com/famedly/famedlysdk.git" source: git version: "0.0.1" diff --git a/pubspec.yaml b/pubspec.yaml index d9947fb..c8974e6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,7 +29,7 @@ dependencies: famedlysdk: git: url: https://gitlab.com/famedly/famedlysdk.git - ref: 4cbfec1ac4c13e6cc72ee98e8290fd802a96cf89 + ref: 920d7144ecd32b8d1077abbfec9f7552362e42a8 localstorage: ^3.0.1+4 bubble: ^1.1.9+1