fluffychat merge - resolve conflicts

pull/1384/head
ggurdin 1 year ago
commit 1b03644380
No known key found for this signature in database
GPG Key ID: A01CB41737CBB478

@ -1,3 +1,88 @@
## v1.22.0
FluffyChat v1.22.0 brings a new design for spaces, replaces the bottom navigation bar with filter chips and makes it finally possible to play ogg audio messages on iOS. A lot of other fixes and improvements have also been added to this release.
- build: (deps): bump docker/build-push-action from 5 to 6 (dependabot[bot])
- build(deps): bump rexml from 3.2.8 to 3.3.3 in /ios (dependabot[bot])
- build: Remove permissions for screensharing until it is fixed (Krille)
- build: Update android target sdk to 34 (Krille)
- build: Update dependencies after release (krille-chan)
- build: Update to Flutter 3.22.3 (krille-chan)
- build: Update to Matrix SDK 0.32.0 (Krille)
- chore: Bring back add to space feature (Krille)
- chore: Bring back navrail (krille-chan)
- chore: Bring back separate chat types (krille-chan)
- chore: Chat permissions page follow up (krille-chan)
- chore: Do not hide error on file sending (Krille)
- chore: Improved create group and space design (Krille)
- chore: Make VOIP plugin less noisy in logs (krille-chan)
- chore: Move default PR template to correct dir (krille-chan)
- chore: nicer bottom sheets (krille-chan)
- chore: Nicer empty chat list placeholder (krille-chan)
- chore: Polish public room bottom sheet (krille-chan)
- chore: Show short forms of months and week days in UI (krille-chan)
- chore: Sligthly improve chat permissions page design (krille-chan)
- design: Add snackbar with link to changelog on new version (Krille)
- docs: Update privacy policy (krille-chan)
- feat: Convert opus to aac on iOS before playing (Krille)
- feat: New spaces and chat list design (krille-chan)
- feat: Record voice message with opus/ogg if supported (Krille)
- feat: Send voice messages from web (Krille)
- fix: Display only available join rules (Krille)
- fix: Path correct userId to ignore list (krille-chan)
- fix: Scroll to event missing the position (Krille)
- Fix web base url and privacy url configuration processing (dlyrsk)
- refactor: Clean up some widths (krille-chan)
- refactor: Design polishment and better user viewer (Krille)
- refactor: Migrate android gradle plugin (Krille)
- refactor: Only initialize FlutterLocalNotificationsPlugin once (krille-chan)
- refactor: Recording dialog (Krille)
- Refactor: Reduce .of(context) calls theme (Thomas Klein Langenhorst)
- refactor: Use cached network image for mxc image uris (Krille)
- Translated using Weblate (Arabic) (kdh8219)
- Translated using Weblate (Arabic) (Rex_sa)
- Translated using Weblate (Basque) (kdh8219)
- Translated using Weblate (Basque) (xabirequejo)
- Translated using Weblate (Chinese (Simplified)) (kdh8219)
- Translated using Weblate (Chinese (Simplified)) (大王叫我来巡山)
- Translated using Weblate (Chinese (Traditional)) (kdh8219)
- Translated using Weblate (Chinese (Traditional)) (Lukas)
- Translated using Weblate (Chinese (Traditional)) (Ricky From Hong Kong)
- Translated using Weblate (Chinese (Traditional)) (不知火 Shiranui)
- Translated using Weblate (Croatian) (Milo Ivir)
- Translated using Weblate (Czech) (Anonymous)
- Translated using Weblate (Czech) (Michal Bedáň)
- Translated using Weblate (Dutch) (Guacamolie)
- Translated using Weblate (Dutch) (Jelv)
- Translated using Weblate (Dutch) (Thomas Klein Langenhorst)
- Translated using Weblate (Esperanto) (Anonymous)
- Translated using Weblate (Estonian) (kdh8219)
- Translated using Weblate (Estonian) (Priit Jõerüüt)
- Translated using Weblate (Finnish) (Anonymous)
- Translated using Weblate (French) (Sovkipyk)
- Translated using Weblate (Galician) (josé m)
- Translated using Weblate (German) (Christian)
- Translated using Weblate (German) (Pixelcode)
- Translated using Weblate (German) (tct123)
- Translated using Weblate (Hebrew) (Anonymous)
- Translated using Weblate (Indonesian) (Linerly)
- Translated using Weblate (Irish) (Anonymous)
- Translated using Weblate (Japanese) (Anonymous)
- Translated using Weblate (Korean) (kdh8219)
- Translated using Weblate (Lithuanian) (Anonymous)
- Translated using Weblate (Norwegian Bokmål) (Anonymous)
- Translated using Weblate (Persian) (Anonymous)
- Translated using Weblate (Portuguese (Portugal)) (Anonymous)
- Translated using Weblate (Romanian) (Anonymous)
- Translated using Weblate (Russian) (-)
- Translated using Weblate (Serbian) (Anonymous)
- Translated using Weblate (Slovenian) (Anonymous)
- Translated using Weblate (Spanish) (Anonymous)
- Translated using Weblate (Turkish) (kdh8219)
- Translated using Weblate (Turkish) (Oğuz Ersen)
- Translated using Weblate (Ukrainian) (Bezruchenko Simon)
- Translated using Weblate (Ukrainian) (Ihor Hordiichuk)
## v1.21.2
Updates the Matrix Dart SDK to fix some minor bugs.

