feat: disable cloud translations in Encrypted Rooms

feat: follow with fluffychat's image viewer
feat: navigation rail on mobile
feat: bring back bubble gradient as an option
feat: darker amoled colors
This commit is contained in:
OfficialDakari 2025-06-20 19:06:03 +05:00
parent 6316dda939
commit 01f13723f5
17 changed files with 93 additions and 39 deletions

View File

@ -16,6 +16,9 @@
"ignoreUser": "Ignore user", "ignoreUser": "Ignore user",
"normalUser": "Normal user", "normalUser": "Normal user",
"pinCode": "PIN code", "pinCode": "PIN code",
"displayNavigationRail": "Display navigation rail on mobile",
"enableGradient": "Enable bubble background gradient",
"translationDisabledInE2e": "Cloud translation is disabled in encrypted rooms to preserve privacy. Select specific words and use system context menu to translate with apps that support it.",
"remove": "Remove", "remove": "Remove",
"@remove": { "@remove": {
"type": "String", "type": "String",

View File

@ -23,6 +23,9 @@
"importNow": "Импортировать сейчас", "importNow": "Импортировать сейчас",
"@importNow": {}, "@importNow": {},
"importEmojis": "Импортировать эмодзи", "importEmojis": "Импортировать эмодзи",
"displayNavigationRail": "Всегда показывать боковую панель",
"enableGradient": "Фоновый градиент для сообщений",
"translationDisabledInE2e": "Облачные переводы недоступны в зашифрованных комнатах для защиты конфиденциальности. Выбирайте отдельные слова и переводите их через другие приложения.",
"@importEmojis": {}, "@importEmojis": {},
"importFromZipFile": "Импортировать из ZIP-файла", "importFromZipFile": "Импортировать из ZIP-файла",
"@importFromZipFile": {}, "@importFromZipFile": {},

View File

@ -12,6 +12,7 @@ abstract class AppConfig {
static String _defaultHomeserver = 'extera.xyz'; static String _defaultHomeserver = 'extera.xyz';
static bool displayNavigationRail = true; static bool displayNavigationRail = true;
static bool enableGradient = true;
static String get defaultHomeserver => _defaultHomeserver; static String get defaultHomeserver => _defaultHomeserver;
static double fontSizeFactor = 1; static double fontSizeFactor = 1;
@ -113,5 +114,8 @@ abstract class AppConfig {
if (json['hide_unknown_events'] is bool) { if (json['hide_unknown_events'] is bool) {
hideUnknownEvents = json['hide_unknown_events']; hideUnknownEvents = json['hide_unknown_events'];
} }
if (json['enable_gradient'] is bool) {
enableGradient = json['enable_gradient'];
}
} }
} }

View File

