feat: Better wallpapers with blur and opacity sliders and improved styles page

pull/1447/head
krille-chan 9 months ago
parent f07cabef98
commit 9ee7551ad4
No known key found for this signature in database

@ -2790,5 +2790,8 @@
"oneOfYourDevicesIsNotVerified": "One of your devices is not verified",
"noticeChatBackupDeviceVerification": "Note: When you connect all your devices to the chat backup, they are automatically verified.",
"continueText": "Continue",
"welcomeText": "Hey Hey 👋 This is FluffyChat. You can sign in to any homeserver, which is compatible with https://matrix.org. And then chat with anyone. It's a huge decentralized messaging network!"
"welcomeText": "Hey Hey 👋 This is FluffyChat. You can sign in to any homeserver, which is compatible with https://matrix.org. And then chat with anyone. It's a huge decentralized messaging network!",
"blur": "Blur:",
"opacity": "Opacity:",
"setWallpaper": "Set wallpaper"
}

@ -1,3 +1,5 @@
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:badges/badges.dart';
@ -262,14 +264,21 @@ class ChatView extends StatelessWidget {
children: <Widget>[
if (accountConfig.wallpaperUrl != null)
Opacity(
opacity: accountConfig.wallpaperOpacity ?? 1,
child: MxcImage(
uri: accountConfig.wallpaperUrl,
fit: BoxFit.cover,
isThumbnail: true,
width: FluffyThemes.columnWidth * 4,
height: FluffyThemes.columnWidth * 4,
placeholder: (_) => Container(),
opacity: accountConfig.wallpaperOpacity ?? 0.5,
child: ImageFiltered(
imageFilter: ui.ImageFilter.blur(
sigmaX: accountConfig.wallpaperBlur ?? 0.0,
sigmaY: accountConfig.wallpaperBlur ?? 0.0,
),
child: MxcImage(
cacheKey: accountConfig.wallpaperUrl.toString(),
uri: accountConfig.wallpaperUrl,
fit: BoxFit.cover,
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
isThumbnail: false,
placeholder: (_) => Container(),
),
),
),
SafeArea(

@ -449,12 +449,6 @@ class Message extends StatelessWidget {
fontWeight: FontWeight.bold,
fontSize: 12 * AppConfig.fontSizeFactor,
color: theme.colorScheme.secondary,
shadows: [
Shadow(
color: theme.colorScheme.surface,
blurRadius: 3,
),
],
),
),
),
@ -495,12 +489,6 @@ class Message extends StatelessWidget {
fontWeight: FontWeight.bold,
fontSize: 12 * AppConfig.fontSizeFactor,
color: theme.colorScheme.secondary,
shadows: [
Shadow(
color: theme.colorScheme.surface,
blurRadius: 3,
),
],
),
),
),

@ -12,8 +12,6 @@ class StateMessage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Center(
@ -27,12 +25,6 @@ class StateMessage extends StatelessWidget {
style: TextStyle(
fontSize: 12 * AppConfig.fontSizeFactor,
decoration: event.redacted ? TextDecoration.lineThrough : null,
shadows: [
Shadow(
color: theme.colorScheme.surface,
blurRadius: 3,
),
],
),
),
),

