Merge branch 'main' into fix-jagged-avatar-edges
This commit is contained in:
commit
c5b61aba64
47 changed files with 1351 additions and 835 deletions
26
.github/workflows/matrix_notification.yaml
vendored
Normal file
26
.github/workflows/matrix_notification.yaml
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
name: Matrix Notification
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [ opened ]
|
||||||
|
issue_comment:
|
||||||
|
types: [ created ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
notify:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Send Matrix Notification
|
||||||
|
env:
|
||||||
|
MATRIX_URL: https://matrix.janian.de/_matrix/client/v3/rooms/${{ secrets.MATRIX_MANAGEMENT_ROOM }}/send/m.room.message
|
||||||
|
run: |
|
||||||
|
if [ "${{ github.event.action }}" == "opened" ]; then
|
||||||
|
PAYLOAD="{\"msgtype\": \"m.notice\", \"body\": \"New Issue from ${{ github.event.issue.user.login }}\\n${{ github.event.issue.title }}\\n\\n${{ github.event.issue.body }}\\n\\nURL: ${{ github.event.issue.html_url }}\"}"
|
||||||
|
elif [ "${{ github.event.action }}" == "created" ]; then
|
||||||
|
PAYLOAD="{\"msgtype\": \"m.notice\", \"body\": \"New Comment from ${{ github.event.comment.user.login }}\\n\\n${{ github.event.comment.body }}\\n\\nURL: ${{ github.event.comment.html_url }}\"}"
|
||||||
|
fi
|
||||||
|
curl -X POST -H "Authorization: Bearer ${{ secrets.MATRIX_BOT_TOKEN }}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$PAYLOAD" \
|
||||||
|
$MATRIX_URL
|
||||||
2
.github/workflows/versions.env
vendored
2
.github/workflows/versions.env
vendored
|
|
@ -1,2 +1,2 @@
|
||||||
FLUTTER_VERSION=3.29.2
|
FLUTTER_VERSION=3.29.3
|
||||||
JAVA_VERSION=17
|
JAVA_VERSION=17
|
||||||
|
|
|
||||||
376
CHANGELOG.md
376
CHANGELOG.md
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
||||||
# Privacy
|
# Privacy
|
||||||
|
|
||||||
FluffyChat is available on Android, iOS and as a web version. Desktop versions for Windows, Linux and macOS may follow.
|
FluffyChat is available on Android, iOS, Linux and as a web version. Desktop versions for Windows and macOS may follow.
|
||||||
|
|
||||||
* [Matrix](#matrix)
|
* [Matrix](#matrix)
|
||||||
* [Database](#database)
|
* [Database](#database)
|
||||||
|
|
@ -109,4 +109,4 @@ To enhance user safety and help protect against the sexual abuse and exploitatio
|
||||||
|
|
||||||
In addition to reporting messages, users can also report other users following a similar process.
|
In addition to reporting messages, users can also report other users following a similar process.
|
||||||
|
|
||||||
We encourage server administrators to adhere to strict safety standards and provide mechanisms for addressing and moderating inappropriate content. For more information on the Matrix protocol and its safety standards, please refer to the following link: https://matrix.org/docs/older/moderation/
|
We encourage server administrators to adhere to strict safety standards and provide mechanisms for addressing and moderating inappropriate content. For more information on the Matrix protocol and its safety standards, please refer to the following link: https://matrix.org/docs/older/moderation/
|
||||||
|
|
|
||||||
|
|
@ -3211,5 +3211,8 @@
|
||||||
"recordAVideo": "Record a video",
|
"recordAVideo": "Record a video",
|
||||||
"optionalMessage": "(Optional) message...",
|
"optionalMessage": "(Optional) message...",
|
||||||
"notSupportedOnThisDevice": "Not supported on this device",
|
"notSupportedOnThisDevice": "Not supported on this device",
|
||||||
"enterNewChat": "Enter new chat"
|
"enterNewChat": "Enter new chat",
|
||||||
|
"approve": "Approve",
|
||||||
|
"youHaveKnocked": "You have knocked",
|
||||||
|
"pleaseWaitUntilInvited": "Please wait now, until someone from the room invites you."
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,14 @@ abstract class FluffyThemes {
|
||||||
),
|
),
|
||||||
contentPadding: const EdgeInsets.all(12),
|
contentPadding: const EdgeInsets.all(12),
|
||||||
),
|
),
|
||||||
|
chipTheme: ChipThemeData(
|
||||||
|
showCheckmark: false,
|
||||||
|
backgroundColor: colorScheme.surfaceContainer,
|
||||||
|
side: BorderSide.none,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||||
|
),
|
||||||
|
),
|
||||||
appBarTheme: AppBarTheme(
|
appBarTheme: AppBarTheme(
|
||||||
toolbarHeight: isColumnMode ? 72 : 56,
|
toolbarHeight: isColumnMode ? 72 : 56,
|
||||||
shadowColor:
|
shadowColor:
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import '../key_verification/key_verification_dialog.dart';
|
||||||
class BootstrapDialog extends StatefulWidget {
|
class BootstrapDialog extends StatefulWidget {
|
||||||
final bool wipe;
|
final bool wipe;
|
||||||
final Client client;
|
final Client client;
|
||||||
|
|
||||||
const BootstrapDialog({
|
const BootstrapDialog({
|
||||||
super.key,
|
super.key,
|
||||||
this.wipe = false,
|
this.wipe = false,
|
||||||
|
|
@ -132,7 +133,7 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
||||||
minLines: 2,
|
minLines: 2,
|
||||||
maxLines: 4,
|
maxLines: 4,
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
style: const TextStyle(fontFamily: 'UbuntuMono'),
|
style: const TextStyle(fontFamily: 'RobotoMono'),
|
||||||
controller: TextEditingController(text: key),
|
controller: TextEditingController(text: key),
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
contentPadding: EdgeInsets.all(16),
|
contentPadding: EdgeInsets.all(16),
|
||||||
|
|
@ -257,7 +258,7 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
||||||
? null
|
? null
|
||||||
: [AutofillHints.password],
|
: [AutofillHints.password],
|
||||||
controller: _recoveryKeyTextEditingController,
|
controller: _recoveryKeyTextEditingController,
|
||||||
style: const TextStyle(fontFamily: 'UbuntuMono'),
|
style: const TextStyle(fontFamily: 'RobotoMono'),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
contentPadding: const EdgeInsets.all(16),
|
contentPadding: const EdgeInsets.all(16),
|
||||||
hintStyle: TextStyle(
|
hintStyle: TextStyle(
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ class ChatAppBarListTile extends StatelessWidget {
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||||
child: Linkify(
|
child: Linkify(
|
||||||
text: title,
|
text: title,
|
||||||
|
textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
|
||||||
options: const LinkifyOptions(humanize: false),
|
options: const LinkifyOptions(humanize: false),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ extension EventInfoDialogExtension on Event {
|
||||||
class EventInfoDialog extends StatelessWidget {
|
class EventInfoDialog extends StatelessWidget {
|
||||||
final Event event;
|
final Event event;
|
||||||
final L10n l10n;
|
final L10n l10n;
|
||||||
|
|
||||||
const EventInfoDialog({
|
const EventInfoDialog({
|
||||||
required this.event,
|
required this.event,
|
||||||
required this.l10n,
|
required this.l10n,
|
||||||
|
|
@ -41,10 +42,8 @@ class EventInfoDialog extends StatelessWidget {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(L10n.of(context).messageInfo),
|
title: Text(L10n.of(context).messageInfo),
|
||||||
leading: IconButton(
|
leading: CloseButton(
|
||||||
icon: const Icon(Icons.arrow_downward_outlined),
|
|
||||||
onPressed: Navigator.of(context, rootNavigator: false).pop,
|
onPressed: Navigator.of(context, rootNavigator: false).pop,
|
||||||
tooltip: L10n.of(context).close,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
|
|
|
||||||
|
|
@ -395,20 +395,27 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
|
||||||
),
|
),
|
||||||
if (fileDescription != null) ...[
|
if (fileDescription != null) ...[
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Linkify(
|
Padding(
|
||||||
text: fileDescription,
|
padding: const EdgeInsets.symmetric(
|
||||||
style: TextStyle(
|
horizontal: 16,
|
||||||
color: widget.color,
|
vertical: 8,
|
||||||
fontSize: widget.fontSize,
|
|
||||||
),
|
),
|
||||||
options: const LinkifyOptions(humanize: false),
|
child: Linkify(
|
||||||
linkStyle: TextStyle(
|
text: fileDescription,
|
||||||
color: widget.linkColor,
|
textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
|
||||||
fontSize: widget.fontSize,
|
style: TextStyle(
|
||||||
decoration: TextDecoration.underline,
|
color: widget.color,
|
||||||
decorationColor: widget.linkColor,
|
fontSize: widget.fontSize,
|
||||||
|
),
|
||||||
|
options: const LinkifyOptions(humanize: false),
|
||||||
|
linkStyle: TextStyle(
|
||||||
|
color: widget.linkColor,
|
||||||
|
fontSize: widget.fontSize,
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
decorationColor: widget.linkColor,
|
||||||
|
),
|
||||||
|
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
|
||||||
),
|
),
|
||||||
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
@ -420,6 +427,7 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
|
||||||
/// To use a MatrixFile as an AudioSource for the just_audio package
|
/// To use a MatrixFile as an AudioSource for the just_audio package
|
||||||
class MatrixFileAudioSource extends StreamAudioSource {
|
class MatrixFileAudioSource extends StreamAudioSource {
|
||||||
final MatrixFile file;
|
final MatrixFile file;
|
||||||
|
|
||||||
MatrixFileAudioSource(this.file);
|
MatrixFileAudioSource(this.file);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
||||||
|
|
@ -250,7 +250,7 @@ class HtmlMessage extends StatelessWidget {
|
||||||
border: Border(
|
border: Border(
|
||||||
left: BorderSide(
|
left: BorderSide(
|
||||||
color: textColor,
|
color: textColor,
|
||||||
width: 3,
|
width: 5,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -295,7 +295,7 @@ class HtmlMessage extends StatelessWidget {
|
||||||
),
|
),
|
||||||
textStyle: TextStyle(
|
textStyle: TextStyle(
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
fontFamily: 'UbuntuMono',
|
fontFamily: 'RobotoMono',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -79,12 +79,19 @@ class ImageBubble extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
final borderRadius =
|
var borderRadius =
|
||||||
this.borderRadius ?? BorderRadius.circular(AppConfig.borderRadius);
|
this.borderRadius ?? BorderRadius.circular(AppConfig.borderRadius);
|
||||||
|
|
||||||
final fileDescription = event.fileDescription;
|
final fileDescription = event.fileDescription;
|
||||||
final textColor = this.textColor;
|
final textColor = this.textColor;
|
||||||
|
|
||||||
|
if (fileDescription != null) {
|
||||||
|
borderRadius = borderRadius.copyWith(
|
||||||
|
bottomLeft: Radius.zero,
|
||||||
|
bottomRight: Radius.zero,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
|
|
@ -122,20 +129,29 @@ class ImageBubble extends StatelessWidget {
|
||||||
if (fileDescription != null && textColor != null)
|
if (fileDescription != null && textColor != null)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: width,
|
width: width,
|
||||||
child: Linkify(
|
child: Padding(
|
||||||
text: fileDescription,
|
padding: const EdgeInsets.symmetric(
|
||||||
style: TextStyle(
|
horizontal: 16,
|
||||||
color: textColor,
|
vertical: 8,
|
||||||
fontSize: AppConfig.fontSizeFactor * AppConfig.messageFontSize,
|
|
||||||
),
|
),
|
||||||
options: const LinkifyOptions(humanize: false),
|
child: Linkify(
|
||||||
linkStyle: TextStyle(
|
text: fileDescription,
|
||||||
color: linkColor,
|
textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
|
||||||
fontSize: AppConfig.fontSizeFactor * AppConfig.messageFontSize,
|
style: TextStyle(
|
||||||
decoration: TextDecoration.underline,
|
color: textColor,
|
||||||
decorationColor: linkColor,
|
fontSize:
|
||||||
|
AppConfig.fontSizeFactor * AppConfig.messageFontSize,
|
||||||
|
),
|
||||||
|
options: const LinkifyOptions(humanize: false),
|
||||||
|
linkStyle: TextStyle(
|
||||||
|
color: linkColor,
|
||||||
|
fontSize:
|
||||||
|
AppConfig.fontSizeFactor * AppConfig.messageFontSize,
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
decorationColor: linkColor,
|
||||||
|
),
|
||||||
|
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
|
||||||
),
|
),
|
||||||
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import 'message_content.dart';
|
||||||
import 'message_reactions.dart';
|
import 'message_reactions.dart';
|
||||||
import 'reply_content.dart';
|
import 'reply_content.dart';
|
||||||
import 'state_message.dart';
|
import 'state_message.dart';
|
||||||
import 'verification_request_content.dart';
|
|
||||||
|
|
||||||
class Message extends StatelessWidget {
|
class Message extends StatelessWidget {
|
||||||
final Event event;
|
final Event event;
|
||||||
|
|
@ -85,7 +84,7 @@ class Message extends StatelessWidget {
|
||||||
|
|
||||||
if (event.type == EventTypes.Message &&
|
if (event.type == EventTypes.Message &&
|
||||||
event.messageType == EventTypes.KeyVerificationRequest) {
|
event.messageType == EventTypes.KeyVerificationRequest) {
|
||||||
return VerificationRequestContent(event: event, timeline: timeline);
|
return StateMessage(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
final client = Matrix.of(context).client;
|
final client = Matrix.of(context).client;
|
||||||
|
|
@ -149,10 +148,6 @@ class Message extends StatelessWidget {
|
||||||
event.onlyEmotes &&
|
event.onlyEmotes &&
|
||||||
event.numberEmotes > 0 &&
|
event.numberEmotes > 0 &&
|
||||||
event.numberEmotes <= 3);
|
event.numberEmotes <= 3);
|
||||||
final noPadding = {
|
|
||||||
MessageTypes.File,
|
|
||||||
MessageTypes.Audio,
|
|
||||||
}.contains(event.messageType);
|
|
||||||
|
|
||||||
if (ownMessage) {
|
if (ownMessage) {
|
||||||
color =
|
color =
|
||||||
|
|
@ -338,12 +333,6 @@ class Message extends StatelessWidget {
|
||||||
AppConfig.borderRadius,
|
AppConfig.borderRadius,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
padding: noBubble || noPadding
|
|
||||||
? EdgeInsets.zero
|
|
||||||
: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 16,
|
|
||||||
vertical: 8,
|
|
||||||
),
|
|
||||||
constraints: const BoxConstraints(
|
constraints: const BoxConstraints(
|
||||||
maxWidth:
|
maxWidth:
|
||||||
FluffyThemes.columnWidth * 1.5,
|
FluffyThemes.columnWidth * 1.5,
|
||||||
|
|
@ -386,22 +375,32 @@ class Message extends StatelessWidget {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding:
|
padding:
|
||||||
const EdgeInsets.only(
|
const EdgeInsets.only(
|
||||||
bottom: 4.0,
|
left: 16,
|
||||||
|
right: 16,
|
||||||
|
top: 8,
|
||||||
),
|
),
|
||||||
child: InkWell(
|
child: Material(
|
||||||
|
color:
|
||||||
|
Colors.transparent,
|
||||||
borderRadius:
|
borderRadius:
|
||||||
ReplyContent
|
ReplyContent
|
||||||
.borderRadius,
|
.borderRadius,
|
||||||
onTap: () =>
|
child: InkWell(
|
||||||
scrollToEventId(
|
borderRadius:
|
||||||
replyEvent.eventId,
|
ReplyContent
|
||||||
),
|
.borderRadius,
|
||||||
child: AbsorbPointer(
|
onTap: () =>
|
||||||
child: ReplyContent(
|
scrollToEventId(
|
||||||
replyEvent,
|
replyEvent.eventId,
|
||||||
ownMessage:
|
),
|
||||||
ownMessage,
|
child: AbsorbPointer(
|
||||||
timeline: timeline,
|
child: ReplyContent(
|
||||||
|
replyEvent,
|
||||||
|
ownMessage:
|
||||||
|
ownMessage,
|
||||||
|
timeline:
|
||||||
|
timeline,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -185,19 +185,26 @@ class MessageContent extends StatelessWidget {
|
||||||
if (event.messageType == MessageTypes.Emote) {
|
if (event.messageType == MessageTypes.Emote) {
|
||||||
html = '* $html';
|
html = '* $html';
|
||||||
}
|
}
|
||||||
return HtmlMessage(
|
return Padding(
|
||||||
html: html,
|
padding: const EdgeInsets.symmetric(
|
||||||
textColor: textColor,
|
horizontal: 16,
|
||||||
room: event.room,
|
vertical: 8,
|
||||||
fontSize: AppConfig.fontSizeFactor * AppConfig.messageFontSize,
|
),
|
||||||
linkStyle: TextStyle(
|
child: HtmlMessage(
|
||||||
color: linkColor,
|
html: html,
|
||||||
|
textColor: textColor,
|
||||||
|
room: event.room,
|
||||||
fontSize:
|
fontSize:
|
||||||
AppConfig.fontSizeFactor * AppConfig.messageFontSize,
|
AppConfig.fontSizeFactor * AppConfig.messageFontSize,
|
||||||
decoration: TextDecoration.underline,
|
linkStyle: TextStyle(
|
||||||
decorationColor: linkColor,
|
color: linkColor,
|
||||||
|
fontSize:
|
||||||
|
AppConfig.fontSizeFactor * AppConfig.messageFontSize,
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
decorationColor: linkColor,
|
||||||
|
),
|
||||||
|
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
|
||||||
),
|
),
|
||||||
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// else we fall through to the normal message rendering
|
// else we fall through to the normal message rendering
|
||||||
|
|
@ -276,24 +283,32 @@ class MessageContent extends StatelessWidget {
|
||||||
final bigEmotes = event.onlyEmotes &&
|
final bigEmotes = event.onlyEmotes &&
|
||||||
event.numberEmotes > 0 &&
|
event.numberEmotes > 0 &&
|
||||||
event.numberEmotes <= 3;
|
event.numberEmotes <= 3;
|
||||||
return Linkify(
|
return Padding(
|
||||||
text: event.calcLocalizedBodyFallback(
|
padding: const EdgeInsets.symmetric(
|
||||||
MatrixLocals(L10n.of(context)),
|
horizontal: 16,
|
||||||
hideReply: true,
|
vertical: 8,
|
||||||
),
|
),
|
||||||
style: TextStyle(
|
child: Linkify(
|
||||||
color: textColor,
|
text: event.calcLocalizedBodyFallback(
|
||||||
fontSize: bigEmotes ? fontSize * 5 : fontSize,
|
MatrixLocals(L10n.of(context)),
|
||||||
decoration: event.redacted ? TextDecoration.lineThrough : null,
|
hideReply: true,
|
||||||
|
),
|
||||||
|
textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
|
||||||
|
style: TextStyle(
|
||||||
|
color: textColor,
|
||||||
|
fontSize: bigEmotes ? fontSize * 5 : fontSize,
|
||||||
|
decoration:
|
||||||
|
event.redacted ? TextDecoration.lineThrough : null,
|
||||||
|
),
|
||||||
|
options: const LinkifyOptions(humanize: false),
|
||||||
|
linkStyle: TextStyle(
|
||||||
|
color: linkColor,
|
||||||
|
fontSize: fontSize,
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
decorationColor: linkColor,
|
||||||
|
),
|
||||||
|
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
|
||||||
),
|
),
|
||||||
options: const LinkifyOptions(humanize: false),
|
|
||||||
linkStyle: TextStyle(
|
|
||||||
color: linkColor,
|
|
||||||
fontSize: fontSize,
|
|
||||||
decoration: TextDecoration.underline,
|
|
||||||
decorationColor: linkColor,
|
|
||||||
),
|
|
||||||
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case EventTypes.CallInvite:
|
case EventTypes.CallInvite:
|
||||||
|
|
@ -350,13 +365,19 @@ class _ButtonContent extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return InkWell(
|
return Padding(
|
||||||
onTap: onPressed,
|
padding: const EdgeInsets.symmetric(
|
||||||
child: Text(
|
horizontal: 16,
|
||||||
'$icon $label',
|
vertical: 8,
|
||||||
style: TextStyle(
|
),
|
||||||
color: textColor,
|
child: InkWell(
|
||||||
fontSize: fontSize,
|
onTap: onPressed,
|
||||||
|
child: Text(
|
||||||
|
'$icon $label',
|
||||||
|
style: TextStyle(
|
||||||
|
color: textColor,
|
||||||
|
fontSize: fontSize,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
|
||||||
import 'package:matrix/matrix.dart';
|
|
||||||
|
|
||||||
import 'package:fluffychat/config/app_config.dart';
|
import 'package:fluffychat/config/app_config.dart';
|
||||||
import 'package:fluffychat/utils/file_description.dart';
|
import 'package:fluffychat/utils/file_description.dart';
|
||||||
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
|
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
|
||||||
import 'package:fluffychat/utils/url_launcher.dart';
|
import 'package:fluffychat/utils/url_launcher.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||||
|
import 'package:matrix/matrix.dart';
|
||||||
|
|
||||||
class MessageDownloadContent extends StatelessWidget {
|
class MessageDownloadContent extends StatelessWidget {
|
||||||
final Event event;
|
final Event event;
|
||||||
|
|
@ -30,83 +28,79 @@ class MessageDownloadContent extends StatelessWidget {
|
||||||
?.tryGet<String>('mimetype')
|
?.tryGet<String>('mimetype')
|
||||||
?.toUpperCase() ??
|
?.toUpperCase() ??
|
||||||
'UNKNOWN');
|
'UNKNOWN');
|
||||||
final sizeString = event.sizeString;
|
final sizeString = event.sizeString ?? '?MB';
|
||||||
final fileDescription = event.fileDescription;
|
final fileDescription = event.fileDescription;
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
InkWell(
|
Material(
|
||||||
onTap: () => event.saveFile(context),
|
color: Colors.transparent,
|
||||||
child: Column(
|
child: InkWell(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
|
||||||
mainAxisSize: MainAxisSize.min,
|
onTap: () => event.saveFile(context),
|
||||||
children: <Widget>[
|
child: Container(
|
||||||
Padding(
|
width: 400,
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
mainAxisSize: MainAxisSize.min,
|
||||||
Icon(
|
spacing: 16,
|
||||||
Icons.file_download_outlined,
|
children: [
|
||||||
color: textColor,
|
CircleAvatar(
|
||||||
),
|
backgroundColor: textColor.withAlpha(32),
|
||||||
const SizedBox(width: 16),
|
child: Icon(Icons.file_download_outlined, color: textColor),
|
||||||
Flexible(
|
),
|
||||||
child: Text(
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
filename,
|
filename,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: textColor,
|
color: textColor,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Divider(height: 1),
|
|
||||||
Padding(
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
filetype,
|
|
||||||
style: TextStyle(
|
|
||||||
color: linkColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
if (sizeString != null)
|
|
||||||
Text(
|
Text(
|
||||||
sizeString,
|
'$sizeString | $filetype',
|
||||||
style: TextStyle(
|
maxLines: 1,
|
||||||
color: linkColor,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
style: TextStyle(color: textColor, fontSize: 10),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (fileDescription != null)
|
if (fileDescription != null) ...[
|
||||||
Linkify(
|
Padding(
|
||||||
text: fileDescription,
|
padding: const EdgeInsets.symmetric(
|
||||||
style: TextStyle(
|
horizontal: 16.0,
|
||||||
color: textColor,
|
vertical: 8.0,
|
||||||
fontSize: AppConfig.fontSizeFactor * AppConfig.messageFontSize,
|
|
||||||
),
|
),
|
||||||
options: const LinkifyOptions(humanize: false),
|
child: Linkify(
|
||||||
linkStyle: TextStyle(
|
text: fileDescription,
|
||||||
color: linkColor,
|
textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
|
||||||
fontSize: AppConfig.fontSizeFactor * AppConfig.messageFontSize,
|
style: TextStyle(
|
||||||
decoration: TextDecoration.underline,
|
color: textColor,
|
||||||
decorationColor: linkColor,
|
fontSize: AppConfig.fontSizeFactor * AppConfig.messageFontSize,
|
||||||
|
),
|
||||||
|
options: const LinkifyOptions(humanize: false),
|
||||||
|
linkStyle: TextStyle(
|
||||||
|
color: linkColor,
|
||||||
|
fontSize: AppConfig.fontSizeFactor * AppConfig.messageFontSize,
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
decorationColor: linkColor,
|
||||||
|
),
|
||||||
|
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
|
||||||
),
|
),
|
||||||
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
|
|
||||||
),
|
),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,12 @@ class ReplyContent extends StatelessWidget {
|
||||||
final Event replyEvent;
|
final Event replyEvent;
|
||||||
final bool ownMessage;
|
final bool ownMessage;
|
||||||
final Timeline? timeline;
|
final Timeline? timeline;
|
||||||
final Color? backgroundColor;
|
|
||||||
|
|
||||||
const ReplyContent(
|
const ReplyContent(
|
||||||
this.replyEvent, {
|
this.replyEvent, {
|
||||||
this.ownMessage = false,
|
this.ownMessage = false,
|
||||||
super.key,
|
super.key,
|
||||||
this.timeline,
|
this.timeline,
|
||||||
this.backgroundColor,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
static const BorderRadius borderRadius = BorderRadius.only(
|
static const BorderRadius borderRadius = BorderRadius.only(
|
||||||
|
|
@ -40,16 +38,18 @@ class ReplyContent extends StatelessWidget {
|
||||||
: theme.colorScheme.tertiary;
|
: theme.colorScheme.tertiary;
|
||||||
|
|
||||||
return Material(
|
return Material(
|
||||||
color: backgroundColor ??
|
color: Colors.transparent,
|
||||||
theme.colorScheme.surface.withAlpha(ownMessage ? 50 : 80),
|
|
||||||
borderRadius: borderRadius,
|
borderRadius: borderRadius,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Container(
|
Container(
|
||||||
width: 3,
|
width: 5,
|
||||||
height: fontSize * 2 + 16,
|
height: fontSize * 2 + 16,
|
||||||
color: color,
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||||
|
color: color,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Flexible(
|
Flexible(
|
||||||
|
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
|
||||||
import 'package:matrix/matrix.dart';
|
|
||||||
|
|
||||||
import '../../../config/app_config.dart';
|
|
||||||
|
|
||||||
class VerificationRequestContent extends StatelessWidget {
|
|
||||||
final Event event;
|
|
||||||
final Timeline timeline;
|
|
||||||
|
|
||||||
const VerificationRequestContent({
|
|
||||||
required this.event,
|
|
||||||
required this.timeline,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final theme = Theme.of(context);
|
|
||||||
|
|
||||||
final events = event.aggregatedEvents(timeline, 'm.reference');
|
|
||||||
final done = events.where((e) => e.type == EventTypes.KeyVerificationDone);
|
|
||||||
final start =
|
|
||||||
events.where((e) => e.type == EventTypes.KeyVerificationStart);
|
|
||||||
final cancel =
|
|
||||||
events.where((e) => e.type == EventTypes.KeyVerificationCancel);
|
|
||||||
final fullyDone = done.length >= 2;
|
|
||||||
final started = start.isNotEmpty;
|
|
||||||
final canceled = cancel.isNotEmpty;
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 8.0,
|
|
||||||
vertical: 4.0,
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border.all(
|
|
||||||
color: theme.dividerColor,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
|
||||||
color: theme.colorScheme.surface,
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: <Widget>[
|
|
||||||
Icon(
|
|
||||||
Icons.lock_outlined,
|
|
||||||
color: canceled
|
|
||||||
? Colors.red
|
|
||||||
: (fullyDone ? Colors.green : Colors.grey),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
canceled
|
|
||||||
? 'Error ${cancel.first.content.tryGet<String>('code')}: ${cancel.first.content.tryGet<String>('reason')}'
|
|
||||||
: (fullyDone
|
|
||||||
? L10n.of(context).verifySuccess
|
|
||||||
: (started
|
|
||||||
? L10n.of(context).loadingPleaseWait
|
|
||||||
: L10n.of(context).newVerificationRequest)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -25,6 +25,7 @@ class EventVideoPlayer extends StatefulWidget {
|
||||||
final Event event;
|
final Event event;
|
||||||
final Color? textColor;
|
final Color? textColor;
|
||||||
final Color? linkColor;
|
final Color? linkColor;
|
||||||
|
|
||||||
const EventVideoPlayer(
|
const EventVideoPlayer(
|
||||||
this.event, {
|
this.event, {
|
||||||
this.textColor,
|
this.textColor,
|
||||||
|
|
@ -188,20 +189,29 @@ class EventVideoPlayerState extends State<EventVideoPlayer> {
|
||||||
if (fileDescription != null && textColor != null && linkColor != null)
|
if (fileDescription != null && textColor != null && linkColor != null)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: width,
|
width: width,
|
||||||
child: Linkify(
|
child: Padding(
|
||||||
text: fileDescription,
|
padding: const EdgeInsets.symmetric(
|
||||||
style: TextStyle(
|
horizontal: 16,
|
||||||
color: textColor,
|
vertical: 8,
|
||||||
fontSize: AppConfig.fontSizeFactor * AppConfig.messageFontSize,
|
|
||||||
),
|
),
|
||||||
options: const LinkifyOptions(humanize: false),
|
child: Linkify(
|
||||||
linkStyle: TextStyle(
|
text: fileDescription,
|
||||||
color: linkColor,
|
textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
|
||||||
fontSize: AppConfig.fontSizeFactor * AppConfig.messageFontSize,
|
style: TextStyle(
|
||||||
decoration: TextDecoration.underline,
|
color: textColor,
|
||||||
decorationColor: linkColor,
|
fontSize:
|
||||||
|
AppConfig.fontSizeFactor * AppConfig.messageFontSize,
|
||||||
|
),
|
||||||
|
options: const LinkifyOptions(humanize: false),
|
||||||
|
linkStyle: TextStyle(
|
||||||
|
color: linkColor,
|
||||||
|
fontSize:
|
||||||
|
AppConfig.fontSizeFactor * AppConfig.messageFontSize,
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
decorationColor: linkColor,
|
||||||
|
),
|
||||||
|
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
|
||||||
),
|
),
|
||||||
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -235,7 +235,7 @@ class InputBar extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
commandExample(command),
|
commandExample(command),
|
||||||
style: const TextStyle(fontFamily: 'UbuntuMono'),
|
style: const TextStyle(fontFamily: 'RobotoMono'),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
hint,
|
hint,
|
||||||
|
|
@ -255,7 +255,7 @@ class InputBar extends StatelessWidget {
|
||||||
waitDuration: const Duration(days: 1), // don't show on hover
|
waitDuration: const Duration(days: 1), // don't show on hover
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: padding,
|
padding: padding,
|
||||||
child: Text(label, style: const TextStyle(fontFamily: 'UbuntuMono')),
|
child: Text(label, style: const TextStyle(fontFamily: 'RobotoMono')),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,6 @@ class ReplyDisplay extends StatelessWidget {
|
||||||
? ReplyContent(
|
? ReplyContent(
|
||||||
controller.replyEvent!,
|
controller.replyEvent!,
|
||||||
timeline: controller.timeline!,
|
timeline: controller.timeline!,
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
)
|
)
|
||||||
: _EditContent(
|
: _EditContent(
|
||||||
controller.editEvent?.getDisplayEvent(controller.timeline!),
|
controller.editEvent?.getDisplayEvent(controller.timeline!),
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import 'package:fluffychat/widgets/chat_settings_popup_menu.dart';
|
||||||
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
|
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
|
||||||
import 'package:fluffychat/widgets/matrix.dart';
|
import 'package:fluffychat/widgets/matrix.dart';
|
||||||
import '../../utils/url_launcher.dart';
|
import '../../utils/url_launcher.dart';
|
||||||
|
import '../../widgets/mxc_image_viewer.dart';
|
||||||
import '../../widgets/qr_code_viewer.dart';
|
import '../../widgets/qr_code_viewer.dart';
|
||||||
|
|
||||||
class ChatDetailsView extends StatelessWidget {
|
class ChatDetailsView extends StatelessWidget {
|
||||||
|
|
@ -38,6 +39,7 @@ class ChatDetailsView extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
final directChatMatrixID = room.directChatMatrixID;
|
final directChatMatrixID = room.directChatMatrixID;
|
||||||
|
final roomAvatar = room.avatar;
|
||||||
|
|
||||||
return StreamBuilder(
|
return StreamBuilder(
|
||||||
stream: room.client.onRoomState.stream
|
stream: room.client.onRoomState.stream
|
||||||
|
|
@ -108,6 +110,13 @@ class ChatDetailsView extends StatelessWidget {
|
||||||
mxContent: room.avatar,
|
mxContent: room.avatar,
|
||||||
name: displayname,
|
name: displayname,
|
||||||
size: Avatar.defaultSize * 2.5,
|
size: Avatar.defaultSize * 2.5,
|
||||||
|
onTap: roomAvatar != null
|
||||||
|
? () => showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) =>
|
||||||
|
MxcImageViewer(roomAvatar),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (!room.isDirectChat &&
|
if (!room.isDirectChat &&
|
||||||
|
|
@ -234,6 +243,8 @@ class ChatDetailsView extends StatelessWidget {
|
||||||
text: room.topic.isEmpty
|
text: room.topic.isEmpty
|
||||||
? L10n.of(context).noChatDescriptionYet
|
? L10n.of(context).noChatDescriptionYet
|
||||||
: room.topic,
|
: room.topic,
|
||||||
|
textScaleFactor:
|
||||||
|
MediaQuery.textScalerOf(context).scale(1),
|
||||||
options: const LinkifyOptions(humanize: false),
|
options: const LinkifyOptions(humanize: false),
|
||||||
linkStyle: const TextStyle(
|
linkStyle: const TextStyle(
|
||||||
color: Colors.blueAccent,
|
color: Colors.blueAccent,
|
||||||
|
|
|
||||||
|
|
@ -30,65 +30,68 @@ class ParticipantListItem extends StatelessWidget {
|
||||||
? L10n.of(context).moderator
|
? L10n.of(context).moderator
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
return Opacity(
|
return ListTile(
|
||||||
opacity: user.membership == Membership.join ? 1 : 0.5,
|
onTap: () => showMemberActionsPopupMenu(context: context, user: user),
|
||||||
child: ListTile(
|
title: Row(
|
||||||
onTap: () => showMemberActionsPopupMenu(context: context, user: user),
|
children: <Widget>[
|
||||||
title: Row(
|
Expanded(
|
||||||
children: <Widget>[
|
child: Text(
|
||||||
Expanded(
|
user.calcDisplayname(),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (permissionBatch.isNotEmpty)
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 6,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: user.powerLevel >= 100
|
||||||
|
? theme.colorScheme.tertiary
|
||||||
|
: theme.colorScheme.tertiaryContainer,
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
AppConfig.borderRadius,
|
||||||
|
),
|
||||||
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
user.calcDisplayname(),
|
permissionBatch,
|
||||||
overflow: TextOverflow.ellipsis,
|
style: theme.textTheme.labelSmall?.copyWith(
|
||||||
|
color: user.powerLevel >= 100
|
||||||
|
? theme.colorScheme.onTertiary
|
||||||
|
: theme.colorScheme.onTertiaryContainer,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (permissionBatch.isNotEmpty)
|
membershipBatch == null
|
||||||
Container(
|
? const SizedBox.shrink()
|
||||||
padding: const EdgeInsets.symmetric(
|
: Container(
|
||||||
horizontal: 12,
|
padding:
|
||||||
vertical: 6,
|
const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
||||||
),
|
margin: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: user.powerLevel >= 100
|
color: theme.colorScheme.secondaryContainer,
|
||||||
? theme.colorScheme.tertiary
|
borderRadius: BorderRadius.circular(8),
|
||||||
: theme.colorScheme.tertiaryContainer,
|
|
||||||
borderRadius: BorderRadius.circular(
|
|
||||||
AppConfig.borderRadius,
|
|
||||||
),
|
),
|
||||||
),
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
permissionBatch,
|
membershipBatch,
|
||||||
style: theme.textTheme.labelSmall?.copyWith(
|
style: theme.textTheme.labelSmall?.copyWith(
|
||||||
color: user.powerLevel >= 100
|
color: theme.colorScheme.onSecondaryContainer,
|
||||||
? theme.colorScheme.onTertiary
|
|
||||||
: theme.colorScheme.onTertiaryContainer,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
membershipBatch == null
|
|
||||||
? const SizedBox.shrink()
|
|
||||||
: Container(
|
|
||||||
padding: const EdgeInsets.all(4),
|
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: theme.secondaryHeaderColor,
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: Text(
|
|
||||||
membershipBatch,
|
|
||||||
style: theme.textTheme.labelSmall,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
subtitle: Text(
|
),
|
||||||
user.id,
|
subtitle: Text(
|
||||||
maxLines: 1,
|
user.id,
|
||||||
overflow: TextOverflow.ellipsis,
|
maxLines: 1,
|
||||||
),
|
overflow: TextOverflow.ellipsis,
|
||||||
leading: Avatar(
|
),
|
||||||
|
leading: Opacity(
|
||||||
|
opacity: user.membership == Membership.join ? 1 : 0.5,
|
||||||
|
child: Avatar(
|
||||||
mxContent: user.avatarUrl,
|
mxContent: user.avatarUrl,
|
||||||
name: user.calcDisplayname(),
|
name: user.calcDisplayname(),
|
||||||
presenceUserId: user.stateKey,
|
presenceUserId: user.stateKey,
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,7 @@ class ChatEncryptionSettingsView extends StatelessWidget {
|
||||||
deviceKeys[i].ed25519Key?.beautified ??
|
deviceKeys[i].ed25519Key?.beautified ??
|
||||||
L10n.of(context).unknownEncryptionAlgorithm,
|
L10n.of(context).unknownEncryptionAlgorithm,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontFamily: 'UbuntuMono',
|
fontFamily: 'RobotoMono',
|
||||||
color: theme.colorScheme.secondary,
|
color: theme.colorScheme.secondary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,9 @@ import 'package:fluffychat/pages/chat_list/dummy_chat_list_item.dart';
|
||||||
import 'package:fluffychat/pages/chat_list/search_title.dart';
|
import 'package:fluffychat/pages/chat_list/search_title.dart';
|
||||||
import 'package:fluffychat/pages/chat_list/space_view.dart';
|
import 'package:fluffychat/pages/chat_list/space_view.dart';
|
||||||
import 'package:fluffychat/pages/chat_list/status_msg_list.dart';
|
import 'package:fluffychat/pages/chat_list/status_msg_list.dart';
|
||||||
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
|
|
||||||
import 'package:fluffychat/utils/stream_extension.dart';
|
import 'package:fluffychat/utils/stream_extension.dart';
|
||||||
|
import 'package:fluffychat/widgets/adaptive_dialogs/public_room_dialog.dart';
|
||||||
import 'package:fluffychat/widgets/avatar.dart';
|
import 'package:fluffychat/widgets/avatar.dart';
|
||||||
import 'package:fluffychat/widgets/hover_builder.dart';
|
|
||||||
import 'package:fluffychat/widgets/public_room_bottom_sheet.dart';
|
|
||||||
import '../../config/themes.dart';
|
import '../../config/themes.dart';
|
||||||
import '../../widgets/adaptive_dialogs/user_dialog.dart';
|
import '../../widgets/adaptive_dialogs/user_dialog.dart';
|
||||||
import '../../widgets/matrix.dart';
|
import '../../widgets/matrix.dart';
|
||||||
|
|
@ -155,7 +153,7 @@ class ChatListViewBody extends StatelessWidget {
|
||||||
child: ListView(
|
child: ListView(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 12.0,
|
horizontal: 12.0,
|
||||||
vertical: 16.0,
|
vertical: 12.0,
|
||||||
),
|
),
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
|
|
@ -172,53 +170,15 @@ class ChatListViewBody extends StatelessWidget {
|
||||||
]
|
]
|
||||||
.map(
|
.map(
|
||||||
(filter) => Padding(
|
(filter) => Padding(
|
||||||
padding:
|
padding: const EdgeInsets.symmetric(
|
||||||
const EdgeInsets.symmetric(horizontal: 4),
|
horizontal: 4.0,
|
||||||
child: HoverBuilder(
|
),
|
||||||
builder: (context, hovered) =>
|
child: FilterChip(
|
||||||
AnimatedScale(
|
selected: filter == controller.activeFilter,
|
||||||
duration: FluffyThemes.animationDuration,
|
onSelected: (_) =>
|
||||||
curve: FluffyThemes.animationCurve,
|
controller.setActiveFilter(filter),
|
||||||
scale: hovered ? 1.1 : 1.0,
|
label:
|
||||||
child: InkWell(
|
Text(filter.toLocalizedString(context)),
|
||||||
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.colorScheme.primary
|
|
||||||
: theme.colorScheme
|
|
||||||
.secondaryContainer,
|
|
||||||
borderRadius: BorderRadius.circular(
|
|
||||||
AppConfig.borderRadius,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Text(
|
|
||||||
filter.toLocalizedString(context),
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: filter ==
|
|
||||||
controller.activeFilter
|
|
||||||
? FontWeight.w500
|
|
||||||
: FontWeight.normal,
|
|
||||||
color: filter ==
|
|
||||||
controller.activeFilter
|
|
||||||
? theme.colorScheme.onPrimary
|
|
||||||
: theme.colorScheme
|
|
||||||
.onSecondaryContainer,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -341,12 +301,11 @@ class PublicRoomsHorizontalList extends StatelessWidget {
|
||||||
publicRooms[i].canonicalAlias?.localpart ??
|
publicRooms[i].canonicalAlias?.localpart ??
|
||||||
L10n.of(context).group,
|
L10n.of(context).group,
|
||||||
avatar: publicRooms[i].avatarUrl,
|
avatar: publicRooms[i].avatarUrl,
|
||||||
onPressed: () => showAdaptiveBottomSheet(
|
onPressed: () => showAdaptiveDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (c) => PublicRoomBottomSheet(
|
builder: (c) => PublicRoomDialog(
|
||||||
roomAlias:
|
roomAlias:
|
||||||
publicRooms[i].canonicalAlias ?? publicRooms[i].roomId,
|
publicRooms[i].canonicalAlias ?? publicRooms[i].roomId,
|
||||||
outerContext: context,
|
|
||||||
chunk: publicRooms[i],
|
chunk: publicRooms[i],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -10,16 +10,15 @@ import 'package:fluffychat/config/app_config.dart';
|
||||||
import 'package:fluffychat/config/themes.dart';
|
import 'package:fluffychat/config/themes.dart';
|
||||||
import 'package:fluffychat/pages/chat_list/chat_list_item.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/search_title.dart';
|
||||||
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
|
|
||||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||||
import 'package:fluffychat/utils/stream_extension.dart';
|
import 'package:fluffychat/utils/stream_extension.dart';
|
||||||
|
import 'package:fluffychat/widgets/adaptive_dialogs/public_room_dialog.dart';
|
||||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_modal_action_popup.dart';
|
import 'package:fluffychat/widgets/adaptive_dialogs/show_modal_action_popup.dart';
|
||||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_text_input_dialog.dart';
|
import 'package:fluffychat/widgets/adaptive_dialogs/show_text_input_dialog.dart';
|
||||||
import 'package:fluffychat/widgets/avatar.dart';
|
import 'package:fluffychat/widgets/avatar.dart';
|
||||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||||
import 'package:fluffychat/widgets/matrix.dart';
|
import 'package:fluffychat/widgets/matrix.dart';
|
||||||
import 'package:fluffychat/widgets/public_room_bottom_sheet.dart';
|
|
||||||
|
|
||||||
enum AddRoomType { chat, subspace }
|
enum AddRoomType { chat, subspace }
|
||||||
|
|
||||||
|
|
@ -100,10 +99,9 @@ class _SpaceViewState extends State<SpaceView> {
|
||||||
final client = Matrix.of(context).client;
|
final client = Matrix.of(context).client;
|
||||||
final space = client.getRoomById(widget.spaceId);
|
final space = client.getRoomById(widget.spaceId);
|
||||||
|
|
||||||
final joined = await showAdaptiveBottomSheet<bool>(
|
final joined = await showAdaptiveDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => PublicRoomBottomSheet(
|
builder: (_) => PublicRoomDialog(
|
||||||
outerContext: context,
|
|
||||||
chunk: item,
|
chunk: item,
|
||||||
via: space?.spaceChildren
|
via: space?.spaceChildren
|
||||||
.firstWhereOrNull(
|
.firstWhereOrNull(
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
|
|
@ -7,6 +9,7 @@ import 'chat_members_view.dart';
|
||||||
|
|
||||||
class ChatMembersPage extends StatefulWidget {
|
class ChatMembersPage extends StatefulWidget {
|
||||||
final String roomId;
|
final String roomId;
|
||||||
|
|
||||||
const ChatMembersPage({required this.roomId, super.key});
|
const ChatMembersPage({required this.roomId, super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -17,12 +20,27 @@ class ChatMembersController extends State<ChatMembersPage> {
|
||||||
List<User>? members;
|
List<User>? members;
|
||||||
List<User>? filteredMembers;
|
List<User>? filteredMembers;
|
||||||
Object? error;
|
Object? error;
|
||||||
|
Membership membershipFilter = Membership.join;
|
||||||
|
|
||||||
final TextEditingController filterController = TextEditingController();
|
final TextEditingController filterController = TextEditingController();
|
||||||
|
|
||||||
|
void setMembershipFilter(Membership membership) {
|
||||||
|
membershipFilter = membership;
|
||||||
|
setFilter();
|
||||||
|
}
|
||||||
|
|
||||||
void setFilter([_]) async {
|
void setFilter([_]) async {
|
||||||
final filter = filterController.text.toLowerCase().trim();
|
final filter = filterController.text.toLowerCase().trim();
|
||||||
|
|
||||||
|
final members = this
|
||||||
|
.members
|
||||||
|
?.where(
|
||||||
|
(member) =>
|
||||||
|
membershipFilter == Membership.join ||
|
||||||
|
member.membership == membershipFilter,
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
if (filter.isEmpty) {
|
if (filter.isEmpty) {
|
||||||
setState(() {
|
setState(() {
|
||||||
filteredMembers = members
|
filteredMembers = members
|
||||||
|
|
@ -42,7 +60,8 @@ class ChatMembersController extends State<ChatMembersPage> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void refreshMembers() async {
|
void refreshMembers([_]) async {
|
||||||
|
Logs().d('Load room members from', widget.roomId);
|
||||||
try {
|
try {
|
||||||
setState(() {
|
setState(() {
|
||||||
error = null;
|
error = null;
|
||||||
|
|
@ -50,7 +69,9 @@ class ChatMembersController extends State<ChatMembersPage> {
|
||||||
final participants = await Matrix.of(context)
|
final participants = await Matrix.of(context)
|
||||||
.client
|
.client
|
||||||
.getRoomById(widget.roomId)
|
.getRoomById(widget.roomId)
|
||||||
?.requestParticipants();
|
?.requestParticipants(
|
||||||
|
[...Membership.values]..remove(Membership.leave),
|
||||||
|
);
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
|
|
@ -67,10 +88,30 @@ class ChatMembersController extends State<ChatMembersPage> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StreamSubscription? _updateSub;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
refreshMembers();
|
refreshMembers();
|
||||||
|
|
||||||
|
_updateSub = Matrix.of(context)
|
||||||
|
.client
|
||||||
|
.onSync
|
||||||
|
.stream
|
||||||
|
.where(
|
||||||
|
(syncUpdate) =>
|
||||||
|
syncUpdate.rooms?.join?[widget.roomId]?.timeline?.events
|
||||||
|
?.any((state) => state.type == EventTypes.RoomMember) ??
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.listen(refreshMembers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_updateSub?.cancel();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:matrix/matrix.dart';
|
||||||
|
|
||||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||||
import '../../widgets/layouts/max_width_body.dart';
|
import '../../widgets/layouts/max_width_body.dart';
|
||||||
|
|
@ -11,6 +12,7 @@ import 'chat_members.dart';
|
||||||
|
|
||||||
class ChatMembersView extends StatelessWidget {
|
class ChatMembersView extends StatelessWidget {
|
||||||
final ChatMembersController controller;
|
final ChatMembersController controller;
|
||||||
|
|
||||||
const ChatMembersView(this.controller, {super.key});
|
const ChatMembersView(this.controller, {super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -84,29 +86,89 @@ class ChatMembersView extends StatelessWidget {
|
||||||
: ListView.builder(
|
: ListView.builder(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: members.length + 1,
|
itemCount: members.length + 1,
|
||||||
itemBuilder: (context, i) => i == 0
|
itemBuilder: (context, i) {
|
||||||
? Padding(
|
if (i == 0) {
|
||||||
padding: const EdgeInsets.all(16.0),
|
final availableFilters = Membership.values
|
||||||
child: TextField(
|
.where(
|
||||||
controller: controller.filterController,
|
(membership) =>
|
||||||
onChanged: controller.setFilter,
|
controller.members?.any(
|
||||||
decoration: InputDecoration(
|
(member) => member.membership == membership,
|
||||||
filled: true,
|
) ??
|
||||||
fillColor: theme.colorScheme.secondaryContainer,
|
false,
|
||||||
border: OutlineInputBorder(
|
)
|
||||||
borderSide: BorderSide.none,
|
.toList();
|
||||||
borderRadius: BorderRadius.circular(99),
|
availableFilters
|
||||||
|
.sort((a, b) => a == Membership.join ? -1 : 1);
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: TextField(
|
||||||
|
controller: controller.filterController,
|
||||||
|
onChanged: controller.setFilter,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
filled: true,
|
||||||
|
fillColor:
|
||||||
|
theme.colorScheme.secondaryContainer,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide.none,
|
||||||
|
borderRadius: BorderRadius.circular(99),
|
||||||
|
),
|
||||||
|
hintStyle: TextStyle(
|
||||||
|
color: theme.colorScheme.onPrimaryContainer,
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
),
|
||||||
|
prefixIcon: const Icon(Icons.search_outlined),
|
||||||
|
hintText: L10n.of(context).search,
|
||||||
),
|
),
|
||||||
hintStyle: TextStyle(
|
|
||||||
color: theme.colorScheme.onPrimaryContainer,
|
|
||||||
fontWeight: FontWeight.normal,
|
|
||||||
),
|
|
||||||
prefixIcon: const Icon(Icons.search_outlined),
|
|
||||||
hintText: L10n.of(context).search,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
if (availableFilters.length > 1)
|
||||||
: ParticipantListItem(members[i - 1]),
|
SizedBox(
|
||||||
|
height: 64,
|
||||||
|
child: ListView.builder(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12.0,
|
||||||
|
vertical: 12.0,
|
||||||
|
),
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
itemCount: availableFilters.length,
|
||||||
|
itemBuilder: (context, i) => Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 4.0,
|
||||||
|
),
|
||||||
|
child: FilterChip(
|
||||||
|
label: Text(
|
||||||
|
switch (availableFilters[i]) {
|
||||||
|
Membership.ban =>
|
||||||
|
L10n.of(context).banned,
|
||||||
|
Membership.invite =>
|
||||||
|
L10n.of(context).invited,
|
||||||
|
Membership.join =>
|
||||||
|
L10n.of(context).all,
|
||||||
|
Membership.knock =>
|
||||||
|
L10n.of(context).knocking,
|
||||||
|
Membership.leave =>
|
||||||
|
L10n.of(context).leftTheChat,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
selected: controller.membershipFilter ==
|
||||||
|
availableFilters[i],
|
||||||
|
onSelected: (_) =>
|
||||||
|
controller.setMembershipFilter(
|
||||||
|
availableFilters[i],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
i--;
|
||||||
|
return ParticipantListItem(members[i]);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,7 @@ class _MessageSearchResultListTile extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
subtitle: Linkify(
|
subtitle: Linkify(
|
||||||
|
textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
|
||||||
options: const LinkifyOptions(humanize: false),
|
options: const LinkifyOptions(humanize: false),
|
||||||
linkStyle: TextStyle(
|
linkStyle: TextStyle(
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,8 @@ class HomeserverPickerView extends StatelessWidget {
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 32.0),
|
padding: const EdgeInsets.symmetric(horizontal: 32.0),
|
||||||
child: SelectableLinkify(
|
child: SelectableLinkify(
|
||||||
text: L10n.of(context).appIntroduction,
|
text: L10n.of(context).appIntroduction,
|
||||||
|
textScaleFactor:
|
||||||
|
MediaQuery.textScalerOf(context).scale(1),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
linkStyle: TextStyle(
|
linkStyle: TextStyle(
|
||||||
color: theme.colorScheme.secondary,
|
color: theme.colorScheme.secondary,
|
||||||
|
|
@ -169,6 +171,19 @@ class HomeserverPickerView extends StatelessWidget {
|
||||||
content: Linkify(
|
content: Linkify(
|
||||||
text: L10n.of(context)
|
text: L10n.of(context)
|
||||||
.homeserverDescription,
|
.homeserverDescription,
|
||||||
|
textScaleFactor:
|
||||||
|
MediaQuery.textScalerOf(context)
|
||||||
|
.scale(1),
|
||||||
|
options: const LinkifyOptions(
|
||||||
|
humanize: false,
|
||||||
|
),
|
||||||
|
linkStyle: TextStyle(
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
decorationColor:
|
||||||
|
theme.colorScheme.primary,
|
||||||
|
),
|
||||||
|
onOpen: (link) =>
|
||||||
|
launchUrlString(link.url),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
AdaptiveDialogAction(
|
AdaptiveDialogAction(
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import 'package:fluffychat/utils/platform_infos.dart';
|
||||||
import 'package:fluffychat/widgets/avatar.dart';
|
import 'package:fluffychat/widgets/avatar.dart';
|
||||||
import 'package:fluffychat/widgets/matrix.dart';
|
import 'package:fluffychat/widgets/matrix.dart';
|
||||||
import 'package:fluffychat/widgets/navigation_rail.dart';
|
import 'package:fluffychat/widgets/navigation_rail.dart';
|
||||||
|
import '../../widgets/mxc_image_viewer.dart';
|
||||||
import 'settings.dart';
|
import 'settings.dart';
|
||||||
|
|
||||||
class SettingsView extends StatelessWidget {
|
class SettingsView extends StatelessWidget {
|
||||||
|
|
@ -65,6 +66,7 @@ class SettingsView extends StatelessWidget {
|
||||||
future: controller.profileFuture,
|
future: controller.profileFuture,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
final profile = snapshot.data;
|
final profile = snapshot.data;
|
||||||
|
final avatar = profile?.avatarUrl;
|
||||||
final mxid = Matrix.of(context).client.userID ??
|
final mxid = Matrix.of(context).client.userID ??
|
||||||
L10n.of(context).user;
|
L10n.of(context).user;
|
||||||
final displayname =
|
final displayname =
|
||||||
|
|
@ -76,9 +78,16 @@ class SettingsView extends StatelessWidget {
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Avatar(
|
Avatar(
|
||||||
mxContent: profile?.avatarUrl,
|
mxContent: avatar,
|
||||||
name: displayname,
|
name: displayname,
|
||||||
size: Avatar.defaultSize * 2.5,
|
size: Avatar.defaultSize * 2.5,
|
||||||
|
onTap: avatar != null
|
||||||
|
? () => showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) =>
|
||||||
|
MxcImageViewer(avatar),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
if (profile != null)
|
if (profile != null)
|
||||||
Positioned(
|
Positioned(
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,8 @@ class SettingsHomeserverView extends StatelessWidget {
|
||||||
title: const Text('Federation Base URL'),
|
title: const Text('Federation Base URL'),
|
||||||
subtitle: Linkify(
|
subtitle: Linkify(
|
||||||
text: data.federationBaseUrl.toString(),
|
text: data.federationBaseUrl.toString(),
|
||||||
|
textScaleFactor:
|
||||||
|
MediaQuery.textScalerOf(context).scale(1),
|
||||||
options: const LinkifyOptions(humanize: false),
|
options: const LinkifyOptions(humanize: false),
|
||||||
linkStyle: TextStyle(
|
linkStyle: TextStyle(
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
|
|
@ -231,6 +233,8 @@ class SettingsHomeserverView extends StatelessWidget {
|
||||||
title: const Text('Base URL'),
|
title: const Text('Base URL'),
|
||||||
subtitle: Linkify(
|
subtitle: Linkify(
|
||||||
text: wellKnown.mHomeserver.baseUrl.toString(),
|
text: wellKnown.mHomeserver.baseUrl.toString(),
|
||||||
|
textScaleFactor:
|
||||||
|
MediaQuery.textScalerOf(context).scale(1),
|
||||||
options: const LinkifyOptions(humanize: false),
|
options: const LinkifyOptions(humanize: false),
|
||||||
linkStyle: TextStyle(
|
linkStyle: TextStyle(
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
|
|
@ -244,6 +248,8 @@ class SettingsHomeserverView extends StatelessWidget {
|
||||||
title: const Text('Identity Server:'),
|
title: const Text('Identity Server:'),
|
||||||
subtitle: Linkify(
|
subtitle: Linkify(
|
||||||
text: identityServer.baseUrl.toString(),
|
text: identityServer.baseUrl.toString(),
|
||||||
|
textScaleFactor:
|
||||||
|
MediaQuery.textScalerOf(context).scale(1),
|
||||||
options: const LinkifyOptions(humanize: false),
|
options: const LinkifyOptions(humanize: false),
|
||||||
linkStyle: TextStyle(
|
linkStyle: TextStyle(
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import 'settings_security.dart';
|
||||||
|
|
||||||
class SettingsSecurityView extends StatelessWidget {
|
class SettingsSecurityView extends StatelessWidget {
|
||||||
final SettingsSecurityController controller;
|
final SettingsSecurityController controller;
|
||||||
|
|
||||||
const SettingsSecurityView(this.controller, {super.key});
|
const SettingsSecurityView(this.controller, {super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -143,7 +144,7 @@ class SettingsSecurityView extends StatelessWidget {
|
||||||
leading: const Icon(Icons.vpn_key_outlined),
|
leading: const Icon(Icons.vpn_key_outlined),
|
||||||
subtitle: SelectableText(
|
subtitle: SelectableText(
|
||||||
Matrix.of(context).client.fingerprintKey.beautified,
|
Matrix.of(context).client.fingerprintKey.beautified,
|
||||||
style: const TextStyle(fontFamily: 'UbuntuMono'),
|
style: const TextStyle(fontFamily: 'RobotoMono'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (capabilities?.mChangePassword?.enabled != false ||
|
if (capabilities?.mChangePassword?.enabled != false ||
|
||||||
|
|
|
||||||
|
|
@ -12,21 +12,41 @@ Future<T?> showAdaptiveBottomSheet<T>({
|
||||||
bool isScrollControlled = true,
|
bool isScrollControlled = true,
|
||||||
bool useRootNavigator = true,
|
bool useRootNavigator = true,
|
||||||
}) {
|
}) {
|
||||||
final maxHeight = min(MediaQuery.of(context).size.height - 32, 600);
|
if (FluffyThemes.isColumnMode(context)) {
|
||||||
final dialogMode = FluffyThemes.isColumnMode(context);
|
return showDialog<T>(
|
||||||
return showModalBottomSheet(
|
context: context,
|
||||||
|
useRootNavigator: useRootNavigator,
|
||||||
|
barrierDismissible: isDismissible,
|
||||||
|
useSafeArea: true,
|
||||||
|
builder: (context) => Center(
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.all(16),
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 480,
|
||||||
|
maxHeight: 720,
|
||||||
|
),
|
||||||
|
child: Material(
|
||||||
|
elevation: Theme.of(context).dialogTheme.elevation ?? 4,
|
||||||
|
shadowColor: Theme.of(context).dialogTheme.shadowColor,
|
||||||
|
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||||
|
color: Theme.of(context).scaffoldBackgroundColor,
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
child: builder(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return showModalBottomSheet<T>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => Padding(
|
builder: (context) => Padding(
|
||||||
padding: dialogMode
|
padding: EdgeInsets.zero,
|
||||||
? const EdgeInsets.symmetric(vertical: 32.0)
|
|
||||||
: EdgeInsets.zero,
|
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: dialogMode
|
borderRadius: const BorderRadius.only(
|
||||||
? BorderRadius.circular(AppConfig.borderRadius)
|
topLeft: Radius.circular(AppConfig.borderRadius / 2),
|
||||||
: const BorderRadius.only(
|
topRight: Radius.circular(AppConfig.borderRadius / 2),
|
||||||
topLeft: Radius.circular(AppConfig.borderRadius),
|
),
|
||||||
topRight: Radius.circular(AppConfig.borderRadius),
|
|
||||||
),
|
|
||||||
child: builder(context),
|
child: builder(context),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -34,7 +54,7 @@ Future<T?> showAdaptiveBottomSheet<T>({
|
||||||
isDismissible: isDismissible,
|
isDismissible: isDismissible,
|
||||||
isScrollControlled: isScrollControlled,
|
isScrollControlled: isScrollControlled,
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
maxHeight: maxHeight + (dialogMode ? 64 : 0),
|
maxHeight: min(MediaQuery.of(context).size.height - 32, 600),
|
||||||
maxWidth: FluffyThemes.columnWidth * 1.25,
|
maxWidth: FluffyThemes.columnWidth * 1.25,
|
||||||
),
|
),
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ extension StringColor on String {
|
||||||
number += codeUnitAt(i);
|
number += codeUnitAt(i);
|
||||||
}
|
}
|
||||||
number = (number % 12) * 25.5;
|
number = (number % 12) * 25.5;
|
||||||
return HSLColor.fromAHSL(1, number, 1, light).toColor();
|
return HSLColor.fromAHSL(0.75, number, 1, light).toColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
Color get color {
|
Color get color {
|
||||||
|
|
@ -29,6 +29,6 @@ extension StringColor on String {
|
||||||
|
|
||||||
Color get lightColorAvatar {
|
Color get lightColorAvatar {
|
||||||
_colorCache[this] ??= {};
|
_colorCache[this] ??= {};
|
||||||
return _colorCache[this]![0.4] ??= _getColorLight(0.4);
|
return _colorCache[this]![0.45] ??= _getColorLight(0.45);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,11 @@ import 'package:punycode/punycode.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
import 'package:fluffychat/config/app_config.dart';
|
import 'package:fluffychat/config/app_config.dart';
|
||||||
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
|
|
||||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||||
import 'package:fluffychat/widgets/adaptive_dialogs/user_dialog.dart';
|
import 'package:fluffychat/widgets/adaptive_dialogs/user_dialog.dart';
|
||||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||||
import 'package:fluffychat/widgets/matrix.dart';
|
import 'package:fluffychat/widgets/matrix.dart';
|
||||||
import 'package:fluffychat/widgets/public_room_bottom_sheet.dart';
|
import '../widgets/adaptive_dialogs/public_room_dialog.dart';
|
||||||
import 'platform_infos.dart';
|
import 'platform_infos.dart';
|
||||||
|
|
||||||
class UrlLauncher {
|
class UrlLauncher {
|
||||||
|
|
@ -179,11 +178,10 @@ class UrlLauncher {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
await showAdaptiveBottomSheet(
|
await showAdaptiveDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (c) => PublicRoomBottomSheet(
|
builder: (c) => PublicRoomDialog(
|
||||||
roomAlias: identityParts.primaryIdentifier,
|
roomAlias: identityParts.primaryIdentifier,
|
||||||
outerContext: context,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
231
lib/widgets/adaptive_dialogs/public_room_dialog.dart
Normal file
231
lib/widgets/adaptive_dialogs/public_room_dialog.dart
Normal file
|
|
@ -0,0 +1,231 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
|
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:matrix/matrix.dart';
|
||||||
|
|
||||||
|
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||||
|
import '../../config/themes.dart';
|
||||||
|
import '../../utils/url_launcher.dart';
|
||||||
|
import '../avatar.dart';
|
||||||
|
import '../future_loading_dialog.dart';
|
||||||
|
import '../hover_builder.dart';
|
||||||
|
import '../matrix.dart';
|
||||||
|
import '../mxc_image_viewer.dart';
|
||||||
|
import 'adaptive_dialog_action.dart';
|
||||||
|
|
||||||
|
class PublicRoomDialog extends StatelessWidget {
|
||||||
|
final String? roomAlias;
|
||||||
|
final PublicRoomsChunk? chunk;
|
||||||
|
final List<String>? via;
|
||||||
|
|
||||||
|
const PublicRoomDialog({super.key, this.roomAlias, this.chunk, this.via});
|
||||||
|
|
||||||
|
void _joinRoom(BuildContext context) async {
|
||||||
|
final client = Matrix.of(context).client;
|
||||||
|
final chunk = this.chunk;
|
||||||
|
final knock = chunk?.joinRule == 'knock';
|
||||||
|
final result = await showFutureLoadingDialog<String>(
|
||||||
|
context: context,
|
||||||
|
future: () async {
|
||||||
|
if (chunk != null && client.getRoomById(chunk.roomId) != null) {
|
||||||
|
return chunk.roomId;
|
||||||
|
}
|
||||||
|
final roomId = chunk != null && knock
|
||||||
|
? await client.knockRoom(chunk.roomId, via: via)
|
||||||
|
: await client.joinRoom(
|
||||||
|
roomAlias ?? chunk!.roomId,
|
||||||
|
via: via,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!knock && client.getRoomById(roomId) == null) {
|
||||||
|
await client.waitForRoomInSync(roomId);
|
||||||
|
}
|
||||||
|
return roomId;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
final roomId = result.result;
|
||||||
|
if (roomId == null) return;
|
||||||
|
if (knock && client.getRoomById(roomId) == null) {
|
||||||
|
Navigator.of(context).pop<bool>(true);
|
||||||
|
await showOkAlertDialog(
|
||||||
|
context: context,
|
||||||
|
title: L10n.of(context).youHaveKnocked,
|
||||||
|
message: L10n.of(context).pleaseWaitUntilInvited,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (result.error != null) return;
|
||||||
|
if (!context.mounted) return;
|
||||||
|
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) {
|
||||||
|
context.go('/rooms/$roomId');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _testRoom(PublicRoomsChunk r) => r.canonicalAlias == roomAlias;
|
||||||
|
|
||||||
|
Future<PublicRoomsChunk> _search(BuildContext context) async {
|
||||||
|
final chunk = this.chunk;
|
||||||
|
if (chunk != null) return chunk;
|
||||||
|
final query = await Matrix.of(context).client.queryPublicRooms(
|
||||||
|
server: roomAlias!.domain,
|
||||||
|
filter: PublicRoomQueryFilter(
|
||||||
|
genericSearchTerm: roomAlias,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (!query.chunk.any(_testRoom)) {
|
||||||
|
throw (L10n.of(context).noRoomsFound);
|
||||||
|
}
|
||||||
|
return query.chunk.firstWhere(_testRoom);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final roomAlias = this.roomAlias ?? chunk?.canonicalAlias;
|
||||||
|
final roomLink = roomAlias ?? chunk?.roomId;
|
||||||
|
var copied = false;
|
||||||
|
return AlertDialog.adaptive(
|
||||||
|
title: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 256),
|
||||||
|
child: Text(
|
||||||
|
chunk?.name ?? roomAlias?.localpart ?? chunk?.roomId ?? 'Unknown',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
content: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 256, maxHeight: 256),
|
||||||
|
child: FutureBuilder<PublicRoomsChunk>(
|
||||||
|
future: _search(context),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
final profile = snapshot.data;
|
||||||
|
final avatar = profile?.avatarUrl;
|
||||||
|
final topic = profile?.topic;
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
spacing: 8,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
if (roomLink != null)
|
||||||
|
HoverBuilder(
|
||||||
|
builder: (context, hovered) => StatefulBuilder(
|
||||||
|
builder: (context, setState) => MouseRegion(
|
||||||
|
cursor: SystemMouseCursors.click,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
Clipboard.setData(
|
||||||
|
ClipboardData(text: roomLink),
|
||||||
|
);
|
||||||
|
setState(() {
|
||||||
|
copied = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
children: [
|
||||||
|
WidgetSpan(
|
||||||
|
child: Padding(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.only(right: 4.0),
|
||||||
|
child: AnimatedScale(
|
||||||
|
duration:
|
||||||
|
FluffyThemes.animationDuration,
|
||||||
|
curve: FluffyThemes.animationCurve,
|
||||||
|
scale: hovered
|
||||||
|
? 1.33
|
||||||
|
: copied
|
||||||
|
? 1.25
|
||||||
|
: 1.0,
|
||||||
|
child: Icon(
|
||||||
|
copied
|
||||||
|
? Icons.check_circle
|
||||||
|
: Icons.copy,
|
||||||
|
size: 12,
|
||||||
|
color: copied ? Colors.green : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(text: roomLink),
|
||||||
|
],
|
||||||
|
style: theme.textTheme.bodyMedium
|
||||||
|
?.copyWith(fontSize: 10),
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Center(
|
||||||
|
child: Avatar(
|
||||||
|
mxContent: avatar,
|
||||||
|
name: profile?.name ?? roomLink,
|
||||||
|
size: Avatar.defaultSize * 2,
|
||||||
|
onTap: avatar != null
|
||||||
|
? () => showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => MxcImageViewer(avatar),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (profile?.numJoinedMembers != null)
|
||||||
|
Text(
|
||||||
|
L10n.of(context).countParticipants(
|
||||||
|
profile?.numJoinedMembers ?? 0,
|
||||||
|
),
|
||||||
|
style: const TextStyle(fontSize: 10),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
if (topic != null && topic.isNotEmpty)
|
||||||
|
SelectableLinkify(
|
||||||
|
text: topic,
|
||||||
|
textScaleFactor:
|
||||||
|
MediaQuery.textScalerOf(context).scale(1),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
options: const LinkifyOptions(humanize: false),
|
||||||
|
linkStyle: TextStyle(
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
decorationColor: theme.colorScheme.primary,
|
||||||
|
),
|
||||||
|
onOpen: (url) =>
|
||||||
|
UrlLauncher(context, url.url).launchUrl(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
AdaptiveDialogAction(
|
||||||
|
bigButtons: true,
|
||||||
|
onPressed: () => _joinRoom(context),
|
||||||
|
child: Text(
|
||||||
|
chunk?.joinRule == 'knock' &&
|
||||||
|
Matrix.of(context).client.getRoomById(chunk!.roomId) == null
|
||||||
|
? L10n.of(context).knock
|
||||||
|
: chunk?.roomType == 'm.space'
|
||||||
|
? L10n.of(context).joinSpace
|
||||||
|
: L10n.of(context).joinRoom,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
AdaptiveDialogAction(
|
||||||
|
bigButtons: true,
|
||||||
|
onPressed: Navigator.of(context).pop,
|
||||||
|
child: Text(L10n.of(context).close),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -31,6 +31,7 @@ Future<OkCancelResult?> showOkCancelAlertDialog({
|
||||||
? null
|
? null
|
||||||
: SelectableLinkify(
|
: SelectableLinkify(
|
||||||
text: message,
|
text: message,
|
||||||
|
textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
|
||||||
linkStyle: TextStyle(
|
linkStyle: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
decorationColor: Theme.of(context).colorScheme.primary,
|
decorationColor: Theme.of(context).colorScheme.primary,
|
||||||
|
|
@ -81,6 +82,7 @@ Future<OkCancelResult?> showOkAlertDialog({
|
||||||
? null
|
? null
|
||||||
: SelectableLinkify(
|
: SelectableLinkify(
|
||||||
text: message,
|
text: message,
|
||||||
|
textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
|
||||||
linkStyle: TextStyle(
|
linkStyle: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
decorationColor: Theme.of(context).colorScheme.primary,
|
decorationColor: Theme.of(context).colorScheme.primary,
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ Future<String?> showTextInputDialog({
|
||||||
if (message != null)
|
if (message != null)
|
||||||
SelectableLinkify(
|
SelectableLinkify(
|
||||||
text: message,
|
text: message,
|
||||||
|
textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
|
||||||
linkStyle: TextStyle(
|
linkStyle: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
decorationColor: Theme.of(context).colorScheme.primary,
|
decorationColor: Theme.of(context).colorScheme.primary,
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import '../../utils/url_launcher.dart';
|
||||||
import '../future_loading_dialog.dart';
|
import '../future_loading_dialog.dart';
|
||||||
import '../hover_builder.dart';
|
import '../hover_builder.dart';
|
||||||
import '../matrix.dart';
|
import '../matrix.dart';
|
||||||
|
import '../mxc_image_viewer.dart';
|
||||||
|
|
||||||
class UserDialog extends StatelessWidget {
|
class UserDialog extends StatelessWidget {
|
||||||
static Future<void> show({
|
static Future<void> show({
|
||||||
|
|
@ -45,6 +46,7 @@ class UserDialog extends StatelessWidget {
|
||||||
L10n.of(context).user;
|
L10n.of(context).user;
|
||||||
var copied = false;
|
var copied = false;
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
final avatar = profile.avatarUrl;
|
||||||
return AlertDialog.adaptive(
|
return AlertDialog.adaptive(
|
||||||
title: ConstrainedBox(
|
title: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 256),
|
constraints: const BoxConstraints(maxWidth: 256),
|
||||||
|
|
@ -52,30 +54,31 @@ class UserDialog extends StatelessWidget {
|
||||||
),
|
),
|
||||||
content: ConstrainedBox(
|
content: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 256, maxHeight: 256),
|
constraints: const BoxConstraints(maxWidth: 256, maxHeight: 256),
|
||||||
child: SelectionArea(
|
child: PresenceBuilder(
|
||||||
child: PresenceBuilder(
|
userId: profile.userId,
|
||||||
userId: profile.userId,
|
client: Matrix.of(context).client,
|
||||||
client: Matrix.of(context).client,
|
builder: (context, presence) {
|
||||||
builder: (context, presence) {
|
if (presence == null) return const SizedBox.shrink();
|
||||||
if (presence == null) return const SizedBox.shrink();
|
final statusMsg = presence.statusMsg;
|
||||||
final statusMsg = presence.statusMsg;
|
final lastActiveTimestamp = presence.lastActiveTimestamp;
|
||||||
final lastActiveTimestamp = presence.lastActiveTimestamp;
|
final presenceText = presence.currentlyActive == true
|
||||||
final presenceText = presence.currentlyActive == true
|
? L10n.of(context).currentlyActive
|
||||||
? L10n.of(context).currentlyActive
|
: lastActiveTimestamp != null
|
||||||
: lastActiveTimestamp != null
|
? L10n.of(context).lastActiveAgo(
|
||||||
? L10n.of(context).lastActiveAgo(
|
lastActiveTimestamp.localizedTimeShort(context),
|
||||||
lastActiveTimestamp.localizedTimeShort(context),
|
)
|
||||||
)
|
: null;
|
||||||
: null;
|
return SingleChildScrollView(
|
||||||
return SingleChildScrollView(
|
child: Column(
|
||||||
child: Column(
|
spacing: 8,
|
||||||
spacing: 8,
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
children: [
|
||||||
children: [
|
HoverBuilder(
|
||||||
HoverBuilder(
|
builder: (context, hovered) => StatefulBuilder(
|
||||||
builder: (context, hovered) => StatefulBuilder(
|
builder: (context, setState) => MouseRegion(
|
||||||
builder: (context, setState) => GestureDetector(
|
cursor: SystemMouseCursors.click,
|
||||||
|
child: GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Clipboard.setData(
|
Clipboard.setData(
|
||||||
ClipboardData(text: profile.userId),
|
ClipboardData(text: profile.userId),
|
||||||
|
|
@ -118,37 +121,45 @@ class UserDialog extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Center(
|
),
|
||||||
child: Avatar(
|
Center(
|
||||||
mxContent: profile.avatarUrl,
|
child: Avatar(
|
||||||
name: displayname,
|
mxContent: avatar,
|
||||||
size: Avatar.defaultSize * 2,
|
name: displayname,
|
||||||
),
|
size: Avatar.defaultSize * 2,
|
||||||
|
onTap: avatar != null
|
||||||
|
? () => showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => MxcImageViewer(avatar),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
if (presenceText != null)
|
),
|
||||||
Text(
|
if (presenceText != null)
|
||||||
presenceText,
|
Text(
|
||||||
style: const TextStyle(fontSize: 10),
|
presenceText,
|
||||||
textAlign: TextAlign.center,
|
style: const TextStyle(fontSize: 10),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
if (statusMsg != null)
|
||||||
|
SelectableLinkify(
|
||||||
|
text: statusMsg,
|
||||||
|
textScaleFactor:
|
||||||
|
MediaQuery.textScalerOf(context).scale(1),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
options: const LinkifyOptions(humanize: false),
|
||||||
|
linkStyle: TextStyle(
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
decorationColor: theme.colorScheme.primary,
|
||||||
),
|
),
|
||||||
if (statusMsg != null)
|
onOpen: (url) =>
|
||||||
Linkify(
|
UrlLauncher(context, url.url).launchUrl(),
|
||||||
text: statusMsg,
|
),
|
||||||
textAlign: TextAlign.center,
|
],
|
||||||
options: const LinkifyOptions(humanize: false),
|
),
|
||||||
linkStyle: TextStyle(
|
);
|
||||||
color: theme.colorScheme.primary,
|
},
|
||||||
decoration: TextDecoration.underline,
|
|
||||||
decorationColor: theme.colorScheme.primary,
|
|
||||||
),
|
|
||||||
onOpen: (url) =>
|
|
||||||
UrlLauncher(context, url.url).launchUrl(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
|
|
|
||||||
|
|
@ -62,18 +62,13 @@ class Avatar extends StatelessWidget {
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
child: noPic
|
child: noPic
|
||||||
? Container(
|
? Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(color: name?.lightColorAvatar),
|
||||||
gradient: LinearGradient(
|
|
||||||
colors: [name!.lightColorAvatar, name.color],
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Text(
|
child: Text(
|
||||||
fallbackLetters,
|
fallbackLetters,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
fontFamily: 'RobotoMono',
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: (size / 2.5).roundToDouble(),
|
fontSize: (size / 2.5).roundToDouble(),
|
||||||
|
|
@ -143,10 +138,12 @@ class Avatar extends StatelessWidget {
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
if (onTap == null) return container;
|
if (onTap == null) return container;
|
||||||
return InkWell(
|
return MouseRegion(
|
||||||
onTap: onTap,
|
cursor: SystemMouseCursors.click,
|
||||||
borderRadius: borderRadius,
|
child: GestureDetector(
|
||||||
child: container,
|
onTap: onTap,
|
||||||
|
child: container,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,17 @@ void showMemberActionsPopupMenu({
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (user.membership == Membership.knock)
|
||||||
|
PopupMenuItem(
|
||||||
|
value: _MemberActions.approve,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.how_to_reg_outlined),
|
||||||
|
const SizedBox(width: 18),
|
||||||
|
Text(L10n.of(context).approve),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
enabled: user.room.canChangePowerLevel && user.canChangeUserPowerLevel,
|
enabled: user.room.canChangePowerLevel && user.canChangeUserPowerLevel,
|
||||||
value: _MemberActions.setRole,
|
value: _MemberActions.setRole,
|
||||||
|
|
@ -202,9 +213,14 @@ void showMemberActionsPopupMenu({
|
||||||
future: () => user.setPower(power),
|
future: () => user.setPower(power),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
case _MemberActions.approve:
|
||||||
|
await showFutureLoadingDialog(
|
||||||
|
context: context,
|
||||||
|
future: () => user.room.invite(user.id),
|
||||||
|
);
|
||||||
|
return;
|
||||||
case _MemberActions.kick:
|
case _MemberActions.kick:
|
||||||
if (await showOkCancelAlertDialog(
|
if (await showOkCancelAlertDialog(
|
||||||
useRootNavigator: false,
|
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).areYouSure,
|
title: L10n.of(context).areYouSure,
|
||||||
okLabel: L10n.of(context).yes,
|
okLabel: L10n.of(context).yes,
|
||||||
|
|
@ -220,7 +236,6 @@ void showMemberActionsPopupMenu({
|
||||||
return;
|
return;
|
||||||
case _MemberActions.ban:
|
case _MemberActions.ban:
|
||||||
if (await showOkCancelAlertDialog(
|
if (await showOkCancelAlertDialog(
|
||||||
useRootNavigator: false,
|
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).areYouSure,
|
title: L10n.of(context).areYouSure,
|
||||||
okLabel: L10n.of(context).yes,
|
okLabel: L10n.of(context).yes,
|
||||||
|
|
@ -268,7 +283,6 @@ void showMemberActionsPopupMenu({
|
||||||
return;
|
return;
|
||||||
case _MemberActions.unban:
|
case _MemberActions.unban:
|
||||||
if (await showOkCancelAlertDialog(
|
if (await showOkCancelAlertDialog(
|
||||||
useRootNavigator: false,
|
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).areYouSure,
|
title: L10n.of(context).areYouSure,
|
||||||
okLabel: L10n.of(context).yes,
|
okLabel: L10n.of(context).yes,
|
||||||
|
|
@ -290,6 +304,7 @@ enum _MemberActions {
|
||||||
setRole,
|
setRole,
|
||||||
kick,
|
kick,
|
||||||
ban,
|
ban,
|
||||||
|
approve,
|
||||||
unban,
|
unban,
|
||||||
report,
|
report,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
60
lib/widgets/mxc_image_viewer.dart
Normal file
60
lib/widgets/mxc_image_viewer.dart
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
|
|
||||||
|
import 'mxc_image.dart';
|
||||||
|
|
||||||
|
class MxcImageViewer extends StatelessWidget {
|
||||||
|
final Uri mxContent;
|
||||||
|
|
||||||
|
const MxcImageViewer(this.mxContent, {super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final iconButtonStyle = IconButton.styleFrom(
|
||||||
|
backgroundColor: Colors.black.withAlpha(200),
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
);
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => Navigator.of(context).pop(),
|
||||||
|
child: Scaffold(
|
||||||
|
backgroundColor: Colors.black.withAlpha(128),
|
||||||
|
extendBodyBehindAppBar: true,
|
||||||
|
appBar: AppBar(
|
||||||
|
elevation: 0,
|
||||||
|
leading: IconButton(
|
||||||
|
style: iconButtonStyle,
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
onPressed: Navigator.of(context).pop,
|
||||||
|
color: Colors.white,
|
||||||
|
tooltip: L10n.of(context).close,
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
),
|
||||||
|
body: InteractiveViewer(
|
||||||
|
minScale: 1.0,
|
||||||
|
maxScale: 10.0,
|
||||||
|
onInteractionEnd: (endDetails) {
|
||||||
|
if (endDetails.velocity.pixelsPerSecond.dy >
|
||||||
|
MediaQuery.of(context).size.height * 1.5) {
|
||||||
|
Navigator.of(context, rootNavigator: false).pop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Center(
|
||||||
|
child: GestureDetector(
|
||||||
|
// Ignore taps to not go back here:
|
||||||
|
onTap: () {},
|
||||||
|
child: MxcImage(
|
||||||
|
key: ValueKey(mxContent.toString()),
|
||||||
|
uri: mxContent,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
isThumbnail: false,
|
||||||
|
animated: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,25 +17,24 @@ Future<int?> showPermissionChooser(
|
||||||
builder: (context) => AlertDialog.adaptive(
|
builder: (context) => AlertDialog.adaptive(
|
||||||
title: Text(L10n.of(context).chatPermissions),
|
title: Text(L10n.of(context).chatPermissions),
|
||||||
content: ConstrainedBox(
|
content: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 256),
|
constraints: const BoxConstraints(maxWidth: 256, maxHeight: 256),
|
||||||
child: Center(
|
child: Column(
|
||||||
child: Column(
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
spacing: 12.0,
|
spacing: 12.0,
|
||||||
children: [
|
children: [
|
||||||
Text(L10n.of(context).setPermissionsLevelDescription),
|
Text(L10n.of(context).setPermissionsLevelDescription),
|
||||||
ValueListenableBuilder(
|
ValueListenableBuilder(
|
||||||
valueListenable: error,
|
valueListenable: error,
|
||||||
builder: (context, errorText, _) => DialogTextField(
|
builder: (context, errorText, _) => DialogTextField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
hintText: currentLevel.toString(),
|
hintText: currentLevel.toString(),
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
labelText: L10n.of(context).custom,
|
labelText: L10n.of(context).custom,
|
||||||
errorText: errorText,
|
errorText: errorText,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
|
|
|
||||||
|
|
@ -1,233 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
|
||||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
|
||||||
import 'package:go_router/go_router.dart';
|
|
||||||
import 'package:matrix/matrix.dart';
|
|
||||||
|
|
||||||
import 'package:fluffychat/utils/fluffy_share.dart';
|
|
||||||
import 'package:fluffychat/utils/url_launcher.dart';
|
|
||||||
import 'package:fluffychat/widgets/avatar.dart';
|
|
||||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
|
||||||
import 'package:fluffychat/widgets/matrix.dart';
|
|
||||||
import 'package:fluffychat/widgets/qr_code_viewer.dart';
|
|
||||||
|
|
||||||
class PublicRoomBottomSheet extends StatelessWidget {
|
|
||||||
final String? roomAlias;
|
|
||||||
final BuildContext outerContext;
|
|
||||||
final PublicRoomsChunk? chunk;
|
|
||||||
final List<String>? via;
|
|
||||||
|
|
||||||
PublicRoomBottomSheet({
|
|
||||||
this.roomAlias,
|
|
||||||
required this.outerContext,
|
|
||||||
this.chunk,
|
|
||||||
this.via,
|
|
||||||
super.key,
|
|
||||||
}) {
|
|
||||||
assert(roomAlias != null || chunk != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _joinRoom(BuildContext context) async {
|
|
||||||
final client = Matrix.of(outerContext).client;
|
|
||||||
final chunk = this.chunk;
|
|
||||||
final knock = chunk?.joinRule == 'knock';
|
|
||||||
final result = await showFutureLoadingDialog<String>(
|
|
||||||
context: context,
|
|
||||||
future: () async {
|
|
||||||
if (chunk != null && client.getRoomById(chunk.roomId) != null) {
|
|
||||||
return chunk.roomId;
|
|
||||||
}
|
|
||||||
final roomId = chunk != null && knock
|
|
||||||
? await client.knockRoom(chunk.roomId, via: via)
|
|
||||||
: await client.joinRoom(
|
|
||||||
roomAlias ?? chunk!.roomId,
|
|
||||||
via: via,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!knock && client.getRoomById(roomId) == null) {
|
|
||||||
await client.waitForRoomInSync(roomId);
|
|
||||||
}
|
|
||||||
return roomId;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (knock) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (result.error == null) {
|
|
||||||
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) {
|
|
||||||
outerContext.go('/rooms/${result.result!}');
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _testRoom(PublicRoomsChunk r) => r.canonicalAlias == roomAlias;
|
|
||||||
|
|
||||||
Future<PublicRoomsChunk> _search() async {
|
|
||||||
final chunk = this.chunk;
|
|
||||||
if (chunk != null) return chunk;
|
|
||||||
final query = await Matrix.of(outerContext).client.queryPublicRooms(
|
|
||||||
server: roomAlias!.domain,
|
|
||||||
filter: PublicRoomQueryFilter(
|
|
||||||
genericSearchTerm: roomAlias,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (!query.chunk.any(_testRoom)) {
|
|
||||||
throw (L10n.of(outerContext).noRoomsFound);
|
|
||||||
}
|
|
||||||
return query.chunk.firstWhere(_testRoom);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final roomAlias = this.roomAlias ?? chunk?.canonicalAlias;
|
|
||||||
final roomLink = roomAlias ?? chunk?.roomId;
|
|
||||||
return SafeArea(
|
|
||||||
child: Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: Text(
|
|
||||||
chunk?.name ?? roomAlias ?? chunk?.roomId ?? 'Unknown',
|
|
||||||
overflow: TextOverflow.fade,
|
|
||||||
),
|
|
||||||
leading: Center(
|
|
||||||
child: CloseButton(
|
|
||||||
onPressed: Navigator.of(context, rootNavigator: false).pop,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
actions: roomAlias == null
|
|
||||||
? null
|
|
||||||
: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
|
||||||
child: IconButton(
|
|
||||||
icon: const Icon(Icons.qr_code_rounded),
|
|
||||||
onPressed: () => showQrCodeViewer(
|
|
||||||
context,
|
|
||||||
roomAlias,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: FutureBuilder<PublicRoomsChunk>(
|
|
||||||
future: _search(),
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
final theme = Theme.of(context);
|
|
||||||
|
|
||||||
final profile = snapshot.data;
|
|
||||||
return ListView(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
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.colorScheme.onSurface,
|
|
||||||
iconColor: theme.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.colorScheme.onSurface,
|
|
||||||
iconColor: theme.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(outerContext)
|
|
||||||
.client
|
|
||||||
.getRoomById(chunk!.roomId) ==
|
|
||||||
null
|
|
||||||
? L10n.of(context).knock
|
|
||||||
: chunk?.roomType == 'm.space'
|
|
||||||
? L10n.of(context).joinSpace
|
|
||||||
: L10n.of(context).joinRoom,
|
|
||||||
),
|
|
||||||
icon: const Icon(Icons.navigate_next),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
if (profile?.topic?.isNotEmpty ?? false)
|
|
||||||
ListTile(
|
|
||||||
subtitle: SelectableLinkify(
|
|
||||||
text: profile!.topic!,
|
|
||||||
linkStyle: TextStyle(
|
|
||||||
color: theme.colorScheme.primary,
|
|
||||||
decorationColor: theme.colorScheme.primary,
|
|
||||||
),
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: theme.textTheme.bodyMedium!.color,
|
|
||||||
),
|
|
||||||
options: const LinkifyOptions(humanize: false),
|
|
||||||
onOpen: (url) =>
|
|
||||||
UrlLauncher(context, url.url).launchUrl(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
94
pubspec.lock
94
pubspec.lock
|
|
@ -90,10 +90,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: async
|
name: async
|
||||||
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.11.0"
|
version: "2.12.0"
|
||||||
audio_session:
|
audio_session:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -138,10 +138,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: boolean_selector
|
name: boolean_selector
|
||||||
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
|
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.2"
|
||||||
canonical_json:
|
canonical_json:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -154,10 +154,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: characters
|
name: characters
|
||||||
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.4.0"
|
||||||
charcode:
|
charcode:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -194,18 +194,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: clock
|
name: clock
|
||||||
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
|
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.2"
|
||||||
collection:
|
collection:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: collection
|
name: collection
|
||||||
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
|
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.19.0"
|
version: "1.19.1"
|
||||||
colorize:
|
colorize:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -354,10 +354,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: fake_async
|
name: fake_async
|
||||||
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
|
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "1.3.2"
|
||||||
ffi:
|
ffi:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -370,10 +370,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: file
|
name: file
|
||||||
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
|
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.0"
|
version: "7.0.1"
|
||||||
file_picker:
|
file_picker:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -1047,18 +1047,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker
|
name: leak_tracker
|
||||||
sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
|
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.0.7"
|
version: "10.0.8"
|
||||||
leak_tracker_flutter_testing:
|
leak_tracker_flutter_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker_flutter_testing
|
name: leak_tracker_flutter_testing
|
||||||
sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
|
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.8"
|
version: "3.0.9"
|
||||||
leak_tracker_testing:
|
leak_tracker_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1135,10 +1135,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.16+1"
|
version: "0.12.17"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1159,10 +1159,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.15.0"
|
version: "1.16.0"
|
||||||
mgrs_dart:
|
mgrs_dart:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1271,10 +1271,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path
|
name: path
|
||||||
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.0"
|
version: "1.9.1"
|
||||||
path_provider:
|
path_provider:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -1383,10 +1383,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: platform
|
name: platform
|
||||||
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
|
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.5"
|
version: "3.1.6"
|
||||||
platform_detect:
|
platform_detect:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1463,10 +1463,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: process
|
name: process
|
||||||
sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32"
|
sha256: "107d8be718f120bbba9dcd1e95e3bd325b1b4a4f07db64154635ba03f2567a0d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.2"
|
version: "5.0.3"
|
||||||
proj4dart:
|
proj4dart:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1780,10 +1780,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_span
|
name: source_span
|
||||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.10.0"
|
version: "1.10.1"
|
||||||
sprintf:
|
sprintf:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1836,26 +1836,26 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stack_trace
|
name: stack_trace
|
||||||
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
|
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.12.0"
|
version: "1.12.1"
|
||||||
stream_channel:
|
stream_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stream_channel
|
name: stream_channel
|
||||||
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.4"
|
||||||
string_scanner:
|
string_scanner:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: string_scanner
|
name: string_scanner
|
||||||
sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
|
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.4.1"
|
||||||
string_validator:
|
string_validator:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1900,34 +1900,34 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: term_glyph
|
name: term_glyph
|
||||||
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.1"
|
version: "1.2.2"
|
||||||
test:
|
test:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test
|
name: test
|
||||||
sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f"
|
sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.25.8"
|
version: "1.25.15"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
|
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.3"
|
version: "0.7.4"
|
||||||
test_core:
|
test_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_core
|
name: test_core
|
||||||
sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d"
|
sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.5"
|
version: "0.6.8"
|
||||||
timezone:
|
timezone:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -2164,10 +2164,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
|
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.3.0"
|
version: "14.3.1"
|
||||||
wakelock_plus:
|
wakelock_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -2297,5 +2297,5 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
version: "3.1.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.6.0 <4.0.0"
|
dart: ">=3.7.0-0 <4.0.0"
|
||||||
flutter: ">=3.27.0"
|
flutter: ">=3.27.0"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ name: fluffychat
|
||||||
description: Chat with your friends.
|
description: Chat with your friends.
|
||||||
publish_to: none
|
publish_to: none
|
||||||
# On version bump also increase the build number for F-Droid
|
# On version bump also increase the build number for F-Droid
|
||||||
version: 1.25.0+3537
|
version: 1.26.0+3538
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.0.0 <4.0.0"
|
sdk: ">=3.0.0 <4.0.0"
|
||||||
|
|
|
||||||
|
|
@ -3,21 +3,21 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
app_links
|
app_links
|
||||||
desktop_drop
|
desktop_drop
|
||||||
dynamic_color
|
dynamic_color
|
||||||
emoji_picker_flutter
|
emoji_picker_flutter
|
||||||
file_selector_windows
|
file_selector_windows
|
||||||
flutter_secure_storage_windows
|
flutter_secure_storage_windows
|
||||||
flutter_webrtc
|
flutter_webrtc
|
||||||
geolocator_windows
|
geolocator_windows
|
||||||
pasteboard
|
pasteboard
|
||||||
permission_handler_windows
|
permission_handler_windows
|
||||||
record_windows
|
record_windows
|
||||||
share_plus
|
share_plus
|
||||||
sqlcipher_flutter_libs
|
sqlcipher_flutter_libs
|
||||||
url_launcher_windows
|
url_launcher_windows
|
||||||
window_to_front
|
window_to_front
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
|
@ -25,14 +25,14 @@ list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
|
||||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||||
|
|
||||||
foreach (plugin ${FLUTTER_PLUGIN_LIST})
|
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
||||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
|
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
|
||||||
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
|
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
|
||||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
|
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
|
||||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
||||||
endforeach (plugin)
|
endforeach(plugin)
|
||||||
|
|
||||||
foreach (ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
|
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
|
||||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
|
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
|
||||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
|
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
|
||||||
endforeach (ffi_plugin)
|
endforeach(ffi_plugin)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue