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", "oneOfYourDevicesIsNotVerified": "One of your devices is not verified",
"noticeChatBackupDeviceVerification": "Note: When you connect all your devices to the chat backup, they are automatically verified.", "noticeChatBackupDeviceVerification": "Note: When you connect all your devices to the chat backup, they are automatically verified.",
"continueText": "Continue", "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:flutter/material.dart';
import 'package:badges/badges.dart'; import 'package:badges/badges.dart';
@ -262,14 +264,21 @@ class ChatView extends StatelessWidget {
children: <Widget>[ children: <Widget>[
if (accountConfig.wallpaperUrl != null) if (accountConfig.wallpaperUrl != null)
Opacity( Opacity(
opacity: accountConfig.wallpaperOpacity ?? 1, opacity: accountConfig.wallpaperOpacity ?? 0.5,
child: MxcImage( child: ImageFiltered(
uri: accountConfig.wallpaperUrl, imageFilter: ui.ImageFilter.blur(
fit: BoxFit.cover, sigmaX: accountConfig.wallpaperBlur ?? 0.0,
isThumbnail: true, sigmaY: accountConfig.wallpaperBlur ?? 0.0,
width: FluffyThemes.columnWidth * 4, ),
height: FluffyThemes.columnWidth * 4, child: MxcImage(
placeholder: (_) => Container(), 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( SafeArea(

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

@ -12,8 +12,6 @@ class StateMessage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context);
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0), padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Center( child: Center(
@ -27,12 +25,6 @@ class StateMessage extends StatelessWidget {
style: TextStyle( style: TextStyle(
fontSize: 12 * AppConfig.fontSizeFactor, fontSize: 12 * AppConfig.fontSizeFactor,
decoration: event.redacted ? TextDecoration.lineThrough : null, 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; final client = Matrix.of(context).client;
showFutureLoadingDialog( final result = await showFutureLoadingDialog(
context: context, context: context,
future: () => client.updateApplicationAccountConfig( future: () => client.updateApplicationAccountConfig(
ApplicationAccountConfig(wallpaperOpacity: opacity), 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( void deleteChatWallpaper() => showFutureLoadingDialog(
@ -60,7 +105,7 @@ class SettingsStyleController extends State<SettingsStyle> {
future: () => Matrix.of(context).client.setApplicationAccountConfig( future: () => Matrix.of(context).client.setApplicationAccountConfig(
const ApplicationAccountConfig( const ApplicationAccountConfig(
wallpaperUrl: null, wallpaperUrl: null,
wallpaperOpacity: null, wallpaperBlur: null,
), ),
), ),
); );
@ -72,10 +117,26 @@ class SettingsStyleController extends State<SettingsStyle> {
null, null,
AppConfig.chatColor, AppConfig.chatColor,
Colors.indigo, Colors.indigo,
Colors.blue,
Colors.blueAccent,
Colors.teal,
Colors.tealAccent,
Colors.green, Colors.green,
Colors.greenAccent,
Colors.yellow,
Colors.yellowAccent,
Colors.orange, Colors.orange,
Colors.orangeAccent,
Colors.red,
Colors.redAccent,
Colors.pink, Colors.pink,
Colors.pinkAccent,
Colors.purple,
Colors.purpleAccent,
Colors.blueGrey, Colors.blueGrey,
Colors.grey,
Colors.white,
Colors.black,
]; ];
void switchTheme(ThemeMode? newTheme) { void switchTheme(ThemeMode? newTheme) {

@ -1,9 +1,14 @@
import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter_gen/gen_l10n/l10n.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/setting_keys.dart';
import 'package:fluffychat/config/themes.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/utils/account_config.dart';
import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/layouts/max_width_body.dart'; import 'package:fluffychat/widgets/layouts/max_width_body.dart';
@ -32,7 +37,36 @@ class SettingsStyleView extends StatelessWidget {
backgroundColor: theme.colorScheme.surface, backgroundColor: theme.colorScheme.surface,
body: MaxWidthBody( body: MaxWidthBody(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ 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( ListTile(
title: Text( title: Text(
L10n.of(context).setColorTheme, L10n.of(context).setColorTheme,
@ -42,152 +76,61 @@ class SettingsStyleView extends StatelessWidget {
), ),
), ),
), ),
SizedBox( DynamicColorBuilder(
height: colorPickerSize + 24, builder: (light, dark) {
child: ListView( final systemColor =
shrinkWrap: true, Theme.of(context).brightness == Brightness.light
scrollDirection: Axis.horizontal, ? light?.primary
children: SettingsStyleController.customColors : dark?.primary;
.map( final colors =
(color) => Padding( List<Color?>.from(SettingsStyleController.customColors);
padding: const EdgeInsets.all(12.0), 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( child: InkWell(
borderRadius: BorderRadius.circular(colorPickerSize), borderRadius: BorderRadius.circular(colorPickerSize),
onTap: () => controller.setChatColor(color), onTap: () => controller.setChatColor(color),
child: color == null child: Material(
? Material( color: color ?? systemColor,
elevation: 6, elevation: 6,
shadowColor: AppConfig.colorSchemeSeed, borderRadius:
borderRadius: BorderRadius.circular(colorPickerSize),
BorderRadius.circular(colorPickerSize), child: SizedBox(
child: DecoratedBox( width: colorPickerSize,
decoration: BoxDecoration( height: colorPickerSize,
borderRadius: BorderRadius.circular( child: controller.currentColor == color
colorPickerSize, ? Center(
), child: Icon(
gradient: FluffyThemes.backgroundGradient( Icons.check,
context, size: 16,
255, color: Theme.of(context)
.colorScheme
.onPrimary,
), ),
), )
child: SizedBox( : null,
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,
),
),
), ),
), ),
) );
.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( Divider(
color: theme.dividerColor, color: theme.dividerColor,
@ -213,8 +156,6 @@ class SettingsStyleView extends StatelessWidget {
), ),
builder: (context, snapshot) { builder: (context, snapshot) {
final accountConfig = client.applicationAccountConfig; final accountConfig = client.applicationAccountConfig;
final wallpaperOpacity = accountConfig.wallpaperOpacity ?? 1;
final wallpaperOpacityIsDefault = wallpaperOpacity == 1;
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -222,56 +163,119 @@ class SettingsStyleView extends StatelessWidget {
AnimatedContainer( AnimatedContainer(
duration: FluffyThemes.animationDuration, duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve, curve: FluffyThemes.animationCurve,
alignment: Alignment.centerLeft,
decoration: const BoxDecoration(), decoration: const BoxDecoration(),
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
child: Stack( child: Stack(
alignment: Alignment.center,
children: [ children: [
if (accountConfig.wallpaperUrl != null) if (accountConfig.wallpaperUrl != null)
Opacity( Opacity(
opacity: wallpaperOpacity, opacity: controller.wallpaperOpacity,
child: MxcImage( child: ImageFiltered(
uri: accountConfig.wallpaperUrl, imageFilter: ImageFilter.blur(
fit: BoxFit.cover, sigmaX: controller.wallpaperBlur,
isThumbnail: true, sigmaY: controller.wallpaperBlur,
width: FluffyThemes.columnWidth * 2, ),
height: 156, child: MxcImage(
key: ValueKey(accountConfig.wallpaperUrl),
uri: accountConfig.wallpaperUrl,
fit: BoxFit.cover,
isThumbnail: true,
width: FluffyThemes.columnWidth * 2,
height: 212,
),
), ),
), ),
Padding( Column(
padding: EdgeInsets.only( mainAxisSize: MainAxisSize.min,
left: 12 + 12 + Avatar.defaultSize, children: [
right: 12, const SizedBox(height: 16),
top: accountConfig.wallpaperUrl == null ? 0 : 12, StateMessage(
bottom: 12, Event(
), eventId: 'style_dummy',
child: Material( room:
color: theme.colorScheme.primary, Room(id: '!style_dummy', client: client),
borderRadius: BorderRadius.circular( content: {'membership': 'join'},
AppConfig.borderRadius, type: EventTypes.RoomMember,
senderId: client.userID!,
originServerTs: DateTime.now(),
stateKey: client.userID!,
),
), ),
child: Padding( Padding(
padding: const EdgeInsets.symmetric( padding: EdgeInsets.only(
horizontal: 16, left: 12 + 12 + Avatar.defaultSize,
vertical: 8, right: 12,
top: accountConfig.wallpaperUrl == null
? 0
: 12,
bottom: 12,
), ),
child: Text( child: Material(
'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor', color: theme.colorScheme.primary,
style: TextStyle( borderRadius: BorderRadius.circular(
color: theme.colorScheme.onPrimary, AppConfig.borderRadius,
fontSize: AppConfig.messageFontSize * ),
AppConfig.fontSizeFactor, 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( ListTile(
title: Text(L10n.of(context).wallpaper), title: OutlinedButton(
leading: const Icon(Icons.photo_outlined), onPressed: controller.setWallpaper,
child: Text(L10n.of(context).setWallpaper),
),
trailing: accountConfig.wallpaperUrl == null trailing: accountConfig.wallpaperUrl == null
? null ? null
: IconButton( : IconButton(
@ -279,23 +283,29 @@ class SettingsStyleView extends StatelessWidget {
color: theme.colorScheme.error, color: theme.colorScheme.error,
onPressed: controller.deleteChatWallpaper, 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(), semanticFormatterCallback: (d) => d.toString(),
onChanged: controller.changeFontSizeFactor, 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, wallpaperUrl: config.wallpaperUrl ?? currentConfig.wallpaperUrl,
wallpaperOpacity: wallpaperOpacity:
config.wallpaperOpacity ?? currentConfig.wallpaperOpacity, config.wallpaperOpacity ?? currentConfig.wallpaperOpacity,
wallpaperBlur: config.wallpaperBlur ?? currentConfig.wallpaperBlur,
).toJson(), ).toJson(),
); );
} }
@ -37,10 +38,12 @@ extension ApplicationAccountConfigExtension on Client {
class ApplicationAccountConfig { class ApplicationAccountConfig {
final Uri? wallpaperUrl; final Uri? wallpaperUrl;
final double? wallpaperOpacity; final double? wallpaperOpacity;
final double? wallpaperBlur;
const ApplicationAccountConfig({ const ApplicationAccountConfig({
this.wallpaperUrl, this.wallpaperUrl,
this.wallpaperOpacity, this.wallpaperOpacity,
this.wallpaperBlur,
}); });
static double _sanitizedOpacity(double? opacity) { static double _sanitizedOpacity(double? opacity) {
@ -56,10 +59,12 @@ class ApplicationAccountConfig {
: null, : null,
wallpaperOpacity: wallpaperOpacity:
_sanitizedOpacity(json.tryGet<double>('wallpaper_opacity')), _sanitizedOpacity(json.tryGet<double>('wallpaper_opacity')),
wallpaperBlur: json.tryGet<double>('wallpaper_blur'),
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
'wallpaper_url': wallpaperUrl?.toString(), 'wallpaper_url': wallpaperUrl?.toString(),
'wallpaper_opacity': wallpaperOpacity, 'wallpaper_opacity': wallpaperOpacity,
'wallpaper_blur': wallpaperBlur,
}; };
} }

Loading…
Cancel
Save