fluffychat merge - resolve conflicts

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

@ -201,6 +201,19 @@
"supportedVersions": {}
}
},
"countChatsAndCountParticipants": "{chats} chats and {participants} participants",
"@countChatsAndCountParticipants": {
"type": "text",
"placeholders": {
"chats": {},
"participants": {}
}
},
"noMoreChatsFound": "No more chats found...",
"joinedChats": "Joined chats",
"unread": "Unread",
"space": "Space",
"spaces": "Spaces",
"banFromChat": "Ban from chat",
"@banFromChat": {
"type": "text",

@ -71,7 +71,6 @@ abstract class AppConfig {
static bool hideRedactedEvents = false;
static bool hideUnknownEvents = true;
static bool hideUnimportantStateEvents = true;
static bool separateChatTypes = false;
static bool autoplayImages = true;
static bool sendTypingNotifications = true;
static bool sendPublicReadReceipts = true;

@ -136,12 +136,8 @@ abstract class AppRoutes {
FluffyThemes.isColumnMode(context) &&
state.fullPath?.startsWith('/rooms/settings') == false
? TwoColumnLayout(
displayNavigationRail:
state.path?.startsWith('/rooms/settings') != true,
mainView: ChatList(
activeChat: state.pathParameters['roomid'],
displayNavigationRail:
state.path?.startsWith('/rooms/settings') != true,
),
sideView: child,
)
@ -284,7 +280,6 @@ abstract class AppRoutes {
? TwoColumnLayout(
mainView: const Settings(),
sideView: child,
displayNavigationRail: false,
)
: child,
),

@ -4,7 +4,6 @@ abstract class SettingKeys {
static const String hideUnknownEvents = 'chat.fluffy.hideUnknownEvents';
static const String hideUnimportantStateEvents =
'chat.fluffy.hideUnimportantStateEvents';
static const String separateChatTypes = 'chat.fluffy.separateChatTypes';
static const String sentry = 'sentry';
static const String theme = 'theme';
static const String amoledEnabled = 'amoled_enabled';

File diff suppressed because it is too large Load Diff

@ -1,11 +1,11 @@
import 'package:animations/animations.dart';
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/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/pangea/widgets/chat_list/chat_list_header_wrapper.dart';
import 'package:fluffychat/pangea/widgets/chat_list/chat_list_item_wrapper.dart';
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
import 'package:fluffychat/utils/stream_extension.dart';
import 'package:fluffychat/widgets/avatar.dart';
@ -25,6 +25,17 @@ class ChatListViewBody extends StatelessWidget {
@override
Widget build(BuildContext context) {
final activeSpace = controller.activeSpaceId;
if (activeSpace != null) {
return SpaceView(
spaceId: activeSpace,
onBack: controller.clearActiveSpace,
onChatTab: (room) => controller.onChatTap(room, context),
onChatContext: (room) => controller.chatContextAction(room),
activeChat: controller.activeChat,
toParentSpace: controller.setActiveSpace,
);
}
final publicRooms = controller.roomSearchResult?.chunk
.where((room) => room.roomType != 'm.space')
.toList();
@ -39,242 +50,290 @@ class ChatListViewBody extends StatelessWidget {
final subtitleColor =
Theme.of(context).textTheme.bodyLarge!.color!.withAlpha(50);
final filter = controller.searchController.text.toLowerCase();
return PageTransitionSwitcher(
transitionBuilder: (
Widget child,
Animation<double> primaryAnimation,
Animation<double> secondaryAnimation,
) {
return SharedAxisTransition(
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
transitionType: SharedAxisTransitionType.vertical,
fillColor: Theme.of(context).scaffoldBackgroundColor,
child: child,
);
},
child: StreamBuilder(
key: ValueKey(
client.userID.toString() +
controller.activeFilter.toString() +
controller.activeSpaceId.toString(),
),
stream: client.onSync.stream
.where((s) => s.hasRoomUpdate)
.rateLimit(const Duration(seconds: 1)),
builder: (context, _) {
if (controller.activeFilter == ActiveFilter.spaces) {
return SpaceView(
controller,
scrollController: controller.scrollController,
key: Key(controller.activeSpaceId ?? 'Spaces'),
);
return StreamBuilder(
key: ValueKey(
client.userID.toString(),
),
stream: client.onSync.stream
.where((s) => s.hasRoomUpdate)
.rateLimit(const Duration(seconds: 1)),
builder: (context, _) {
final rooms = controller.filteredRooms;
final spaces = rooms.where((r) => r.isSpace);
final spaceDelegateCandidates = <String, Room>{};
for (final space in spaces) {
spaceDelegateCandidates[space.id] = space;
for (final spaceChild in space.spaceChildren) {
final roomId = spaceChild.roomId;
if (roomId == null) continue;
spaceDelegateCandidates[roomId] = space;
}
final rooms = controller.filteredRooms;
return SafeArea(
child: CustomScrollView(
controller: controller.scrollController,
slivers: [
// #Pangea
// ChatListHeader(controller: controller),
ChatListHeaderWrapper(controller: controller),
// Pangea#
SliverList(
delegate: SliverChildListDelegate(
[
if (controller.isSearchMode) ...[
SearchTitle(
title: L10n.of(context)!.publicRooms,
icon: const Icon(Icons.explore_outlined),
),
PublicRoomsHorizontalList(publicRooms: publicRooms),
SearchTitle(
title: L10n.of(context)!.publicSpaces,
icon: const Icon(Icons.workspaces_outlined),
),
PublicRoomsHorizontalList(publicRooms: publicSpaces),
SearchTitle(
title: L10n.of(context)!.users,
icon: const Icon(Icons.group_outlined),
}
final spaceDelegates = <String>{};
return SafeArea(
child: CustomScrollView(
controller: controller.scrollController,
slivers: [
ChatListHeader(controller: controller),
SliverList(
delegate: SliverChildListDelegate(
[
if (controller.isSearchMode) ...[
SearchTitle(
title: L10n.of(context)!.publicRooms,
icon: const Icon(Icons.explore_outlined),
),
PublicRoomsHorizontalList(publicRooms: publicRooms),
SearchTitle(
title: L10n.of(context)!.publicSpaces,
icon: const Icon(Icons.workspaces_outlined),
),
PublicRoomsHorizontalList(publicRooms: publicSpaces),
SearchTitle(
title: L10n.of(context)!.users,
icon: const Icon(Icons.group_outlined),
),
AnimatedContainer(
clipBehavior: Clip.hardEdge,
decoration: const BoxDecoration(),
height: userSearchResult == null ||
userSearchResult.results.isEmpty
? 0
: 106,
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
child: userSearchResult == null
? null
: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: userSearchResult.results.length,
itemBuilder: (context, i) => _SearchItem(
title:
userSearchResult.results[i].displayName ??
userSearchResult
.results[i].userId.localpart ??
L10n.of(context)!.unknownDevice,
avatar: userSearchResult.results[i].avatarUrl,
onPressed: () => showAdaptiveBottomSheet(
context: context,
builder: (c) => UserBottomSheet(
profile: userSearchResult.results[i],
outerContext: context,
),
),
),
),
),
],
// #Pangea
// if (!controller.isSearchMode && AppConfig.showPresences)
// GestureDetector(
// onLongPress: () => controller.dismissStatusList(),
// child: StatusMessageList(
// onStatusEdit: controller.setStatus,
// ),
// ),
// Pangea#
const ConnectionStatusHeader(),
AnimatedContainer(
height: controller.isTorBrowser ? 64 : 0,
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
clipBehavior: Clip.hardEdge,
decoration: const BoxDecoration(),
child: Material(
color: Theme.of(context).colorScheme.surface,
child: ListTile(
leading: const Icon(Icons.vpn_key),
title: Text(L10n.of(context)!.dehydrateTor),
subtitle: Text(L10n.of(context)!.dehydrateTorLong),
trailing: const Icon(Icons.chevron_right_outlined),
onTap: controller.dehydrate,
),
AnimatedContainer(
clipBehavior: Clip.hardEdge,
decoration: const BoxDecoration(),
height: userSearchResult == null ||
userSearchResult.results.isEmpty
? 0
: 106,
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
child: userSearchResult == null
? null
: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: userSearchResult.results.length,
itemBuilder: (context, i) => _SearchItem(
title: userSearchResult
.results[i].displayName ??
userSearchResult
.results[i].userId.localpart ??
L10n.of(context)!.unknownDevice,
avatar:
userSearchResult.results[i].avatarUrl,
onPressed: () => showAdaptiveBottomSheet(
context: context,
builder: (c) => UserBottomSheet(
profile: userSearchResult.results[i],
outerContext: context,
),
),
if (client.rooms.isNotEmpty && !controller.isSearchMode)
SizedBox(
height: 44,
child: ListView(
padding: const EdgeInsets.symmetric(
horizontal: 12.0,
vertical: 6,
),
shrinkWrap: true,
scrollDirection: Axis.horizontal,
children: ActiveFilter.values
.map(
(filter) => Padding(
padding:
const EdgeInsets.symmetric(horizontal: 4),
child: InkWell(
borderRadius: BorderRadius.circular(
AppConfig.borderRadius,
),
onTap: () =>
controller.setActiveFilter(filter),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
decoration: BoxDecoration(
color: filter == controller.activeFilter
? Theme.of(context)
.colorScheme
.primary
: Theme.of(context)
.colorScheme
.secondaryContainer,
borderRadius: BorderRadius.circular(
AppConfig.borderRadius,
),
),
alignment: Alignment.center,
child: Text(
filter.toLocalizedString(context),
style: TextStyle(
fontWeight:
filter == controller.activeFilter
? FontWeight.bold
: FontWeight.normal,
color:
filter == controller.activeFilter
? Theme.of(context)
.colorScheme
.onPrimary
: Theme.of(context)
.colorScheme
.onSecondaryContainer,
),
),
),
),
),
)
.toList(),
),
],
),
if (controller.isSearchMode)
SearchTitle(
title: L10n.of(context)!.chats,
icon: const Icon(Icons.forum_outlined),
),
if (client.prevBatch != null &&
rooms.isEmpty &&
!controller.isSearchMode) ...[
// #Pangea
// if (!controller.isSearchMode &&
// controller.activeFilter != ActiveFilter.groups &&
// AppConfig.showPresences)
// GestureDetector(
// onLongPress: () => controller.dismissStatusList(),
// child: StatusMessageList(
// onStatusEdit: controller.setStatus,
// ),
// ),
// Pangea#
const ConnectionStatusHeader(),
AnimatedContainer(
height: controller.isTorBrowser ? 64 : 0,
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
clipBehavior: Clip.hardEdge,
decoration: const BoxDecoration(),
child: Material(
color: Theme.of(context).colorScheme.surface,
child: ListTile(
leading: const Icon(Icons.vpn_key),
title: Text(L10n.of(context)!.dehydrateTor),
subtitle: Text(L10n.of(context)!.dehydrateTorLong),
trailing: const Icon(Icons.chevron_right_outlined),
onTap: controller.dehydrate,
),
// Padding(
// padding: const EdgeInsets.all(32.0),
// child: Icon(
// CupertinoIcons.chat_bubble_2,
// size: 128,
// color: Theme.of(context).colorScheme.secondary,
// ),
// ),
Center(
child: ChatListBodyStartText(
controller: controller,
),
),
if (controller.isSearchMode)
SearchTitle(
title: L10n.of(context)!.chats,
icon: const Icon(Icons.forum_outlined),
),
if (client.prevBatch != null &&
rooms.isEmpty &&
!controller.isSearchMode) ...[
// #Pangea
// Padding(
// padding: const EdgeInsets.all(32.0),
// child: Icon(
// CupertinoIcons.chat_bubble_2,
// size: 128,
// color:
// Theme.of(context).colorScheme.onInverseSurface,
// ),
// ),
Center(
child: ChatListBodyStartText(
controller: controller,
),
),
// Pangea#
],
// Pangea#
],
),
],
),
if (client.prevBatch == null)
SliverList(
delegate: SliverChildBuilderDelegate(
(context, i) => Opacity(
opacity: (dummyChatCount - i) / dummyChatCount,
child: ListTile(
leading: CircleAvatar(
backgroundColor: titleColor,
child: CircularProgressIndicator(
strokeWidth: 1,
color:
Theme.of(context).textTheme.bodyLarge!.color,
),
),
if (client.prevBatch == null)
SliverList(
delegate: SliverChildBuilderDelegate(
(context, i) => Opacity(
opacity: (dummyChatCount - i) / dummyChatCount,
child: ListTile(
leading: CircleAvatar(
backgroundColor: titleColor,
child: CircularProgressIndicator(
strokeWidth: 1,
color: Theme.of(context).textTheme.bodyLarge!.color,
),
title: Row(
children: [
Expanded(
child: Container(
height: 14,
decoration: BoxDecoration(
color: titleColor,
borderRadius: BorderRadius.circular(3),
),
),
),
const SizedBox(width: 36),
Container(
),
title: Row(
children: [
Expanded(
child: Container(
height: 14,
width: 14,
decoration: BoxDecoration(
color: subtitleColor,
borderRadius: BorderRadius.circular(14),
color: titleColor,
borderRadius: BorderRadius.circular(3),
),
),
const SizedBox(width: 12),
Container(
height: 14,
width: 14,
decoration: BoxDecoration(
color: subtitleColor,
borderRadius: BorderRadius.circular(14),
),
),
const SizedBox(width: 36),
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),
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),
),
),
childCount: dummyChatCount,
),
childCount: dummyChatCount,
),
if (client.prevBatch != null)
SliverList.builder(
itemCount: rooms.length,
itemBuilder: (BuildContext context, int i) {
// #Pangea
// return ChatListItem(
return ChatListItemWrapper(
controller: controller,
// Pangea#
rooms[i],
key: Key('chat_list_item_${rooms[i].id}'),
filter: filter,
// #Pangea
// selected:
// controller.selectedRoomIds.contains(rooms[i].id),
// onTap: controller.selectMode == SelectMode.select
// ? () => controller.toggleSelection(rooms[i].id)
// : () => onChatTap(rooms[i], context),
// onLongPress: () =>
// controller.toggleSelection(rooms[i].id),
// Pangea#
activeChat: controller.activeChat == rooms[i].id,
);
},
),
],
),
);
},
),
),
if (client.prevBatch != null)
SliverList.builder(
itemCount: rooms.length,
itemBuilder: (BuildContext context, int i) {
var room = rooms[i];
if (controller.activeFilter != ActiveFilter.groups) {
final parent = room.isSpace
? room
: spaceDelegateCandidates[room.id];
if (parent != null) {
if (spaceDelegates.contains(parent.id)) {
return const SizedBox.shrink();
}
spaceDelegates.add(parent.id);
room = parent;
}
}
return ChatListItem(
room,
lastEventRoom: rooms[i],
key: Key('chat_list_item_${room.id}'),
filter: filter,
onTap: () => controller.onChatTap(room, context),
onLongPress: () => controller.chatContextAction(room),
activeChat: controller.activeChat == room.id,
);
},
),
],
),
);
},
);
}
}

@ -1,8 +1,6 @@
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat_list/chat_list.dart';
import 'package:fluffychat/pages/chat_list/client_chooser_button.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
@ -42,24 +40,23 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
L10n.of(context)!.share,
key: const ValueKey(SelectMode.share),
)
: selectMode == SelectMode.select
? Text(
controller.selectedRoomIds.length.toString(),
key: const ValueKey(SelectMode.select),
)
// #Pangea
: ClientChooserButton(controller),
// #Pangea
: ClientChooserButton(controller),
// : TextField(
// controller: controller.searchController,
// focusNode: controller.searchFocusNode,
// textInputAction: TextInputAction.search,
// onChanged: controller.onSearchEnter,
// onChanged: (text) => controller.onSearchEnter(
// text,
// globalSearch: globalSearch,
// ),
// decoration: InputDecoration(
// fillColor: Theme.of(context).colorScheme.secondaryContainer,
// border: UnderlineInputBorder(
// border: OutlineInputBorder(
// borderSide: BorderSide.none,
// borderRadius: BorderRadius.circular(99),
// ),
// contentPadding: EdgeInsets.zero,
// hintText: L10n.of(context)!.searchChatsRooms,
// hintStyle: TextStyle(
// color: Theme.of(context).colorScheme.onPrimaryContainer,
@ -71,20 +68,17 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
// tooltip: L10n.of(context)!.cancel,
// icon: const Icon(Icons.close_outlined),
// onPressed: controller.cancelSearch,
// color: Theme.of(context)
// .colorScheme
// .onPrimaryContainer,
// color: Theme.of(context).colorScheme.onPrimaryContainer,
// )
// : IconButton(
// onPressed: controller.startSearch,
// icon: Icon(
// Icons.search_outlined,
// color: Theme.of(context)
// .colorScheme
// .onPrimaryContainer,
// color:
// Theme.of(context).colorScheme.onPrimaryContainer,
// ),
// ),
// suffixIcon: controller.isSearchMode
// suffixIcon: controller.isSearchMode && globalSearch
// ? controller.isSearching
// ? const Padding(
// padding: EdgeInsets.symmetric(
@ -109,10 +103,7 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
// icon: const Icon(Icons.edit_outlined, size: 16),
// label: Text(
// controller.searchServer ??
// Matrix.of(context)
// .client
// .homeserver!
// .host,
// Matrix.of(context).client.homeserver!.host,
// maxLines: 2,
// ),
// )
@ -135,80 +126,7 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
// ),
// Pangea#
]
: selectMode == SelectMode.select
? [
// #Pangea
// if (controller.spaces.isNotEmpty)
if (controller.spaces.isNotEmpty &&
controller.selectedRoomIds.length == 1)
// Pangea#
IconButton(
tooltip: L10n.of(context)!.addToSpace,
icon: const Icon(Icons.workspaces_outlined),
onPressed: controller.addToSpace,
),
IconButton(
tooltip: L10n.of(context)!.toggleUnread,
icon: Icon(
controller.anySelectedRoomNotMarkedUnread
? Icons.mark_chat_unread_outlined
: Icons.mark_chat_read_outlined,
),
onPressed: controller.toggleUnread,
),
IconButton(
tooltip: L10n.of(context)!.toggleFavorite,
icon: Icon(
controller.anySelectedRoomNotFavorite
? Icons.push_pin
: Icons.push_pin_outlined,
),
onPressed: controller.toggleFavouriteRoom,
),
IconButton(
icon: Icon(
controller.anySelectedRoomNotMuted
? Icons.notifications_off_outlined
: Icons.notifications_outlined,
),
tooltip: L10n.of(context)!.toggleMuted,
onPressed: controller.toggleMuted,
),
// #Pangea
if (controller.selectedRoomIds.length > 1)
IconButton(
icon: const Icon(Icons.arrow_forward),
tooltip: L10n.of(context)!.leave,
onPressed: controller.leaveAction,
),
if (controller.selectedRoomIds.length == 1 &&
!(Matrix.of(context)
.client
.getRoomById(controller.selectedRoomIds.single)
?.isRoomAdmin ??
false))
IconButton(
icon: const Icon(Icons.arrow_forward),
tooltip: L10n.of(context)!.leave,
onPressed: controller.leaveAction,
),
if (controller.selectedRoomIds.length == 1 &&
(Matrix.of(context)
.client
.getRoomById(controller.selectedRoomIds.single)
?.isRoomAdmin ??
false))
// Pangea#
IconButton(
// #Pangea
// icon: const Icon(Icons.delete_outlined),
icon: const Icon(Icons.archive_outlined),
// Pangea#
tooltip: L10n.of(context)!.archive,
onPressed: controller.archiveAction,
),
]
: null,
: null,
);
}

@ -19,8 +19,8 @@ enum ArchivedRoomAction { delete, rejoin }
class ChatListItem extends StatelessWidget {
final Room room;
final Room? lastEventRoom;
final bool activeChat;
final bool selected;
final void Function()? onLongPress;
final void Function()? onForget;
final void Function() onTap;
@ -29,11 +29,11 @@ class ChatListItem extends StatelessWidget {
const ChatListItem(
this.room, {
this.activeChat = false,
this.selected = false,
required this.onTap,
this.onLongPress,
this.onForget,
this.filter,
this.lastEventRoom,
super.key,
});
@ -57,10 +57,7 @@ class ChatListItem extends StatelessWidget {
if (confirmed == OkCancelResult.cancel) return;
await showFutureLoadingDialog(
context: context,
// #Pangea
// future: () => room.leave(),
future: () => room.archive(),
// Pangea#
future: () => room.leave(),
);
return;
}
@ -69,24 +66,23 @@ class ChatListItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isMuted = room.pushRuleState != PushRuleState.notify;
final typingText = room.getLocalizedTypingText(context);
final lastEvent = room.lastEvent;
final lastEventRoom = this.lastEventRoom ?? room;
final typingText = lastEventRoom.getLocalizedTypingText(context);
final lastEvent = lastEventRoom.lastEvent;
final ownMessage = lastEvent?.senderId == room.client.userID;
final unread = room.isUnread || room.membership == Membership.invite;
final unread =
lastEventRoom.isUnread || lastEventRoom.membership == Membership.invite;
final theme = Theme.of(context);
final directChatMatrixId = room.directChatMatrixID;
final isDirectChat = directChatMatrixId != null;
final unreadBubbleSize = unread || room.hasNewMessages
? room.notificationCount > 0
final unreadBubbleSize = unread || lastEventRoom.hasNewMessages
? lastEventRoom.notificationCount > 0
? 20.0
: 14.0
: 0.0;
final hasNotifications = room.notificationCount > 0;
final backgroundColor = selected
? theme.colorScheme.primaryContainer
: activeChat
? theme.colorScheme.secondaryContainer
: null;
final hasNotifications = lastEventRoom.notificationCount > 0;
final backgroundColor =
activeChat ? theme.colorScheme.secondaryContainer : null;
final displayname = room.getLocalizedDisplayname(
MatrixLocals(L10n.of(context)!),
);
@ -124,11 +120,11 @@ class ChatListItem extends StatelessWidget {
curve: FluffyThemes.animationCurve,
scale: hovered ? 1.1 : 1.0,
child: Avatar(
borderRadius: room.isSpace
? BorderRadius.circular(AppConfig.borderRadius / 3)
: null,
mxContent: room.avatar,
name: displayname,
//#Pangea
littleIcon: room.roomTypeIcon,
// Pangea#
presenceUserId: directChatMatrixId,
presenceBackgroundColor: backgroundColor,
onTap: onLongPress,
@ -141,14 +137,12 @@ class ChatListItem extends StatelessWidget {
child: AnimatedScale(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
scale: (hovered || selected) ? 1.0 : 0.0,
scale: (hovered) ? 1.0 : 0.0,
child: Material(
color: backgroundColor,
borderRadius: BorderRadius.circular(16),
child: Icon(
selected
? Icons.check_circle
: Icons.check_circle_outlined,
child: const Icon(
Icons.check_circle_outlined,
size: 18,
),
),
@ -188,7 +182,9 @@ class ChatListItem extends StatelessWidget {
color: theme.colorScheme.primary,
),
),
if (lastEvent != null && room.membership != Membership.invite)
if (!room.isSpace &&
lastEvent != null &&
room.membership != Membership.invite)
Padding(
padding: const EdgeInsets.only(left: 4.0),
child: Text(
@ -201,11 +197,30 @@ class ChatListItem extends StatelessWidget {
),
),
),
if (room.isSpace)
const Icon(
Icons.arrow_circle_right_outlined,
size: 18,
),
],
),
subtitle: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
if (room.isSpace) ...[
room.id != lastEventRoom.id &&
lastEventRoom.isUnreadOrInvited
? Avatar(
mxContent: lastEventRoom.avatar,
name: lastEventRoom.name,
size: 18,
)
: const Icon(
Icons.workspaces_outlined,
size: 18,
),
const SizedBox(width: 4),
],
if (typingText.isEmpty &&
ownMessage &&
room.lastEvent!.status.isSending) ...[
@ -230,71 +245,80 @@ class ChatListItem extends StatelessWidget {
),
),
Expanded(
child: typingText.isNotEmpty
child: room.isSpace && !lastEventRoom.isUnreadOrInvited
? Text(
typingText,
style: TextStyle(
color: theme.colorScheme.primary,
L10n.of(context)!.countChatsAndCountParticipants(
room.spaceChildren.length.toString(),
(room.summary.mJoinedMemberCount ?? 1).toString(),
),
maxLines: 1,
softWrap: false,
)
: FutureBuilder(
key: ValueKey(
'${lastEvent?.eventId}_${lastEvent?.type}',
),
// #Pangea
future: room.lastEvent != null
? GetChatListItemSubtitle().getSubtitle(
L10n.of(context)!,
room.lastEvent,
MatrixState.pangeaController,
)
: Future.value(L10n.of(context)!.emptyChat),
// future: needLastEventSender
// ? lastEvent.calcLocalizedBody(
// MatrixLocals(L10n.of(context)!),
// hideReply: true,
// hideEdit: true,
// plaintextBody: true,
// removeMarkdown: true,
// withSenderNamePrefix: !isDirectChat ||
// directChatMatrixId !=
// room.lastEvent?.senderId,
// )
// : null,
// Pangea#
initialData: lastEvent?.calcLocalizedBodyFallback(
MatrixLocals(L10n.of(context)!),
hideReply: true,
hideEdit: true,
plaintextBody: true,
removeMarkdown: true,
withSenderNamePrefix: !isDirectChat ||
directChatMatrixId !=
room.lastEvent?.senderId,
),
builder: (context, snapshot) => Text(
room.membership == Membership.invite
? isDirectChat
? L10n.of(context)!.invitePrivateChat
: L10n.of(context)!.inviteGroupChat
: snapshot.data ??
L10n.of(context)!.emptyChat,
softWrap: false,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontWeight: unread || room.hasNewMessages
? FontWeight.bold
: null,
color: theme.colorScheme.onSurfaceVariant,
decoration: room.lastEvent?.redacted == true
? TextDecoration.lineThrough
: null,
: typingText.isNotEmpty
? Text(
typingText,
style: TextStyle(
color: theme.colorScheme.primary,
),
maxLines: 1,
softWrap: false,
)
: FutureBuilder(
key: ValueKey(
'${lastEvent?.eventId}_${lastEvent?.type}',
),
// #Pangea
future: room.lastEvent != null
? GetChatListItemSubtitle().getSubtitle(
L10n.of(context)!,
room.lastEvent,
MatrixState.pangeaController,
)
: Future.value(L10n.of(context)!.emptyChat),
// future: needLastEventSender
// ? lastEvent.calcLocalizedBody(
// MatrixLocals(L10n.of(context)!),
// hideReply: true,
// hideEdit: true,
// plaintextBody: true,
// removeMarkdown: true,
// withSenderNamePrefix: (!isDirectChat ||
// directChatMatrixId !=
// room.lastEvent?.senderId),
// )
// : null,
// Pangea#
initialData:
lastEvent?.calcLocalizedBodyFallback(
MatrixLocals(L10n.of(context)!),
hideReply: true,
hideEdit: true,
plaintextBody: true,
removeMarkdown: true,
withSenderNamePrefix: (!isDirectChat ||
directChatMatrixId !=
room.lastEvent?.senderId),
),
builder: (context, snapshot) => Text(
room.membership == Membership.invite
? isDirectChat
? L10n.of(context)!.invitePrivateChat
: L10n.of(context)!.inviteGroupChat
: snapshot.data ??
L10n.of(context)!.emptyChat,
softWrap: false,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontWeight:
unread || lastEventRoom.hasNewMessages
? FontWeight.bold
: null,
color: theme.colorScheme.onSurfaceVariant,
decoration: room.lastEvent?.redacted == true
? TextDecoration.lineThrough
: null,
),
),
),
),
),
),
const SizedBox(width: 8),
// #Pangea
@ -315,7 +339,9 @@ class ChatListItem extends StatelessWidget {
width: !hasNotifications && !unread && !room.hasNewMessages
? 0
: (unreadBubbleSize - 9) *
room.notificationCount.toString().length +
lastEventRoom.notificationCount
.toString()
.length +
9,
decoration: BoxDecoration(
color: room.highlightCount > 0 ||
@ -330,7 +356,7 @@ class ChatListItem extends StatelessWidget {
child: Center(
child: hasNotifications
? Text(
room.notificationCount.toString(),
lastEventRoom.notificationCount.toString(),
style: TextStyle(
color: room.highlightCount > 0
? Colors.white

@ -1,104 +1,17 @@
import 'package:badges/badges.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat_list/chat_list.dart';
import 'package:fluffychat/pages/chat_list/navi_rail_item.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
import 'package:fluffychat/pangea/utils/chat_list_handle_space_tap.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/unread_rooms_badge.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart';
import '../../widgets/matrix.dart';
import 'chat_list_body.dart';
import 'start_chat_fab.dart';
class ChatListView extends StatelessWidget {
final ChatListController controller;
const ChatListView(this.controller, {super.key});
List<NavigationDestination> getNavigationDestinations(BuildContext context) {
final badgePosition = BadgePosition.topEnd(top: -12, end: -8);
return [
if (AppConfig.separateChatTypes) ...[
NavigationDestination(
icon: UnreadRoomsBadge(
badgePosition: badgePosition,
filter:
controller.getRoomFilterByActiveFilter(ActiveFilter.messages),
child: const Icon(Icons.chat_outlined),
),
selectedIcon: UnreadRoomsBadge(
badgePosition: badgePosition,
filter:
controller.getRoomFilterByActiveFilter(ActiveFilter.messages),
child: const Icon(Icons.chat),
),
//#Pangea
// label: L10n.of(context)!.messages,
label: L10n.of(context)!.directChats,
//Pangea#
),
NavigationDestination(
icon: UnreadRoomsBadge(
badgePosition: badgePosition,
filter: controller.getRoomFilterByActiveFilter(ActiveFilter.groups),
child: const Icon(Icons.group_outlined),
),
selectedIcon: UnreadRoomsBadge(
badgePosition: badgePosition,
filter: controller.getRoomFilterByActiveFilter(ActiveFilter.groups),
child: const Icon(Icons.group),
),
label: L10n.of(context)!.groups,
),
] else
NavigationDestination(
icon: UnreadRoomsBadge(
badgePosition: badgePosition,
filter:
controller.getRoomFilterByActiveFilter(ActiveFilter.allChats),
child: const Icon(Icons.chat_outlined),
),
selectedIcon: UnreadRoomsBadge(
badgePosition: badgePosition,
filter:
controller.getRoomFilterByActiveFilter(ActiveFilter.allChats),
child: const Icon(Icons.chat),
),
// #Pangea
// label: L10n.of(context)!.chats,
label: L10n.of(context)!.allChats,
// Pangea#
),
if (controller.spaces.isNotEmpty
// #Pangea
&&
!FluffyThemes.isColumnMode(context)
// Pangea#
)
// #Pangea
// const NavigationDestination(
// icon: Icon(Icons.workspaces_outlined),
// selectedIcon: Icon(Icons.workspaces),
// label: 'Spaces',
// ),
NavigationDestination(
icon: const Icon(Icons.workspaces_outlined),
selectedIcon: const Icon(Icons.workspaces),
label: L10n.of(context)!.allSpaces,
),
// Pangea#
];
}
@override
Widget build(BuildContext context) {
final client = Matrix.of(context).client;
return StreamBuilder<Object?>(
stream: Matrix.of(context).onShareContentChanged.stream,
builder: (_, __) {
@ -106,10 +19,7 @@ class ChatListView extends StatelessWidget {
return PopScope(
canPop: controller.selectMode == SelectMode.normal &&
!controller.isSearchMode &&
controller.activeFilter ==
(AppConfig.separateChatTypes
? ActiveFilter.messages
: ActiveFilter.allChats),
controller.activeFilter == ActiveFilter.allChats,
onPopInvoked: (pop) async {
if (pop) return;
final selMode = controller.selectMode;
@ -121,151 +31,35 @@ class ChatListView extends StatelessWidget {
controller.cancelAction();
return;
}
if (controller.activeFilter !=
(AppConfig.separateChatTypes
? ActiveFilter.messages
: ActiveFilter.allChats)) {
controller
.onDestinationSelected(AppConfig.separateChatTypes ? 1 : 0);
return;
}
},
child: Row(
children: [
if (FluffyThemes.isColumnMode(context) &&
controller.widget.displayNavigationRail) ...[
Builder(
builder: (context) {
final allSpaces =
client.rooms.where((room) => room.isSpace);
final rootSpaces = allSpaces
.where(
(space) => !allSpaces.any(
(parentSpace) => parentSpace.spaceChildren
.any((child) => child.roomId == space.id),
child: GestureDetector(
onTap: FocusManager.instance.primaryFocus?.unfocus,
excludeFromSemantics: true,
behavior: HitTestBehavior.translucent,
child: Scaffold(
body: ChatListViewBody(controller),
floatingActionButton:
// #Pangea
// KeyBoardShortcuts(
// keysToPress: {
// LogicalKeyboardKey.controlLeft,
// LogicalKeyboardKey.keyN,
// },
// onKeysPressed: () => context.go('/rooms/newprivatechat'),
// helpLabel: L10n.of(context)!.newChat,
// child:
// Pangea#
selectMode == SelectMode.normal && !controller.isSearchMode
? FloatingActionButton.extended(
onPressed: controller.addChatAction,
icon: const Icon(Icons.add_outlined),
label: Text(
L10n.of(context)!.chat,
overflow: TextOverflow.fade,
),
)
.toList();
final destinations = getNavigationDestinations(context);
return SizedBox(
width: FluffyThemes.navRailWidth,
child: ListView.builder(
scrollDirection: Axis.vertical,
itemCount: rootSpaces.length + destinations.length,
itemBuilder: (context, i) {
if (i < destinations.length) {
return NaviRailItem(
// #Pangea
// isSelected: i == controller.selectedIndex,
isSelected: controller.isSelected(i),
// Pangea#
onTap: () => controller.onDestinationSelected(i),
icon: destinations[i].icon,
selectedIcon: destinations[i].selectedIcon,
toolTip: destinations[i].label,
);
}
i -= destinations.length;
final isSelected =
controller.activeFilter == ActiveFilter.spaces &&
rootSpaces[i].id == controller.activeSpaceId;
//#Pangea
final Room? room = Matrix.of(context)
.client
.getRoomById(rootSpaces[i].id);
// Pangea#
return NaviRailItem(
toolTip: rootSpaces[i].getLocalizedDisplayname(
MatrixLocals(L10n.of(context)!),
),
isSelected: isSelected,
// #Pangea
// onTap: () =>
// controller.setActiveSpace(rootSpaces[i].id),
onTap: () => chatListHandleSpaceTap(
context,
controller,
rootSpaces[i],
),
// Pangea#
icon: Avatar(
mxContent: rootSpaces[i].avatar,
name: rootSpaces[i].getLocalizedDisplayname(
MatrixLocals(L10n.of(context)!),
),
size: 32,
// #Pangea
littleIcon: room?.roomTypeIcon,
// Pangea#
),
);
},
),
);
},
),
Container(
color: Theme.of(context).dividerColor,
width: 1,
),
],
Expanded(
child: GestureDetector(
onTap: FocusManager.instance.primaryFocus?.unfocus,
excludeFromSemantics: true,
behavior: HitTestBehavior.translucent,
child: Scaffold(
body: ChatListViewBody(controller),
bottomNavigationBar: controller.displayNavigationBar
? NavigationBar(
elevation: 4,
labelBehavior:
NavigationDestinationLabelBehavior.alwaysShow,
shadowColor:
Theme.of(context).colorScheme.onSurface,
backgroundColor:
Theme.of(context).colorScheme.surface,
surfaceTintColor:
Theme.of(context).colorScheme.surface,
selectedIndex: controller.selectedIndex,
onDestinationSelected:
controller.onDestinationSelected,
destinations: getNavigationDestinations(context),
)
: null,
// #Pangea
// floatingActionButton: KeyBoardShortcuts(
// keysToPress: {
// LogicalKeyboardKey.controlLeft,
// LogicalKeyboardKey.keyN,
// },
// onKeysPressed: () => context.go('/rooms/newprivatechat'),
// helpLabel: L10n.of(context)!.newChat,
// child: selectMode == SelectMode.normal &&
// !controller.isSearchMode
// ? StartChatFloatingActionButton(
// activeFilter: controller.activeFilter,
// roomsIsEmpty: false,
// scrolledToTop: controller.scrolledToTop,
// createNewSpace: controller.createNewSpace,
// )
// : const SizedBox.shrink(),
// ),
floatingActionButton: selectMode == SelectMode.normal
? StartChatFloatingActionButton(
activeFilter: controller.activeFilter,
roomsIsEmpty: false,
scrolledToTop: controller.scrolledToTop,
controller: controller,
createNewSpace: () {},
)
: null,
// Pangea#
),
),
),
],
: const SizedBox.shrink(),
),
),
);
},

@ -392,13 +392,13 @@ class ClientChooserButton extends StatelessWidget {
case SettingsAction.joinWithClassCode:
SpaceCodeUtil.joinWithSpaceCodeDialog(
context,
controller.pangeaController,
MatrixState.pangeaController,
);
break;
case SettingsAction.findAConversationPartner:
findConversationPartnerDialog(
context,
controller.pangeaController,
MatrixState.pangeaController,
);
break;
// case SettingsAction.spaceAnalytics:

File diff suppressed because it is too large Load Diff

@ -1,125 +0,0 @@
import 'dart:core';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:go_router/go_router.dart';
import '../../config/themes.dart';
import 'chat_list.dart';
class StartChatFloatingActionButton extends StatelessWidget {
final ActiveFilter activeFilter;
final ValueNotifier<bool> scrolledToTop;
final bool roomsIsEmpty;
// #Pangea
final ChatListController controller;
// Pangea#
final void Function() createNewSpace;
const StartChatFloatingActionButton({
super.key,
required this.activeFilter,
required this.scrolledToTop,
required this.roomsIsEmpty,
required this.createNewSpace,
// #Pangea
required this.controller,
// Pangea#
});
void _onPressed(BuildContext context) async {
//#Pangea
if (controller.activeSpaceId != null) {
context.go(
'/rooms/newgroup${controller.activeSpaceId != null ? '?spaceId=${controller.activeSpaceId}' : ''}',
);
return;
}
//Pangea#
switch (activeFilter) {
case ActiveFilter.allChats:
case ActiveFilter.messages:
// #Pangea
// context.go('/rooms/newprivatechat');
// break;
// Pangea#
case ActiveFilter.groups:
// #Pangea
// context.go('/rooms/newgroup');
context.go(
'/rooms/newgroup${controller.activeSpaceId != null ? '?spaceId=${controller.activeSpaceId}' : ''}',
);
// Pangea#
break;
case ActiveFilter.spaces:
// #Pangea
// createNewSpace();
context.go('/rooms/newspace');
// Pangea#
break;
}
}
IconData get icon {
// #Pangea
if (controller.activeSpaceId != null) {
return Icons.group_add_outlined;
}
// Pangea#
switch (activeFilter) {
case ActiveFilter.allChats:
case ActiveFilter.messages:
// #Pangea
// return Icons.add_outlined;
// Pangea#
case ActiveFilter.groups:
return Icons.group_add_outlined;
case ActiveFilter.spaces:
return Icons.workspaces_outlined;
}
}
String getLabel(BuildContext context) {
// #Pangea
if (controller.activeSpaceId != null) {
return L10n.of(context)!.newGroup;
}
// Pangea#
switch (activeFilter) {
case ActiveFilter.allChats:
case ActiveFilter.messages:
return roomsIsEmpty
? L10n.of(context)!.startFirstChat
: L10n.of(context)!.newChat;
case ActiveFilter.groups:
return L10n.of(context)!.newGroup;
case ActiveFilter.spaces:
return L10n.of(context)!.newSpace;
}
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<bool>(
valueListenable: scrolledToTop,
builder: (context, scrolledToTop, _) => AnimatedSize(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
clipBehavior: Clip.none,
child: scrolledToTop
? FloatingActionButton.extended(
onPressed: () => _onPressed(context),
icon: Icon(icon),
label: Text(
getLabel(context),
overflow: TextOverflow.fade,
),
)
: FloatingActionButton(
onPressed: () => _onPressed(context),
child: Icon(icon),
),
),
);
}
}

@ -1,136 +0,0 @@
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:fluffychat/pages/chat/send_file_dialog.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';
void onChatTap(Room room, BuildContext context) async {
if (room.membership == Membership.invite) {
final inviterId =
room.getState(EventTypes.RoomMember, room.client.userID!)?.senderId;
final inviteAction = await showModalActionSheet<InviteActions>(
context: context,
message: room.isDirectChat
? L10n.of(context)!.invitePrivateChat
: L10n.of(context)!.inviteGroupChat,
title: room.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)),
actions: [
SheetAction(
key: InviteActions.accept,
label: L10n.of(context)!.accept,
icon: Icons.check_outlined,
isDefaultAction: true,
),
SheetAction(
key: InviteActions.decline,
label: L10n.of(context)!.decline,
icon: Icons.close_outlined,
isDestructiveAction: true,
),
SheetAction(
key: InviteActions.block,
label: L10n.of(context)!.block,
icon: Icons.block_outlined,
isDestructiveAction: true,
),
],
);
if (inviteAction == null) return;
if (inviteAction == InviteActions.block) {
context.go('/rooms/settings/security/ignorelist', extra: inviterId);
return;
}
if (inviteAction == InviteActions.decline) {
// #Pangea
if (!room.isSpace &&
room.membership == Membership.join &&
room.isUnread) {
await room.markUnread(false);
}
// Pangea#
await showFutureLoadingDialog(
context: context,
future: room.leave,
);
return;
}
final joinResult = await showFutureLoadingDialog(
context: context,
future: () async {
final waitForRoom = room.client.waitForRoomInSync(
room.id,
join: true,
);
if (await room.leaveIfFull()) {
throw L10n.of(context)!.roomFull;
}
await room.join();
await waitForRoom;
},
);
if (joinResult.error != null) return;
}
if (room.membership == Membership.ban) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(L10n.of(context)!.youHaveBeenBannedFromThisChat),
),
);
return;
}
if (room.membership == Membership.leave) {
context.go('/rooms/archive/${room.id}');
return;
}
// Share content into this room
final shareContent = Matrix.of(context).shareContent;
if (shareContent != null) {
final shareFile = shareContent.tryGet<MatrixFile>('file');
if (shareContent.tryGet<String>('msgtype') == 'chat.fluffy.shared_file' &&
shareFile != null) {
await showDialog(
context: context,
useRootNavigator: false,
builder: (c) => SendFileDialog(
files: [shareFile],
room: room,
),
);
Matrix.of(context).shareContent = null;
} else {
final consent = await showOkCancelAlertDialog(
context: context,
title: L10n.of(context)!.forward,
message: L10n.of(context)!.forwardMessageTo(
room.getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)),
),
okLabel: L10n.of(context)!.forward,
cancelLabel: L10n.of(context)!.cancel,
);
if (consent == OkCancelResult.cancel) {
Matrix.of(context).shareContent = null;
return;
}
if (consent == OkCancelResult.ok) {
room.sendEvent(shareContent);
Matrix.of(context).shareContent = null;
}
}
}
context.go('/rooms/${room.id}');
}
enum InviteActions {
accept,
decline,
block,
}

@ -185,12 +185,6 @@ class SettingsStyleView extends StatelessWidget {
storeKey: SettingKeys.showPresences,
defaultValue: AppConfig.showPresences,
),
SettingsSwitchListTile.adaptive(
title: L10n.of(context)!.separateChatTypes,
onChanged: (b) => AppConfig.separateChatTypes = b,
storeKey: SettingKeys.separateChatTypes,
defaultValue: AppConfig.separateChatTypes,
),
Divider(
height: 1,
color: Theme.of(context).dividerColor,

@ -1,11 +1,9 @@
import 'dart:async';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';
import '../../../../utils/date_time_extension.dart';
import '../../../widgets/avatar.dart';
@ -84,8 +82,7 @@ class AnalyticsListTileState extends State<AnalyticsListTile> {
@override
Widget build(BuildContext context) {
final Room? room =
Matrix.of(context).client.getRoomById(widget.selected.id);
final room = Matrix.of(context).client.getRoomById(widget.selected.id);
return Material(
color: widget.isSelected
? Theme.of(context).colorScheme.secondaryContainer
@ -101,7 +98,6 @@ class AnalyticsListTileState extends State<AnalyticsListTile> {
: Avatar(
mxContent: widget.avatar,
name: widget.selected.displayName,
littleIcon: room?.roomTypeIcon,
),
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,

@ -1,47 +0,0 @@
import 'dart:async';
import 'package:fluffychat/pages/chat_list/chat_list.dart';
import 'package:fluffychat/pages/chat_list/chat_list_header.dart';
import 'package:flutter/material.dart';
/// A wrapper around ChatListHeader to allow rebuilding on state changes.
/// Prevents having to rebuild the entire ChatList when a single item changes.
class ChatListHeaderWrapper extends StatefulWidget {
final ChatListController controller;
final bool globalSearch;
const ChatListHeaderWrapper({
super.key,
required this.controller,
this.globalSearch = true,
});
@override
ChatListHeaderWrapperState createState() => ChatListHeaderWrapperState();
}
class ChatListHeaderWrapperState extends State<ChatListHeaderWrapper> {
StreamSubscription? stateSub;
@override
void initState() {
super.initState();
stateSub = widget.controller.selectionsStream.stream.listen((roomID) {
setState(() {});
});
}
@override
void dispose() {
stateSub?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ChatListHeader(
controller: widget.controller,
globalSearch: widget.globalSearch,
);
}
}

@ -1,71 +0,0 @@
import 'dart:async';
import 'package:fluffychat/pages/chat_list/chat_list.dart';
import 'package:fluffychat/pages/chat_list/chat_list_item.dart';
import 'package:fluffychat/pages/chat_list/utils/on_chat_tap.dart';
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
/// A wrapper around ChatListItem to allow rebuilding on state changes.
/// Prevents having to rebuild the entire ChatList when a single item changes.
class ChatListItemWrapper extends StatefulWidget {
final Room room;
final bool activeChat;
final void Function()? onForget;
final String? filter;
final ChatListController controller;
final void Function()? onLongPress;
final void Function()? onTap;
const ChatListItemWrapper(
this.room, {
this.activeChat = false,
this.onForget,
this.filter,
required this.controller,
this.onLongPress,
this.onTap,
super.key,
});
@override
ChatListItemWrapperState createState() => ChatListItemWrapperState();
}
class ChatListItemWrapperState extends State<ChatListItemWrapper> {
StreamSubscription? stateSub;
@override
void initState() {
super.initState();
stateSub = widget.controller.selectionsStream.stream.listen((roomID) {
if (roomID == widget.room.id) {
setState(() {});
}
});
}
@override
void dispose() {
stateSub?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ChatListItem(
widget.room,
activeChat: widget.activeChat,
selected: widget.controller.selectedRoomIds.contains(widget.room.id),
onTap: widget.onTap ??
(widget.controller.selectMode == SelectMode.select
? () => widget.controller.toggleSelection(widget.room.id)
: () => onChatTap(widget.room, context)),
onLongPress: widget.onLongPress ??
() => widget.controller.toggleSelection(widget.room.id),
onForget: widget.onForget,
filter: widget.filter,
);
}
}

@ -1,5 +1,6 @@
import 'package:fluffychat/utils/string_color.dart';
import 'package:fluffychat/widgets/mxc_image.dart';
import 'package:fluffychat/widgets/presence_builder.dart';
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
@ -12,9 +13,8 @@ class Avatar extends StatelessWidget {
final Client? client;
final String? presenceUserId;
final Color? presenceBackgroundColor;
//#Pangea
final IconData? littleIcon;
// Pangea#
final BorderRadius? borderRadius;
final IconData? icon;
const Avatar({
this.mxContent,
@ -24,9 +24,8 @@ class Avatar extends StatelessWidget {
this.client,
this.presenceUserId,
this.presenceBackgroundColor,
//#Pangea
this.littleIcon,
// Pangea#
this.borderRadius,
this.icon,
super.key,
});
@ -53,18 +52,25 @@ class Avatar extends StatelessWidget {
),
),
);
final borderRadius = BorderRadius.circular(size / 2);
final borderRadius = this.borderRadius ?? BorderRadius.circular(size / 2);
final presenceUserId = this.presenceUserId;
final color =
noPic ? name?.lightColorAvatar : Theme.of(context).secondaryHeaderColor;
final container = Stack(
children: [
ClipRRect(
borderRadius: borderRadius,
child: Container(
width: size,
height: size,
SizedBox(
width: size,
height: size,
child: Material(
color: color,
shape: RoundedRectangleBorder(
borderRadius: borderRadius,
side: BorderSide(
width: 0,
color: Theme.of(context).dividerColor,
),
),
clipBehavior: Clip.hardEdge,
child: noPic
? textWidget
: MxcImage(
@ -78,71 +84,49 @@ class Avatar extends StatelessWidget {
),
),
),
// #Pangea
if (littleIcon != null)
Positioned(
bottom: 0,
right: 0,
child: ClipRRect(
borderRadius: borderRadius,
child: Container(
height: 16,
width: 16,
color: Colors.white,
child: Icon(
littleIcon,
color: noPic
? name?.lightColorAvatar
: Theme.of(context).secondaryHeaderColor,
size: 14,
if (presenceUserId != null)
PresenceBuilder(
client: client,
userId: presenceUserId,
builder: (context, presence) {
if (presence == null ||
(presence.presence == PresenceType.offline &&
presence.lastActiveTimestamp == null)) {
return const SizedBox.shrink();
}
final dotColor = presence.presence.isOnline
? Colors.green
: presence.presence.isUnavailable
? Colors.orange
: Colors.grey;
return Positioned(
bottom: -3,
right: -3,
child: Container(
width: 16,
height: 16,
decoration: BoxDecoration(
color: presenceBackgroundColor ??
Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(32),
),
alignment: Alignment.center,
child: Container(
width: 10,
height: 10,
decoration: BoxDecoration(
color: dotColor,
borderRadius: BorderRadius.circular(16),
border: Border.all(
width: 1,
color: Theme.of(context).colorScheme.surface,
),
),
),
),
),
),
);
},
),
// #Pangea
// PresenceBuilder(
// client: client,
// userId: presenceUserId,
// builder: (context, presence) {
// if (presence == null ||
// (presence.presence == PresenceType.offline &&
// presence.lastActiveTimestamp == null)) {
// return const SizedBox.shrink();
// }
// final dotColor = presence.presence.isOnline
// ? Colors.green
// : presence.presence.isUnavailable
// ? Colors.orange
// : Colors.grey;
// return Positioned(
// bottom: -3,
// right: -3,
// child: Container(
// width: 16,
// height: 16,
// decoration: BoxDecoration(
// color: presenceBackgroundColor ??
// Theme.of(context).colorScheme.surface,
// borderRadius: BorderRadius.circular(32),
// ),
// alignment: Alignment.center,
// child: Container(
// width: 10,
// height: 10,
// decoration: BoxDecoration(
// color: dotColor,
// borderRadius: BorderRadius.circular(16),
// border: Border.all(
// width: 1,
// color: Theme.of(context).colorScheme.surface,
// ),
// ),
// ),
// ),
// );
// },
// ),
// Pangea#
],
);
if (onTap == null) return container;

@ -3,13 +3,11 @@ import 'package:flutter/material.dart';
class TwoColumnLayout extends StatelessWidget {
final Widget mainView;
final Widget sideView;
final bool displayNavigationRail;
const TwoColumnLayout({
super.key,
required this.mainView,
required this.sideView,
required this.displayNavigationRail,
});
@override
Widget build(BuildContext context) {
@ -20,7 +18,7 @@ class TwoColumnLayout extends StatelessWidget {
Container(
clipBehavior: Clip.antiAlias,
decoration: const BoxDecoration(),
width: 360.0 + (displayNavigationRail ? 64 : 0),
width: 384.0,
child: mainView,
),
Container(

@ -485,10 +485,6 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
store.getBool(SettingKeys.hideUnimportantStateEvents) ??
AppConfig.hideUnimportantStateEvents;
AppConfig.separateChatTypes =
store.getBool(SettingKeys.separateChatTypes) ??
AppConfig.separateChatTypes;
AppConfig.autoplayImages =
store.getBool(SettingKeys.autoplayImages) ?? AppConfig.autoplayImages;

@ -60,7 +60,7 @@ static void my_application_activate(GApplication* application) {
gtk_window_set_title(window, "FluffyChat");
}
gtk_window_set_default_size(window, 864, 680);
gtk_window_set_default_size(window, 800, 600);
gtk_widget_show(GTK_WIDGET(window));
g_autoptr(FlDartProject) project = fl_dart_project_new();

Loading…
Cancel
Save