Add message font scale setting

This commit is contained in:
Janez T
2026-03-08 20:29:55 +01:00
parent 771cddb80d
commit b471ab0990
4 changed files with 128 additions and 35 deletions

View File

@@ -90,6 +90,8 @@ class AppProvider with ChangeNotifier {
bool get isVoiceCompressorEnabled => _isVoiceCompressorEnabled; bool get isVoiceCompressorEnabled => _isVoiceCompressorEnabled;
bool _isVoiceLimiterEnabled = true; bool _isVoiceLimiterEnabled = true;
bool get isVoiceLimiterEnabled => _isVoiceLimiterEnabled; bool get isVoiceLimiterEnabled => _isVoiceLimiterEnabled;
double _messageFontScale = 1.0;
double get messageFontScale => _messageFontScale;
bool _autoAddDiscoveredContacts = false; bool _autoAddDiscoveredContacts = false;
bool get autoAddDiscoveredContacts => _autoAddDiscoveredContacts; bool get autoAddDiscoveredContacts => _autoAddDiscoveredContacts;
bool _autoRouteRotationEnabled = bool _autoRouteRotationEnabled =
@@ -144,6 +146,7 @@ class AppProvider with ChangeNotifier {
_loadVoiceBandPassFilterEnabled(); _loadVoiceBandPassFilterEnabled();
_loadVoiceCompressorEnabled(); _loadVoiceCompressorEnabled();
_loadVoiceLimiterEnabled(); _loadVoiceLimiterEnabled();
_loadMessageFontScale();
_loadAutoAddDiscoveredContacts(); _loadAutoAddDiscoveredContacts();
_loadMessagingRouteSettings(); _loadMessagingRouteSettings();
unawaited(_pathHistoryService.initialize()); unawaited(_pathHistoryService.initialize());
@@ -431,6 +434,30 @@ class AppProvider with ChangeNotifier {
} }
} }
Future<void> _loadMessageFontScale() async {
try {
final prefs = await SharedPreferences.getInstance();
_messageFontScale = (prefs.getDouble('message_font_scale') ?? 1.0).clamp(
0.85,
1.4,
);
notifyListeners();
} catch (e) {
debugPrint('Error loading message font scale setting: $e');
}
}
Future<void> setMessageFontScale(double scale) async {
try {
_messageFontScale = scale.clamp(0.85, 1.4);
final prefs = await SharedPreferences.getInstance();
await prefs.setDouble('message_font_scale', _messageFontScale);
notifyListeners();
} catch (e) {
debugPrint('Error saving message font scale setting: $e');
}
}
/// Load auto-add discovered contacts setting from shared preferences. /// Load auto-add discovered contacts setting from shared preferences.
Future<void> _loadAutoAddDiscoveredContacts() async { Future<void> _loadAutoAddDiscoveredContacts() async {
try { try {

View File

@@ -1023,6 +1023,28 @@ class _SettingsScreenState extends State<SettingsScreen> {
subtitle: const Text('Delete all stored message history'), subtitle: const Text('Delete all stored message history'),
onTap: _clearMessages, onTap: _clearMessages,
), ),
Consumer<AppProvider>(
builder: (context, appProvider, child) => ListTile(
leading: const Icon(Icons.format_size),
title: const Text('Message font size'),
subtitle: Text(
'${(appProvider.messageFontScale * 100).round()}% of default',
),
trailing: SizedBox(
width: 150,
child: Slider(
value: appProvider.messageFontScale,
min: 0.85,
max: 1.4,
divisions: 11,
label: '${(appProvider.messageFontScale * 100).round()}%',
onChanged: (value) {
appProvider.setMessageFontScale(value);
},
),
),
),
),
]), ]),
_buildSectionHeader('Voice'), _buildSectionHeader('Voice'),

View File

