mirror of
https://gitlab.com/famedly/fluffychat.git
synced 2025-01-12 02:32:54 +01:00
feat: Groups and Direct Chats virtual spaces option
This commit is contained in:
parent
e93218d862
commit
af06611efd
@ -2083,6 +2083,11 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
|
"separateChatTypes": "Separate Direct Chats, Groups, and Spaces",
|
||||||
|
"@separateChatTypes": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
"setAProfilePicture": "Set a profile picture",
|
"setAProfilePicture": "Set a profile picture",
|
||||||
"@setAProfilePicture": {
|
"@setAProfilePicture": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -36,6 +36,7 @@ abstract class AppConfig {
|
|||||||
static bool renderHtml = true;
|
static bool renderHtml = true;
|
||||||
static bool hideRedactedEvents = false;
|
static bool hideRedactedEvents = false;
|
||||||
static bool hideUnknownEvents = true;
|
static bool hideUnknownEvents = true;
|
||||||
|
static bool separateChatTypes = false;
|
||||||
static bool autoplayImages = true;
|
static bool autoplayImages = true;
|
||||||
static bool sendOnEnter = false;
|
static bool sendOnEnter = false;
|
||||||
static bool experimentalVoip = false;
|
static bool experimentalVoip = false;
|
||||||
|
@ -3,6 +3,7 @@ abstract class SettingKeys {
|
|||||||
static const String renderHtml = 'chat.fluffy.renderHtml';
|
static const String renderHtml = 'chat.fluffy.renderHtml';
|
||||||
static const String hideRedactedEvents = 'chat.fluffy.hideRedactedEvents';
|
static const String hideRedactedEvents = 'chat.fluffy.hideRedactedEvents';
|
||||||
static const String hideUnknownEvents = 'chat.fluffy.hideUnknownEvents';
|
static const String hideUnknownEvents = 'chat.fluffy.hideUnknownEvents';
|
||||||
|
static const String separateChatTypes = 'chat.fluffy.separateChatTypes';
|
||||||
static const String chatColor = 'chat.fluffy.chat_color';
|
static const String chatColor = 'chat.fluffy.chat_color';
|
||||||
static const String sentry = 'sentry';
|
static const String sentry = 'sentry';
|
||||||
static const String theme = 'theme';
|
static const String theme = 'theme';
|
||||||
|
@ -13,8 +13,8 @@ import 'package:vrouter/vrouter.dart';
|
|||||||
|
|
||||||
import 'package:fluffychat/config/app_config.dart';
|
import 'package:fluffychat/config/app_config.dart';
|
||||||
import 'package:fluffychat/pages/chat_list/chat_list_view.dart';
|
import 'package:fluffychat/pages/chat_list/chat_list_view.dart';
|
||||||
|
import 'package:fluffychat/pages/chat_list/spaces_entry.dart';
|
||||||
import 'package:fluffychat/utils/fluffy_share.dart';
|
import 'package:fluffychat/utils/fluffy_share.dart';
|
||||||
import 'package:fluffychat/utils/matrix_sdk_extensions.dart/client_stories_extension.dart';
|
|
||||||
import 'package:fluffychat/utils/platform_infos.dart';
|
import 'package:fluffychat/utils/platform_infos.dart';
|
||||||
import '../../../utils/account_bundles.dart';
|
import '../../../utils/account_bundles.dart';
|
||||||
import '../../main.dart';
|
import '../../main.dart';
|
||||||
@ -47,15 +47,15 @@ class ChatListController extends State<ChatList> {
|
|||||||
|
|
||||||
StreamSubscription? _intentUriStreamSubscription;
|
StreamSubscription? _intentUriStreamSubscription;
|
||||||
|
|
||||||
String? _activeSpaceId;
|
SpacesEntry? _activeSpacesEntry;
|
||||||
|
|
||||||
String? get activeSpaceId {
|
SpacesEntry get activeSpacesEntry {
|
||||||
final id = _activeSpaceId;
|
final id = _activeSpacesEntry;
|
||||||
return id != null && Matrix.of(context).client.getRoomById(id) == null
|
return (id == null || !id.stillValid(context)) ? defaultSpacesEntry : id;
|
||||||
? null
|
|
||||||
: _activeSpaceId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String? get activeSpaceId => activeSpacesEntry.getSpace(context)?.id;
|
||||||
|
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
bool scrolledToTop = true;
|
bool scrolledToTop = true;
|
||||||
|
|
||||||
@ -72,8 +72,8 @@ class ChatListController extends State<ChatList> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setActiveSpaceId(BuildContext context, String? spaceId) {
|
void setActiveSpacesEntry(BuildContext context, SpacesEntry spaceId) {
|
||||||
setState(() => _activeSpaceId = spaceId);
|
setState(() => _activeSpacesEntry = spaceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void editSpace(BuildContext context, String spaceId) async {
|
void editSpace(BuildContext context, String spaceId) async {
|
||||||
@ -81,9 +81,30 @@ class ChatListController extends State<ChatList> {
|
|||||||
VRouter.of(context).toSegments(['spaces', spaceId]);
|
VRouter.of(context).toSegments(['spaces', spaceId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Needs to match GroupsSpacesEntry for 'separate group' checking.
|
||||||
List<Room> get spaces =>
|
List<Room> get spaces =>
|
||||||
Matrix.of(context).client.rooms.where((r) => r.isSpace).toList();
|
Matrix.of(context).client.rooms.where((r) => r.isSpace).toList();
|
||||||
|
|
||||||
|
// Note that this could change due to configuration, etc.
|
||||||
|
// Also be aware that _activeSpacesEntry = null is the expected reset method.
|
||||||
|
SpacesEntry get defaultSpacesEntry => AppConfig.separateChatTypes
|
||||||
|
? DirectChatsSpacesEntry()
|
||||||
|
: AllRoomsSpacesEntry();
|
||||||
|
|
||||||
|
List<SpacesEntry> get spacesEntries {
|
||||||
|
if (AppConfig.separateChatTypes) {
|
||||||
|
return [
|
||||||
|
defaultSpacesEntry,
|
||||||
|
GroupsSpacesEntry(),
|
||||||
|
...spaces.map((space) => SpaceSpacesEntry(space)).toList()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
defaultSpacesEntry,
|
||||||
|
...spaces.map((space) => SpaceSpacesEntry(space)).toList()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
final selectedRoomIds = <String>{};
|
final selectedRoomIds = <String>{};
|
||||||
bool? crossSigningCached;
|
bool? crossSigningCached;
|
||||||
bool showChatBackupBanner = false;
|
bool showChatBackupBanner = false;
|
||||||
@ -206,35 +227,6 @@ class ChatListController extends State<ChatList> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool roomCheck(Room room) {
|
|
||||||
if (room.isSpace && room.membership == Membership.join && !room.isUnread) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (room.getState(EventTypes.RoomCreate)?.content.tryGet<String>('type') ==
|
|
||||||
ClientStoriesExtension.storiesRoomType) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (activeSpaceId != null) {
|
|
||||||
final space = Matrix.of(context).client.getRoomById(activeSpaceId!)!;
|
|
||||||
if (space.spaceChildren.any((child) => child.roomId == room.id)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (room.spaceParents.any((parent) => parent.roomId == activeSpaceId)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (room.isDirectChat &&
|
|
||||||
room.summary.mHeroes != null &&
|
|
||||||
room.summary.mHeroes!.any((userId) {
|
|
||||||
final user = space.getState(EventTypes.RoomMember, userId)?.asUser;
|
|
||||||
return user != null && user.membership == Membership.join;
|
|
||||||
})) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void toggleSelection(String roomId) {
|
void toggleSelection(String roomId) {
|
||||||
setState(() => selectedRoomIds.contains(roomId)
|
setState(() => selectedRoomIds.contains(roomId)
|
||||||
? selectedRoomIds.remove(roomId)
|
? selectedRoomIds.remove(roomId)
|
||||||
@ -370,7 +362,8 @@ class ChatListController extends State<ChatList> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addOrRemoveToSpace() async {
|
Future<void> addOrRemoveToSpace() async {
|
||||||
if (activeSpaceId != null) {
|
final id = activeSpaceId;
|
||||||
|
if (id != null) {
|
||||||
final consent = await showOkCancelAlertDialog(
|
final consent = await showOkCancelAlertDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context)!.removeFromSpace,
|
title: L10n.of(context)!.removeFromSpace,
|
||||||
@ -382,7 +375,7 @@ class ChatListController extends State<ChatList> {
|
|||||||
);
|
);
|
||||||
if (consent != OkCancelResult.ok) return;
|
if (consent != OkCancelResult.ok) return;
|
||||||
|
|
||||||
final space = Matrix.of(context).client.getRoomById(activeSpaceId!);
|
final space = Matrix.of(context).client.getRoomById(id);
|
||||||
final result = await showFutureLoadingDialog(
|
final result = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () async {
|
future: () async {
|
||||||
@ -458,8 +451,9 @@ class ChatListController extends State<ChatList> {
|
|||||||
await client.onFirstSync.stream.first;
|
await client.onFirstSync.stream.first;
|
||||||
}
|
}
|
||||||
// Load space members to display DM rooms
|
// Load space members to display DM rooms
|
||||||
if (activeSpaceId != null) {
|
final spaceId = activeSpaceId;
|
||||||
final space = client.getRoomById(activeSpaceId!)!;
|
if (spaceId != null) {
|
||||||
|
final space = client.getRoomById(spaceId)!;
|
||||||
final localMembers = space.getParticipants().length;
|
final localMembers = space.getParticipants().length;
|
||||||
final actualMembersCount = (space.summary.mInvitedMemberCount ?? 0) +
|
final actualMembersCount = (space.summary.mInvitedMemberCount ?? 0) +
|
||||||
(space.summary.mJoinedMemberCount ?? 0);
|
(space.summary.mJoinedMemberCount ?? 0);
|
||||||
@ -485,7 +479,7 @@ class ChatListController extends State<ChatList> {
|
|||||||
void setActiveClient(Client client) {
|
void setActiveClient(Client client) {
|
||||||
VRouter.of(context).to('/rooms');
|
VRouter.of(context).to('/rooms');
|
||||||
setState(() {
|
setState(() {
|
||||||
_activeSpaceId = null;
|
_activeSpacesEntry = null;
|
||||||
selectedRoomIds.clear();
|
selectedRoomIds.clear();
|
||||||
Matrix.of(context).setActiveClient(client);
|
Matrix.of(context).setActiveClient(client);
|
||||||
});
|
});
|
||||||
@ -495,7 +489,7 @@ class ChatListController extends State<ChatList> {
|
|||||||
void setActiveBundle(String bundle) {
|
void setActiveBundle(String bundle) {
|
||||||
VRouter.of(context).to('/rooms');
|
VRouter.of(context).to('/rooms');
|
||||||
setState(() {
|
setState(() {
|
||||||
_activeSpaceId = null;
|
_activeSpacesEntry = null;
|
||||||
selectedRoomIds.clear();
|
selectedRoomIds.clear();
|
||||||
Matrix.of(context).activeBundle = bundle;
|
Matrix.of(context).activeBundle = bundle;
|
||||||
if (!Matrix.of(context)
|
if (!Matrix.of(context)
|
||||||
|
@ -294,11 +294,7 @@ class _ChatListViewBodyState extends State<_ChatListViewBody> {
|
|||||||
Widget child;
|
Widget child;
|
||||||
if (widget.controller.waitForFirstSync &&
|
if (widget.controller.waitForFirstSync &&
|
||||||
Matrix.of(context).client.prevBatch != null) {
|
Matrix.of(context).client.prevBatch != null) {
|
||||||
final rooms = Matrix.of(context)
|
final rooms = widget.controller.activeSpacesEntry.getRooms(context);
|
||||||
.client
|
|
||||||
.rooms
|
|
||||||
.where(widget.controller.roomCheck)
|
|
||||||
.toList();
|
|
||||||
if (rooms.isEmpty) {
|
if (rooms.isEmpty) {
|
||||||
child = Column(
|
child = Column(
|
||||||
key: const ValueKey(null),
|
key: const ValueKey(null),
|
||||||
@ -323,7 +319,8 @@ class _ChatListViewBodyState extends State<_ChatListViewBody> {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
final displayStoriesHeader = widget.controller.activeSpaceId == null;
|
final displayStoriesHeader = widget.controller.activeSpacesEntry
|
||||||
|
.shouldShowStoriesHeader(context);
|
||||||
child = ListView.builder(
|
child = ListView.builder(
|
||||||
key: ValueKey(Matrix.of(context).client.userID.toString() +
|
key: ValueKey(Matrix.of(context).client.userID.toString() +
|
||||||
widget.controller.activeSpaceId.toString()),
|
widget.controller.activeSpaceId.toString()),
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
|
||||||
import 'package:salomon_bottom_bar/salomon_bottom_bar.dart';
|
import 'package:salomon_bottom_bar/salomon_bottom_bar.dart';
|
||||||
|
|
||||||
import 'package:fluffychat/pages/chat_list/chat_list.dart';
|
import 'package:fluffychat/pages/chat_list/chat_list.dart';
|
||||||
|
import 'package:fluffychat/pages/chat_list/spaces_entry.dart';
|
||||||
import 'package:fluffychat/widgets/avatar.dart';
|
import 'package:fluffychat/widgets/avatar.dart';
|
||||||
import 'package:fluffychat/widgets/matrix.dart';
|
import 'package:fluffychat/widgets/matrix.dart';
|
||||||
|
|
||||||
@ -14,11 +13,9 @@ class SpacesBottomBar extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final currentIndex = controller.activeSpaceId == null
|
final foundIndex = controller.spacesEntries.indexWhere(
|
||||||
? 0
|
(se) => spacesEntryRoughEquivalence(se, controller.activeSpacesEntry));
|
||||||
: controller.spaces
|
final currentIndex = foundIndex == -1 ? 0 : foundIndex;
|
||||||
.indexWhere((space) => controller.activeSpaceId == space.id) +
|
|
||||||
1;
|
|
||||||
return Material(
|
return Material(
|
||||||
color: Theme.of(context).appBarTheme.backgroundColor,
|
color: Theme.of(context).appBarTheme.backgroundColor,
|
||||||
elevation: 6,
|
elevation: 6,
|
||||||
@ -39,39 +36,14 @@ class SpacesBottomBar extends StatelessWidget {
|
|||||||
child: SalomonBottomBar(
|
child: SalomonBottomBar(
|
||||||
itemPadding: const EdgeInsets.all(8),
|
itemPadding: const EdgeInsets.all(8),
|
||||||
currentIndex: currentIndex,
|
currentIndex: currentIndex,
|
||||||
onTap: (i) => controller.setActiveSpaceId(
|
onTap: (i) => controller.setActiveSpacesEntry(
|
||||||
context,
|
context,
|
||||||
i == 0 ? null : controller.spaces[i - 1].id,
|
controller.spacesEntries[i],
|
||||||
),
|
),
|
||||||
selectedItemColor: Theme.of(context).colorScheme.primary,
|
selectedItemColor: Theme.of(context).colorScheme.primary,
|
||||||
items: [
|
items: controller.spacesEntries
|
||||||
SalomonBottomBarItem(
|
.map((entry) => _buildSpacesEntryUI(context, entry))
|
||||||
icon: const Icon(CupertinoIcons.chat_bubble_2),
|
|
||||||
activeIcon:
|
|
||||||
const Icon(CupertinoIcons.chat_bubble_2_fill),
|
|
||||||
title: Text(L10n.of(context)!.allChats),
|
|
||||||
),
|
|
||||||
...controller.spaces
|
|
||||||
.map((space) => SalomonBottomBarItem(
|
|
||||||
icon: InkWell(
|
|
||||||
borderRadius: BorderRadius.circular(28),
|
|
||||||
onTap: () => controller.setActiveSpaceId(
|
|
||||||
context,
|
|
||||||
space.id,
|
|
||||||
),
|
|
||||||
onLongPress: () =>
|
|
||||||
controller.editSpace(context, space.id),
|
|
||||||
child: Avatar(
|
|
||||||
mxContent: space.avatar,
|
|
||||||
name: space.displayname,
|
|
||||||
size: 24,
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
title: Text(space.displayname),
|
|
||||||
))
|
|
||||||
.toList(),
|
.toList(),
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -79,4 +51,33 @@ class SpacesBottomBar extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SalomonBottomBarItem _buildSpacesEntryUI(
|
||||||
|
BuildContext context, SpacesEntry entry) {
|
||||||
|
final space = entry.getSpace(context);
|
||||||
|
if (space != null) {
|
||||||
|
return SalomonBottomBarItem(
|
||||||
|
icon: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(28),
|
||||||
|
onTap: () => controller.setActiveSpacesEntry(
|
||||||
|
context,
|
||||||
|
entry,
|
||||||
|
),
|
||||||
|
onLongPress: () => controller.editSpace(context, space.id),
|
||||||
|
child: Avatar(
|
||||||
|
mxContent: space.avatar,
|
||||||
|
name: space.displayname,
|
||||||
|
size: 24,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
title: Text(entry.getName(context)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return SalomonBottomBarItem(
|
||||||
|
icon: entry.getIcon(false),
|
||||||
|
activeIcon: entry.getIcon(true),
|
||||||
|
title: Text(entry.getName(context)),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
189
lib/pages/chat_list/spaces_entry.dart
Normal file
189
lib/pages/chat_list/spaces_entry.dart
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
|
import 'package:matrix/matrix.dart';
|
||||||
|
|
||||||
|
import 'package:fluffychat/utils/matrix_sdk_extensions.dart/client_stories_extension.dart';
|
||||||
|
import '../../widgets/matrix.dart';
|
||||||
|
|
||||||
|
// This is not necessarily a Space, but an abstract categorization of a room.
|
||||||
|
// More to the point, it's a selectable entry that *could* be a Space.
|
||||||
|
// Note that view code is in spaces_bottom_bar.dart because of type-specific UI.
|
||||||
|
// So only really generic functions (so far, anything ChatList cares about) go here.
|
||||||
|
// If getRoom returns something non-null, then it gets the avatar and such of a Space.
|
||||||
|
// Otherwise it gets to look like All Rooms. Future work impending.
|
||||||
|
abstract class SpacesEntry {
|
||||||
|
const SpacesEntry();
|
||||||
|
|
||||||
|
// Gets the (translated) name of this entry.
|
||||||
|
String getName(BuildContext context);
|
||||||
|
// Gets an icon for this entry (avoided if a space is given)
|
||||||
|
Icon getIcon(bool active) => active
|
||||||
|
? const Icon(CupertinoIcons.chat_bubble_2_fill)
|
||||||
|
: const Icon(CupertinoIcons.chat_bubble_2);
|
||||||
|
// If this is a specific Room, returns the space Room for various purposes.
|
||||||
|
Room? getSpace(BuildContext context) => null;
|
||||||
|
// Gets a list of rooms - this is done as part of _ChatListViewBodyState to get the full list of rooms visible from this SpacesEntry.
|
||||||
|
List<Room> getRooms(BuildContext context);
|
||||||
|
// Checks that this entry is still valid.
|
||||||
|
bool stillValid(BuildContext context) => true;
|
||||||
|
// Returns true if the Stories header should be shown.
|
||||||
|
bool shouldShowStoriesHeader(BuildContext context) => false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common room validity checks
|
||||||
|
bool _roomCheckCommon(Room room, BuildContext context) {
|
||||||
|
if (room.isSpace && room.membership == Membership.join && !room.isUnread) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (room.getState(EventTypes.RoomCreate)?.content.tryGet<String>('type') ==
|
||||||
|
ClientStoriesExtension.storiesRoomType) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _roomInsideSpace(Room room, Room space) {
|
||||||
|
if (space.spaceChildren.any((child) => child.roomId == room.id)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (room.spaceParents.any((parent) => parent.roomId == space.id)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "All rooms" entry.
|
||||||
|
class AllRoomsSpacesEntry extends SpacesEntry {
|
||||||
|
static final AllRoomsSpacesEntry _value = AllRoomsSpacesEntry._();
|
||||||
|
AllRoomsSpacesEntry._();
|
||||||
|
factory AllRoomsSpacesEntry() {
|
||||||
|
return _value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getName(BuildContext context) => L10n.of(context)!.allChats;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Room> getRooms(BuildContext context) {
|
||||||
|
return Matrix.of(context)
|
||||||
|
.client
|
||||||
|
.rooms
|
||||||
|
.where((room) => _roomCheckCommon(room, context))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldShowStoriesHeader(BuildContext context) => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Direct Chats" entry.
|
||||||
|
class DirectChatsSpacesEntry extends SpacesEntry {
|
||||||
|
static final DirectChatsSpacesEntry _value = DirectChatsSpacesEntry._();
|
||||||
|
DirectChatsSpacesEntry._();
|
||||||
|
factory DirectChatsSpacesEntry() {
|
||||||
|
return _value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getName(BuildContext context) => L10n.of(context)!.directChats;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Room> getRooms(BuildContext context) {
|
||||||
|
return Matrix.of(context)
|
||||||
|
.client
|
||||||
|
.rooms
|
||||||
|
.where((room) => room.isDirectChat && _roomCheckCommon(room, context))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldShowStoriesHeader(BuildContext context) => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Groups" entry.
|
||||||
|
class GroupsSpacesEntry extends SpacesEntry {
|
||||||
|
static final GroupsSpacesEntry _value = GroupsSpacesEntry._();
|
||||||
|
GroupsSpacesEntry._();
|
||||||
|
factory GroupsSpacesEntry() {
|
||||||
|
return _value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getName(BuildContext context) => L10n.of(context)!.groups;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Icon getIcon(bool active) =>
|
||||||
|
active ? const Icon(Icons.group) : const Icon(Icons.group_outlined);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Room> getRooms(BuildContext context) {
|
||||||
|
final rooms = Matrix.of(context).client.rooms;
|
||||||
|
// Needs to match ChatList's definition of a space.
|
||||||
|
final spaces = rooms.where((room) => room.isSpace).toList();
|
||||||
|
return rooms
|
||||||
|
.where((room) =>
|
||||||
|
(!room.isDirectChat) &&
|
||||||
|
_roomCheckCommon(room, context) &&
|
||||||
|
separatedGroup(room, spaces))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool separatedGroup(Room room, List<Room> spaces) {
|
||||||
|
return !spaces.any((space) => _roomInsideSpace(room, space));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All rooms associated with a specific space.
|
||||||
|
class SpaceSpacesEntry extends SpacesEntry {
|
||||||
|
final Room space;
|
||||||
|
const SpaceSpacesEntry(this.space);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getName(BuildContext context) => space.displayname;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Room? getSpace(BuildContext context) => space;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Room> getRooms(BuildContext context) {
|
||||||
|
return Matrix.of(context)
|
||||||
|
.client
|
||||||
|
.rooms
|
||||||
|
.where((room) => roomCheck(room, context))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool roomCheck(Room room, BuildContext context) {
|
||||||
|
if (!_roomCheckCommon(room, context)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (_roomInsideSpace(room, space)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (true) {
|
||||||
|
if (room.isDirectChat &&
|
||||||
|
room.summary.mHeroes != null &&
|
||||||
|
room.summary.mHeroes!.any((userId) {
|
||||||
|
final user = space.getState(EventTypes.RoomMember, userId)?.asUser;
|
||||||
|
return user != null && user.membership == Membership.join;
|
||||||
|
})) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool stillValid(BuildContext context) =>
|
||||||
|
Matrix.of(context).client.getRoomById(space.id) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Produces a "rough equivalence" for maintaining the current spaces index.
|
||||||
|
bool spacesEntryRoughEquivalence(SpacesEntry a, SpacesEntry b) {
|
||||||
|
if ((a is SpaceSpacesEntry) && (b is SpaceSpacesEntry)) {
|
||||||
|
return a.space.id == b.space.id;
|
||||||
|
}
|
||||||
|
return a == b;
|
||||||
|
}
|
@ -76,6 +76,13 @@ class SettingsChatView extends StatelessWidget {
|
|||||||
child: Icon(Icons.insert_emoticon_outlined),
|
child: Icon(Icons.insert_emoticon_outlined),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
SettingsSwitchListTile.adaptive(
|
||||||
|
title: L10n.of(context)!.separateChatTypes,
|
||||||
|
onChanged: (b) => AppConfig.separateChatTypes = b,
|
||||||
|
storeKey: SettingKeys.separateChatTypes,
|
||||||
|
defaultValue: AppConfig.separateChatTypes,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -480,6 +480,9 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
|
|||||||
store
|
store
|
||||||
.getItemBool(SettingKeys.hideUnknownEvents, AppConfig.hideUnknownEvents)
|
.getItemBool(SettingKeys.hideUnknownEvents, AppConfig.hideUnknownEvents)
|
||||||
.then((value) => AppConfig.hideUnknownEvents = value);
|
.then((value) => AppConfig.hideUnknownEvents = value);
|
||||||
|
store
|
||||||
|
.getItemBool(SettingKeys.separateChatTypes, AppConfig.separateChatTypes)
|
||||||
|
.then((value) => AppConfig.separateChatTypes = value);
|
||||||
store
|
store
|
||||||
.getItemBool(SettingKeys.autoplayImages, AppConfig.autoplayImages)
|
.getItemBool(SettingKeys.autoplayImages, AppConfig.autoplayImages)
|
||||||
.then((value) => AppConfig.autoplayImages = value);
|
.then((value) => AppConfig.autoplayImages = value);
|
||||||
|
Loading…
Reference in New Issue
Block a user