@ -45,14 +45,59 @@ class SettingsStyleController extends State<SettingsStyle> {
);
}
void setChatWallpaperOpacity(double opacity) {
double get wallpaperOpacity =>
_wallpaperOpacity ??
Matrix.of(context).client.applicationAccountConfig.wallpaperOpacity ??
0.5;
double? _wallpaperOpacity;
void saveWallpaperOpacity(double opacity) async {
final client = Matrix.of(context).client;
showFutureLoadingDialog(
final result = await showFutureLoadingDialog(
context: context,
future: () => client.updateApplicationAccountConfig(
ApplicationAccountConfig(wallpaperOpacity: opacity),
),
);
if (result.isValue) return;
setState(() {
_wallpaperOpacity = client.applicationAccountConfig.wallpaperOpacity;
});
}
void updateWallpaperOpacity(double opacity) {
setState(() {
_wallpaperOpacity = opacity;
});
}
double get wallpaperBlur =>
_wallpaperBlur ??
Matrix.of(context).client.applicationAccountConfig.wallpaperBlur ??
0.5;
double? _wallpaperBlur;
void saveWallpaperBlur(double blur) async {
final client = Matrix.of(context).client;
final result = await showFutureLoadingDialog(
context: context,
future: () => client.updateApplicationAccountConfig(
ApplicationAccountConfig(wallpaperBlur: blur),
),
);
if (result.isValue) return;
setState(() {
_wallpaperBlur = client.applicationAccountConfig.wallpaperBlur;
});
}
void updateWallpaperBlur(double blur) {
setState(() {
_wallpaperBlur = blur;
});
}
void deleteChatWallpaper() => showFutureLoadingDialog(
@ -60,7 +105,7 @@ class SettingsStyleController extends State<SettingsStyle> {
future: () => Matrix.of(context).client.setApplicationAccountConfig(
const ApplicationAccountConfig(
wallpaperUrl: null,
wallpaperOpacity: null,
wallpaperBlur: null,
),
),
);
@ -72,10 +117,26 @@ class SettingsStyleController extends State<SettingsStyle> {
null,
AppConfig.chatColor,
Colors.indigo,
Colors.blue,
Colors.blueAccent,
Colors.teal,
Colors.tealAccent,
Colors.green,
Colors.greenAccent,
Colors.yellow,
Colors.yellowAccent,
Colors.orange,
Colors.orangeAccent,
Colors.red,
Colors.redAccent,
Colors.pink,
Colors.pinkAccent,
Colors.purple,
Colors.purpleAccent,
Colors.blueGrey,
Colors.grey,
Colors.white,
Colors.black,
];
void switchTheme(ThemeMode? newTheme) {

@ -1,9 +1,14 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat/events/state_message.dart';
import 'package:fluffychat/utils/account_config.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
@ -32,7 +37,36 @@ class SettingsStyleView extends StatelessWidget {
backgroundColor: theme.colorScheme.surface,
body: MaxWidthBody(
child: Column(
crossAxisAlignment: 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,
@ -42,152 +76,61 @@ class SettingsStyleView extends StatelessWidget {
),
),
),
SizedBox(
height: colorPickerSize + 24,
child: ListView(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
children: SettingsStyleController.customColors
.map(
(color) => Padding(
padding: const EdgeInsets.all(12.0),
DynamicColorBuilder(
builder: (light, dark) {
final systemColor =
Theme.of(context).brightness == Brightness.light
? light?.primary
: dark?.primary;
final colors =
List<Color?>.from(SettingsStyleController.customColors);
if (systemColor == null) {
colors.remove(null);
}
return GridView.builder(
shrinkWrap: true,
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.value.toRadixString(16).toUpperCase()}',
child: InkWell(
borderRadius: BorderRadius.circular(colorPickerSize),
onTap: () => controller.setChatColor(color),
child: color == null
? Material(
elevation: 6,
shadowColor: AppConfig.colorSchemeSeed,
borderRadius:
BorderRadius.circular(colorPickerSize),
child: DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
colorPickerSize,
),
gradient: FluffyThemes.backgroundGradient(
context,
255,
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,
),
),
child: SizedBox(
height: colorPickerSize,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0,
),
child: Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (controller.currentColor ==
null)
Padding(
padding:
const EdgeInsets.only(
right: 8.0,
),
child: Icon(
Icons.check,
size: 16,
color: theme
.colorScheme.onSurface,
),
),
Text(
L10n.of(context).systemTheme,
textAlign: TextAlign.center,
style: TextStyle(
color: theme
.colorScheme.onSurface,
),
),
],
),
),
),
),
),
)
: Material(
color: color,
elevation: 6,
borderRadius:
BorderRadius.circular(colorPickerSize),
child: SizedBox(
width: colorPickerSize,
height: colorPickerSize,
child: controller.currentColor == color
? const Center(
child: Icon(
Icons.check,
size: 16,
color: Colors.white,
),
)
: null,
),
),
)
: null,
),
),
),
),
)
.toList(),
),
),
const SizedBox(height: 8),
Divider(
color: theme.dividerColor,
),
ListTile(
title: Text(
L10n.of(context).setTheme,
style: TextStyle(
color: theme.colorScheme.secondary,
fontWeight: FontWeight.bold,
),
),
),
RadioListTile<ThemeMode>(
groupValue: controller.currentTheme,
value: ThemeMode.system,
title: Text(L10n.of(context).systemTheme),
onChanged: controller.switchTheme,
),
RadioListTile<ThemeMode>(
groupValue: controller.currentTheme,
value: ThemeMode.light,
title: Text(L10n.of(context).lightTheme),
onChanged: controller.switchTheme,
),
RadioListTile<ThemeMode>(
groupValue: controller.currentTheme,
value: ThemeMode.dark,
title: Text(L10n.of(context).darkTheme),
onChanged: controller.switchTheme,
),
Divider(
color: theme.dividerColor,
),
ListTile(
title: Text(
L10n.of(context).overview,
style: TextStyle(
color: theme.colorScheme.secondary,
fontWeight: FontWeight.bold,
),
),
),
SettingsSwitchListTile.adaptive(
title: L10n.of(context).presencesToggle,
onChanged: (b) => AppConfig.showPresences = b,
storeKey: SettingKeys.showPresences,
defaultValue: AppConfig.showPresences,
),
SettingsSwitchListTile.adaptive(
title: L10n.of(context).separateChatTypes,
onChanged: (b) => AppConfig.separateChatTypes = b,
storeKey: SettingKeys.separateChatTypes,
defaultValue: AppConfig.separateChatTypes,
);
},
);
},
),
Divider(
color: theme.dividerColor,
@ -213,8 +156,6 @@ class SettingsStyleView extends StatelessWidget {
),
builder: (context, snapshot) {
final accountConfig = client.applicationAccountConfig;
final wallpaperOpacity = accountConfig.wallpaperOpacity ?? 1;
final wallpaperOpacityIsDefault = wallpaperOpacity == 1;
return Column(
mainAxisSize: MainAxisSize.min,
@ -222,56 +163,119 @@ class SettingsStyleView extends StatelessWidget {
AnimatedContainer(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
alignment: Alignment.centerLeft,
decoration: const BoxDecoration(),
clipBehavior: Clip.hardEdge,
child: Stack(
alignment: Alignment.center,
children: [
if (accountConfig.wallpaperUrl != null)
Opacity(
opacity: wallpaperOpacity,
child: MxcImage(
uri: accountConfig.wallpaperUrl,
fit: BoxFit.cover,
isThumbnail: true,
width: FluffyThemes.columnWidth * 2,
height: 156,
opacity: controller.wallpaperOpacity,
child: ImageFiltered(
imageFilter: ImageFilter.blur(
sigmaX: controller.wallpaperBlur,
sigmaY: controller.wallpaperBlur,
),
child: MxcImage(
key: ValueKey(accountConfig.wallpaperUrl),
uri: accountConfig.wallpaperUrl,
fit: BoxFit.cover,
isThumbnail: true,
width: FluffyThemes.columnWidth * 2,
height: 212,
),
),
),
Padding(
padding: EdgeInsets.only(
left: 12 + 12 + Avatar.defaultSize,
right: 12,
top: accountConfig.wallpaperUrl == null ? 0 : 12,
bottom: 12,
),
child: Material(
color: theme.colorScheme.primary,
borderRadius: BorderRadius.circular(
AppConfig.borderRadius,
Column(
mainAxisSize: 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!,
),
),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
Padding(
padding: EdgeInsets.only(
left: 12 + 12 + Avatar.defaultSize,
right: 12,
top: accountConfig.wallpaperUrl == null
? 0
: 12,
bottom: 12,
),
child: Text(
'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor',
style: TextStyle(
color: theme.colorScheme.onPrimary,
fontSize: AppConfig.messageFontSize *
AppConfig.fontSizeFactor,
child: Material(
color: theme.colorScheme.primary,
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.colorScheme.onPrimary,
fontSize: AppConfig.messageFontSize *
AppConfig.fontSizeFactor,
),
),
),
),
),
),
Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: EdgeInsets.only(
right: 12,
left: 12,
top: accountConfig.wallpaperUrl == null
? 0
: 12,
bottom: 12,
),
child: Material(
color: theme
.colorScheme.surfaceContainerHighest,
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 *
AppConfig.fontSizeFactor,
),
),
),
),
),
),
],
),
],
),
),
ListTile(
title: Text(L10n.of(context).wallpaper),
leading: const Icon(Icons.photo_outlined),
title: OutlinedButton(
onPressed: controller.setWallpaper,
child: Text(L10n.of(context).setWallpaper),
),
trailing: accountConfig.wallpaperUrl == null
? null
: IconButton(
@ -279,23 +283,29 @@ class SettingsStyleView extends StatelessWidget {
color: theme.colorScheme.error,
onPressed: controller.deleteChatWallpaper,
),
onTap: controller.setWallpaper,
),
AnimatedSize(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
child: accountConfig.wallpaperUrl != null
? SwitchListTile.adaptive(
title: Text(L10n.of(context).transparent),
secondary: const Icon(Icons.blur_linear_outlined),
value: !wallpaperOpacityIsDefault,
onChanged: (_) =>
controller.setChatWallpaperOpacity(
wallpaperOpacityIsDefault ? 0.4 : 1,
),
)
: null,
),
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,
),
],
],
);
},
@ -312,6 +322,30 @@ class SettingsStyleView extends StatelessWidget {
semanticFormatterCallback: (d) => d.toString(),
onChanged: controller.changeFontSizeFactor,
),
Divider(
color: theme.dividerColor,
),
ListTile(
title: Text(
L10n.of(context).overview,
style: TextStyle(
color: theme.colorScheme.secondary,
fontWeight: FontWeight.bold,
),
),
),
SettingsSwitchListTile.adaptive(
title: L10n.of(context).presencesToggle,
onChanged: (b) => AppConfig.showPresences = b,
storeKey: SettingKeys.showPresences,
defaultValue: AppConfig.showPresences,
),
SettingsSwitchListTile.adaptive(
title: L10n.of(context).separateChatTypes,
onChanged: (b) => AppConfig.separateChatTypes = b,
storeKey: SettingKeys.separateChatTypes,
defaultValue: AppConfig.separateChatTypes,
),
],
),
),