@ -3,31 +3,29 @@
FluffyChat is available on Android, iOS and as a web version. Desktop versions for Windows, Linux and macOS may follow.
* [Matrix](#matrix)
* Sentry
* [Database](#database)
* [Encryption](#encryption)
* [App Permissions](#app-permissions)
* [Push Notifications](#push-notifications)
* [Stories](#stories)
## Matrix<a id="matrix"/>
FluffyChat uses the Matrix protocol. This means that FluffyChat is just a client that can be connected to any compatible matrix server. The respective data protection agreement of the server selected by the user then applies.
For convenience, one or more servers are set as default that the FluffyChat developers consider trustworthy. The developers of FluffyChat do not guarantee their trustworthiness. Before the first communication, users are informed which server they are connecting to.
FluffyChat only communicates with the selected server, with sentry.io if enabled and with [OpenStreetMap](https://openstreetmap.org) to display maps.
FluffyChat only communicates with the selected server and with [OpenStreetMap](https://openstreetmap.org) to display maps.
More information is available at: [https://matrix.org](https://matrix.org)
## Database<a id="database"/>
FluffyChat caches some data received from the server in a local database on the device of the user.
FluffyChat caches some data received from the server in a local sqflite database on the device of the user. On web indexedDB is used. FluffyChat always tries to encrypt the database by using SQLCipher and stores the encryption key in the [Secure Storage](https://pub.dev/packages/flutter_secure_storage) of the device.
More information is available at: [https://pub.dev/packages/hive](https://pub.dev/packages/hive)
More information is available at: [https://pub.dev/packages/sqflite](https://pub.dev/packages/sqflite) and [https://pub.dev/packages/sqlcipher_flutter_libs](https://pub.dev/packages/sqlcipher_flutter_libs)
## Encryption<a id="encryption"/>
All communication of substantive content between Fluffychat and any server is done in secure way, using transport encryption to protect it.
FluffyChat is able to use End-To-End-Encryption as a tech preview.
FluffyChat also uses End-To-End-Encryption by using [libolm](https://gitlab.matrix.org/matrix-org/olm) and enables it by default for private chats.
## App Permissions<a id="app-permissions"/>
@ -94,23 +92,3 @@ A typical push notification could look like this:
```
FluffyChat sets the `event_id_only` flag at the Matrix Server. This server is then responsible to send the correct data.
## Stories<a id="stories"/>
FluffyChat supports stories which is a feature similar to WhatsApp status or Instagram stories. However it is just a different GUI for the same room-related communication. More information about the feature can be found here:
https://github.com/krillefear/matrix-doc/blob/main/proposals/3588-stories-as-rooms.md
Stories are basically:
- End to end encrypted rooms
- Read-only rooms with only one admin who can post stuff (while there is no technical limitation to have multiple admins)
By default:
- The user has to invite all contacts manually to a story room
- The user can only invite contacts (matrix users the user shares a DM room with) to the story room
- The story room is created when the first story is posted
- User can mute and leave story rooms
The user is informed in the app that in theory all contacts can see each other in the story room. The user must give consent here. However the user is at any time able to create a group chat and invite all of their contacts to this chat in any matrix client which has the same result.

@ -2367,5 +2367,17 @@
"accessAndVisibilityDescription": "Kdo se může připojit a najít tuto konverzaci.",
"@accessAndVisibilityDescription": {},
"customEmojisAndStickersBody": "Přidat nebo sdílet vlastní emoji nebo nálepky, které mohou být použité v konverzaci.",
"@customEmojisAndStickersBody": {}
"@customEmojisAndStickersBody": {},
"swipeRightToLeftToReply": "Potáhněte z prava do leva pro odpověď",
"@swipeRightToLeftToReply": {},
"countChatsAndCountParticipants": "{chats} konverzaci a {participants} účastníci",
"@countChatsAndCountParticipants": {
"type": "text",
"placeholders": {
"chats": {},
"participants": {}
}
},
"noMoreChatsFound": "Žádné další konverzace nalezeny...",
"@noMoreChatsFound": {}
}

@ -210,6 +210,7 @@
}
},
"noMoreChatsFound": "No more chats found...",
"noChatsFoundHere": "No chats found here yet. Start a new chat with someone by using the button below. ⤵️",
"joinedChats": "Joined chats",
"unread": "Unread",
"space": "Space",

@ -125,7 +125,7 @@ class ChatController extends State<ChatPageWithRoom>
Timeline? timeline;
String? readMarkerEventId;
late final String readMarkerEventId;
String get roomId => widget.room.id;
@ -310,6 +310,7 @@ class ChatController extends State<ChatPageWithRoom>
);
sendingClient = Matrix.of(context).client;
readMarkerEventId = room.hasNewMessages ? room.fullyRead : '';
WidgetsBinding.instance.addObserver(this);
// #Pangea
if (!mounted) return;
@ -346,19 +347,22 @@ class ChatController extends State<ChatPageWithRoom>
await loadTimelineFuture;
if (initialEventId != null) scrollToEventId(initialEventId);
final fullyRead = room.fullyRead;
if (fullyRead.isEmpty) {
setReadMarker();
return;
}
if (timeline?.events.any((event) => event.eventId == fullyRead) ??
false) {
Logs().v('Scroll up to visible event', fullyRead);
scrollToEventId(fullyRead, highlightEvent: false);
final readMarkerEventIndex = readMarkerEventId.isEmpty
? -1
: timeline!.events
.where((e) => e.isVisibleInGui)
.toList()
.indexWhere((e) => e.eventId == readMarkerEventId);
if (readMarkerEventIndex > 1) {
Logs().v('Scroll up to visible event', readMarkerEventId);
scrollToEventId(readMarkerEventId, highlightEvent: false);
return;
} else if (readMarkerEventId.isNotEmpty && readMarkerEventIndex == -1) {
showScrollUpMaterialBanner(readMarkerEventId);
}
if (!mounted) return;
showScrollUpMaterialBanner(fullyRead);
} catch (e, s) {
ErrorReporter(context, 'Unable to load timeline').onErrorCallback(e, s);
rethrow;
@ -385,13 +389,16 @@ class ChatController extends State<ChatPageWithRoom>
int? animateInEventIndex;
void onInsert(int i) {
onChange(i);
// setState will be called by updateView() anyway
animateInEventIndex = i;
}
void onChange(int i) {
if (timeline?.events[i].status == EventStatus.synced) {
final index = timeline!.events.firstIndexWhereNotError;
if (i == index) setReadMarker(eventId: timeline?.events[i].eventId);
}
// setState will be called by updateView() anyway
animateInEventIndex = i;
}
// #Pangea
@ -419,6 +426,7 @@ class ChatController extends State<ChatPageWithRoom>
onUpdate: updateView,
eventContextId: eventContextId,
onInsert: onInsert,
onChange: onChange,
);
// #Pangea
if (visibleEvents.length < 10 && timeline != null) {
@ -440,6 +448,7 @@ class ChatController extends State<ChatPageWithRoom>
timeline = await room.getTimeline(
onUpdate: updateView,
onInsert: onInsert,
onChange: onChange,
);
if (!mounted) return;
if (e is TimeoutException || e is IOException) {
@ -466,6 +475,7 @@ class ChatController extends State<ChatPageWithRoom>
if (setReadMarkerFuture != null) return;
if (_scrolledUp) return;
if (scrollUpBannerEventId != null) return;
if (eventId == null &&
!room.hasNewMessages &&
room.notificationCount == 0) {

@ -180,8 +180,7 @@ class ChatEventList extends StatelessWidget {
.any((e) => e.eventId == event.eventId),
timeline: controller.timeline!,
displayReadMarker:
controller.readMarkerEventId == event.eventId &&
controller.timeline?.allowNewEvent == false,
i > 0 && controller.readMarkerEventId == event.eventId,
nextEvent: i + 1 < events.length ? events[i + 1] : null,
previousEvent: i > 0 ? events[i - 1] : null,
avatarPresenceBackgroundColor:

@ -2,15 +2,16 @@ import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pages/chat_list/chat_list.dart';
import 'package:fluffychat/pages/chat_list/chat_list_header.dart';
import 'package:fluffychat/pages/chat_list/chat_list_item.dart';
import 'package:fluffychat/pages/chat_list/dummy_chat_list_item.dart';
import 'package:fluffychat/pages/chat_list/search_title.dart';
import 'package:fluffychat/pages/chat_list/space_view.dart';
import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart';
import 'package:fluffychat/pangea/widgets/chat_list/chat_list_body_text.dart';
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
import 'package:fluffychat/utils/stream_extension.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/hover_builder.dart';
import 'package:fluffychat/widgets/public_room_bottom_sheet.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart';
@ -59,8 +60,6 @@ class ChatListViewBody extends StatelessWidget {
.toList();
final userSearchResult = controller.userSearchResult;
const dummyChatCount = 4;
final titleColor = theme.textTheme.bodyLarge!.color!.withAlpha(100);
final subtitleColor = theme.textTheme.bodyLarge!.color!.withAlpha(50);
final filter = controller.searchController.text.toLowerCase();
return StreamBuilder(
key: ValueKey(
@ -238,11 +237,46 @@ class ChatListViewBody extends StatelessWidget {
if (client.prevBatch != null &&
rooms.isEmpty &&
!controller.isSearchMode) ...[
// #Pangea
Center(
child: ChatListBodyStartText(
controller: controller,
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Stack(
alignment: Alignment.center,
children: [
const Column(
mainAxisSize: MainAxisSize.min,
children: [
DummyChatListItem(
opacity: 0.5,
animate: false,
),
DummyChatListItem(
opacity: 0.3,
animate: false,
),
],
),
Icon(
CupertinoIcons.chat_bubble_text_fill,
size: 128,
color: theme.colorScheme.secondary,
),
],
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
client.rooms.isEmpty
? L10n.of(context)!.noChatsFoundHere
: L10n.of(context)!.noMoreChatsFound,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18,
color: theme.colorScheme.secondary,
),
),
),
],
),
// Padding(
// padding: const EdgeInsets.all(32.0),
@ -260,56 +294,9 @@ class ChatListViewBody extends StatelessWidget {
if (client.prevBatch == null)
SliverList(
delegate: SliverChildBuilderDelegate(
(context, i) => Opacity(
(context, i) => DummyChatListItem(
opacity: (dummyChatCount - i) / dummyChatCount,
child: ListTile(
leading: CircleAvatar(
backgroundColor: titleColor,
child: CircularProgressIndicator(
strokeWidth: 1,
color: theme.textTheme.bodyLarge!.color,
),
),
title: Row(
children: [
Expanded(
child: Container(
height: 14,
decoration: BoxDecoration(
color: titleColor,
borderRadius: BorderRadius.circular(3),
),
),
),
const SizedBox(width: 36),
Container(
height: 14,
width: 14,
decoration: BoxDecoration(
color: subtitleColor,
borderRadius: BorderRadius.circular(14),
),
),
const SizedBox(width: 12),
Container(
height: 14,
width: 14,
decoration: BoxDecoration(
color: subtitleColor,
borderRadius: BorderRadius.circular(14),
),
),
],
),
subtitle: Container(
decoration: BoxDecoration(
color: subtitleColor,
borderRadius: BorderRadius.circular(3),
),
height: 12,
margin: const EdgeInsets.only(right: 22),
),
),
animate: true,
),
childCount: dummyChatCount,
),

@ -0,0 +1,72 @@
import 'package:flutter/material.dart';
class DummyChatListItem extends StatelessWidget {
final double opacity;
final bool animate;
const DummyChatListItem({
required this.opacity,
required this.animate,
super.key,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final titleColor = theme.textTheme.bodyLarge!.color!.withAlpha(100);
final subtitleColor = theme.textTheme.bodyLarge!.color!.withAlpha(50);
return Opacity(
opacity: opacity,
child: ListTile(
leading: CircleAvatar(
backgroundColor: titleColor,
child: animate
? CircularProgressIndicator(
strokeWidth: 1,
color: theme.textTheme.bodyLarge!.color,
)
: const SizedBox.shrink(),
),
title: Row(
children: [
Expanded(
child: Container(
height: 14,
decoration: BoxDecoration(
color: titleColor,
borderRadius: BorderRadius.circular(3),
),
),
),
const SizedBox(width: 36),
Container(
height: 14,
width: 14,
decoration: BoxDecoration(
color: subtitleColor,
borderRadius: BorderRadius.circular(14),
),
),
const SizedBox(width: 12),
Container(
height: 14,
width: 14,
decoration: BoxDecoration(
color: subtitleColor,
borderRadius: BorderRadius.circular(14),
),
),
],
),
subtitle: Container(
decoration: BoxDecoration(
color: subtitleColor,
borderRadius: BorderRadius.circular(3),
),
height: 12,
margin: const EdgeInsets.only(right: 22),
),
),
);
}
}

@ -56,19 +56,14 @@ extension DateTimeExtension on DateTime {
if (sameDay) {
return localizedTimeOfDay(context);
} else if (sameWeek) {
return DateFormat.EEEE(Localizations.localeOf(context).languageCode)
return DateFormat.E(Localizations.localeOf(context).languageCode)
.format(this);
} else if (sameYear) {
return L10n.of(context)!.dateWithoutYear(
month.toString().padLeft(2, '0'),
day.toString().padLeft(2, '0'),
);
return DateFormat.MMMd(Localizations.localeOf(context).languageCode)
.format(this);
}
return L10n.of(context)!.dateWithYear(
year.toString(),
month.toString().padLeft(2, '0'),
day.toString().padLeft(2, '0'),
);
return DateFormat.yMMMd(Localizations.localeOf(context).languageCode)
.format(this);
}
/// If the DateTime is today, this returns [localizedTimeOfDay()], if not it also

@ -36,7 +36,6 @@ class VoipPlugin with WidgetsBindingObserver implements WebRTCDelegate {
void didChangeAppLifecycleState(AppLifecycleState? state) {
background = (state == AppLifecycleState.detached ||
state == AppLifecycleState.paused);
Logs().w('Set background mode in VOIP plugin', background);
}
void addCallingOverlay(String callId, CallSession call) {

Loading…
Cancel
Save