fluffychat merge - resolve conflicts
This commit is contained in:
commit
cca5bb382f
29 changed files with 493 additions and 517 deletions
|
|
@ -4180,5 +4180,6 @@
|
|||
"changeTheVisibilityOfChatHistory": "Change the visibility of the chat history",
|
||||
"changeTheCanonicalRoomAlias": "Change the main public chat address",
|
||||
"sendRoomNotifications": "Send a @room notifications",
|
||||
"changeTheDescriptionOfTheGroup": "Change the description of the chat"
|
||||
"changeTheDescriptionOfTheGroup": "Change the description of the chat",
|
||||
"chatPermissionsDescription": "Define which power level is necessary for certain actions in this chat. The power levels 0, 50 and 100 are usually representing users, moderators and admins, but any gradation is possible."
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ abstract class AppConfig {
|
|||
static double fontSizeFactor = 1;
|
||||
static const Color chatColor = primaryColor;
|
||||
static Color? colorSchemeSeed = primaryColor;
|
||||
static const double messageFontSize = 15.75;
|
||||
static const double messageFontSize = 16.0;
|
||||
static const bool allowOtherHomeservers = true;
|
||||
static const bool enableRegistration = true;
|
||||
// #Pangea
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
|||
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/utils/url_launcher.dart';
|
||||
|
||||
class ChatAppBarListTile extends StatelessWidget {
|
||||
|
|
@ -11,6 +10,8 @@ class ChatAppBarListTile extends StatelessWidget {
|
|||
final Widget? trailing;
|
||||
final void Function()? onTap;
|
||||
|
||||
static const double fixedHeight = 40.0;
|
||||
|
||||
const ChatAppBarListTile({
|
||||
super.key,
|
||||
this.leading,
|
||||
|
|
@ -23,38 +24,40 @@ class ChatAppBarListTile extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
final leading = this.leading;
|
||||
final trailing = this.trailing;
|
||||
final fontSize = AppConfig.messageFontSize * AppConfig.fontSizeFactor;
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: Row(
|
||||
children: [
|
||||
if (leading != null) leading,
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
child: Linkify(
|
||||
text: title,
|
||||
options: const LinkifyOptions(humanize: false),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
return SizedBox(
|
||||
height: fixedHeight,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Row(
|
||||
children: [
|
||||
if (leading != null) leading,
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
child: Linkify(
|
||||
text: title,
|
||||
options: const LinkifyOptions(humanize: false),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
fontSize: fontSize,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
fontSize: 14,
|
||||
),
|
||||
linkStyle: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor:
|
||||
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
|
||||
),
|
||||
linkStyle: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
fontSize: fontSize,
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor:
|
||||
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (trailing != null) trailing,
|
||||
],
|
||||
if (trailing != null) trailing,
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -178,15 +178,15 @@ class ChatView extends StatelessWidget {
|
|||
builder: (BuildContext context, snapshot) {
|
||||
var appbarBottomHeight = 0.0;
|
||||
if (controller.room.pinnedEventIds.isNotEmpty) {
|
||||
appbarBottomHeight += 42;
|
||||
appbarBottomHeight += ChatAppBarListTile.fixedHeight;
|
||||
}
|
||||
if (scrollUpBannerEventId != null) {
|
||||
appbarBottomHeight += 42;
|
||||
appbarBottomHeight += ChatAppBarListTile.fixedHeight;
|
||||
}
|
||||
final tombstoneEvent =
|
||||
controller.room.getState(EventTypes.RoomTombstone);
|
||||
if (tombstoneEvent != null) {
|
||||
appbarBottomHeight += 42;
|
||||
appbarBottomHeight += ChatAppBarListTile.fixedHeight;
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
|
|
|
|||
|
|
@ -557,22 +557,20 @@ class Message extends StatelessWidget {
|
|||
? const EdgeInsets.symmetric(vertical: 8.0)
|
||||
: EdgeInsets.zero,
|
||||
child: Center(
|
||||
child: Material(
|
||||
color: displayTime
|
||||
? Theme.of(context).colorScheme.surface
|
||||
: Theme.of(context).colorScheme.surface.withOpacity(0.33),
|
||||
borderRadius:
|
||||
BorderRadius.circular(AppConfig.borderRadius / 2),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 4.0),
|
||||
child: Text(
|
||||
event.originServerTs.localizedTime(context),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12 * AppConfig.fontSizeFactor,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 4.0),
|
||||
child: Text(
|
||||
event.originServerTs.localizedTime(context),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12 * AppConfig.fontSizeFactor,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
blurRadius: 3,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -17,10 +17,6 @@ class StateMessage extends StatelessWidget {
|
|||
child: Center(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
|
||||
),
|
||||
child: Text(
|
||||
event.calcLocalizedBodyFallback(
|
||||
MatrixLocals(L10n.of(context)!),
|
||||
|
|
@ -29,6 +25,12 @@ class StateMessage extends StatelessWidget {
|
|||
style: TextStyle(
|
||||
fontSize: 12 * AppConfig.fontSizeFactor,
|
||||
decoration: event.redacted ? TextDecoration.lineThrough : null,
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
blurRadius: 3,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -71,8 +71,8 @@ class PinnedEvents extends StatelessWidget {
|
|||
) ??
|
||||
L10n.of(context)!.loadingPleaseWait,
|
||||
leading: IconButton(
|
||||
splashRadius: 20,
|
||||
iconSize: 20,
|
||||
splashRadius: 18,
|
||||
iconSize: 18,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
icon: const Icon(Icons.push_pin),
|
||||
tooltip: L10n.of(context)!.unpin,
|
||||
|
|
|
|||
|
|
@ -32,10 +32,7 @@ class ChatDetailsView extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final room = Matrix.of(context).client.getRoomById(controller.roomId!);
|
||||
// #Pangea
|
||||
if (room == null || room.membership == Membership.leave) {
|
||||
// if (room == null) {
|
||||
// Pangea#
|
||||
if (room == null) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(L10n.of(context)!.oopsSomethingWentWrong),
|
||||
|
|
@ -62,31 +59,20 @@ class ChatDetailsView extends StatelessWidget {
|
|||
);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leading:
|
||||
// #Pangea
|
||||
!room.isSpace
|
||||
?
|
||||
// Pangea#
|
||||
controller.widget.embeddedCloseButton ??
|
||||
const Center(child: BackButton())
|
||||
// #Pangea
|
||||
: BackButton(
|
||||
onPressed: () => context.go("/rooms"),
|
||||
)
|
||||
// Pangea#
|
||||
,
|
||||
leading: controller.widget.embeddedCloseButton ??
|
||||
const Center(child: BackButton()),
|
||||
elevation: Theme.of(context).appBarTheme.elevation,
|
||||
actions: <Widget>[
|
||||
// #Pangea
|
||||
// if (room.canonicalAlias.isNotEmpty)
|
||||
// IconButton(
|
||||
// tooltip: L10n.of(context)!.share,
|
||||
// icon: Icon(Icons.adaptive.share_outlined),
|
||||
// onPressed: () => FluffyShare.share(
|
||||
// AppConfig.inviteLinkPrefix + room.canonicalAlias,
|
||||
// context,
|
||||
// IconButton(
|
||||
// tooltip: L10n.of(context)!.share,
|
||||
// icon: Icon(Icons.adaptive.share_outlined),
|
||||
// onPressed: () => FluffyShare.share(
|
||||
// AppConfig.inviteLinkPrefix + room.canonicalAlias,
|
||||
// context,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// Pangea#
|
||||
if (controller.widget.embeddedCloseButton == null)
|
||||
ChatSettingsPopupMenu(room, false),
|
||||
|
|
@ -122,32 +108,12 @@ class ChatDetailsView extends StatelessWidget {
|
|||
padding: const EdgeInsets.all(32.0),
|
||||
child: Stack(
|
||||
children: [
|
||||
Material(
|
||||
elevation: Theme.of(context)
|
||||
.appBarTheme
|
||||
.scrolledUnderElevation ??
|
||||
4,
|
||||
shadowColor: Theme.of(context)
|
||||
.appBarTheme
|
||||
.shadowColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
side: BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(
|
||||
Avatar.defaultSize * 2.5,
|
||||
),
|
||||
),
|
||||
// #Pangea
|
||||
// Hero animation is causing weird visual glitch
|
||||
// Probably not worth keeping
|
||||
// child: Hero(
|
||||
// tag: controller.widget
|
||||
// .embeddedCloseButton !=
|
||||
// null
|
||||
// ? 'embedded_content_banner'
|
||||
// : 'content_banner',
|
||||
// Pangea#
|
||||
Hero(
|
||||
tag: controller
|
||||
.widget.embeddedCloseButton !=
|
||||
null
|
||||
? 'embedded_content_banner'
|
||||
: 'content_banner',
|
||||
child: Avatar(
|
||||
mxContent: room.avatar,
|
||||
name: displayname,
|
||||
|
|
@ -211,7 +177,7 @@ class ChatDetailsView extends StatelessWidget {
|
|||
: displayname,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
// style: const TextStyle(fontSize: 18),
|
||||
style: const TextStyle(fontSize: 18),
|
||||
),
|
||||
),
|
||||
TextButton.icon(
|
||||
|
|
@ -243,13 +209,9 @@ class ChatDetailsView extends StatelessWidget {
|
|||
),
|
||||
],
|
||||
),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
// if (room.canSendEvent('m.room.name'))
|
||||
|
||||
// #Pangea
|
||||
if (room.isRoomAdmin)
|
||||
// #Pangea
|
||||
ClassNameButton(
|
||||
room: room,
|
||||
controller: controller,
|
||||
|
|
@ -259,32 +221,18 @@ class ChatDetailsView extends StatelessWidget {
|
|||
room: room,
|
||||
controller: controller,
|
||||
),
|
||||
// #Pangea
|
||||
RoomCapacityButton(
|
||||
room: room,
|
||||
controller: controller,
|
||||
),
|
||||
// Pangea#
|
||||
// commenting out language settings in spaces for now
|
||||
// if (room.languageSettings != null && room.isRoomAdmin)
|
||||
// LanguageSettings(
|
||||
// roomId: controller.roomId,
|
||||
// startOpen: false,
|
||||
// ),
|
||||
|
||||
// Commenting out pangea room rules for now
|
||||
// if (room.pangeaRoomRules != null)
|
||||
// RoomRulesEditor(
|
||||
// roomId: controller.roomId,
|
||||
// startOpen: false,
|
||||
// ),
|
||||
|
||||
// Divider(color: Theme.of(context).dividerColor),
|
||||
// if (!room.canChangeStateEvent(EventTypes.RoomTopic))
|
||||
// ListTile(
|
||||
// title: Text(
|
||||
// L10n.of(context)!.chatDescription,
|
||||
// style: TextStyle(
|
||||
// color: Theme.of(context).colorScheme.secondary,
|
||||
// color:
|
||||
// Theme.of(context).colorScheme.secondary,
|
||||
// fontWeight: FontWeight.bold,
|
||||
// ),
|
||||
// ),
|
||||
|
|
@ -294,7 +242,8 @@ class ChatDetailsView extends StatelessWidget {
|
|||
// padding: const EdgeInsets.all(16.0),
|
||||
// child: TextButton.icon(
|
||||
// onPressed: controller.setTopicAction,
|
||||
// label: Text(L10n.of(context)!.setChatDescription),
|
||||
// label:
|
||||
// Text(L10n.of(context)!.setChatDescription),
|
||||
// icon: const Icon(Icons.edit_outlined),
|
||||
// style: TextButton.styleFrom(
|
||||
// backgroundColor: Theme.of(context)
|
||||
|
|
@ -324,20 +273,21 @@ class ChatDetailsView extends StatelessWidget {
|
|||
// fontStyle: room.topic.isEmpty
|
||||
// ? FontStyle.italic
|
||||
// : FontStyle.normal,
|
||||
// color:
|
||||
// Theme.of(context).textTheme.bodyMedium!.color,
|
||||
// decorationColor:
|
||||
// Theme.of(context).textTheme.bodyMedium!.color,
|
||||
// color: Theme.of(context)
|
||||
// .textTheme
|
||||
// .bodyMedium!
|
||||
// .color,
|
||||
// decorationColor: Theme.of(context)
|
||||
// .textTheme
|
||||
// .bodyMedium!
|
||||
// .color,
|
||||
// ),
|
||||
// onOpen: (url) =>
|
||||
// UrlLauncher(context, url.url).launchUrl(),
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(height: 16),
|
||||
// Divider(
|
||||
// height: 1,
|
||||
// color: Theme.of(context).dividerColor,
|
||||
// ),
|
||||
// Divider(color: Theme.of(context).dividerColor),
|
||||
// ListTile(
|
||||
// leading: CircleAvatar(
|
||||
// backgroundColor:
|
||||
|
|
@ -354,23 +304,25 @@ class ChatDetailsView extends StatelessWidget {
|
|||
// trailing: const Icon(Icons.chevron_right_outlined),
|
||||
// ),
|
||||
// if (!room.isDirectChat)
|
||||
// ListTile(
|
||||
// leading: CircleAvatar(
|
||||
// backgroundColor:
|
||||
// Theme.of(context).scaffoldBackgroundColor,
|
||||
// foregroundColor: iconColor,
|
||||
// child: const Icon(Icons.shield_outlined),
|
||||
// ListTile(
|
||||
// leading: CircleAvatar(
|
||||
// backgroundColor:
|
||||
// Theme.of(context).scaffoldBackgroundColor,
|
||||
// foregroundColor: iconColor,
|
||||
// child: const Icon(Icons.shield_outlined),
|
||||
// ),
|
||||
// title: Text(
|
||||
// L10n.of(context)!.accessAndVisibility,
|
||||
// ),
|
||||
// subtitle: Text(
|
||||
// L10n.of(context)!
|
||||
// .accessAndVisibilityDescription,
|
||||
// ),
|
||||
// onTap: () => context
|
||||
// .push('/rooms/${room.id}/details/access'),
|
||||
// trailing:
|
||||
// const Icon(Icons.chevron_right_outlined),
|
||||
// ),
|
||||
// title: Text(
|
||||
// L10n.of(context)!.accessAndVisibility,
|
||||
// ),
|
||||
// subtitle: Text(
|
||||
// L10n.of(context)!.accessAndVisibilityDescription,
|
||||
// ),
|
||||
// onTap: () => context
|
||||
// .push('/rooms/${room.id}/details/access'),
|
||||
// trailing: const Icon(Icons.chevron_right_outlined),
|
||||
// ),
|
||||
// if (!room.isDirectChat)
|
||||
if (!room.isDirectChat &&
|
||||
!room.isSpace &&
|
||||
|
|
@ -399,17 +351,13 @@ class ChatDetailsView extends StatelessWidget {
|
|||
Icons.edit_attributes_outlined,
|
||||
),
|
||||
),
|
||||
// #Pangea
|
||||
// trailing: const Icon(Icons.chevron_right_outlined),
|
||||
// Pangea#
|
||||
trailing:
|
||||
const Icon(Icons.chevron_right_outlined),
|
||||
onTap: () => context.push(
|
||||
'/rooms/${room.id}/details/permissions',
|
||||
),
|
||||
),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
Divider(color: Theme.of(context).dividerColor),
|
||||
// #Pangea
|
||||
if (room.canInvite &&
|
||||
!room.isDirectChat &&
|
||||
|
|
@ -486,8 +434,8 @@ class ChatDetailsView extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
onTap: () async {
|
||||
OkCancelResult confirmed = OkCancelResult.ok;
|
||||
bool shouldGo = false;
|
||||
var confirmed = OkCancelResult.ok;
|
||||
var shouldGo = false;
|
||||
// archiveSpace has its own popup; only show if not space
|
||||
if (!room.isSpace) {
|
||||
confirmed = await showOkCancelAlertDialog(
|
||||
|
|
@ -539,10 +487,10 @@ class ChatDetailsView extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
onTap: () async {
|
||||
OkCancelResult confirmed = OkCancelResult.ok;
|
||||
bool shouldGo = false;
|
||||
var confirmed = OkCancelResult.ok;
|
||||
var shouldGo = false;
|
||||
// If user is only admin, room will be archived
|
||||
final bool onlyAdmin = await room.isOnlyAdmin();
|
||||
final onlyAdmin = await room.isOnlyAdmin();
|
||||
// archiveSpace has its own popup; only show if not space
|
||||
if (!room.isSpace) {
|
||||
confirmed = await showOkCancelAlertDialog(
|
||||
|
|
@ -649,8 +597,10 @@ class ChatDetailsView extends StatelessWidget {
|
|||
// radius: Avatar.defaultSize / 2,
|
||||
// child: const Icon(Icons.add_outlined),
|
||||
// ),
|
||||
// trailing: const Icon(Icons.chevron_right_outlined),
|
||||
// onTap: () => context.go('/rooms/${room.id}/invite'),
|
||||
// trailing:
|
||||
// const Icon(Icons.chevron_right_outlined),
|
||||
// onTap: () =>
|
||||
// context.go('/rooms/${room.id}/invite'),
|
||||
// ),
|
||||
// Pangea#
|
||||
],
|
||||
|
|
|
|||
|
|
@ -831,9 +831,10 @@ class ChatListController extends State<ChatList>
|
|||
isDestructiveAction: true,
|
||||
);
|
||||
if (confirmed == OkCancelResult.cancel) return;
|
||||
if (!mounted) {
|
||||
await showFutureLoadingDialog(context: context, future: room.leave);
|
||||
}
|
||||
if (!mounted) return;
|
||||
|
||||
await showFutureLoadingDialog(context: context, future: room.leave);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ class NaviRailItem extends StatelessWidget {
|
|||
return HoverBuilder(
|
||||
builder: (context, hovered) {
|
||||
return SizedBox(
|
||||
height: 64,
|
||||
width: 64,
|
||||
height: FluffyThemes.navRailWidth,
|
||||
width: FluffyThemes.navRailWidth,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@ import 'package:collection/collection.dart';
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pages/chat_list/chat_list_item.dart';
|
||||
import 'package:fluffychat/pages/chat_list/search_title.dart';
|
||||
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
|
||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||
import 'package:fluffychat/utils/stream_extension.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:fluffychat/widgets/public_room_bottom_sheet.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
||||
|
|
@ -93,38 +95,23 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
final client = Matrix.of(context).client;
|
||||
final space = client.getRoomById(widget.spaceId);
|
||||
|
||||
final consent = await showOkCancelAlertDialog(
|
||||
final joined = await showAdaptiveBottomSheet<bool>(
|
||||
context: context,
|
||||
title: item.name ?? item.canonicalAlias ?? L10n.of(context)!.emptyChat,
|
||||
message: item.topic,
|
||||
okLabel: L10n.of(context)!.joinRoom,
|
||||
cancelLabel: L10n.of(context)!.cancel,
|
||||
builder: (_) => PublicRoomBottomSheet(
|
||||
outerContext: context,
|
||||
chunk: item,
|
||||
via: space?.spaceChildren
|
||||
.firstWhereOrNull(
|
||||
(child) => child.roomId == item.roomId,
|
||||
)
|
||||
?.via,
|
||||
),
|
||||
);
|
||||
if (consent != OkCancelResult.ok) return;
|
||||
if (!mounted) return;
|
||||
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async {
|
||||
await client.joinRoom(
|
||||
item.roomId,
|
||||
serverName: space?.spaceChildren
|
||||
.firstWhereOrNull(
|
||||
(child) => child.roomId == item.roomId,
|
||||
)
|
||||
?.via,
|
||||
);
|
||||
if (client.getRoomById(item.roomId) == null) {
|
||||
// Wait for room actually appears in sync
|
||||
await client.waitForRoomInSync(item.roomId, join: true);
|
||||
}
|
||||
},
|
||||
);
|
||||
if (!mounted) return;
|
||||
|
||||
setState(() {
|
||||
_discoveredChildren.remove(item);
|
||||
});
|
||||
if (mounted && joined == true) {
|
||||
setState(() {
|
||||
_discoveredChildren.remove(item);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _onSpaceAction(SpaceActions action) async {
|
||||
|
|
|
|||
|
|
@ -57,6 +57,22 @@ class ChatPermissionsSettingsView extends StatelessWidget {
|
|||
// Pangea#
|
||||
return Column(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.info_outlined),
|
||||
subtitle: Text(
|
||||
L10n.of(context)!.chatPermissionsDescription,
|
||||
),
|
||||
),
|
||||
Divider(color: Theme.of(context).dividerColor),
|
||||
ListTile(
|
||||
title: Text(
|
||||
L10n.of(context)!.chatPermissions,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
|
|
|
|||
|
|
@ -80,11 +80,8 @@ class PermissionsListTile extends StatelessWidget {
|
|||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
trailing: Material(
|
||||
color: color.withAlpha(64),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
|
||||
side: BorderSide(color: color),
|
||||
),
|
||||
color: color.withAlpha(32),
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
|
||||
child: DropdownButton<int>(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
|
||||
|
|
@ -94,15 +91,24 @@ class PermissionsListTile extends StatelessWidget {
|
|||
items: [
|
||||
DropdownMenuItem(
|
||||
value: permission < 50 ? permission : 0,
|
||||
child: Text(L10n.of(context)!.userLevel(permission)),
|
||||
child: Text(
|
||||
L10n.of(context)!.userLevel(permission < 50 ? permission : 0),
|
||||
),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: permission < 100 && permission >= 50 ? permission : 50,
|
||||
child: Text(L10n.of(context)!.moderatorLevel(permission)),
|
||||
child: Text(
|
||||
L10n.of(context)!.moderatorLevel(
|
||||
permission < 100 && permission >= 50 ? permission : 50,
|
||||
),
|
||||
),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: permission >= 100 ? permission : 100,
|
||||
child: Text(L10n.of(context)!.adminLevel(permission)),
|
||||
child: Text(
|
||||
L10n.of(context)!
|
||||
.adminLevel(permission >= 100 ? permission : 100),
|
||||
),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: null,
|
||||
|
|
|
|||
|
|
@ -129,10 +129,6 @@ class NewGroupView extends StatelessWidget {
|
|||
// child: SizedBox(
|
||||
// width: double.infinity,
|
||||
// child: ElevatedButton(
|
||||
// style: ElevatedButton.styleFrom(
|
||||
// foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||
// backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
// ),
|
||||
// onPressed:
|
||||
// controller.loading ? null : controller.submitAction,
|
||||
// child: controller.loading
|
||||
|
|
|
|||
|
|
@ -95,10 +95,6 @@ class NewSpaceView extends StatelessWidget {
|
|||
// child: SizedBox(
|
||||
// width: double.infinity,
|
||||
// child: ElevatedButton(
|
||||
// style: ElevatedButton.styleFrom(
|
||||
// foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||
// backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
// ),
|
||||
// onPressed:
|
||||
// controller.loading ? null : controller.submitAction,
|
||||
// child: controller.loading
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ 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';
|
||||
import 'package:package_info_plus/package_info_plus.dart'; //adding to check app version
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
import 'settings.dart';
|
||||
|
|
@ -20,7 +20,7 @@ class SettingsView extends StatelessWidget {
|
|||
|
||||
// #Pangea
|
||||
Future<String> getAppVersion(BuildContext context) async {
|
||||
final PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
return L10n.of(context)!
|
||||
.versionText(packageInfo.version, packageInfo.buildNumber);
|
||||
}
|
||||
|
|
@ -28,9 +28,7 @@ class SettingsView extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// #Pangea
|
||||
// final showChatBackupBanner = controller.showChatBackupBanner;
|
||||
// Pangea#
|
||||
final showChatBackupBanner = controller.showChatBackupBanner;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: Center(
|
||||
|
|
@ -39,13 +37,6 @@ class SettingsView extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
title: Text(L10n.of(context)!.settings),
|
||||
actions: [
|
||||
TextButton.icon(
|
||||
onPressed: controller.logoutAction,
|
||||
label: Text(L10n.of(context)!.logout),
|
||||
icon: const Icon(Icons.logout_outlined),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: ListTileTheme(
|
||||
iconColor: Theme.of(context).colorScheme.onSurface,
|
||||
|
|
@ -66,32 +57,17 @@ class SettingsView extends StatelessWidget {
|
|||
padding: const EdgeInsets.all(32.0),
|
||||
child: Stack(
|
||||
children: [
|
||||
Material(
|
||||
elevation: Theme.of(context)
|
||||
.appBarTheme
|
||||
.scrolledUnderElevation ??
|
||||
4,
|
||||
shadowColor:
|
||||
Theme.of(context).appBarTheme.shadowColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
side: BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(
|
||||
Avatar.defaultSize * 2.5,
|
||||
),
|
||||
),
|
||||
child: Avatar(
|
||||
mxContent: profile?.avatarUrl,
|
||||
name: displayname,
|
||||
size: Avatar.defaultSize * 2.5,
|
||||
),
|
||||
Avatar(
|
||||
mxContent: profile?.avatarUrl,
|
||||
name: displayname,
|
||||
size: Avatar.defaultSize * 2.5,
|
||||
),
|
||||
if (profile != null)
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: FloatingActionButton.small(
|
||||
elevation: 2,
|
||||
onPressed: controller.setAvatarAction,
|
||||
heroTag: null,
|
||||
child: const Icon(Icons.camera_alt_outlined),
|
||||
|
|
@ -119,7 +95,9 @@ class SettingsView extends StatelessWidget {
|
|||
displayname,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
// style: const TextStyle(fontSize: 18),
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton.icon(
|
||||
|
|
@ -147,10 +125,7 @@ class SettingsView extends StatelessWidget {
|
|||
},
|
||||
),
|
||||
// #Pangea
|
||||
// Divider(
|
||||
// height: 1,
|
||||
// color: Theme.of(context).dividerColor,
|
||||
// ),
|
||||
// Divider(color: Theme.of(context).dividerColor),
|
||||
// if (showChatBackupBanner == null)
|
||||
// ListTile(
|
||||
// leading: const Icon(Icons.backup_outlined),
|
||||
|
|
@ -166,7 +141,6 @@ class SettingsView extends StatelessWidget {
|
|||
// onChanged: controller.firstRunBootstrapAction,
|
||||
// ),
|
||||
// Divider(
|
||||
// height: 1,
|
||||
// color: Theme.of(context).dividerColor,
|
||||
// ),
|
||||
// Pangea#
|
||||
|
|
@ -174,52 +148,40 @@ class SettingsView extends StatelessWidget {
|
|||
leading: const Icon(Icons.format_paint_outlined),
|
||||
title: Text(L10n.of(context)!.changeTheme),
|
||||
onTap: () => context.go('/rooms/settings/style'),
|
||||
trailing: const Icon(Icons.chevron_right_outlined),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.notifications_outlined),
|
||||
title: Text(L10n.of(context)!.notifications),
|
||||
onTap: () => context.go('/rooms/settings/notifications'),
|
||||
trailing: const Icon(Icons.chevron_right_outlined),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.devices_outlined),
|
||||
title: Text(L10n.of(context)!.devices),
|
||||
onTap: () => context.go('/rooms/settings/devices'),
|
||||
trailing: const Icon(Icons.chevron_right_outlined),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.forum_outlined),
|
||||
title: Text(L10n.of(context)!.chat),
|
||||
onTap: () => context.go('/rooms/settings/chat'),
|
||||
trailing: const Icon(Icons.chevron_right_outlined),
|
||||
),
|
||||
// #Pangea
|
||||
ListTile(
|
||||
leading: const Icon(Icons.account_circle_outlined),
|
||||
title: Text(L10n.of(context)!.subscriptionManagement),
|
||||
onTap: () => context.go('/rooms/settings/subscription'),
|
||||
trailing: const Icon(
|
||||
Icons.chevron_right_outlined,
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.psychology_outlined),
|
||||
title: Text(L10n.of(context)!.learningSettings),
|
||||
onTap: () => context.go('/rooms/settings/learning'),
|
||||
trailing: const Icon(Icons.chevron_right_outlined),
|
||||
),
|
||||
// Pangea#
|
||||
ListTile(
|
||||
leading: const Icon(Icons.shield_outlined),
|
||||
title: Text(L10n.of(context)!.security),
|
||||
onTap: () => context.go('/rooms/settings/security'),
|
||||
trailing: const Icon(Icons.chevron_right_outlined),
|
||||
),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
Divider(color: Theme.of(context).dividerColor),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.help_outline_outlined),
|
||||
title: Text(L10n.of(context)!.help),
|
||||
|
|
@ -229,7 +191,7 @@ class SettingsView extends StatelessWidget {
|
|||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async {
|
||||
final String roomId =
|
||||
final roomId =
|
||||
await Matrix.of(context).client.startDirectChat(
|
||||
Environment.supportUserId,
|
||||
enableEncryption: false,
|
||||
|
|
@ -239,20 +201,17 @@ class SettingsView extends StatelessWidget {
|
|||
);
|
||||
},
|
||||
// Pangea#
|
||||
trailing: const Icon(Icons.open_in_new_outlined),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.shield_sharp),
|
||||
title: Text(L10n.of(context)!.privacy),
|
||||
onTap: () => launchUrlString(AppConfig.privacyUrl),
|
||||
trailing: const Icon(Icons.open_in_new_outlined),
|
||||
),
|
||||
// #Pangea
|
||||
// ListTile(
|
||||
// leading: const Icon(Icons.info_outline_rounded),
|
||||
// title: Text(L10n.of(context)!.about),
|
||||
// onTap: () => PlatformInfos.showDialog(context),
|
||||
// trailing: const Icon(Icons.chevron_right_outlined),
|
||||
// ),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.shield_outlined),
|
||||
|
|
@ -290,6 +249,12 @@ class SettingsView extends StatelessWidget {
|
|||
title: Text(L10n.of(context)!.connectedToStaging),
|
||||
),
|
||||
// Pangea#
|
||||
Divider(color: Theme.of(context).dividerColor),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.logout_outlined),
|
||||
title: Text(L10n.of(context)!.logout),
|
||||
onTap: controller.logoutAction,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ class Settings3PidView extends StatelessWidget {
|
|||
.withTheseAddressesRecoveryDescription,
|
||||
),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
const Divider(),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: identifier.length,
|
||||
|
|
|
|||
|
|
@ -71,10 +71,7 @@ class SettingsChatView extends StatelessWidget {
|
|||
defaultValue: AppConfig.swipeRightToLeftToReply,
|
||||
),
|
||||
// #Pangea
|
||||
// Divider(
|
||||
// height: 1,
|
||||
// color: Theme.of(context).dividerColor,
|
||||
// ),
|
||||
// Divider(color: Theme.of(context).dividerColor),
|
||||
// ListTile(
|
||||
// title: Text(
|
||||
// L10n.of(context)!.customEmojisAndStickers,
|
||||
|
|
@ -93,10 +90,7 @@ class SettingsChatView extends StatelessWidget {
|
|||
// child: Icon(Icons.chevron_right_outlined),
|
||||
// ),
|
||||
// ),
|
||||
// Divider(
|
||||
// height: 1,
|
||||
// color: Theme.of(context).dividerColor,
|
||||
// ),
|
||||
// Divider(color: Theme.of(context).dividerColor),
|
||||
// ListTile(
|
||||
// title: Text(
|
||||
// L10n.of(context)!.calls,
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ class EmotesSettingsView extends StatelessWidget {
|
|||
onChanged: controller.setIsGloballyActive,
|
||||
),
|
||||
if (!controller.readonly || controller.room != null)
|
||||
const Divider(thickness: 1),
|
||||
const Divider(),
|
||||
imageKeys.isEmpty
|
||||
? Center(
|
||||
child: Padding(
|
||||
|
|
|
|||
|
|
@ -85,7 +85,6 @@ class SettingsSecurityView extends StatelessWidget {
|
|||
),
|
||||
},
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
ListTile(
|
||||
|
|
|
|||
|
|
@ -136,7 +136,6 @@ class SettingsStyleView extends StatelessWidget {
|
|||
),
|
||||
const SizedBox(height: 8),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
ListTile(
|
||||
|
|
@ -167,7 +166,6 @@ class SettingsStyleView extends StatelessWidget {
|
|||
onChanged: controller.switchTheme,
|
||||
),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
ListTile(
|
||||
|
|
@ -192,7 +190,6 @@ class SettingsStyleView extends StatelessWidget {
|
|||
defaultValue: AppConfig.separateChatTypes,
|
||||
),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
ListTile(
|
||||
|
|
|
|||
|
|
@ -236,6 +236,45 @@ class UserBottomSheetController extends State<UserBottomSheet> {
|
|||
}
|
||||
}
|
||||
|
||||
bool isSending = false;
|
||||
|
||||
Object? sendError;
|
||||
|
||||
final TextEditingController sendController = TextEditingController();
|
||||
|
||||
void sendAction([_]) async {
|
||||
final userId = widget.user?.id ?? widget.profile?.userId;
|
||||
final client = Matrix.of(widget.outerContext).client;
|
||||
if (userId == null) throw ('user or profile must not be null!');
|
||||
|
||||
final input = sendController.text.trim();
|
||||
if (input.isEmpty) return;
|
||||
|
||||
setState(() {
|
||||
isSending = true;
|
||||
sendError = null;
|
||||
});
|
||||
try {
|
||||
final roomId = await client.startDirectChat(userId);
|
||||
if (!mounted) return;
|
||||
final room = client.getRoomById(roomId);
|
||||
if (room == null) {
|
||||
throw ('DM Room found or created but room not found in client');
|
||||
}
|
||||
await room.sendTextEvent(input);
|
||||
setState(() {
|
||||
isSending = false;
|
||||
sendController.clear();
|
||||
});
|
||||
} catch (e, s) {
|
||||
Logs().d('Unable to send message', e, s);
|
||||
setState(() {
|
||||
isSending = false;
|
||||
sendError = e;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void knockAccept() async {
|
||||
final user = widget.user!;
|
||||
final result = await showFutureLoadingDialog(
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'package:fluffychat/config/app_config.dart';
|
|||
import 'package:fluffychat/pangea/utils/bot_name.dart';
|
||||
import 'package:fluffychat/utils/date_time_extension.dart';
|
||||
import 'package:fluffychat/utils/fluffy_share.dart';
|
||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||
import 'package:fluffychat/utils/url_launcher.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/presence_builder.dart';
|
||||
|
|
@ -29,6 +30,7 @@ class UserBottomSheetView extends StatelessWidget {
|
|||
|
||||
final client = Matrix.of(controller.widget.outerContext).client;
|
||||
final profileSearchError = controller.widget.profileSearchError;
|
||||
final dmRoomId = client.getDirectChatFromUserId(userId);
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
|
|
@ -36,78 +38,20 @@ class UserBottomSheetView extends StatelessWidget {
|
|||
onPressed: Navigator.of(context, rootNavigator: false).pop,
|
||||
),
|
||||
centerTitle: false,
|
||||
title: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(displayname),
|
||||
PresenceBuilder(
|
||||
userId: userId,
|
||||
client: client,
|
||||
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;
|
||||
|
||||
final lastActiveTimestamp = presence.lastActiveTimestamp;
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
margin: const EdgeInsets.only(right: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: dotColor,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
),
|
||||
if (presence.currentlyActive == true)
|
||||
Text(
|
||||
L10n.of(context)!.currentlyActive,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
)
|
||||
else if (lastActiveTimestamp != null)
|
||||
Text(
|
||||
L10n.of(context)!.lastActiveAgo(
|
||||
lastActiveTimestamp.localizedTimeShort(context),
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
if (userId != client.userID &&
|
||||
!client.ignoredUsers.contains(userId)
|
||||
// #Pangea
|
||||
&&
|
||||
userId != BotName.byEnvironment
|
||||
// Pangea#
|
||||
)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.block_outlined),
|
||||
tooltip: L10n.of(context)!.block,
|
||||
onPressed: () => controller
|
||||
.participantAction(UserBottomSheetAction.ignore),
|
||||
),
|
||||
),
|
||||
],
|
||||
title: Text(displayname),
|
||||
actions: dmRoomId == null
|
||||
? null
|
||||
: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: FloatingActionButton.small(
|
||||
elevation: 0,
|
||||
onPressed: () => controller
|
||||
.participantAction(UserBottomSheetAction.message),
|
||||
child: const Icon(Icons.chat_outlined),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: StreamBuilder<Object>(
|
||||
stream: user?.room.client.onSync.stream.where(
|
||||
|
|
@ -174,25 +118,12 @@ class UserBottomSheetView extends StatelessWidget {
|
|||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Material(
|
||||
elevation: Theme.of(context)
|
||||
.appBarTheme
|
||||
.scrolledUnderElevation ??
|
||||
4,
|
||||
shadowColor: Theme.of(context).appBarTheme.shadowColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
side: BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(
|
||||
Avatar.defaultSize * 2.5,
|
||||
),
|
||||
),
|
||||
child: Avatar(
|
||||
mxContent: avatarUrl,
|
||||
name: displayname,
|
||||
size: Avatar.defaultSize * 2.5,
|
||||
),
|
||||
child: Avatar(
|
||||
client:
|
||||
Matrix.of(controller.widget.outerContext).client,
|
||||
mxContent: avatarUrl,
|
||||
name: displayname,
|
||||
size: Avatar.defaultSize * 2.5,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
|
|
@ -200,26 +131,6 @@ class UserBottomSheetView extends StatelessWidget {
|
|||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextButton.icon(
|
||||
onPressed: () => FluffyShare.share(
|
||||
'https://matrix.to/#/$userId',
|
||||
context,
|
||||
),
|
||||
icon: Icon(
|
||||
Icons.adaptive.share_outlined,
|
||||
size: 16,
|
||||
),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor:
|
||||
Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
label: Text(
|
||||
displayname,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
// style: const TextStyle(fontSize: 18),
|
||||
),
|
||||
),
|
||||
TextButton.icon(
|
||||
onPressed: () => FluffyShare.share(
|
||||
userId,
|
||||
|
|
@ -232,37 +143,72 @@ class UserBottomSheetView extends StatelessWidget {
|
|||
),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor:
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
label: Text(
|
||||
userId,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
// style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
),
|
||||
PresenceBuilder(
|
||||
userId: userId,
|
||||
client: client,
|
||||
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;
|
||||
|
||||
final lastActiveTimestamp =
|
||||
presence.lastActiveTimestamp;
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(width: 16),
|
||||
Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
color: dotColor,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
if (presence.currentlyActive == true)
|
||||
Text(
|
||||
L10n.of(context)!.currentlyActive,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style:
|
||||
Theme.of(context).textTheme.bodySmall,
|
||||
)
|
||||
else if (lastActiveTimestamp != null)
|
||||
Text(
|
||||
L10n.of(context)!.lastActiveAgo(
|
||||
lastActiveTimestamp
|
||||
.localizedTimeShort(context),
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style:
|
||||
Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (userId != client.userID)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
vertical: 8.0,
|
||||
),
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () => controller
|
||||
.participantAction(UserBottomSheetAction.message),
|
||||
icon: const Icon(Icons.forum_outlined),
|
||||
label: Text(
|
||||
controller.widget.user == null
|
||||
? L10n.of(context)!.startConversation
|
||||
: L10n.of(context)!.sendAMessage,
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceBuilder(
|
||||
userId: userId,
|
||||
client: client,
|
||||
|
|
@ -286,6 +232,49 @@ class UserBottomSheetView extends StatelessWidget {
|
|||
);
|
||||
},
|
||||
),
|
||||
if (userId != client.userID)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
vertical: 8.0,
|
||||
),
|
||||
child: dmRoomId == null
|
||||
? ElevatedButton.icon(
|
||||
onPressed: () => controller.participantAction(
|
||||
UserBottomSheetAction.message,
|
||||
),
|
||||
icon: const Icon(Icons.chat_outlined),
|
||||
label: Text(L10n.of(context)!.startConversation),
|
||||
)
|
||||
: TextField(
|
||||
controller: controller.sendController,
|
||||
readOnly: controller.isSending,
|
||||
onSubmitted: controller.sendAction,
|
||||
minLines: 1,
|
||||
maxLines: 1,
|
||||
textInputAction: TextInputAction.send,
|
||||
decoration: InputDecoration(
|
||||
errorText: controller.sendError
|
||||
?.toLocalizedString(context),
|
||||
hintText: L10n.of(context)!.sendMessages,
|
||||
suffix: controller.isSending
|
||||
? const SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator.adaptive(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
suffixIcon: controller.isSending
|
||||
? null
|
||||
: IconButton(
|
||||
icon: const Icon(Icons.send_outlined),
|
||||
onPressed: controller.sendAction,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (controller.widget.onMention != null)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.alternate_email_outlined),
|
||||
|
|
@ -339,8 +328,8 @@ class UserBottomSheetView extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
Divider(color: Theme.of(context).dividerColor),
|
||||
],
|
||||
Divider(color: Theme.of(context).dividerColor),
|
||||
if (user != null && user.canKick)
|
||||
ListTile(
|
||||
textColor: Theme.of(context).colorScheme.error,
|
||||
|
|
@ -376,7 +365,7 @@ class UserBottomSheetView extends StatelessWidget {
|
|||
// textColor: Theme.of(context).colorScheme.onErrorContainer,
|
||||
// iconColor: Theme.of(context).colorScheme.onErrorContainer,
|
||||
// title: Text(L10n.of(context)!.reportUser),
|
||||
// leading: const Icon(Icons.report_outlined),
|
||||
// leading: const Icon(Icons.gavel_outlined),
|
||||
// onTap: () => controller
|
||||
// .participantAction(UserBottomSheetAction.report),
|
||||
// ),
|
||||
|
|
@ -392,6 +381,21 @@ class UserBottomSheetView extends StatelessWidget {
|
|||
style: const TextStyle(color: Colors.orange),
|
||||
),
|
||||
),
|
||||
if (userId != client.userID &&
|
||||
!client.ignoredUsers.contains(userId)
|
||||
// #Pangea
|
||||
&&
|
||||
userId != BotName.byEnvironment
|
||||
// Pangea#
|
||||
)
|
||||
ListTile(
|
||||
textColor: Theme.of(context).colorScheme.onErrorContainer,
|
||||
iconColor: Theme.of(context).colorScheme.onErrorContainer,
|
||||
leading: const Icon(Icons.block_outlined),
|
||||
title: Text(L10n.of(context)!.block),
|
||||
onTap: () => controller
|
||||
.participantAction(UserBottomSheetAction.ignore),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -8,18 +8,19 @@ Future<T?> showAdaptiveBottomSheet<T>({
|
|||
required Widget Function(BuildContext) builder,
|
||||
bool isDismissible = true,
|
||||
bool isScrollControlled = true,
|
||||
double maxHeight = 480.0,
|
||||
double maxHeight = 512,
|
||||
bool useRootNavigator = true,
|
||||
}) =>
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: builder,
|
||||
// this sadly is ugly on desktops but otherwise breaks `.of(context)` calls
|
||||
useRootNavigator: false,
|
||||
useRootNavigator: useRootNavigator,
|
||||
isDismissible: isDismissible,
|
||||
isScrollControlled: isScrollControlled,
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: maxHeight,
|
||||
maxWidth: FluffyThemes.columnWidth * 1.5,
|
||||
maxWidth: FluffyThemes.columnWidth * 1.25,
|
||||
),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: const RoundedRectangleBorder(
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ class Avatar extends StatelessWidget {
|
|||
child: noPic
|
||||
? textWidget
|
||||
: MxcImage(
|
||||
client: client,
|
||||
key: ValueKey(mxContent.toString()),
|
||||
cacheKey: '${mxContent}_$size',
|
||||
uri: mxContent,
|
||||
|
|
|
|||
|
|
@ -1,20 +1,17 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
|
||||
class MaxWidthBody extends StatelessWidget {
|
||||
final Widget? child;
|
||||
final Widget child;
|
||||
final double maxWidth;
|
||||
final bool withFrame;
|
||||
final bool withScrolling;
|
||||
final EdgeInsets? innerPadding;
|
||||
|
||||
const MaxWidthBody({
|
||||
this.child,
|
||||
required this.child,
|
||||
this.maxWidth = 600,
|
||||
this.withFrame = true,
|
||||
this.withScrolling = true,
|
||||
this.innerPadding,
|
||||
super.key,
|
||||
|
|
@ -24,36 +21,35 @@ class MaxWidthBody extends StatelessWidget {
|
|||
return SafeArea(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final paddingVal = max(0, (constraints.maxWidth - maxWidth) / 2);
|
||||
final hasPadding = paddingVal > 0;
|
||||
final padding = EdgeInsets.symmetric(
|
||||
vertical: hasPadding ? 32 : 0,
|
||||
horizontal: max(0, (constraints.maxWidth - maxWidth) / 2),
|
||||
);
|
||||
final childWithPadding = Padding(
|
||||
padding: padding,
|
||||
child: withFrame && hasPadding
|
||||
? Material(
|
||||
elevation:
|
||||
Theme.of(context).appBarTheme.scrolledUnderElevation ??
|
||||
4,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||
shadowColor: Theme.of(context).appBarTheme.shadowColor,
|
||||
child: child,
|
||||
)
|
||||
: child,
|
||||
);
|
||||
if (!withScrolling) {
|
||||
return Padding(
|
||||
padding: innerPadding ?? EdgeInsets.zero,
|
||||
child: childWithPadding,
|
||||
);
|
||||
}
|
||||
const desiredWidth = FluffyThemes.columnWidth * 1.5;
|
||||
final body = constraints.maxWidth <= desiredWidth
|
||||
? child
|
||||
: Container(
|
||||
alignment: Alignment.topCenter,
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: FluffyThemes.columnWidth * 1.5,
|
||||
),
|
||||
child: Material(
|
||||
elevation: Theme.of(context)
|
||||
.appBarTheme
|
||||
.scrolledUnderElevation ??
|
||||
4,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
borderRadius:
|
||||
BorderRadius.circular(AppConfig.borderRadius),
|
||||
shadowColor: Theme.of(context).appBarTheme.shadowColor,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
if (!withScrolling) return body;
|
||||
|
||||
return SingleChildScrollView(
|
||||
padding: innerPadding,
|
||||
physics: const ScrollPhysics(),
|
||||
child: childWithPadding,
|
||||
child: body,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
|
||||
class TwoColumnLayout extends StatelessWidget {
|
||||
final Widget mainView;
|
||||
final Widget sideView;
|
||||
|
|
@ -20,7 +22,8 @@ class TwoColumnLayout extends StatelessWidget {
|
|||
Container(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: const BoxDecoration(),
|
||||
width: 360.0 + (displayNavigationRail ? 64 : 0),
|
||||
width: FluffyThemes.columnWidth +
|
||||
(displayNavigationRail ? FluffyThemes.navRailWidth : 0),
|
||||
child: mainView,
|
||||
),
|
||||
Container(
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ class MxcImage extends StatefulWidget {
|
|||
final ThumbnailMethod thumbnailMethod;
|
||||
final Widget Function(BuildContext context)? placeholder;
|
||||
final String? cacheKey;
|
||||
final Client? client;
|
||||
|
||||
const MxcImage({
|
||||
this.uri,
|
||||
|
|
@ -38,6 +39,7 @@ class MxcImage extends StatefulWidget {
|
|||
this.animationCurve = FluffyThemes.animationCurve,
|
||||
this.thumbnailMethod = ThumbnailMethod.scale,
|
||||
this.cacheKey,
|
||||
this.client,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
|
@ -64,7 +66,7 @@ class _MxcImageState extends State<MxcImage> {
|
|||
bool? _isCached;
|
||||
|
||||
Future<void> _load() async {
|
||||
final client = Matrix.of(context).client;
|
||||
final client = widget.client ?? Matrix.of(context).client;
|
||||
final uri = widget.uri;
|
||||
final event = widget.event;
|
||||
|
||||
|
|
|
|||
|
|
@ -10,19 +10,17 @@ import 'package:future_loading_dialog/future_loading_dialog.dart';
|
|||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import '../utils/localized_exception_extension.dart';
|
||||
|
||||
class PublicRoomBottomSheet extends StatelessWidget {
|
||||
final String? roomAlias;
|
||||
final BuildContext outerContext;
|
||||
final PublicRoomsChunk? chunk;
|
||||
final VoidCallback? onRoomJoined;
|
||||
final List<String>? via;
|
||||
|
||||
PublicRoomBottomSheet({
|
||||
this.roomAlias,
|
||||
required this.outerContext,
|
||||
this.chunk,
|
||||
this.onRoomJoined,
|
||||
this.via,
|
||||
super.key,
|
||||
}) {
|
||||
assert(roomAlias != null || chunk != null);
|
||||
|
|
@ -39,8 +37,11 @@ class PublicRoomBottomSheet extends StatelessWidget {
|
|||
return chunk.roomId;
|
||||
}
|
||||
final roomId = chunk != null && knock
|
||||
? await client.knockRoom(chunk.roomId)
|
||||
: await client.joinRoom(roomAlias ?? chunk!.roomId);
|
||||
? await client.knockRoom(chunk.roomId, serverName: via)
|
||||
: await client.joinRoom(
|
||||
roomAlias ?? chunk!.roomId,
|
||||
serverName: via,
|
||||
);
|
||||
|
||||
if (!knock && client.getRoomById(roomId) == null) {
|
||||
await client.waitForRoomInSync(roomId);
|
||||
|
|
@ -58,7 +59,7 @@ class PublicRoomBottomSheet extends StatelessWidget {
|
|||
return;
|
||||
}
|
||||
if (result.error == null) {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop<bool>(true);
|
||||
// don't open the room if the joined room is a space
|
||||
if (chunk?.roomType != 'm.space' &&
|
||||
!client.getRoomById(result.result!)!.isSpace) {
|
||||
|
|
@ -70,17 +71,17 @@ class PublicRoomBottomSheet extends StatelessWidget {
|
|||
|
||||
bool _testRoom(PublicRoomsChunk r) => r.canonicalAlias == roomAlias;
|
||||
|
||||
Future<PublicRoomsChunk> _search(BuildContext context) async {
|
||||
Future<PublicRoomsChunk> _search() async {
|
||||
final chunk = this.chunk;
|
||||
if (chunk != null) return chunk;
|
||||
final query = await Matrix.of(context).client.queryPublicRooms(
|
||||
final query = await Matrix.of(outerContext).client.queryPublicRooms(
|
||||
server: roomAlias!.domain,
|
||||
filter: PublicRoomQueryFilter(
|
||||
genericSearchTerm: roomAlias,
|
||||
),
|
||||
);
|
||||
if (!query.chunk.any(_testRoom)) {
|
||||
throw (L10n.of(context)!.noRoomsFound);
|
||||
throw (L10n.of(outerContext)!.noRoomsFound);
|
||||
}
|
||||
return query.chunk.firstWhere(_testRoom);
|
||||
}
|
||||
|
|
@ -88,6 +89,7 @@ class PublicRoomBottomSheet extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final roomAlias = this.roomAlias ?? chunk?.canonicalAlias;
|
||||
final roomLink = roomAlias ?? chunk?.roomId;
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
|
|
@ -114,41 +116,84 @@ class PublicRoomBottomSheet extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
body: FutureBuilder<PublicRoomsChunk>(
|
||||
future: _search(context),
|
||||
future: _search(),
|
||||
builder: (context, snapshot) {
|
||||
final profile = snapshot.data;
|
||||
return ListView(
|
||||
padding: EdgeInsets.zero,
|
||||
children: [
|
||||
if (profile == null)
|
||||
Container(
|
||||
height: 156,
|
||||
alignment: Alignment.center,
|
||||
color: Theme.of(context).secondaryHeaderColor,
|
||||
child: snapshot.hasError
|
||||
? Text(snapshot.error!.toLocalizedString(context))
|
||||
: const CircularProgressIndicator.adaptive(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
)
|
||||
else
|
||||
Center(
|
||||
child: Padding(
|
||||
Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Avatar(
|
||||
mxContent: profile.avatarUrl,
|
||||
name: profile.name ?? roomAlias,
|
||||
size: Avatar.defaultSize * 3,
|
||||
child: profile == null
|
||||
? const Center(
|
||||
child: CircularProgressIndicator.adaptive(),
|
||||
)
|
||||
: Avatar(
|
||||
client: Matrix.of(outerContext).client,
|
||||
mxContent: profile.avatarUrl,
|
||||
name: profile.name ?? roomAlias,
|
||||
size: Avatar.defaultSize * 3,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextButton.icon(
|
||||
onPressed: roomLink != null
|
||||
? () => FluffyShare.share(
|
||||
roomLink,
|
||||
context,
|
||||
copyOnly: true,
|
||||
)
|
||||
: null,
|
||||
icon: const Icon(
|
||||
Icons.copy_outlined,
|
||||
size: 14,
|
||||
),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor:
|
||||
Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
label: Text(
|
||||
roomLink ?? '...',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
TextButton.icon(
|
||||
onPressed: () {},
|
||||
icon: const Icon(
|
||||
Icons.groups_3_outlined,
|
||||
size: 14,
|
||||
),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor:
|
||||
Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
label: Text(
|
||||
L10n.of(context)!.countParticipants(
|
||||
profile?.numJoinedMembers ?? 0,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () => _joinRoom(context),
|
||||
label: Text(
|
||||
chunk?.joinRule == 'knock' &&
|
||||
Matrix.of(context)
|
||||
Matrix.of(outerContext)
|
||||
.client
|
||||
.getRoomById(chunk!.roomId) ==
|
||||
null
|
||||
|
|
@ -157,36 +202,10 @@ class PublicRoomBottomSheet extends StatelessWidget {
|
|||
? L10n.of(context)!.joinSpace
|
||||
: L10n.of(context)!.joinRoom,
|
||||
),
|
||||
icon: const Icon(Icons.login_outlined),
|
||||
icon: const Icon(Icons.navigate_next),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ListTile(
|
||||
title: Text(
|
||||
profile?.name ??
|
||||
roomAlias?.localpart ??
|
||||
chunk?.roomId.localpart ??
|
||||
L10n.of(context)!.chat,
|
||||
),
|
||||
subtitle: Text(
|
||||
'${L10n.of(context)!.participant}: ${profile?.numJoinedMembers ?? 0}',
|
||||
),
|
||||
trailing: const Icon(Icons.account_box_outlined),
|
||||
),
|
||||
if (roomAlias != null)
|
||||
ListTile(
|
||||
title: Text(L10n.of(context)!.publicLink),
|
||||
subtitle: SelectableText(roomAlias),
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.copy_outlined),
|
||||
onPressed: () => FluffyShare.share(
|
||||
roomAlias,
|
||||
context,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (profile?.topic?.isNotEmpty ?? false)
|
||||
ListTile(
|
||||
subtitle: SelectableLinkify(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue