Merge branch 'krille-chan:main' into scanqr
This commit is contained in:
commit
154ff5ee7b
19 changed files with 313 additions and 297 deletions
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
|
|
@ -191,7 +191,7 @@ jobs:
|
|||
- name: Check out Git repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Install Snapcraft
|
||||
uses: samuelmeuli/action-snapcraft@v2
|
||||
uses: samuelmeuli/action-snapcraft@v3
|
||||
- name: Get Tag Name
|
||||
id: tag_name
|
||||
run: echo "::set-output name=tag::$(echo ${GITHUB_REF#refs/tags/})"
|
||||
|
|
|
|||
2
.github/workflows/versions.env
vendored
2
.github/workflows/versions.env
vendored
|
|
@ -1,2 +1,2 @@
|
|||
FLUTTER_VERSION=3.24.3
|
||||
FLUTTER_VERSION=3.24.5
|
||||
JAVA_VERSION=17
|
||||
|
|
|
|||
|
|
@ -2806,5 +2806,11 @@
|
|||
"name": "Name",
|
||||
"version": "Version",
|
||||
"website": "Website",
|
||||
"compressBeforeSending": "Compress before sending"
|
||||
"sendUncompressed": "Send uncompressed",
|
||||
"boldText": "Bold text",
|
||||
"italicText": "Italic text",
|
||||
"strikeThrough": "Strikethrough",
|
||||
"pleaseFillOut": "Please fill out",
|
||||
"invalidUrl": "Invalid url",
|
||||
"addLink": "Add link"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -282,7 +282,7 @@
|
|||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"chatBackupDescription": "La copia de respaldo del chat está protegida por una clave de seguridad. Procure no perderla.",
|
||||
"chatBackupDescription": "La copia de respaldo del chat está protegida por una llave de seguridad. Procure no perderla.",
|
||||
"@chatBackupDescription": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
|
|
@ -662,7 +662,7 @@
|
|||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"inviteText": "{username} te invitó a FluffyChat.\n1. Instale FluffyChat: https://fluffychat.im\n2. Regístrate o inicia sesión \n3. Abra el enlace de invitación: {link}",
|
||||
"inviteText": "{username} te invitó a FluffyChat.\n1.Visita fluffychat.im e instala la app\n2. Regístrate o inicia sesión\n3. Abre el enlace de invitación:\n{link}",
|
||||
"@inviteText": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
|
|
@ -1667,7 +1667,7 @@
|
|||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"defaultPermissionLevel": "Nivel de permiso predeterminado",
|
||||
"defaultPermissionLevel": "Nivel de permiso predeterminado para nuevo usuarios",
|
||||
"@defaultPermissionLevel": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
|
|
@ -2315,5 +2315,66 @@
|
|||
}
|
||||
},
|
||||
"commandHint_googly": "Enviar unos ojos saltones",
|
||||
"@commandHint_googly": {}
|
||||
"@commandHint_googly": {},
|
||||
"noChatsFoundHere": "No se han encontrado chats. Inicia un nuevo chat usando el botón de abajo. ⤵️",
|
||||
"@noChatsFoundHere": {},
|
||||
"joinedChats": "Chats Unidos",
|
||||
"@joinedChats": {},
|
||||
"space": "Espacio",
|
||||
"@space": {},
|
||||
"spaces": "Espacios",
|
||||
"@spaces": {},
|
||||
"block": "Bloquear",
|
||||
"@block": {},
|
||||
"blockListDescription": "Puedes bloquear usuarios que te estén molestando. No podrás recibir mensajes ni invitaciones de chat de los usuarios de tu lista de bloqueo.",
|
||||
"@blockListDescription": {},
|
||||
"aboutHomeserver": "Acerca de {homeserver}",
|
||||
"@aboutHomeserver": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"homeserver": {}
|
||||
}
|
||||
},
|
||||
"unread": "No leídos",
|
||||
"@unread": {},
|
||||
"swipeRightToLeftToReply": "Desliza a la izquierda para responder",
|
||||
"@swipeRightToLeftToReply": {},
|
||||
"hideRedactedMessagesBody": "Si alguien elimina un mensaje, este mensaje ya no será visible en el chat.",
|
||||
"@hideRedactedMessagesBody": {},
|
||||
"hideInvalidOrUnknownMessageFormats": "Esconde formatos de mensajes inválidos o desconocidos",
|
||||
"@hideInvalidOrUnknownMessageFormats": {},
|
||||
"hideRedactedMessages": "Esconde mensajes eliminados",
|
||||
"@hideRedactedMessages": {},
|
||||
"appLockDescription": "Bloquear la aplicación cuando no se use con código pin",
|
||||
"@appLockDescription": {},
|
||||
"alwaysUse24HourFormat": "Falso",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"description": "Set to true to always display time of day in 24 hour format."
|
||||
},
|
||||
"accessAndVisibility": "Acceso y visibilidad",
|
||||
"@accessAndVisibility": {},
|
||||
"globalChatId": "ID de chat Global",
|
||||
"@globalChatId": {},
|
||||
"accessAndVisibilityDescription": "A quién se le permite unirse a este chat y cómo se puede descubrir el chat.",
|
||||
"@accessAndVisibilityDescription": {},
|
||||
"calls": "Llamadas",
|
||||
"@calls": {},
|
||||
"customEmojisAndStickers": "Emojis y stickers personalizados",
|
||||
"@customEmojisAndStickers": {},
|
||||
"customEmojisAndStickersBody": "Agrega o comparte emojis y stickers personalizados que se pueden utilizar en cualquier chat.",
|
||||
"@customEmojisAndStickersBody": {},
|
||||
"blockedUsers": "Usuarios bloqueados",
|
||||
"@blockedUsers": {},
|
||||
"blockUsername": "Ignorar nombre de usuario",
|
||||
"@blockUsername": {},
|
||||
"noMoreChatsFound": "No se encontraron más chats...",
|
||||
"@noMoreChatsFound": {},
|
||||
"countChatsAndCountParticipants": "{chats} chats y {participants} participantes",
|
||||
"@countChatsAndCountParticipants": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"chats": {},
|
||||
"participants": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:animations/animations.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:keyboard_shortcuts/keyboard_shortcuts.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
|
|
@ -96,130 +94,113 @@ class ChatInputRow extends StatelessWidget {
|
|||
]
|
||||
: <Widget>[
|
||||
const SizedBox(width: 4),
|
||||
KeyBoardShortcuts(
|
||||
keysToPress: {
|
||||
LogicalKeyboardKey.altLeft,
|
||||
LogicalKeyboardKey.keyA,
|
||||
},
|
||||
onKeysPressed: () =>
|
||||
controller.onAddPopupMenuButtonSelected('file'),
|
||||
helpLabel: L10n.of(context).sendFile,
|
||||
child: AnimatedContainer(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
height: height,
|
||||
width: controller.sendController.text.isEmpty ? height : 0,
|
||||
alignment: Alignment.center,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: const BoxDecoration(),
|
||||
child: PopupMenuButton<String>(
|
||||
icon: const Icon(Icons.add_outlined),
|
||||
onSelected: controller.onAddPopupMenuButtonSelected,
|
||||
itemBuilder: (BuildContext context) =>
|
||||
<PopupMenuEntry<String>>[
|
||||
AnimatedContainer(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
height: height,
|
||||
width: controller.sendController.text.isEmpty ? height : 0,
|
||||
alignment: Alignment.center,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: const BoxDecoration(),
|
||||
child: PopupMenuButton<String>(
|
||||
icon: const Icon(Icons.add_outlined),
|
||||
onSelected: controller.onAddPopupMenuButtonSelected,
|
||||
itemBuilder: (BuildContext context) =>
|
||||
<PopupMenuEntry<String>>[
|
||||
PopupMenuItem<String>(
|
||||
value: 'file',
|
||||
child: ListTile(
|
||||
leading: const CircleAvatar(
|
||||
backgroundColor: Colors.green,
|
||||
foregroundColor: Colors.white,
|
||||
child: Icon(Icons.attachment_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).sendFile),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
PopupMenuItem<String>(
|
||||
value: 'image',
|
||||
child: ListTile(
|
||||
leading: const CircleAvatar(
|
||||
backgroundColor: Colors.blue,
|
||||
foregroundColor: Colors.white,
|
||||
child: Icon(Icons.image_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).sendImage),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
if (PlatformInfos.isMobile)
|
||||
PopupMenuItem<String>(
|
||||
value: 'file',
|
||||
value: 'camera',
|
||||
child: ListTile(
|
||||
leading: const CircleAvatar(
|
||||
backgroundColor: Colors.green,
|
||||
backgroundColor: Colors.purple,
|
||||
foregroundColor: Colors.white,
|
||||
child: Icon(Icons.attachment_outlined),
|
||||
child: Icon(Icons.camera_alt_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).sendFile),
|
||||
title: Text(L10n.of(context).openCamera),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
if (PlatformInfos.isMobile)
|
||||
PopupMenuItem<String>(
|
||||
value: 'image',
|
||||
value: 'camera-video',
|
||||
child: ListTile(
|
||||
leading: const CircleAvatar(
|
||||
backgroundColor: Colors.blue,
|
||||
backgroundColor: Colors.red,
|
||||
foregroundColor: Colors.white,
|
||||
child: Icon(Icons.image_outlined),
|
||||
child: Icon(Icons.videocam_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).sendImage),
|
||||
title: Text(L10n.of(context).openVideoCamera),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
if (PlatformInfos.isMobile)
|
||||
PopupMenuItem<String>(
|
||||
value: 'camera',
|
||||
child: ListTile(
|
||||
leading: const CircleAvatar(
|
||||
backgroundColor: Colors.purple,
|
||||
foregroundColor: Colors.white,
|
||||
child: Icon(Icons.camera_alt_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).openCamera),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
if (PlatformInfos.isMobile)
|
||||
PopupMenuItem<String>(
|
||||
value: 'location',
|
||||
child: ListTile(
|
||||
leading: const CircleAvatar(
|
||||
backgroundColor: Colors.brown,
|
||||
foregroundColor: Colors.white,
|
||||
child: Icon(Icons.gps_fixed_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).shareLocation),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
if (PlatformInfos.isMobile)
|
||||
PopupMenuItem<String>(
|
||||
value: 'camera-video',
|
||||
child: ListTile(
|
||||
leading: const CircleAvatar(
|
||||
backgroundColor: Colors.red,
|
||||
foregroundColor: Colors.white,
|
||||
child: Icon(Icons.videocam_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).openVideoCamera),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
if (PlatformInfos.isMobile)
|
||||
PopupMenuItem<String>(
|
||||
value: 'location',
|
||||
child: ListTile(
|
||||
leading: const CircleAvatar(
|
||||
backgroundColor: Colors.brown,
|
||||
foregroundColor: Colors.white,
|
||||
child: Icon(Icons.gps_fixed_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).shareLocation),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: height,
|
||||
width: height,
|
||||
alignment: Alignment.center,
|
||||
child: KeyBoardShortcuts(
|
||||
keysToPress: {
|
||||
LogicalKeyboardKey.altLeft,
|
||||
LogicalKeyboardKey.keyE,
|
||||
},
|
||||
onKeysPressed: controller.emojiPickerAction,
|
||||
helpLabel: L10n.of(context).emojis,
|
||||
child: IconButton(
|
||||
tooltip: L10n.of(context).emojis,
|
||||
icon: PageTransitionSwitcher(
|
||||
transitionBuilder: (
|
||||
Widget child,
|
||||
Animation<double> primaryAnimation,
|
||||
Animation<double> secondaryAnimation,
|
||||
) {
|
||||
return SharedAxisTransition(
|
||||
animation: primaryAnimation,
|
||||
secondaryAnimation: secondaryAnimation,
|
||||
transitionType: SharedAxisTransitionType.scaled,
|
||||
fillColor: Colors.transparent,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: Icon(
|
||||
controller.showEmojiPicker
|
||||
? Icons.keyboard
|
||||
: Icons.add_reaction_outlined,
|
||||
key: ValueKey(controller.showEmojiPicker),
|
||||
),
|
||||
child: IconButton(
|
||||
tooltip: L10n.of(context).emojis,
|
||||
icon: PageTransitionSwitcher(
|
||||
transitionBuilder: (
|
||||
Widget child,
|
||||
Animation<double> primaryAnimation,
|
||||
Animation<double> secondaryAnimation,
|
||||
) {
|
||||
return SharedAxisTransition(
|
||||
animation: primaryAnimation,
|
||||
secondaryAnimation: secondaryAnimation,
|
||||
transitionType: SharedAxisTransitionType.scaled,
|
||||
fillColor: Colors.transparent,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: Icon(
|
||||
controller.showEmojiPicker
|
||||
? Icons.keyboard
|
||||
: Icons.add_reaction_outlined,
|
||||
key: ValueKey(controller.showEmojiPicker),
|
||||
),
|
||||
onPressed: controller.emojiPickerAction,
|
||||
),
|
||||
onPressed: controller.emojiPickerAction,
|
||||
),
|
||||
),
|
||||
if (Matrix.of(context).isMultiAccount &&
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import 'package:pasteboard/pasteboard.dart';
|
|||
import 'package:slugify/slugify.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/utils/markdown_context_builder.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/widgets/mxc_image.dart';
|
||||
import '../../widgets/avatar.dart';
|
||||
|
|
@ -456,6 +457,8 @@ class InputBar extends StatelessWidget {
|
|||
builder: (context, controller, focusNode) => TextField(
|
||||
controller: controller,
|
||||
focusNode: focusNode,
|
||||
contextMenuBuilder: (c, e) =>
|
||||
markdownContextBuilder(c, e, controller),
|
||||
contentInsertionConfiguration: ContentInsertionConfiguration(
|
||||
onContentInserted: (KeyboardInsertedContent content) {
|
||||
final data = content.data;
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ class SendFileDialogState extends State<SendFileDialog> {
|
|||
|
||||
var sendStr = L10n.of(context).sendFile;
|
||||
final uniqueMimeType = widget.files
|
||||
.map((file) => file.mimeType ?? lookupMimeType(file.path))
|
||||
.map((file) => file.mimeType ?? lookupMimeType(file.name))
|
||||
.toSet()
|
||||
.singleOrNull;
|
||||
|
||||
|
|
@ -250,23 +250,42 @@ class SendFileDialogState extends State<SendFileDialog> {
|
|||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
CupertinoSwitch(
|
||||
value: compress,
|
||||
onChanged: uniqueMimeType.startsWith('video') &&
|
||||
!PlatformInfos.isMobile
|
||||
? null
|
||||
: (v) => setState(() => compress = v),
|
||||
),
|
||||
if ({TargetPlatform.iOS, TargetPlatform.macOS}
|
||||
.contains(theme.platform))
|
||||
CupertinoSwitch(
|
||||
value: !compress,
|
||||
onChanged: uniqueMimeType.startsWith('video') &&
|
||||
!PlatformInfos.isMobile
|
||||
? null
|
||||
: (v) => setState(() => compress = !v),
|
||||
)
|
||||
else
|
||||
Switch.adaptive(
|
||||
value: !compress,
|
||||
onChanged: uniqueMimeType.startsWith('video') &&
|
||||
!PlatformInfos.isMobile
|
||||
? null
|
||||
: (v) => setState(() => compress = !v),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
L10n.of(context).sendUncompressed,
|
||||
style: theme.textTheme.titleMedium,
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
L10n.of(context).compressBeforeSending,
|
||||
style: theme.textTheme.labelMedium,
|
||||
textAlign: TextAlign.left,
|
||||
' ($sizeString)',
|
||||
style: theme.textTheme.labelSmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:keyboard_shortcuts/keyboard_shortcuts.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
|
|
@ -138,27 +136,19 @@ class ChatListView extends StatelessWidget {
|
|||
behavior: HitTestBehavior.translucent,
|
||||
child: Scaffold(
|
||||
body: ChatListViewBody(controller),
|
||||
floatingActionButton: KeyBoardShortcuts(
|
||||
keysToPress: {
|
||||
LogicalKeyboardKey.controlLeft,
|
||||
LogicalKeyboardKey.keyN,
|
||||
},
|
||||
onKeysPressed: () => context.go('/rooms/newprivatechat'),
|
||||
helpLabel: L10n.of(context).newChat,
|
||||
child: selectMode == SelectMode.normal &&
|
||||
!controller.isSearchMode &&
|
||||
controller.activeSpaceId == null
|
||||
? FloatingActionButton.extended(
|
||||
onPressed: () =>
|
||||
context.go('/rooms/newprivatechat'),
|
||||
icon: const Icon(Icons.add_outlined),
|
||||
label: Text(
|
||||
L10n.of(context).chat,
|
||||
overflow: TextOverflow.fade,
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
floatingActionButton: selectMode == SelectMode.normal &&
|
||||
!controller.isSearchMode &&
|
||||
controller.activeSpaceId == null
|
||||
? FloatingActionButton.extended(
|
||||
onPressed: () =>
|
||||
context.go('/rooms/newprivatechat'),
|
||||
icon: const Icon(Icons.add_outlined),
|
||||
label: Text(
|
||||
L10n.of(context).chat,
|
||||
overflow: TextOverflow.fade,
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:keyboard_shortcuts/keyboard_shortcuts.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
|
|
@ -166,36 +164,10 @@ class ClientChooserButton extends StatelessWidget {
|
|||
children: [
|
||||
...List.generate(
|
||||
clientCount,
|
||||
(index) => KeyBoardShortcuts(
|
||||
keysToPress: _buildKeyboardShortcut(index + 1),
|
||||
helpLabel: L10n.of(context).switchToAccount(index + 1),
|
||||
onKeysPressed: () => _handleKeyboardShortcut(
|
||||
matrix,
|
||||
index,
|
||||
context,
|
||||
),
|
||||
child: const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
KeyBoardShortcuts(
|
||||
keysToPress: {
|
||||
LogicalKeyboardKey.controlLeft,
|
||||
LogicalKeyboardKey.tab,
|
||||
},
|
||||
helpLabel: L10n.of(context).nextAccount,
|
||||
onKeysPressed: () => _nextAccount(matrix, context),
|
||||
child: const SizedBox.shrink(),
|
||||
),
|
||||
KeyBoardShortcuts(
|
||||
keysToPress: {
|
||||
LogicalKeyboardKey.controlLeft,
|
||||
LogicalKeyboardKey.shiftLeft,
|
||||
LogicalKeyboardKey.tab,
|
||||
},
|
||||
helpLabel: L10n.of(context).previousAccount,
|
||||
onKeysPressed: () => _previousAccount(matrix, context),
|
||||
child: const SizedBox.shrink(),
|
||||
(index) => const SizedBox.shrink(),
|
||||
),
|
||||
const SizedBox.shrink(),
|
||||
const SizedBox.shrink(),
|
||||
PopupMenuButton<Object>(
|
||||
onSelected: (o) => _clientSelected(o, context),
|
||||
itemBuilder: _bundleMenuItems,
|
||||
|
|
@ -215,17 +187,6 @@ class ClientChooserButton extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
Set<LogicalKeyboardKey>? _buildKeyboardShortcut(int index) {
|
||||
if (index > 0 && index < 10) {
|
||||
return {
|
||||
LogicalKeyboardKey.altLeft,
|
||||
LogicalKeyboardKey(0x00000000030 + index),
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void _clientSelected(
|
||||
Object object,
|
||||
BuildContext context,
|
||||
|
|
@ -265,75 +226,6 @@ class ClientChooserButton extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _handleKeyboardShortcut(
|
||||
MatrixState matrix,
|
||||
int index,
|
||||
BuildContext context,
|
||||
) {
|
||||
final bundles = matrix.accountBundles.keys.toList()
|
||||
..sort(
|
||||
(a, b) => a!.isValidMatrixId == b!.isValidMatrixId
|
||||
? 0
|
||||
: a.isValidMatrixId && !b.isValidMatrixId
|
||||
? -1
|
||||
: 1,
|
||||
);
|
||||
// beginning from end if negative
|
||||
if (index < 0) {
|
||||
var clientCount = 0;
|
||||
matrix.accountBundles
|
||||
.forEach((key, value) => clientCount += value.length);
|
||||
_handleKeyboardShortcut(matrix, clientCount, context);
|
||||
}
|
||||
for (final bundleName in bundles) {
|
||||
final bundle = matrix.accountBundles[bundleName];
|
||||
if (bundle != null) {
|
||||
if (index < bundle.length) {
|
||||
return _clientSelected(bundle[index]!, context);
|
||||
} else {
|
||||
index -= bundle.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
// if index too high, restarting from 0
|
||||
_handleKeyboardShortcut(matrix, 0, context);
|
||||
}
|
||||
|
||||
int? _shortcutIndexOfClient(MatrixState matrix, Client client) {
|
||||
var index = 0;
|
||||
|
||||
final bundles = matrix.accountBundles.keys.toList()
|
||||
..sort(
|
||||
(a, b) => a!.isValidMatrixId == b!.isValidMatrixId
|
||||
? 0
|
||||
: a.isValidMatrixId && !b.isValidMatrixId
|
||||
? -1
|
||||
: 1,
|
||||
);
|
||||
for (final bundleName in bundles) {
|
||||
final bundle = matrix.accountBundles[bundleName];
|
||||
if (bundle == null) return null;
|
||||
if (bundle.contains(client)) {
|
||||
return index + bundle.indexOf(client);
|
||||
} else {
|
||||
index += bundle.length;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void _nextAccount(MatrixState matrix, BuildContext context) {
|
||||
final client = matrix.client;
|
||||
final lastIndex = _shortcutIndexOfClient(matrix, client);
|
||||
_handleKeyboardShortcut(matrix, lastIndex! + 1, context);
|
||||
}
|
||||
|
||||
void _previousAccount(MatrixState matrix, BuildContext context) {
|
||||
final client = matrix.client;
|
||||
final lastIndex = _shortcutIndexOfClient(matrix, client);
|
||||
_handleKeyboardShortcut(matrix, lastIndex! - 1, context);
|
||||
}
|
||||
}
|
||||
|
||||
enum SettingsAction {
|
||||
|
|
|
|||
102
lib/utils/markdown_context_builder.dart
Normal file
102
lib/utils/markdown_context_builder.dart
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
Widget markdownContextBuilder(
|
||||
BuildContext context,
|
||||
EditableTextState editableTextState,
|
||||
TextEditingController controller,
|
||||
) {
|
||||
final value = editableTextState.textEditingValue;
|
||||
final selectedText = value.selection.textInside(value.text);
|
||||
final buttonItems = editableTextState.contextMenuButtonItems;
|
||||
final l10n = L10n.of(context);
|
||||
|
||||
return AdaptiveTextSelectionToolbar.buttonItems(
|
||||
anchors: editableTextState.contextMenuAnchors,
|
||||
buttonItems: [
|
||||
...buttonItems,
|
||||
if (selectedText.isNotEmpty) ...[
|
||||
ContextMenuButtonItem(
|
||||
label: l10n.link,
|
||||
onPressed: () async {
|
||||
final input = await showTextInputDialog(
|
||||
context: context,
|
||||
title: l10n.addLink,
|
||||
okLabel: l10n.ok,
|
||||
cancelLabel: l10n.cancel,
|
||||
textFields: [
|
||||
DialogTextField(
|
||||
validator: (text) {
|
||||
if (text == null || text.isEmpty) {
|
||||
return l10n.pleaseFillOut;
|
||||
}
|
||||
try {
|
||||
text.startsWith('http')
|
||||
? Uri.parse(text)
|
||||
: Uri.https(text);
|
||||
} catch (_) {
|
||||
return l10n.invalidUrl;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
hintText: 'www...',
|
||||
keyboardType: TextInputType.url,
|
||||
),
|
||||
],
|
||||
);
|
||||
final urlString = input?.singleOrNull;
|
||||
if (urlString == null) return;
|
||||
final url = urlString.startsWith('http')
|
||||
? Uri.parse(urlString)
|
||||
: Uri.https(urlString);
|
||||
final selection = controller.selection;
|
||||
controller.text = controller.text.replaceRange(
|
||||
selection.start,
|
||||
selection.end,
|
||||
'[$selectedText](${url.toString()})',
|
||||
);
|
||||
ContextMenuController.removeAny();
|
||||
},
|
||||
),
|
||||
ContextMenuButtonItem(
|
||||
label: l10n.boldText,
|
||||
onPressed: () {
|
||||
final selection = controller.selection;
|
||||
controller.text = controller.text.replaceRange(
|
||||
selection.start,
|
||||
selection.end,
|
||||
'**$selectedText**',
|
||||
);
|
||||
ContextMenuController.removeAny();
|
||||
},
|
||||
),
|
||||
ContextMenuButtonItem(
|
||||
label: l10n.italicText,
|
||||
onPressed: () {
|
||||
final selection = controller.selection;
|
||||
controller.text = controller.text.replaceRange(
|
||||
selection.start,
|
||||
selection.end,
|
||||
'*$selectedText*',
|
||||
);
|
||||
ContextMenuController.removeAny();
|
||||
},
|
||||
),
|
||||
ContextMenuButtonItem(
|
||||
label: l10n.strikeThrough,
|
||||
onPressed: () {
|
||||
final selection = controller.selection;
|
||||
controller.text = controller.text.replaceRange(
|
||||
selection.start,
|
||||
selection.end,
|
||||
'~~$selectedText~~',
|
||||
);
|
||||
ContextMenuController.removeAny();
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:keyboard_shortcuts/keyboard_shortcuts.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
|
|
@ -52,15 +50,7 @@ class ChatSettingsPopupMenuState extends State<ChatSettingsPopupMenu> {
|
|||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
KeyBoardShortcuts(
|
||||
keysToPress: {
|
||||
LogicalKeyboardKey.controlLeft,
|
||||
LogicalKeyboardKey.keyI,
|
||||
},
|
||||
helpLabel: L10n.of(context).chatDetails,
|
||||
onKeysPressed: _showChatDetails,
|
||||
child: const SizedBox.shrink(),
|
||||
),
|
||||
const SizedBox.shrink(),
|
||||
PopupMenuButton<ChatPopupMenuActions>(
|
||||
onSelected: (choice) async {
|
||||
switch (choice) {
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@
|
|||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0920;
|
||||
LastUpgradeCheck = 1430;
|
||||
LastUpgradeCheck = 1510;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
33CC10EC2044A3C60003C045 = {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1430"
|
||||
LastUpgradeVersion = "1510"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@NSApplicationMain
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
|
|
|
|||
21
pubspec.lock
21
pubspec.lock
|
|
@ -1098,15 +1098,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.13"
|
||||
keyboard_shortcuts:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: null-safety
|
||||
resolved-ref: a3d4020911860ff091d90638ab708604b71d2c5a
|
||||
url: "https://github.com/TheOneWithTheBraid/keyboard_shortcuts.git"
|
||||
source: git
|
||||
version: "0.1.4"
|
||||
latlong2:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -1247,10 +1238,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: matrix
|
||||
sha256: e06783394db3a49dbcd98a45803cac7d735a2fb1e3aafef65ddf01e72e16fc83
|
||||
sha256: "94a66e563b89fabbeb67f24428f05f4547c6ee98878ec20f647530cbfb6f04db"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.34.0"
|
||||
version: "0.35.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -2320,14 +2311,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
visibility_detector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: visibility_detector
|
||||
sha256: "15c54a459ec2c17b4705450483f3d5a2858e733aee893dcee9d75fd04814940d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.3"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -62,10 +62,9 @@ dependencies:
|
|||
image_picker: ^1.1.0
|
||||
intl: any
|
||||
just_audio: ^0.9.39
|
||||
keyboard_shortcuts: ^0.1.4
|
||||
latlong2: ^0.9.1
|
||||
linkify: ^5.0.0
|
||||
matrix: ^0.34.0
|
||||
matrix: ^0.35.0
|
||||
mime: ^1.0.6
|
||||
native_imaging: ^0.1.1
|
||||
opus_caf_converter_dart: ^1.0.1
|
||||
|
|
@ -153,10 +152,4 @@ msix_config:
|
|||
install_certificate: false
|
||||
|
||||
dependency_overrides:
|
||||
# waiting for null safety
|
||||
# Upstream pull request: https://github.com/AntoineMarcel/keyboard_shortcuts/pull/13
|
||||
keyboard_shortcuts:
|
||||
git:
|
||||
url: https://github.com/TheOneWithTheBraid/keyboard_shortcuts.git
|
||||
ref: null-safety
|
||||
win32: 5.5.3
|
||||
|
|
|
|||
|
|
@ -73,10 +73,6 @@ parts:
|
|||
fluffychat:
|
||||
plugin: flutter
|
||||
source: .
|
||||
override-build: |
|
||||
# Workaround for Flutter build error:
|
||||
rm -rf build
|
||||
craftctl default
|
||||
build-packages:
|
||||
- libjsoncpp-dev
|
||||
- curl
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 49 KiB |
Loading…
Add table
Reference in a new issue