@@ -13,6 +13,7 @@ import '../../providers/connection_provider.dart';
import '../../providers/drawing_provider.dart'; import '../../providers/drawing_provider.dart';
import '../../providers/voice_provider.dart'; import '../../providers/voice_provider.dart';
import '../../providers/image_provider.dart' as ip; import '../../providers/image_provider.dart' as ip;
import '../../providers/app_provider.dart';
import '../drawing_minimap_preview.dart'; import '../drawing_minimap_preview.dart';
import '../../models/ble_packet_log.dart'; import '../../models/ble_packet_log.dart';
import '../../services/sar_template_service.dart'; import '../../services/sar_template_service.dart';
@@ -1649,6 +1650,36 @@ class _MessageBubbleState extends State<MessageBubble> {
return null; return null;
} }
Widget _buildDirectMessageStatusIndicator(
BuildContext context,
Message message,
) {
switch (message.deliveryStatus) {
case MessageDeliveryStatus.delivered:
return Icon(
Icons.done_all,
size: 16,
color: Theme.of(context).colorScheme.primary,
);
case MessageDeliveryStatus.sent:
return Icon(
Icons.done,
size: 16,
color: Theme.of(context).colorScheme.onSurfaceVariant,
);
case MessageDeliveryStatus.sending:
return Icon(Icons.schedule, size: 15, color: Colors.orange);
case MessageDeliveryStatus.failed:
return const Icon(Icons.error_outline, size: 15, color: Colors.red);
case MessageDeliveryStatus.received:
return Icon(
Icons.done_all,
size: 16,
color: Theme.of(context).colorScheme.onSurfaceVariant,
);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Display system messages with minimal styling // Display system messages with minimal styling
@@ -1666,6 +1697,7 @@ class _MessageBubbleState extends State<MessageBubble> {
} }
final isSarMarker = message.isSarMarker; final isSarMarker = message.isSarMarker;
final isDarkMode = Theme.of(context).brightness == Brightness.dark; final isDarkMode = Theme.of(context).brightness == Brightness.dark;
final messageFontScale = context.watch<AppProvider>().messageFontScale;
// Determine if this is own message // Determine if this is own message
final connectionProvider = context.read<ConnectionProvider>(); final connectionProvider = context.read<ConnectionProvider>();
@@ -1779,6 +1811,9 @@ class _MessageBubbleState extends State<MessageBubble> {
: null; : null;
final shouldFloatBubble = widget.isCompact; final shouldFloatBubble = widget.isCompact;
final baseBodyStyle = Theme.of(
context,
).textTheme.bodyMedium?.copyWith(fontSize: 14 * messageFontScale);
final bubble = ConstrainedBox( final bubble = ConstrainedBox(
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: shouldFloatBubble maxWidth: shouldFloatBubble
@@ -2197,10 +2232,7 @@ class _MessageBubbleState extends State<MessageBubble> {
); );
if (drawing == null) { if (drawing == null) {
return Text( return Text(message.text, style: baseBodyStyle);
message.text,
style: Theme.of(context).textTheme.bodyMedium,
);
} }
final String drawingTypeLabel; final String drawingTypeLabel;
@@ -2286,10 +2318,7 @@ class _MessageBubbleState extends State<MessageBubble> {
) )
// Regular message content // Regular message content
else if (!message.isDrawing || widget.isCompact) else if (!message.isDrawing || widget.isCompact)
Text( Text(message.text, style: baseBodyStyle),
message.text,
style: Theme.of(context).textTheme.bodyMedium,
),
if (!widget.isCompact && if (!widget.isCompact &&
!isSarMarker && !isSarMarker &&
@@ -2509,33 +2538,42 @@ class _MessageBubbleState extends State<MessageBubble> {
Row( Row(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
children: [ children: [
Icon( if (message.isContactMessage)
getDeliveryStatusIcon(message.deliveryStatus), _buildDirectMessageStatusIndicator(
size: 12, context,
color: getDeliveryStatusColor( message,
message.deliveryStatus, )
), else ...[
), Icon(
const SizedBox(width: 3), getDeliveryStatusIcon(message.deliveryStatus),
Expanded( size: 12,
child: Align( color: getDeliveryStatusColor(
alignment: Alignment.centerLeft, message.deliveryStatus,
child: Text(
message.getLocalizedDeliveryStatus(context),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context)
.textTheme
.labelSmall
?.copyWith(
color: getDeliveryStatusColor(
message.deliveryStatus,
),
fontStyle: FontStyle.italic,
),
), ),
), ),
), const SizedBox(width: 3),
Expanded(
child: Align(
alignment: Alignment.centerLeft,
child: Text(
message.getLocalizedDeliveryStatus(
context,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context)
.textTheme
.labelSmall
?.copyWith(
color: getDeliveryStatusColor(
message.deliveryStatus,
),
fontStyle: FontStyle.italic,
),
),
),
),
],
// Show retry button for failed messages // Show retry button for failed messages
if (message.deliveryStatus == if (message.deliveryStatus ==
MessageDeliveryStatus.failed) ...[ MessageDeliveryStatus.failed) ...[

View File

@@ -1,7 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import '../../l10n/app_localizations.dart'; import '../../l10n/app_localizations.dart';
import '../../providers/app_provider.dart';
class MessagesComposer extends StatelessWidget { class MessagesComposer extends StatelessWidget {
final TextEditingController textController; final TextEditingController textController;
@@ -253,6 +255,10 @@ class _MessageInput extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final messageFontScale = context.watch<AppProvider>().messageFontScale;
const baseFontSize = 15.0;
final resolvedFontSize = baseFontSize * messageFontScale;
return AnimatedContainer( return AnimatedContainer(
duration: const Duration(milliseconds: 180), duration: const Duration(milliseconds: 180),
constraints: const BoxConstraints(minHeight: 46, maxHeight: 132), constraints: const BoxConstraints(minHeight: 46, maxHeight: 132),
@@ -286,12 +292,12 @@ class _MessageInput extends StatelessWidget {
maxLines: 4, maxLines: 4,
keyboardType: TextInputType.multiline, keyboardType: TextInputType.multiline,
inputFormatters: [messageByteLimiter], inputFormatters: [messageByteLimiter],
style: const TextStyle(fontSize: 15), style: TextStyle(fontSize: resolvedFontSize),
textAlignVertical: TextAlignVertical.center, textAlignVertical: TextAlignVertical.center,
decoration: InputDecoration( decoration: InputDecoration(
hintText: AppLocalizations.of(context)!.typeYourMessage, hintText: AppLocalizations.of(context)!.typeYourMessage,
hintStyle: TextStyle( hintStyle: TextStyle(
fontSize: 15, fontSize: resolvedFontSize,
color: Theme.of( color: Theme.of(
context, context,
).colorScheme.onSurfaceVariant.withValues(alpha: 0.9), ).colorScheme.onSurfaceVariant.withValues(alpha: 0.9),