chore: Adjust styles and animations

This commit is contained in:
Christian Kußowski 2026-03-12 12:03:03 +01:00
parent 5c88133691
commit 9724b852bb
No known key found for this signature in database
GPG key ID: E067ECD60F1A0652
5 changed files with 656 additions and 761 deletions

View file

@ -462,21 +462,18 @@ class ChatController extends State<ChatPageWithRoom>
scrollUpBannerEventId = eventId; scrollUpBannerEventId = eventId;
}); });
bool firstUpdateReceived = false;
void updateView() { void updateView() {
if (!mounted) return; if (!mounted) return;
setReadMarker(); setReadMarker();
setState(() {}); setState(() {
firstUpdateReceived = true;
});
} }
Future<void>? loadTimelineFuture; Future<void>? loadTimelineFuture;
int? animateInEventIndex;
void onInsert(int i) {
// setState will be called by updateView() anyway
if (timeline?.allowNewEvent == true) animateInEventIndex = i;
}
Future<void> _getTimeline({String? eventContextId}) async { Future<void> _getTimeline({String? eventContextId}) async {
await Matrix.of(context).client.roomsLoading; await Matrix.of(context).client.roomsLoading;
await Matrix.of(context).client.accountDataLoading; await Matrix.of(context).client.accountDataLoading;
@ -489,15 +486,11 @@ class ChatController extends State<ChatPageWithRoom>
timeline = await room.getTimeline( timeline = await room.getTimeline(
onUpdate: updateView, onUpdate: updateView,
eventContextId: eventContextId, eventContextId: eventContextId,
onInsert: onInsert,
); );
} catch (e, s) { } catch (e, s) {
Logs().w('Unable to load timeline on event ID $eventContextId', e, s); Logs().w('Unable to load timeline on event ID $eventContextId', e, s);
if (!mounted) return; if (!mounted) return;
timeline = await room.getTimeline( timeline = await room.getTimeline(onUpdate: updateView);
onUpdate: updateView,
onInsert: onInsert,
);
if (!mounted) return; if (!mounted) return;
if (e is TimeoutException || e is IOException) { if (e is TimeoutException || e is IOException) {
_showScrollUpMaterialBanner(eventContextId!); _showScrollUpMaterialBanner(eventContextId!);

View file

@ -35,7 +35,6 @@ class ChatEventList extends StatelessWidget {
final events = timeline.events.filterByVisibleInGui( final events = timeline.events.filterByVisibleInGui(
threadId: controller.activeThreadId, threadId: controller.activeThreadId,
); );
final animateInEventIndex = controller.animateInEventIndex;
// create a map of eventId --> index to greatly improve performance of // create a map of eventId --> index to greatly improve performance of
// ListView's findChildIndexCallback // ListView's findChildIndexCallback
@ -120,10 +119,7 @@ class ChatEventList extends StatelessWidget {
// The message at this index: // The message at this index:
final event = events[i]; final event = events[i];
final animateIn = final animateIn = i == 0 && controller.firstUpdateReceived;
animateInEventIndex != null &&
timeline.events.length > animateInEventIndex &&
event == timeline.events[animateInEventIndex];
final nextEvent = i + 1 < events.length ? events[i + 1] : null; final nextEvent = i + 1 < events.length ? events[i + 1] : null;
final previousEvent = i > 0 ? events[i - 1] : null; final previousEvent = i > 0 ? events[i - 1] : null;
@ -139,16 +135,13 @@ class ChatEventList extends StatelessWidget {
!controller.expandedEventIds.contains(event.eventId); !controller.expandedEventIds.contains(event.eventId);
return AutoScrollTag( return AutoScrollTag(
key: ValueKey(event.eventId), key: ValueKey(event.transactionId ?? event.eventId),
index: i, index: i,
controller: controller.scrollController, controller: controller.scrollController,
child: Message( child: Message(
event, event,
bigEmojis: controller.bigEmojis, bigEmojis: controller.bigEmojis,
animateIn: animateIn, animateIn: animateIn,
resetAnimateIn: () {
controller.animateInEventIndex = null;
},
onSwipe: () => controller.replyAction(replyTo: event), onSwipe: () => controller.replyAction(replyTo: event),
onInfoTab: controller.showEventInfo, onInfoTab: controller.showEventInfo,
onMention: () => controller.sendController.text += onMention: () => controller.sendController.text +=

View file

@ -42,7 +42,6 @@ class Message extends StatelessWidget {
final Timeline timeline; final Timeline timeline;
final bool highlightMarker; final bool highlightMarker;
final bool animateIn; final bool animateIn;
final void Function()? resetAnimateIn;
final bool wallpaperMode; final bool wallpaperMode;
final ScrollController scrollController; final ScrollController scrollController;
final List<Color> colors; final List<Color> colors;
@ -67,7 +66,6 @@ class Message extends StatelessWidget {
required this.timeline, required this.timeline,
this.highlightMarker = false, this.highlightMarker = false,
this.animateIn = false, this.animateIn = false,
this.resetAnimateIn,
this.wallpaperMode = false, this.wallpaperMode = false,
required this.onMention, required this.onMention,
required this.scrollController, required this.scrollController,
@ -171,9 +169,6 @@ class Message extends StatelessWidget {
: theme.bubbleColor; : theme.bubbleColor;
} }
final resetAnimateIn = this.resetAnimateIn;
var animateIn = this.animateIn;
final sentReactions = <String>{}; final sentReactions = <String>{};
if (singleSelected) { if (singleSelected) {
sentReactions.addAll( sentReactions.addAll(
@ -209,7 +204,9 @@ class Message extends StatelessWidget {
final enterThread = this.enterThread; final enterThread = this.enterThread;
final sender = event.senderFromMemoryOrFallback; final sender = event.senderFromMemoryOrFallback;
return Center( return _AnimateIn(
animateIn: animateIn,
child: Center(
child: Swipeable( child: Swipeable(
key: ValueKey(event.eventId), key: ValueKey(event.eventId),
background: const Padding( background: const Padding(
@ -265,24 +262,7 @@ class Message extends StatelessWidget {
), ),
), ),
), ),
StatefulBuilder( Stack(
builder: (context, setState) {
if (animateIn && resetAnimateIn != null) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
animateIn = false;
setState(resetAnimateIn);
});
}
return AnimatedSize(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
clipBehavior: Clip.none,
alignment: ownMessage
? Alignment.bottomRight
: Alignment.bottomLeft,
child: animateIn
? const SizedBox(height: 0, width: double.infinity)
: Stack(
clipBehavior: Clip.none, clipBehavior: Clip.none,
children: [ children: [
Positioned( Positioned(
@ -291,13 +271,9 @@ class Message extends StatelessWidget {
left: 0, left: 0,
right: 0, right: 0,
child: InkWell( child: InkWell(
hoverColor: longPressSelect hoverColor: longPressSelect ? Colors.transparent : null,
? Colors.transparent
: null,
enableFeedback: !selected, enableFeedback: !selected,
onTap: longPressSelect onTap: longPressSelect ? null : () => onSelect(event),
? null
: () => onSelect(event),
borderRadius: BorderRadius.circular( borderRadius: BorderRadius.circular(
AppConfig.borderRadius / 2, AppConfig.borderRadius / 2,
), ),
@ -306,8 +282,9 @@ class Message extends StatelessWidget {
AppConfig.borderRadius / 2, AppConfig.borderRadius / 2,
), ),
color: selected || highlightMarker color: selected || highlightMarker
? theme.colorScheme.secondaryContainer ? theme.colorScheme.secondaryContainer.withAlpha(
.withAlpha(128) 128,
)
: Colors.transparent, : Colors.transparent,
), ),
), ),
@ -338,12 +315,8 @@ class Message extends StatelessWidget {
child: SizedBox( child: SizedBox(
width: 16, width: 16,
height: 16, height: 16,
child: child: event.status == EventStatus.error
event.status == EventStatus.error ? const Icon(Icons.error, color: Colors.red)
? const Icon(
Icons.error,
color: Colors.red,
)
: event.fileSendingStatus != null : event.fileSendingStatus != null
? const CircularProgressIndicator.adaptive( ? const CircularProgressIndicator.adaptive(
strokeWidth: 1, strokeWidth: 1,
@ -360,8 +333,7 @@ class Message extends StatelessWidget {
return Avatar( return Avatar(
mxContent: user.avatarUrl, mxContent: user.avatarUrl,
name: user.calcDisplayname(), name: user.calcDisplayname(),
onTap: () => onTap: () => showMemberActionsPopupMenu(
showMemberActionsPopupMenu(
context: context, context: context,
user: user, user: user,
onMention: onMention, onMention: onMention,
@ -384,22 +356,17 @@ class Message extends StatelessWidget {
left: 8.0, left: 8.0,
bottom: 4, bottom: 4,
), ),
child: child: ownMessage || event.room.isDirectChat
ownMessage ||
event.room.isDirectChat
? const SizedBox(height: 12) ? const SizedBox(height: 12)
: Row( : Row(
children: [ children: [
if (sender.powerLevel >= if (sender.powerLevel >= 50)
50)
Padding( Padding(
padding: padding: const EdgeInsets.only(
const EdgeInsets.only(
right: 2.0, right: 2.0,
), ),
child: Icon( child: Icon(
sender.powerLevel >= sender.powerLevel >= 100
100
? Icons ? Icons
.admin_panel_settings .admin_panel_settings
: Icons : Icons
@ -412,31 +379,25 @@ class Message extends StatelessWidget {
), ),
Expanded( Expanded(
child: FutureBuilder<User?>( child: FutureBuilder<User?>(
future: event future: event.fetchSenderUser(),
.fetchSenderUser(),
builder: (context, snapshot) { builder: (context, snapshot) {
final displayname = final displayname =
snapshot.data snapshot.data
?.calcDisplayname() ?? ?.calcDisplayname() ??
sender sender.calcDisplayname();
.calcDisplayname();
return Text( return Text(
displayname, displayname,
style: TextStyle( style: TextStyle(
fontSize: 11, fontSize: 11,
fontWeight: fontWeight:
FontWeight FontWeight.bold,
.bold,
color: color:
(theme.brightness == (theme.brightness ==
Brightness Brightness.light
.light ? displayname.color
? displayname
.color
: displayname : displayname
.lightColorText), .lightColorText),
shadows: shadows: !wallpaperMode
!wallpaperMode
? null ? null
: [ : [
const Shadow( const Shadow(
@ -444,17 +405,15 @@ class Message extends StatelessWidget {
0.0, 0.0,
0.0, 0.0,
), ),
blurRadius: blurRadius: 3,
3, color: Colors
color: .black,
Colors.black,
), ),
], ],
), ),
maxLines: 1, maxLines: 1,
overflow: overflow:
TextOverflow TextOverflow.ellipsis,
.ellipsis,
); );
}, },
), ),
@ -464,9 +423,7 @@ class Message extends StatelessWidget {
), ),
Container( Container(
alignment: alignment, alignment: alignment,
padding: const EdgeInsets.only( padding: const EdgeInsets.only(left: 8),
left: 8,
),
child: GestureDetector( child: GestureDetector(
onLongPress: longPressSelect onLongPress: longPressSelect
? null ? null
@ -474,19 +431,6 @@ class Message extends StatelessWidget {
HapticFeedback.heavyImpact(); HapticFeedback.heavyImpact();
onSelect(event); onSelect(event);
}, },
child: AnimatedOpacity(
opacity: animateIn
? 0
: event.messageType ==
MessageTypes
.BadEncrypted ||
event.status.isSending
? 0.5
: 1,
duration: FluffyThemes
.animationDuration,
curve:
FluffyThemes.animationCurve,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: noBubble color: noBubble
@ -500,69 +444,49 @@ class Message extends StatelessWidget {
ignore: ignore:
noBubble || noBubble ||
!ownMessage || !ownMessage ||
MediaQuery.highContrastOf( MediaQuery.highContrastOf(context),
context, scrollController: scrollController,
),
scrollController:
scrollController,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: borderRadius: BorderRadius.circular(
BorderRadius.circular( AppConfig.borderRadius,
AppConfig
.borderRadius,
), ),
), ),
constraints: constraints: const BoxConstraints(
const BoxConstraints(
maxWidth: maxWidth:
FluffyThemes FluffyThemes.columnWidth * 1.5,
.columnWidth *
1.5,
), ),
child: Column( child: Column(
mainAxisSize: .min, mainAxisSize: .min,
crossAxisAlignment: crossAxisAlignment:
CrossAxisAlignment CrossAxisAlignment.start,
.start,
children: <Widget>[ children: <Widget>[
if (event.inReplyToEventId( if (event.inReplyToEventId(
includingFallback: includingFallback: false,
false,
) != ) !=
null) null)
FutureBuilder<Event?>( FutureBuilder<Event?>(
future: event future: event.getReplyEvent(
.getReplyEvent(
timeline, timeline,
), ),
builder: builder: (BuildContext context, snapshot) {
(
BuildContext
context,
snapshot,
) {
final replyEvent = final replyEvent =
snapshot snapshot.hasData
.hasData ? snapshot.data!
? snapshot
.data!
: Event( : Event(
eventId: eventId:
event.inReplyToEventId() ?? event
.inReplyToEventId() ??
'\$fake_event_id', '\$fake_event_id',
content: { content: {
'msgtype': 'msgtype': 'm.text',
'm.text', 'body': '...',
'body':
'...',
}, },
senderId: senderId:
event.senderId, event.senderId,
type: type:
'm.room.message', 'm.room.message',
room: room: event.room,
event.room,
status: status:
EventStatus.sent, EventStatus.sent,
originServerTs: originServerTs:
@ -571,23 +495,20 @@ class Message extends StatelessWidget {
return Padding( return Padding(
padding: padding:
const EdgeInsets.only( const EdgeInsets.only(
left: left: 16,
16, right: 16,
right: top: 8,
16,
top:
8,
), ),
child: Material( child: Material(
color: Colors color: Colors.transparent,
.transparent, borderRadius: ReplyContent
borderRadius:
ReplyContent
.borderRadius, .borderRadius,
child: InkWell( child: InkWell(
borderRadius: borderRadius:
ReplyContent.borderRadius, ReplyContent
onTap: () => scrollToEventId( .borderRadius,
onTap: () =>
scrollToEventId(
replyEvent replyEvent
.eventId, .eventId,
), ),
@ -596,8 +517,7 @@ class Message extends StatelessWidget {
replyEvent, replyEvent,
ownMessage: ownMessage:
ownMessage, ownMessage,
timeline: timeline: timeline,
timeline,
), ),
), ),
), ),
@ -610,38 +530,30 @@ class Message extends StatelessWidget {
textColor: textColor, textColor: textColor,
linkColor: linkColor, linkColor: linkColor,
onInfoTab: onInfoTab, onInfoTab: onInfoTab,
borderRadius: borderRadius: borderRadius,
borderRadius,
timeline: timeline, timeline: timeline,
selected: selected, selected: selected,
bigEmojis: bigEmojis, bigEmojis: bigEmojis,
), ),
if (event if (event.hasAggregatedEvents(
.hasAggregatedEvents(
timeline, timeline,
RelationshipTypes RelationshipTypes.edit,
.edit,
)) ))
Padding( Padding(
padding: padding: const EdgeInsets.only(
const EdgeInsets.only(
bottom: 8.0, bottom: 8.0,
left: 16.0, left: 16.0,
right: 16.0, right: 16.0,
), ),
child: Row( child: Row(
mainAxisSize: mainAxisSize:
MainAxisSize MainAxisSize.min,
.min,
spacing: 4.0, spacing: 4.0,
children: [ children: [
Icon( Icon(
Icons Icons.edit_outlined,
.edit_outlined,
color: textColor color: textColor
.withAlpha( .withAlpha(164),
164,
),
size: 14, size: 14,
), ),
Text( Text(
@ -652,11 +564,8 @@ class Message extends StatelessWidget {
), ),
style: TextStyle( style: TextStyle(
color: textColor color: textColor
.withAlpha( .withAlpha(164),
164, fontSize: 11,
),
fontSize:
11,
), ),
), ),
], ],
@ -669,76 +578,61 @@ class Message extends StatelessWidget {
), ),
), ),
), ),
),
Align( Align(
alignment: ownMessage alignment: ownMessage
? Alignment.bottomRight ? Alignment.bottomRight
: Alignment.bottomLeft, : Alignment.bottomLeft,
child: AnimatedSize( child: AnimatedSize(
duration: duration: FluffyThemes.animationDuration,
FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve, curve: FluffyThemes.animationCurve,
child: showReactionPicker child: showReactionPicker
? Padding( ? Padding(
padding: padding: const EdgeInsets.all(4.0),
const EdgeInsets.all(
4.0,
),
child: Material( child: Material(
elevation: 4, elevation: 4,
borderRadius: borderRadius: BorderRadius.circular(
BorderRadius.circular( AppConfig.borderRadius,
AppConfig
.borderRadius,
), ),
shadowColor: theme shadowColor: theme
.colorScheme .colorScheme
.surface .surface
.withAlpha(128), .withAlpha(128),
child: SingleChildScrollView( child: SingleChildScrollView(
scrollDirection: scrollDirection: Axis.horizontal,
Axis.horizontal,
child: Row( child: Row(
mainAxisSize: .min, mainAxisSize: .min,
children: [ children: [
...AppConfig.defaultReactions.map( ...AppConfig.defaultReactions.map(
( (emoji) => IconButton(
emoji, padding: EdgeInsets.zero,
) => IconButton(
padding:
EdgeInsets
.zero,
icon: Center( icon: Center(
child: Opacity( child: Opacity(
opacity: opacity:
sentReactions.contains( sentReactions
.contains(
emoji, emoji,
) )
? 0.33 ? 0.33
: 1, : 1,
child: Text( child: Text(
emoji, emoji,
style: const TextStyle( style:
fontSize: const TextStyle(
20, fontSize: 20,
), ),
textAlign: textAlign: TextAlign
TextAlign
.center, .center,
), ),
), ),
), ),
onPressed: onPressed:
sentReactions sentReactions
.contains( .contains(emoji)
emoji,
)
? null ? null
: () { : () {
onSelect( onSelect(event);
event, event.room
); .sendReaction(
event.room.sendReaction(
event event
.eventId, .eventId,
emoji, emoji,
@ -756,8 +650,7 @@ class Message extends StatelessWidget {
).customReaction, ).customReaction,
onPressed: () async { onPressed: () async {
final emoji = await showAdaptiveBottomSheet<String>( final emoji = await showAdaptiveBottomSheet<String>(
context: context: context,
context,
builder: (context) => Scaffold( builder: (context) => Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text( title: Text(
@ -766,82 +659,94 @@ class Message extends StatelessWidget {
).customReaction, ).customReaction,
), ),
leading: CloseButton( leading: CloseButton(
onPressed: () => Navigator.of( onPressed: () =>
Navigator.of(
context, context,
).pop(null), ).pop(null),
), ),
), ),
body: SizedBox( body: SizedBox(
height: double height:
.infinity, double.infinity,
child: EmojiPicker( child: EmojiPicker(
onEmojiSelected: onEmojiSelected:
( (_, emoji) =>
_,
emoji,
) =>
Navigator.of( Navigator.of(
context, context,
).pop( ).pop(
emoji.emoji, emoji
.emoji,
), ),
config: Config( config: Config(
locale: Localizations.localeOf( locale:
Localizations.localeOf(
context, context,
), ),
emojiViewConfig: const EmojiViewConfig( emojiViewConfig:
const EmojiViewConfig(
backgroundColor: backgroundColor:
Colors.transparent, Colors
.transparent,
), ),
bottomActionBarConfig: const BottomActionBarConfig( bottomActionBarConfig:
const BottomActionBarConfig(
enabled: enabled:
false, false,
), ),
categoryViewConfig: CategoryViewConfig( categoryViewConfig: CategoryViewConfig(
initCategory: initCategory:
Category.SMILEYS, Category
backspaceColor: .SMILEYS,
theme.colorScheme.primary, backspaceColor: theme
iconColor: theme.colorScheme.primary.withAlpha( .colorScheme
.primary,
iconColor: theme
.colorScheme
.primary
.withAlpha(
128, 128,
), ),
iconColorSelected: iconColorSelected: theme
theme.colorScheme.primary, .colorScheme
indicatorColor: .primary,
theme.colorScheme.primary, indicatorColor: theme
backgroundColor: .colorScheme
theme.colorScheme.surface, .primary,
backgroundColor: theme
.colorScheme
.surface,
), ),
skinToneConfig: SkinToneConfig( skinToneConfig: SkinToneConfig(
dialogBackgroundColor: Color.lerp( dialogBackgroundColor: Color.lerp(
theme.colorScheme.surface, theme
theme.colorScheme.primaryContainer, .colorScheme
.surface,
theme
.colorScheme
.primaryContainer,
0.75, 0.75,
)!, )!,
indicatorColor: indicatorColor: theme
theme.colorScheme.onSurface, .colorScheme
.onSurface,
), ),
), ),
), ),
), ),
), ),
); );
if (emoji == if (emoji == null) {
null) {
return; return;
} }
if (sentReactions if (sentReactions
.contains( .contains(emoji)) {
emoji,
)) {
return; return;
} }
onSelect(event); onSelect(event);
await event.room await event.room
.sendReaction( .sendReaction(
event event.eventId,
.eventId,
emoji, emoji,
); );
}, },
@ -861,9 +766,6 @@ class Message extends StatelessWidget {
), ),
], ],
), ),
);
},
),
AnimatedSize( AnimatedSize(
duration: FluffyThemes.animationDuration, duration: FluffyThemes.animationDuration,
@ -959,6 +861,7 @@ class Message extends StatelessWidget {
), ),
), ),
), ),
),
); );
} }
} }
@ -1033,3 +936,37 @@ class BubblePainter extends CustomPainter {
return scrollable.position != oldScrollable?.position; return scrollable.position != oldScrollable?.position;
} }
} }
class _AnimateIn extends StatefulWidget {
final bool animateIn;
final Widget child;
const _AnimateIn({required this.animateIn, required this.child});
@override
State<_AnimateIn> createState() => __AnimateInState();
}
class __AnimateInState extends State<_AnimateIn> {
bool _animationFinished = false;
@override
Widget build(BuildContext context) {
if (!widget.animateIn) return widget.child;
if (!_animationFinished) {
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
_animationFinished = true;
});
});
}
return AnimatedOpacity(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
opacity: _animationFinished ? 1 : 0,
child: AnimatedSize(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
child: _animationFinished ? widget.child : const SizedBox.shrink(),
),
);
}
}

View file

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart'; import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/utils/account_config.dart'; import 'package:fluffychat/utils/account_config.dart';
import 'package:fluffychat/utils/file_selector.dart'; import 'package:fluffychat/utils/file_selector.dart';
@ -111,32 +110,6 @@ class SettingsStyleController extends State<SettingsStyle> {
ThemeMode get currentTheme => ThemeController.of(context).themeMode; ThemeMode get currentTheme => ThemeController.of(context).themeMode;
Color? get currentColor => ThemeController.of(context).primaryColor; Color? get currentColor => ThemeController.of(context).primaryColor;
static final List<Color?> customColors = [
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) { void switchTheme(ThemeMode? newTheme) {
if (newTheme == null) return; if (newTheme == null) return;
switch (newTheme) { switch (newTheme) {

View file

@ -82,14 +82,13 @@ class SettingsStyleView extends StatelessWidget {
Theme.of(context).brightness == Brightness.light Theme.of(context).brightness == Brightness.light
? light?.primary ? light?.primary
: dark?.primary; : dark?.primary;
final colors = List<Color?>.from( final colors = [null, AppConfig.chatColor, ...Colors.primaries];
SettingsStyleController.customColors,
);
if (systemColor == null) { if (systemColor == null) {
colors.remove(null); colors.remove(null);
} }
return GridView.builder( return GridView.builder(
shrinkWrap: true, shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 64, maxCrossAxisExtent: 64,
), ),