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 _isVoiceLimiterEnabled = true;
bool get isVoiceLimiterEnabled => _isVoiceLimiterEnabled;
double _messageFontScale = 1.0;
double get messageFontScale => _messageFontScale;
bool _autoAddDiscoveredContacts = false;
bool get autoAddDiscoveredContacts => _autoAddDiscoveredContacts;
bool _autoRouteRotationEnabled =
@@ -144,6 +146,7 @@ class AppProvider with ChangeNotifier {
_loadVoiceBandPassFilterEnabled();
_loadVoiceCompressorEnabled();
_loadVoiceLimiterEnabled();
_loadMessageFontScale();
_loadAutoAddDiscoveredContacts();
_loadMessagingRouteSettings();
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.
Future<void> _loadAutoAddDiscoveredContacts() async {
try {

View File

@@ -1023,6 +1023,28 @@ class _SettingsScreenState extends State<SettingsScreen> {
subtitle: const Text('Delete all stored message history'),
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'),

View File

@@ -13,6 +13,7 @@ import '../../providers/connection_provider.dart';
import '../../providers/drawing_provider.dart';
import '../../providers/voice_provider.dart';
import '../../providers/image_provider.dart' as ip;
import '../../providers/app_provider.dart';
import '../drawing_minimap_preview.dart';
import '../../models/ble_packet_log.dart';
import '../../services/sar_template_service.dart';
@@ -1649,6 +1650,36 @@ class _MessageBubbleState extends State<MessageBubble> {
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
Widget build(BuildContext context) {
// Display system messages with minimal styling
@@ -1666,6 +1697,7 @@ class _MessageBubbleState extends State<MessageBubble> {
}
final isSarMarker = message.isSarMarker;
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
final messageFontScale = context.watch<AppProvider>().messageFontScale;
// Determine if this is own message
final connectionProvider = context.read<ConnectionProvider>();
@@ -1779,6 +1811,9 @@ class _MessageBubbleState extends State<MessageBubble> {
: null;
final shouldFloatBubble = widget.isCompact;
final baseBodyStyle = Theme.of(
context,
).textTheme.bodyMedium?.copyWith(fontSize: 14 * messageFontScale);
final bubble = ConstrainedBox(
constraints: BoxConstraints(
maxWidth: shouldFloatBubble
@@ -2197,10 +2232,7 @@ class _MessageBubbleState extends State<MessageBubble> {
);
if (drawing == null) {
return Text(
message.text,
style: Theme.of(context).textTheme.bodyMedium,
);
return Text(message.text, style: baseBodyStyle);
}
final String drawingTypeLabel;
@@ -2286,10 +2318,7 @@ class _MessageBubbleState extends State<MessageBubble> {
)
// Regular message content
else if (!message.isDrawing || widget.isCompact)
Text(
message.text,
style: Theme.of(context).textTheme.bodyMedium,
),
Text(message.text, style: baseBodyStyle),
if (!widget.isCompact &&
!isSarMarker &&
@@ -2509,6 +2538,12 @@ class _MessageBubbleState extends State<MessageBubble> {
Row(
mainAxisSize: MainAxisSize.max,
children: [
if (message.isContactMessage)
_buildDirectMessageStatusIndicator(
context,
message,
)
else ...[
Icon(
getDeliveryStatusIcon(message.deliveryStatus),
size: 12,
@@ -2521,7 +2556,9 @@ class _MessageBubbleState extends State<MessageBubble> {
child: Align(
alignment: Alignment.centerLeft,
child: Text(
message.getLocalizedDeliveryStatus(context),
message.getLocalizedDeliveryStatus(
context,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context)
@@ -2536,6 +2573,7 @@ class _MessageBubbleState extends State<MessageBubble> {
),
),
),
],
// Show retry button for failed messages
if (message.deliveryStatus ==
MessageDeliveryStatus.failed) ...[

View File

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