@ -35,6 +35,7 @@ abstract class SettingKeys {
'chat.fluffy.swipeRightToLeftToReply'; 'chat.fluffy.swipeRightToLeftToReply';
static const String experimentalVoip = 'chat.fluffy.experimental_voip'; static const String experimentalVoip = 'chat.fluffy.experimental_voip';
static const String showPresences = 'chat.fluffy.show_presences'; static const String showPresences = 'chat.fluffy.show_presences';
static const String enableGradient = 'xyz.extera.next.enableGradient';
} }
enum AppSettings<T> { enum AppSettings<T> {
@ -45,6 +46,7 @@ enum AppSettings<T> {
audioRecordingBitRate<int>('audioRecordingBitRate', 64000), audioRecordingBitRate<int>('audioRecordingBitRate', 64000),
audioRecordingSamplingRate<int>('audioRecordingSamplingRate', 44100), audioRecordingSamplingRate<int>('audioRecordingSamplingRate', 44100),
enableSoftLogout<bool>('enableSoftLogout', false), enableSoftLogout<bool>('enableSoftLogout', false),
enableGradient<bool>('enableGradient', true),
pushNotificationsGatewayUrl<String>( pushNotificationsGatewayUrl<String>(
'pushNotificationsGatewayUrl', 'pushNotificationsGatewayUrl',
'https://push.fluffychat.im/_matrix/push/v1/notify', 'https://push.fluffychat.im/_matrix/push/v1/notify',

View File

@ -46,11 +46,11 @@ abstract class FluffyThemes {
? { ? {
'surface': const Color.fromARGB(255, 0, 0, 0), 'surface': const Color.fromARGB(255, 0, 0, 0),
'surfaceBright': const Color.fromARGB(255, 0, 0, 0), 'surfaceBright': const Color.fromARGB(255, 0, 0, 0),
'surfaceContainer': const Color.fromARGB(255, 22, 22, 22), 'surfaceContainer': const Color.fromARGB(255, 11, 11, 11),
'surfaceContainerHigh': const Color.fromARGB(255, 33, 33, 33), 'surfaceContainerHigh': const Color.fromARGB(255, 22, 22, 22),
'surfaceContainerHighest': const Color.fromARGB(255, 33, 33, 33), 'surfaceContainerHighest': const Color.fromARGB(255, 22, 22, 22),
'surfaceContainerLow': const Color.fromARGB(255, 22, 22, 22), 'surfaceContainerLow': const Color.fromARGB(255, 11, 11, 11),
'surfaceContainerLowest': const Color.fromARGB(255, 22, 22, 22), 'surfaceContainerLowest': const Color.fromARGB(255, 8, 8, 8),
'surfaceDim': const Color.fromARGB(255, 0, 0, 0), 'surfaceDim': const Color.fromARGB(255, 0, 0, 0),
'surfaceTint': const Color.fromARGB(255, 11, 11, 11), 'surfaceTint': const Color.fromARGB(255, 11, 11, 11),
'surfaceVariant': const Color.fromARGB(255, 0, 0, 0), 'surfaceVariant': const Color.fromARGB(255, 0, 0, 0),

View File

@ -761,6 +761,12 @@ class ChatController extends State<ChatPageWithRoom>
} }
void translateEventAction() async { void translateEventAction() async {
if (room.encrypted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(L10n.of(context).translationDisabledInE2e)),
);
return;
}
final event = selectedEvents.single; final event = selectedEvents.single;
var text = event.isRichMessage ? event.formattedText : event.text; var text = event.isRichMessage ? event.formattedText : event.text;
if (text == null) { if (text == null) {

View File

@ -1,3 +1,4 @@
import 'package:fluffychat/config/app_config.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -150,6 +151,7 @@ class ChatEventList extends StatelessWidget {
wallpaperMode: hasWallpaper, wallpaperMode: hasWallpaper,
scrollController: controller.scrollController, scrollController: controller.scrollController,
colors: colors, colors: colors,
gradient: AppConfig.enableGradient,
), ),
); );
}, },

View File

@ -41,6 +41,7 @@ class Message extends StatelessWidget {
final bool wallpaperMode; final bool wallpaperMode;
final ScrollController? scrollController; final ScrollController? scrollController;
final List<Color> colors; final List<Color> colors;
final bool gradient;
const Message( const Message(
this.event, { this.event, {
@ -48,6 +49,7 @@ class Message extends StatelessWidget {
this.previousEvent, this.previousEvent,
this.displayReadMarker = false, this.displayReadMarker = false,
this.longPressSelect = false, this.longPressSelect = false,
this.gradient = false,
required this.onSelect, required this.onSelect,
required this.onInfoTab, required this.onInfoTab,
required this.scrollToEventId, required this.scrollToEventId,
@ -328,7 +330,7 @@ class Message extends StatelessWidget {
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
child: BubbleBackground( child: BubbleBackground(
colors: colors, colors: colors,
ignore: noBubble || !ownMessage, ignore: noBubble || !ownMessage || !gradient,
scrollController: scrollController, scrollController: scrollController,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@ -638,7 +640,7 @@ class BubblePainter extends CustomPainter {
@override @override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
if (!(context.widget is ScrollView || context.widget is SingleChildScrollView)) return; // if (!(context.widget is ScrollView || context.widget is SingleChildScrollView)) return;
final scrollable = _scrollable ??= Scrollable.of(context); final scrollable = _scrollable ??= Scrollable.of(context);
final scrollableBox = scrollable.context.findRenderObject() as RenderBox; final scrollableBox = scrollable.context.findRenderObject() as RenderBox;
final scrollableRect = Offset.zero & scrollableBox.size; final scrollableRect = Offset.zero & scrollableBox.size;

View File

@ -30,7 +30,15 @@ class PollWidgetState extends State<PollWidget> {
padding: EdgeInsetsGeometry.all(16), padding: EdgeInsetsGeometry.all(16),
child: Column( child: Column(
children: [ children: [
Text(content?['question']['m.text'] as String, style: TextStyle(fontWeight: FontWeight.bold)) Text(content?['question']['m.text'] as String, style: TextStyle(fontWeight: FontWeight.bold)),
Padding(
padding: EdgeInsets.fromLTRB(4, 4, 4, 2),
child: Column(
children: [
],
),
)
], ],
), ),
); );

View File

@ -49,6 +49,7 @@ class TranslatedEventDialogState extends State<TranslatedEventDialog> {
longPressSelect: false, longPressSelect: false,
selected: false, selected: false,
wallpaperMode: false, wallpaperMode: false,
gradient: false
); );
return Scaffold( return Scaffold(

View File

@ -1,3 +1,4 @@
import 'package:fluffychat/config/app_config.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fluffychat/generated/l10n/l10n.dart'; import 'package:fluffychat/generated/l10n/l10n.dart';
@ -30,8 +31,7 @@ class ChatListView extends StatelessWidget {
}, },
child: Row( child: Row(
children: [ children: [
if (FluffyThemes.isColumnMode(context) && if (FluffyThemes.isColumnMode(context) || AppConfig.displayNavigationRail) ...[
controller.widget.displayNavigationRail) ...[
SpacesNavigationRail( SpacesNavigationRail(
activeSpaceId: controller.activeSpaceId, activeSpaceId: controller.activeSpaceId,
onGoToChats: controller.clearActiveSpace, onGoToChats: controller.clearActiveSpace,

View File

@ -56,10 +56,10 @@ class ImageViewerController extends State<ImageViewer> {
void onKeyEvent(KeyEvent event) { void onKeyEvent(KeyEvent event) {
switch (event.logicalKey) { switch (event.logicalKey) {
case LogicalKeyboardKey.arrowLeft: case LogicalKeyboardKey.arrowUp:
if (canGoBack) prevImage(); if (canGoBack) prevImage();
break; break;
case LogicalKeyboardKey.arrowRight: case LogicalKeyboardKey.arrowDown:
if (canGoNext) nextImage(); if (canGoNext) nextImage();
break; break;
} }

View File

@ -1,8 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fluffychat/generated/l10n/l10n.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'package:fluffychat/generated/l10n/l10n.dart';
import 'package:fluffychat/pages/image_viewer/video_player.dart'; import 'package:fluffychat/pages/image_viewer/video_player.dart';
import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/hover_builder.dart'; import 'package:fluffychat/widgets/hover_builder.dart';
@ -75,6 +75,7 @@ class ImageViewerView extends StatelessWidget {
focusNode: controller.focusNode, focusNode: controller.focusNode,
onKeyEvent: controller.onKeyEvent, onKeyEvent: controller.onKeyEvent,
child: PageView.builder( child: PageView.builder(
scrollDirection: Axis.vertical,
controller: controller.pageController, controller: controller.pageController,
itemCount: controller.allEvents.length, itemCount: controller.allEvents.length,
itemBuilder: (context, i) { itemBuilder: (context, i) {
@ -119,31 +120,34 @@ class ImageViewerView extends StatelessWidget {
}, },
), ),
), ),
if (hovered && controller.canGoBack) if (hovered)
Align( Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerRight,
child: Padding( child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (controller.canGoBack)
Padding(
padding: const EdgeInsets.all(12.0), padding: const EdgeInsets.all(12.0),
child: IconButton( child: IconButton(
style: iconButtonStyle, style: iconButtonStyle,
tooltip: L10n.of(context).previous, tooltip: L10n.of(context).previous,
icon: const Icon(Icons.chevron_left_outlined), icon: const Icon(Icons.arrow_upward_outlined),
onPressed: controller.prevImage, onPressed: controller.prevImage,
), ),
), ),
), if (controller.canGoNext)
if (hovered && controller.canGoNext) Padding(
Align(
alignment: Alignment.centerRight,
child: Padding(
padding: const EdgeInsets.all(12.0), padding: const EdgeInsets.all(12.0),
child: IconButton( child: IconButton(
style: iconButtonStyle, style: iconButtonStyle,
tooltip: L10n.of(context).next, tooltip: L10n.of(context).next,
icon: const Icon(Icons.chevron_right_outlined), icon: const Icon(Icons.arrow_downward_outlined),
onPressed: controller.nextImage, onPressed: controller.nextImage,
), ),
), ),
],
),
), ),
], ],
), ),

View File

@ -233,7 +233,7 @@ class SettingsStyleView extends StatelessWidget {
vertical: 8, vertical: 8,
), ),
child: Text( child: Text(
'Рассказать шутку?', 'Я такой угарный анекдот про зайца вспомнил, го расскажу прямо на странице настроек?',
style: TextStyle( style: TextStyle(
color: theme.onBubbleColor, color: theme.onBubbleColor,
fontSize: AppConfig.messageFontSize * fontSize: AppConfig.messageFontSize *
@ -314,6 +314,12 @@ class SettingsStyleView extends StatelessWidget {
], ],
), ),
), ),
SettingsSwitchListTile.adaptive(
title: L10n.of(context).enableGradient,
onChanged: (b) => AppConfig.enableGradient = b,
storeKey: SettingKeys.enableGradient,
defaultValue: AppConfig.enableGradient,
),
Divider( Divider(
color: theme.dividerColor, color: theme.dividerColor,
), ),
@ -398,6 +404,12 @@ class SettingsStyleView extends StatelessWidget {
storeKey: SettingKeys.separateChatTypes, storeKey: SettingKeys.separateChatTypes,
defaultValue: AppConfig.separateChatTypes, defaultValue: AppConfig.separateChatTypes,
), ),
SettingsSwitchListTile.adaptive(
title: L10n.of(context).displayNavigationRail,
onChanged: (b) => AppConfig.displayNavigationRail = b,
storeKey: SettingKeys.displayNavigationRail,
defaultValue: AppConfig.displayNavigationRail,
),
], ],
), ),
), ),

View File

@ -442,6 +442,10 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
AppConfig.displayNavigationRail = AppConfig.displayNavigationRail =
store.getBool(SettingKeys.displayNavigationRail) ?? store.getBool(SettingKeys.displayNavigationRail) ??
AppConfig.displayNavigationRail; AppConfig.displayNavigationRail;
AppConfig.enableGradient =
store.getBool(SettingKeys.enableGradient) ??
AppConfig.enableGradient;
} }
@override @override

View File

@ -137,7 +137,7 @@ void showMemberActionsPopupMenu({
], ],
), ),
), ),
if (user.canBan) if (user.canBan && user.membership != Membership.ban)
PopupMenuItem( PopupMenuItem(
value: _MemberActions.ban, value: _MemberActions.ban,
child: Row( child: Row(

View File

@ -32,7 +32,10 @@ class SpacesNavigationRail extends StatelessWidget {
.value .value
.uri .uri
.path .path
.startsWith('/rooms/settings'); .startsWith('/rooms/settings')
&& FluffyThemes.isColumnMode(context);
// workaround on settings button remaining selected.
// who will even see it selected on mobile?
return StreamBuilder( return StreamBuilder(
key: ValueKey( key: ValueKey(
client.userID.toString(), client.userID.toString(),