@ -29,6 +29,7 @@ extension ApplicationAccountConfigExtension on Client {
wallpaperUrl: config.wallpaperUrl ?? currentConfig.wallpaperUrl,
wallpaperOpacity:
config.wallpaperOpacity ?? currentConfig.wallpaperOpacity,
wallpaperBlur: config.wallpaperBlur ?? currentConfig.wallpaperBlur,
).toJson(),
);
}
@ -37,10 +38,12 @@ extension ApplicationAccountConfigExtension on Client {
class ApplicationAccountConfig {
final Uri? wallpaperUrl;
final double? wallpaperOpacity;
final double? wallpaperBlur;
const ApplicationAccountConfig({
this.wallpaperUrl,
this.wallpaperOpacity,
this.wallpaperBlur,
});
static double _sanitizedOpacity(double? opacity) {
@ -56,10 +59,12 @@ class ApplicationAccountConfig {
: null,
wallpaperOpacity:
_sanitizedOpacity(json.tryGet<double>('wallpaper_opacity')),
wallpaperBlur: json.tryGet<double>('wallpaper_blur'),
);
Map<String, dynamic> toJson() => {
'wallpaper_url': wallpaperUrl?.toString(),
'wallpaper_opacity': wallpaperOpacity,
'wallpaper_blur': wallpaperBlur,
};
}

Loading…
Cancel
Save