fluffychat merge

This commit is contained in:
ggurdin 2026-02-03 15:29:31 -05:00
commit 236f192877
No known key found for this signature in database
GPG key ID: A01CB41737CBB478
7 changed files with 264 additions and 145 deletions

View file

@ -5,6 +5,7 @@
// import 'package:fluffychat/config/app_config.dart';
// import 'package:fluffychat/l10n/l10n.dart';
// import 'package:fluffychat/pages/chat/encryption_button.dart';
// import 'package:fluffychat/utils/other_party_can_receive.dart';
// import 'package:fluffychat/utils/platform_infos.dart';
// import 'package:fluffychat/widgets/avatar.dart';
@ -227,7 +228,6 @@
// ),
// Container(
// height: height,
// width: height,
// alignment: Alignment.center,
// child: IconButton(
// tooltip: L10n.of(context).emojis,
@ -256,6 +256,11 @@
// onPressed: controller.emojiPickerAction,
// ),
// ),
// Container(
// height: height,
// alignment: Alignment.center,
// child: EncryptionButton(controller.room),
// ),
// if (Matrix.of(context).isMultiAccount &&
// Matrix.of(context).hasComplexBundles &&
// Matrix.of(context).currentBundle!.length > 1)

View file

@ -138,7 +138,6 @@ class ChatView extends StatelessWidget {
// icon: const Icon(Icons.call_outlined),
// tooltip: L10n.of(context).placeCall,
// ),
// EncryptionButton(controller.room),
// ChatSettingsPopupMenu(controller.room, true),
// ];
// }
@ -488,67 +487,67 @@ class ChatView extends StatelessWidget {
// #Pangea
// else if (controller.room.canSendDefaultMessages &&
// controller.room.membership == Membership.join)
// Container(
// margin: EdgeInsets.all(bottomSheetPadding),
// constraints: const BoxConstraints(
// maxWidth: FluffyThemes.maxTimelineWidth,
// ),
// alignment: Alignment.center,
// child: Material(
// clipBehavior: Clip.hardEdge,
// color: controller.selectedEvents.isNotEmpty
// ? theme.colorScheme.tertiaryContainer
// : theme.colorScheme.surfaceContainerHigh,
// borderRadius: const BorderRadius.all(
// Radius.circular(24),
// Container(
// margin: EdgeInsets.all(bottomSheetPadding),
// constraints: const BoxConstraints(
// maxWidth: FluffyThemes.maxTimelineWidth,
// ),
// child: controller.room.isAbandonedDMRoom == true
// ? Row(
// mainAxisAlignment:
// MainAxisAlignment.spaceEvenly,
// children: [
// TextButton.icon(
// style: TextButton.styleFrom(
// padding: const EdgeInsets.all(
// 16,
// alignment: Alignment.center,
// child: Material(
// clipBehavior: Clip.hardEdge,
// color: controller.selectedEvents.isNotEmpty
// ? theme.colorScheme.tertiaryContainer
// : theme.colorScheme.surfaceContainerHigh,
// borderRadius: const BorderRadius.all(
// Radius.circular(24),
// ),
// child: controller.room.isAbandonedDMRoom == true
// ? Row(
// mainAxisAlignment:
// MainAxisAlignment.spaceEvenly,
// children: [
// TextButton.icon(
// style: TextButton.styleFrom(
// padding: const EdgeInsets.all(
// 16,
// ),
// foregroundColor:
// theme.colorScheme.error,
// ),
// foregroundColor:
// theme.colorScheme.error,
// ),
// icon: const Icon(
// Icons.archive_outlined,
// ),
// onPressed: controller.leaveChat,
// label: Text(
// L10n.of(context).leave,
// ),
// ),
// TextButton.icon(
// style: TextButton.styleFrom(
// padding: const EdgeInsets.all(
// 16,
// icon: const Icon(
// Icons.archive_outlined,
// ),
// onPressed: controller.leaveChat,
// label: Text(
// L10n.of(context).leave,
// ),
// ),
// icon: const Icon(
// Icons.forum_outlined,
// TextButton.icon(
// style: TextButton.styleFrom(
// padding: const EdgeInsets.all(
// 16,
// ),
// ),
// icon: const Icon(
// Icons.forum_outlined,
// ),
// onPressed: controller.recreateChat,
// label: Text(
// L10n.of(context).reopenChat,
// ),
// ),
// onPressed: controller.recreateChat,
// label: Text(
// L10n.of(context).reopenChat,
// ),
// ),
// ],
// )
// : Column(
// mainAxisSize: MainAxisSize.min,
// children: [
// ReplyDisplay(controller),
// ChatInputRow(controller),
// ChatEmojiPicker(controller),
// ],
// ),
// ),
// )
// ],
// )
// : Column(
// mainAxisSize: MainAxisSize.min,
// children: [
// ReplyDisplay(controller),
// ChatInputRow(controller),
// ChatEmojiPicker(controller),
// ],
// ),
// ),
// )
else if (controller.room.canSendDefaultMessages &&
controller.room.membership == Membership.join &&
(controller.room.activityPlan == null ||

View file

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:badges/badges.dart' as b;
import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';
@ -12,6 +13,7 @@ class EncryptionButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return StreamBuilder<SyncUpdate>(
stream: Matrix.of(context)
.client
@ -27,16 +29,27 @@ class EncryptionButton extends StatelessWidget {
tooltip: room.encrypted
? L10n.of(context).encrypted
: L10n.of(context).encryptionNotEnabled,
icon: Icon(
room.encrypted ? Icons.lock_outlined : Icons.lock_open_outlined,
size: 20,
color: room.joinRules != JoinRules.public && !room.encrypted
? Colors.red
: room.joinRules != JoinRules.public &&
snapshot.data ==
EncryptionHealthState.unverifiedDevices
? Colors.orange
: null,
icon: b.Badge(
badgeAnimation: const b.BadgeAnimation.fade(),
showBadge:
snapshot.data == EncryptionHealthState.unverifiedDevices,
badgeStyle: b.BadgeStyle(
badgeColor: theme.colorScheme.error,
elevation: 4,
),
badgeContent: Text(
'!',
style: TextStyle(
fontSize: 9,
color: theme.colorScheme.onError,
fontWeight: FontWeight.bold,
),
),
child: Icon(
room.encrypted ? Icons.lock_outlined : Icons.lock_open_outlined,
size: 20,
color: theme.colorScheme.onSurface,
),
),
onPressed: () => context.go('/rooms/${room.id}/encryption'),
),

View file

@ -3,7 +3,6 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart';
import 'package:matrix/matrix.dart' as sdk;
import 'package:matrix/matrix.dart';
@ -166,20 +165,6 @@ class ChatDetailsController extends State<ChatDetails>
// Pangea#
}
void goToEmoteSettings() async {
final room = Matrix.of(context).client.getRoomById(roomId!)!;
// okay, we need to test if there are any emote state events other than the default one
// if so, we need to be directed to a selection screen for which pack we want to look at
// otherwise, we just open the normal one.
if ((room.states['im.ponies.room_emotes'] ?? <String, Event>{})
.keys
.any((String s) => s.isNotEmpty)) {
context.push('/rooms/${room.id}/details/multiple_emotes');
} else {
context.push('/rooms/${room.id}/details/emotes');
}
}
void setAvatarAction() async {
final room = Matrix.of(context).client.getRoomById(roomId!);
final actions = [

View file

@ -210,68 +210,59 @@ class ChatDetailsView extends StatelessWidget {
),
],
),
Divider(color: theme.dividerColor),
ListTile(
title: Text(
L10n.of(context).chatDescription,
style: TextStyle(
color: theme.colorScheme.secondary,
fontWeight: FontWeight.bold,
if (room.canChangeStateEvent(EventTypes.RoomTopic) ||
room.topic.isNotEmpty) ...[
Divider(color: theme.dividerColor),
ListTile(
title: Text(
L10n.of(context).chatDescription,
style: TextStyle(
color: theme.colorScheme.secondary,
fontWeight: FontWeight.bold,
),
),
trailing:
room.canChangeStateEvent(EventTypes.RoomTopic)
? IconButton(
onPressed: controller.setTopicAction,
tooltip:
L10n.of(context).setChatDescription,
icon: const Icon(Icons.edit_outlined),
)
: null,
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
),
child: SelectableLinkify(
text: room.topic.isEmpty
? L10n.of(context).noChatDescriptionYet
: room.topic,
textScaleFactor:
MediaQuery.textScalerOf(context).scale(1),
options: const LinkifyOptions(humanize: false),
linkStyle: const TextStyle(
color: Colors.blueAccent,
decorationColor: Colors.blueAccent,
),
style: TextStyle(
fontSize: 14,
fontStyle: room.topic.isEmpty
? FontStyle.italic
: FontStyle.normal,
color: theme.textTheme.bodyMedium!.color,
decorationColor:
theme.textTheme.bodyMedium!.color,
),
onOpen: (url) =>
UrlLauncher(context, url.url).launchUrl(),
),
),
trailing: room
.canChangeStateEvent(EventTypes.RoomTopic)
? IconButton(
onPressed: controller.setTopicAction,
tooltip: L10n.of(context).setChatDescription,
icon: const Icon(Icons.edit_outlined),
)
: null,
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
),
child: SelectableLinkify(
text: room.topic.isEmpty
? L10n.of(context).noChatDescriptionYet
: room.topic,
textScaleFactor:
MediaQuery.textScalerOf(context).scale(1),
options: const LinkifyOptions(humanize: false),
linkStyle: const TextStyle(
color: Colors.blueAccent,
decorationColor: Colors.blueAccent,
),
style: TextStyle(
fontSize: 14,
fontStyle: room.topic.isEmpty
? FontStyle.italic
: FontStyle.normal,
color: theme.textTheme.bodyMedium!.color,
decorationColor:
theme.textTheme.bodyMedium!.color,
),
onOpen: (url) =>
UrlLauncher(context, url.url).launchUrl(),
),
),
const SizedBox(height: 16),
Divider(color: theme.dividerColor),
ListTile(
leading: CircleAvatar(
backgroundColor: theme.scaffoldBackgroundColor,
foregroundColor: iconColor,
child: const Icon(
Icons.insert_emoticon_outlined,
),
),
title: Text(L10n.of(context).customEmojisAndStickers),
subtitle: Text(L10n.of(context).setCustomEmotes),
onTap: controller.goToEmoteSettings,
trailing: const Icon(Icons.chevron_right_outlined),
),
if (!room.isDirectChat)
const SizedBox(height: 16),
],
if (!room.isDirectChat) ...[
Divider(color: theme.dividerColor),
ListTile(
leading: CircleAvatar(
backgroundColor: theme.scaffoldBackgroundColor,
@ -288,7 +279,6 @@ class ChatDetailsView extends StatelessWidget {
.push('/rooms/${room.id}/details/access'),
trailing: const Icon(Icons.chevron_right_outlined),
),
if (!room.isDirectChat)
ListTile(
title: Text(L10n.of(context).chatPermissions),
subtitle: Text(
@ -305,6 +295,7 @@ class ChatDetailsView extends StatelessWidget {
onTap: () => context
.push('/rooms/${room.id}/details/permissions'),
),
],
Divider(color: theme.dividerColor),
ListTile(
title: Text(

View file

@ -0,0 +1,100 @@
import 'package:flutter/material.dart';
import 'package:fluffychat/config/themes.dart';
class AvatarPageHeader extends StatelessWidget {
final Widget avatar;
final void Function()? onAvatarEdit;
final Widget? textButtonLeft, textButtonRight;
final List<Widget> iconButtons;
const AvatarPageHeader({
super.key,
required this.avatar,
this.onAvatarEdit,
this.iconButtons = const [],
this.textButtonLeft,
this.textButtonRight,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final onAvatarEdit = this.onAvatarEdit;
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: FluffyThemes.columnWidth),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
spacing: 8.0,
children: [
Stack(
children: [
avatar,
if (onAvatarEdit != null)
Positioned(
bottom: 0,
right: 0,
child: FloatingActionButton.small(
elevation: 2,
onPressed: onAvatarEdit,
heroTag: null,
child: const Icon(Icons.camera_alt_outlined),
),
),
],
),
TextButtonTheme(
data: TextButtonThemeData(
style: TextButton.styleFrom(
disabledForegroundColor: theme.colorScheme.onSurface,
foregroundColor: theme.colorScheme.onSurface,
textStyle: const TextStyle(fontWeight: FontWeight.normal),
),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: LayoutBuilder(
builder: (context, constraints) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ConstrainedBox(
constraints: BoxConstraints(
maxWidth: constraints.maxWidth / 2,
),
child: textButtonLeft,
),
ConstrainedBox(
constraints: BoxConstraints(
maxWidth: constraints.maxWidth / 2,
),
child: textButtonRight,
),
],
);
},
),
),
),
IconButtonTheme(
data: IconButtonThemeData(
style: IconButton.styleFrom(
backgroundColor: theme.colorScheme.surfaceContainer,
iconSize: 24,
padding: const EdgeInsets.all(16),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: iconButtons,
),
),
const SizedBox(height: 0.0),
],
),
),
);
}
}

View file

@ -10,7 +10,7 @@ import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.
import 'package:fluffychat/widgets/future_loading_dialog.dart';
import 'matrix.dart';
enum ChatPopupMenuActions { details, mute, unmute, leave, search }
enum ChatPopupMenuActions { details, mute, unmute, emote, leave, search }
class ChatSettingsPopupMenu extends StatefulWidget {
final Room room;
@ -31,6 +31,20 @@ class ChatSettingsPopupMenuState extends State<ChatSettingsPopupMenu> {
super.dispose();
}
void goToEmoteSettings() async {
final room = widget.room;
// okay, we need to test if there are any emote state events other than the default one
// if so, we need to be directed to a selection screen for which pack we want to look at
// otherwise, we just open the normal one.
if ((room.states['im.ponies.room_emotes'] ?? <String, Event>{})
.keys
.any((String s) => s.isNotEmpty)) {
context.push('/rooms/${room.id}/details/multiple_emotes');
} else {
context.push('/rooms/${room.id}/details/emotes');
}
}
@override
Widget build(BuildContext context) {
notificationChangeSub ??= Matrix.of(context)
@ -98,6 +112,8 @@ class ChatSettingsPopupMenuState extends State<ChatSettingsPopupMenu> {
case ChatPopupMenuActions.search:
context.go('/rooms/${widget.room.id}/search');
break;
case ChatPopupMenuActions.emote:
goToEmoteSettings();
}
},
itemBuilder: (BuildContext context) => [
@ -144,6 +160,16 @@ class ChatSettingsPopupMenuState extends State<ChatSettingsPopupMenu> {
],
),
),
PopupMenuItem<ChatPopupMenuActions>(
value: ChatPopupMenuActions.emote,
child: Row(
children: [
const Icon(Icons.emoji_emotions_outlined),
const SizedBox(width: 12),
Text(L10n.of(context).emoteSettings),
],
),
),
PopupMenuItem<ChatPopupMenuActions>(
value: ChatPopupMenuActions.leave,
child: Row(