Files
tatuchat/lib/pages/settings_style/settings_style_view.dart
Welton Moura 9382a1f946 feat: usar a cor escolhida tal e qual e opcao de suavizar
- TatuPalette.scheme aceita softenPrimary e passa a usar a seed como primary, onPrimary por contraste e surfaceTint, em vez da paleta tonal do Material 3
- nova opcao AppSettings.softenThemeColor (default false, ou seja cor exacta)
- interruptor 'Suavizar cor do tema' em Configuracoes > Aparencia, com l10n en/pt
- a opcao e sincronizada via account data (soften_theme_color) e mantida localmente como fallback, tal como a cor do tema
- setApplicationAccountColor e updateApplicationAccountConfig preservam o novo campo
- um unico sync de account data actualiza cor e opcao
- testes de serializacao e round-trip para o novo campo
2026-09-25 13:46:00 -03:00

367 lines
16 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
//
// SPDX-License-Identifier: AGPL-3.0-or-later
import 'dart:ui';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:tatchat/config/setting_keys.dart';
import 'package:tatchat/config/themes.dart';
import 'package:tatchat/l10n/l10n.dart';
import 'package:tatchat/pages/chat/events/state_message.dart';
import 'package:tatchat/utils/account_config.dart';
import 'package:tatchat/utils/color_value.dart';
import 'package:tatchat/widgets/avatar.dart';
import 'package:tatchat/widgets/layouts/max_width_body.dart';
import 'package:tatchat/widgets/matrix.dart';
import 'package:tatchat/widgets/mxc_image.dart';
import 'package:material_ui/material_ui.dart';
import 'package:matrix/matrix.dart';
import '../../config/app_config.dart';
import 'settings_style.dart';
class SettingsStyleView extends StatelessWidget {
final SettingsStyleController controller;
const SettingsStyleView(this.controller, {super.key});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
const colorPickerSize = 32.0;
final client = Matrix.of(context).client;
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: !FluffyThemes.isColumnMode(context),
centerTitle: FluffyThemes.isColumnMode(context),
title: Text(L10n.of(context).changeTheme),
),
backgroundColor: theme.colorScheme.surface,
body: MaxWidthBody(
child: Column(
crossAxisAlignment: .stretch,
children: [
Padding(
padding: const EdgeInsets.all(12.0),
child: SegmentedButton<ThemeMode>(
selected: {controller.currentTheme},
onSelectionChanged: (selected) =>
controller.switchTheme(selected.single),
segments: [
ButtonSegment(
value: ThemeMode.light,
label: Text(L10n.of(context).lightTheme),
icon: const Icon(Icons.light_mode_outlined),
),
ButtonSegment(
value: ThemeMode.dark,
label: Text(L10n.of(context).darkTheme),
icon: const Icon(Icons.dark_mode_outlined),
),
ButtonSegment(
value: ThemeMode.system,
label: Text(L10n.of(context).systemTheme),
icon: const Icon(Icons.auto_mode_outlined),
),
],
),
),
Divider(color: theme.dividerColor),
ListTile(
title: Text(
L10n.of(context).setColorTheme,
style: TextStyle(
color: theme.colorScheme.secondary,
fontWeight: FontWeight.bold,
),
),
),
DynamicColorBuilder(
builder: (light, dark) {
final systemColor =
Theme.of(context).brightness == Brightness.light
? light?.primary
: dark?.primary;
final colors = [null, AppConfig.chatColor, ...Colors.primaries];
if (systemColor == null) {
colors.remove(null);
}
return GridView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 64,
),
itemCount: colors.length,
itemBuilder: (context, i) {
final color = colors[i];
return Padding(
padding: const EdgeInsets.all(12.0),
child: Tooltip(
message: color == null
? L10n.of(context).systemTheme
: '#${color.hexValue.toRadixString(16).toUpperCase()}',
child: InkWell(
borderRadius: BorderRadius.circular(colorPickerSize),
onTap: () => controller.setChatColor(color),
child: Material(
color: color ?? systemColor,
elevation: 6,
borderRadius: BorderRadius.circular(
colorPickerSize,
),
child: SizedBox(
width: colorPickerSize,
height: colorPickerSize,
child: controller.currentColor == color
? Center(
child: Icon(
Icons.check,
size: 16,
color: Theme.of(
context,
).colorScheme.onPrimary,
),
)
: null,
),
),
),
),
);
},
);
},
),
SwitchListTile(
secondary: const Icon(Icons.palette_outlined),
title: Text(L10n.of(context).softenThemeColor),
subtitle: Text(L10n.of(context).softenThemeColorSubtitle),
value: controller.softenThemeColor,
onChanged: controller.setSoftenThemeColor,
),
Divider(color: theme.dividerColor),
ListTile(
title: Text(
L10n.of(context).messagesStyle,
style: TextStyle(
color: theme.colorScheme.secondary,
fontWeight: FontWeight.bold,
),
),
),
StreamBuilder(
stream: client.onSync.stream.where(
(syncUpdate) =>
syncUpdate.accountData?.any(
(accountData) =>
accountData.type ==
ApplicationAccountConfigExtension.accountDataKey,
) ??
false,
),
builder: (context, snapshot) {
final accountConfig = client.applicationAccountConfig;
return Column(
mainAxisSize: .min,
children: [
MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaler: TextScaler.linear(
AppSettings.fontSizeFactor.value,
),
),
child: AnimatedContainer(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
decoration: const BoxDecoration(),
clipBehavior: Clip.hardEdge,
child: Stack(
alignment: Alignment.center,
children: [
Opacity(
opacity: accountConfig.wallpaperUrl != null
? controller.wallpaperOpacity
: 0.3,
child: ImageFiltered(
imageFilter: ImageFilter.blur(
sigmaX: accountConfig.wallpaperUrl != null
? controller.wallpaperBlur
: 0.0,
sigmaY: accountConfig.wallpaperUrl != null
? controller.wallpaperBlur
: 0.0,
),
child: accountConfig.wallpaperUrl != null
? MxcImage(
key: ValueKey(
accountConfig.wallpaperUrl,
),
uri: accountConfig.wallpaperUrl,
fit: BoxFit.cover,
isThumbnail: true,
width: FluffyThemes.columnWidth * 2,
height: 212,
)
: Image.asset(
'assets/logo/background.png',
fit: BoxFit.cover,
width: FluffyThemes.columnWidth * 2,
height: 212,
),
),
),
Column(
mainAxisSize: .min,
children: [
const SizedBox(height: 16),
StateMessage(
Event(
eventId: 'style_dummy',
room: Room(
id: '!style_dummy',
client: client,
),
content: {'membership': 'join'},
type: EventTypes.RoomMember,
senderId: client.userID!,
originServerTs: DateTime.now(),
stateKey: client.userID,
),
),
Padding(
padding: const EdgeInsets.only(
left: 12 + 12 + Avatar.defaultSize,
right: 12,
top: 12,
bottom: 12,
),
child: DecoratedBox(
decoration: BoxDecoration(
color: theme.bubbleColor,
borderRadius: BorderRadius.circular(
AppConfig.borderRadius,
),
),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
child: Text(
'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor',
style: TextStyle(
color: theme.onBubbleColor,
fontSize: AppConfig.messageFontSize,
),
),
),
),
),
Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.only(
right: 12,
left: 12,
top: 12,
bottom: 12,
),
child: Material(
color: theme
.colorScheme
.surfaceContainerHigh,
borderRadius: BorderRadius.circular(
AppConfig.borderRadius,
),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
child: Text(
'Lorem ipsum dolor sit amet',
style: TextStyle(
color: theme.colorScheme.onSurface,
fontSize: AppConfig.messageFontSize,
),
),
),
),
),
),
],
),
],
),
),
),
Divider(color: theme.dividerColor),
ListTile(
title: TextButton.icon(
style: TextButton.styleFrom(
backgroundColor: theme.colorScheme.secondaryContainer,
foregroundColor:
theme.colorScheme.onSecondaryContainer,
),
onPressed: controller.setWallpaper,
icon: const Icon(Icons.edit_outlined),
label: Text(L10n.of(context).setWallpaper),
),
trailing: accountConfig.wallpaperUrl == null
? null
: IconButton(
icon: const Icon(Icons.delete_outlined),
color: theme.colorScheme.error,
onPressed: controller.deleteChatWallpaper,
),
),
if (accountConfig.wallpaperUrl != null) ...[
ListTile(title: Text(L10n.of(context).opacity)),
Slider.adaptive(
min: 0.1,
max: 1.0,
divisions: 9,
semanticFormatterCallback: (d) => d.toString(),
value: controller.wallpaperOpacity,
onChanged: controller.updateWallpaperOpacity,
onChangeEnd: controller.saveWallpaperOpacity,
),
ListTile(title: Text(L10n.of(context).blur)),
Slider.adaptive(
min: 0.0,
max: 10.0,
divisions: 10,
semanticFormatterCallback: (d) => d.toString(),
value: controller.wallpaperBlur,
onChanged: controller.updateWallpaperBlur,
onChangeEnd: controller.saveWallpaperBlur,
),
],
],
);
},
),
ListTile(
title: Text(L10n.of(context).fontSize),
trailing: Text('× ${AppSettings.fontSizeFactor.value}'),
),
Slider.adaptive(
min: 0.5,
max: 2.5,
divisions: 20,
value: AppSettings.fontSizeFactor.value,
semanticFormatterCallback: (d) => d.toString(),
onChanged: controller.changeFontSizeFactor,
),
],
),
),
);
}
}