Add support for material you and improve message design coherency
This commit is contained in:
parent
042bb5efbc
commit
2b7b4e2dba
17 changed files with 303 additions and 463 deletions
|
@ -9,6 +9,7 @@ import 'package:contacts_plus_plus/models/sem_ver.dart';
|
||||||
import 'package:contacts_plus_plus/widgets/friends/friends_list.dart';
|
import 'package:contacts_plus_plus/widgets/friends/friends_list.dart';
|
||||||
import 'package:contacts_plus_plus/widgets/login_screen.dart';
|
import 'package:contacts_plus_plus/widgets/login_screen.dart';
|
||||||
import 'package:contacts_plus_plus/widgets/update_notifier.dart';
|
import 'package:contacts_plus_plus/widgets/update_notifier.dart';
|
||||||
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_phoenix/flutter_phoenix.dart';
|
import 'package:flutter_phoenix/flutter_phoenix.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
@ -111,13 +112,14 @@ class _ContactsPlusPlusState extends State<ContactsPlusPlus> {
|
||||||
return ClientHolder(
|
return ClientHolder(
|
||||||
settingsClient: widget.settingsClient,
|
settingsClient: widget.settingsClient,
|
||||||
authenticationData: _authData,
|
authenticationData: _authData,
|
||||||
child: MaterialApp(
|
child: DynamicColorBuilder(
|
||||||
|
builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) => MaterialApp(
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
title: 'Contacts++',
|
title: 'Contacts++',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
textTheme: _typography.white,
|
textTheme: _typography.white,
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.purple, brightness: Brightness.dark)
|
colorScheme: darkDynamic ?? ColorScheme.fromSeed(seedColor: Colors.purple, brightness: Brightness.dark),
|
||||||
),
|
),
|
||||||
home: Builder( // Builder is necessary here since we need a context which has access to the ClientHolder
|
home: Builder( // Builder is necessary here since we need a context which has access to the ClientHolder
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
|
@ -144,6 +146,7 @@ class _ContactsPlusPlusState extends State<ContactsPlusPlus> {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,12 +29,10 @@ class _ExpandingInputFabState extends State<ExpandingInputFab> {
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
reverseDuration: const Duration(milliseconds: 200),
|
reverseDuration: const Duration(milliseconds: 200),
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
child: Container(
|
child: Material(
|
||||||
decoration: BoxDecoration(
|
elevation: 4,
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(16),
|
||||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
color: Theme.of(context).colorScheme.primaryContainer,
|
||||||
),
|
|
||||||
padding: const EdgeInsets.all(4),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
@ -49,7 +47,8 @@ class _ExpandingInputFabState extends State<ExpandingInputFab> {
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderSide: BorderSide.none,
|
borderSide: BorderSide.none,
|
||||||
),
|
),
|
||||||
isDense: true
|
isDense: true,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 0, horizontal: 16),
|
||||||
),
|
),
|
||||||
) : null,
|
) : null,
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:ffi';
|
||||||
|
|
||||||
import 'package:contacts_plus_plus/apis/user_api.dart';
|
import 'package:contacts_plus_plus/apis/user_api.dart';
|
||||||
import 'package:contacts_plus_plus/client_holder.dart';
|
import 'package:contacts_plus_plus/client_holder.dart';
|
||||||
|
|
|
@ -2,22 +2,25 @@ import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class GenericAvatar extends StatelessWidget {
|
class GenericAvatar extends StatelessWidget {
|
||||||
const GenericAvatar({this.imageUri="", super.key, this.placeholderIcon=Icons.person, this.radius});
|
const GenericAvatar({this.imageUri="", super.key, this.placeholderIcon=Icons.person, this.radius, this.foregroundColor});
|
||||||
|
|
||||||
final String imageUri;
|
final String imageUri;
|
||||||
final IconData placeholderIcon;
|
final IconData placeholderIcon;
|
||||||
final double? radius;
|
final double? radius;
|
||||||
|
final Color? foregroundColor;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return imageUri.isEmpty ? CircleAvatar(
|
return imageUri.isEmpty ? CircleAvatar(
|
||||||
radius: radius,
|
radius: radius,
|
||||||
|
foregroundColor: foregroundColor,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
child: Icon(placeholderIcon),
|
child: Icon(placeholderIcon, color: foregroundColor,),
|
||||||
) : CachedNetworkImage(
|
) : CachedNetworkImage(
|
||||||
imageBuilder: (context, imageProvider) {
|
imageBuilder: (context, imageProvider) {
|
||||||
return CircleAvatar(
|
return CircleAvatar(
|
||||||
foregroundImage: imageProvider,
|
foregroundImage: imageProvider,
|
||||||
|
foregroundColor: foregroundColor,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
radius: radius,
|
radius: radius,
|
||||||
);
|
);
|
||||||
|
@ -26,17 +29,19 @@ class GenericAvatar extends StatelessWidget {
|
||||||
placeholder: (context, url) {
|
placeholder: (context, url) {
|
||||||
return CircleAvatar(
|
return CircleAvatar(
|
||||||
backgroundColor: Colors.white54,
|
backgroundColor: Colors.white54,
|
||||||
|
foregroundColor: foregroundColor,
|
||||||
radius: radius,
|
radius: radius,
|
||||||
child: const Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: CircularProgressIndicator(color: Colors.black38, strokeWidth: 2),
|
child: CircularProgressIndicator(color: foregroundColor, strokeWidth: 2),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
errorWidget: (context, error, what) => CircleAvatar(
|
errorWidget: (context, error, what) => CircleAvatar(
|
||||||
radius: radius,
|
radius: radius,
|
||||||
|
foregroundColor: foregroundColor,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
child: Icon(placeholderIcon),
|
child: Icon(placeholderIcon, color: foregroundColor,),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,21 +3,19 @@ import 'dart:convert';
|
||||||
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:contacts_plus_plus/auxiliary.dart';
|
import 'package:contacts_plus_plus/auxiliary.dart';
|
||||||
import 'package:contacts_plus_plus/client_holder.dart';
|
|
||||||
import 'package:contacts_plus_plus/models/photo_asset.dart';
|
import 'package:contacts_plus_plus/models/photo_asset.dart';
|
||||||
import 'package:contacts_plus_plus/models/message.dart';
|
import 'package:contacts_plus_plus/models/message.dart';
|
||||||
import 'package:contacts_plus_plus/string_formatter.dart';
|
import 'package:contacts_plus_plus/string_formatter.dart';
|
||||||
import 'package:contacts_plus_plus/widgets/formatted_text.dart';
|
import 'package:contacts_plus_plus/widgets/formatted_text.dart';
|
||||||
import 'package:contacts_plus_plus/widgets/messages/message_state_indicator.dart';
|
import 'package:contacts_plus_plus/widgets/messages/message_state_indicator.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:photo_view/photo_view.dart';
|
import 'package:photo_view/photo_view.dart';
|
||||||
|
|
||||||
class MessageAsset extends StatelessWidget {
|
class MessageAsset extends StatelessWidget {
|
||||||
MessageAsset({required this.message, super.key});
|
const MessageAsset({required this.message, this.foregroundColor, super.key});
|
||||||
|
|
||||||
final Message message;
|
final Message message;
|
||||||
final DateFormat _dateFormat = DateFormat.Hm();
|
final Color? foregroundColor;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -29,7 +27,6 @@ class MessageAsset extends StatelessWidget {
|
||||||
final formattedName = FormatNode.fromText(content["name"]);
|
final formattedName = FormatNode.fromText(content["name"]);
|
||||||
return Container(
|
return Container(
|
||||||
constraints: const BoxConstraints(maxWidth: 300),
|
constraints: const BoxConstraints(maxWidth: 300),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
CachedNetworkImage(
|
CachedNetworkImage(
|
||||||
|
@ -62,6 +59,8 @@ class MessageAsset extends StatelessWidget {
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||||
child: FormattedText(
|
child: FormattedText(
|
||||||
formattedName,
|
formattedName,
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
|
@ -69,27 +68,11 @@ class MessageAsset extends StatelessWidget {
|
||||||
.of(context)
|
.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.bodySmall
|
.bodySmall
|
||||||
?.copyWith(color: Colors.white60),
|
?.copyWith(color: foregroundColor),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
|
||||||
child: Text(
|
|
||||||
_dateFormat.format(message.sendTime.toLocal()),
|
|
||||||
style: Theme
|
|
||||||
.of(context)
|
|
||||||
.textTheme
|
|
||||||
.labelMedium
|
|
||||||
?.copyWith(color: Colors.white54),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (message.senderId == ClientHolder
|
|
||||||
.of(context)
|
|
||||||
.apiClient
|
|
||||||
.userId) Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 4.0),
|
|
||||||
child: MessageStateIndicator(messageState: message.state),
|
|
||||||
),
|
),
|
||||||
|
MessageStateIndicator(message: message, foregroundColor: foregroundColor,),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io' show Platform;
|
import 'dart:io' show Platform;
|
||||||
|
|
||||||
import 'package:contacts_plus_plus/client_holder.dart';
|
|
||||||
import 'package:contacts_plus_plus/auxiliary.dart';
|
import 'package:contacts_plus_plus/auxiliary.dart';
|
||||||
import 'package:contacts_plus_plus/models/message.dart';
|
import 'package:contacts_plus_plus/models/message.dart';
|
||||||
import 'package:contacts_plus_plus/widgets/messages/message_state_indicator.dart';
|
import 'package:contacts_plus_plus/widgets/messages/message_state_indicator.dart';
|
||||||
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:just_audio/just_audio.dart';
|
import 'package:just_audio/just_audio.dart';
|
||||||
|
|
||||||
class MessageAudioPlayer extends StatefulWidget {
|
class MessageAudioPlayer extends StatefulWidget {
|
||||||
const MessageAudioPlayer({required this.message, super.key});
|
const MessageAudioPlayer({required this.message, this.foregroundColor, super.key});
|
||||||
|
|
||||||
final Message message;
|
final Message message;
|
||||||
|
final Color? foregroundColor;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<MessageAudioPlayer> createState() => _MessageAudioPlayerState();
|
State<MessageAudioPlayer> createState() => _MessageAudioPlayerState();
|
||||||
|
@ -20,7 +20,6 @@ class MessageAudioPlayer extends StatefulWidget {
|
||||||
|
|
||||||
class _MessageAudioPlayerState extends State<MessageAudioPlayer> {
|
class _MessageAudioPlayerState extends State<MessageAudioPlayer> {
|
||||||
final AudioPlayer _audioPlayer = AudioPlayer();
|
final AudioPlayer _audioPlayer = AudioPlayer();
|
||||||
final DateFormat _dateFormat = DateFormat.Hm();
|
|
||||||
double _sliderValue = 0;
|
double _sliderValue = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -75,9 +74,12 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer> {
|
||||||
if (snapshot.hasData) {
|
if (snapshot.hasData) {
|
||||||
final playerState = snapshot.data as PlayerState;
|
final playerState = snapshot.data as PlayerState;
|
||||||
return Column(
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
@ -87,7 +89,11 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer> {
|
||||||
case ProcessingState.buffering:
|
case ProcessingState.buffering:
|
||||||
break;
|
break;
|
||||||
case ProcessingState.ready:
|
case ProcessingState.ready:
|
||||||
|
if (playerState.playing) {
|
||||||
|
_audioPlayer.pause();
|
||||||
|
} else {
|
||||||
_audioPlayer.play();
|
_audioPlayer.play();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case ProcessingState.completed:
|
case ProcessingState.completed:
|
||||||
_audioPlayer.seek(Duration.zero);
|
_audioPlayer.seek(Duration.zero);
|
||||||
|
@ -95,6 +101,7 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer> {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
color: widget.foregroundColor,
|
||||||
icon: SizedBox(
|
icon: SizedBox(
|
||||||
width: 24,
|
width: 24,
|
||||||
height: 24,
|
height: 24,
|
||||||
|
@ -110,9 +117,14 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer> {
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
_sliderValue = (_audioPlayer.position.inMilliseconds /
|
_sliderValue = (_audioPlayer.position.inMilliseconds /
|
||||||
(_audioPlayer.duration?.inMilliseconds ?? 0)).clamp(0, 1);
|
(_audioPlayer.duration?.inMilliseconds ?? 0)).clamp(0, 1);
|
||||||
return StatefulBuilder(
|
return StatefulBuilder( // Not sure if this makes sense here...
|
||||||
builder: (context, setState) {
|
builder: (context, setState) {
|
||||||
return Slider(
|
return SliderTheme(
|
||||||
|
data: SliderThemeData(
|
||||||
|
inactiveTrackColor: widget.foregroundColor?.withAlpha(100),
|
||||||
|
),
|
||||||
|
child: Slider(
|
||||||
|
thumbColor: widget.foregroundColor,
|
||||||
value: _sliderValue,
|
value: _sliderValue,
|
||||||
min: 0.0,
|
min: 0.0,
|
||||||
max: 1.0,
|
max: 1.0,
|
||||||
|
@ -125,6 +137,7 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer> {
|
||||||
milliseconds: (value * (_audioPlayer.duration?.inMilliseconds ?? 0)).round(),
|
milliseconds: (value * (_audioPlayer.duration?.inMilliseconds ?? 0)).round(),
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -134,36 +147,24 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer> {
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 4,),
|
||||||
StreamBuilder(
|
StreamBuilder(
|
||||||
stream: _audioPlayer.positionStream,
|
stream: _audioPlayer.positionStream,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
return Text("${snapshot.data?.format() ?? "??"}/${_audioPlayer.duration?.format() ??
|
return Text("${snapshot.data?.format() ?? "??"}/${_audioPlayer.duration?.format() ??
|
||||||
"??"}");
|
"??"}",
|
||||||
}
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
|
||||||
child: Text(
|
|
||||||
_dateFormat.format(widget.message.sendTime.toLocal()),
|
|
||||||
style: Theme
|
style: Theme
|
||||||
.of(context)
|
.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.labelMedium
|
.bodySmall
|
||||||
?.copyWith(color: Colors.white54),
|
?.copyWith(color: widget.foregroundColor?.withAlpha(150)),
|
||||||
),
|
);
|
||||||
),
|
}
|
||||||
const SizedBox(width: 4,),
|
|
||||||
if (widget.message.senderId == ClientHolder
|
|
||||||
.of(context)
|
|
||||||
.apiClient
|
|
||||||
.userId) Padding(
|
|
||||||
padding: const EdgeInsets.only(right: 12.0),
|
|
||||||
child: MessageStateIndicator(messageState: widget.message.state),
|
|
||||||
),
|
),
|
||||||
|
const Spacer(),
|
||||||
|
MessageStateIndicator(message: widget.message, foregroundColor: widget.foregroundColor,),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,267 +1,44 @@
|
||||||
|
import 'package:contacts_plus_plus/client_holder.dart';
|
||||||
import 'package:contacts_plus_plus/models/message.dart';
|
import 'package:contacts_plus_plus/models/message.dart';
|
||||||
import 'package:contacts_plus_plus/widgets/formatted_text.dart';
|
|
||||||
import 'package:contacts_plus_plus/widgets/messages/message_asset.dart';
|
import 'package:contacts_plus_plus/widgets/messages/message_asset.dart';
|
||||||
import 'package:contacts_plus_plus/widgets/messages/message_audio_player.dart';
|
import 'package:contacts_plus_plus/widgets/messages/message_audio_player.dart';
|
||||||
import 'package:contacts_plus_plus/widgets/messages/message_session_invite.dart';
|
import 'package:contacts_plus_plus/widgets/messages/message_session_invite.dart';
|
||||||
import 'package:contacts_plus_plus/widgets/messages/message_state_indicator.dart';
|
import 'package:contacts_plus_plus/widgets/messages/message_text.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
|
|
||||||
// The way these classes are laid out is pretty unclean, there's a lot of stuff that's shared between the different
|
class MessageBubble extends StatelessWidget {
|
||||||
// subwidgets with a lot of room for deduplication. Should probably redo this some day.
|
const MessageBubble({required this.message, super.key});
|
||||||
|
|
||||||
class MyMessageBubble extends StatelessWidget {
|
|
||||||
MyMessageBubble({required this.message, super.key});
|
|
||||||
|
|
||||||
final Message message;
|
final Message message;
|
||||||
final DateFormat _dateFormat = DateFormat.Hm();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
switch (message.type) {
|
final bool mine = message.senderId == ClientHolder.of(context).apiClient.userId;
|
||||||
case MessageType.sessionInvite:
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
return Row(
|
final foregroundColor = mine ? colorScheme.onPrimaryContainer : colorScheme.onSurfaceVariant;
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
final backgroundColor = mine ? colorScheme.primaryContainer : colorScheme.surfaceVariant;
|
||||||
mainAxisSize: MainAxisSize.min,
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(left: mine ? 32 : 12, bottom: 16, right: mine ? 12 : 32),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: mine ? MainAxisAlignment.end : MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Card(
|
Material(
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
color: backgroundColor,
|
||||||
color: Theme
|
textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(color: foregroundColor),
|
||||||
.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.primaryContainer,
|
|
||||||
margin: const EdgeInsets.only(left: 32, bottom: 16, right: 12),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 12),
|
|
||||||
child: MessageSessionInvite(message: message,),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
case MessageType.object:
|
|
||||||
return Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Card(
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
),
|
|
||||||
color: Theme
|
|
||||||
.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.primaryContainer,
|
|
||||||
margin: const EdgeInsets.only(left: 32, bottom: 16, right: 12),
|
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: const BoxConstraints(maxWidth: 300),
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 12),
|
child: switch (message.type) {
|
||||||
child: MessageAsset(message: message,),
|
MessageType.sessionInvite =>
|
||||||
|
MessageSessionInvite(message: message, foregroundColor: foregroundColor,),
|
||||||
|
MessageType.object => MessageAsset(message: message, foregroundColor: foregroundColor,),
|
||||||
|
MessageType.sound => MessageAudioPlayer(message: message, foregroundColor: foregroundColor,),
|
||||||
|
MessageType.unknown || MessageType.text => MessageText(message: message, foregroundColor: foregroundColor,)
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
case MessageType.unknown:
|
|
||||||
case MessageType.text:
|
|
||||||
return Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Flexible(
|
|
||||||
child: Card(
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
),
|
|
||||||
color: Theme
|
|
||||||
.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.primaryContainer,
|
|
||||||
margin: const EdgeInsets.only(left: 32, bottom: 16, right: 12),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
FormattedText(
|
|
||||||
message.formattedContent,
|
|
||||||
softWrap: true,
|
|
||||||
maxLines: null,
|
|
||||||
style: Theme
|
|
||||||
.of(context)
|
|
||||||
.textTheme
|
|
||||||
.bodyLarge,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 6,),
|
|
||||||
Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
|
||||||
child: Text(
|
|
||||||
_dateFormat.format(message.sendTime.toLocal()),
|
|
||||||
style: Theme
|
|
||||||
.of(context)
|
|
||||||
.textTheme
|
|
||||||
.labelMedium
|
|
||||||
?.copyWith(color: Colors.white54),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
MessageStateIndicator(messageState: message.state),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
case MessageType.sound:
|
|
||||||
return Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Card(
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
),
|
|
||||||
color: Theme
|
|
||||||
.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.primaryContainer,
|
|
||||||
margin: const EdgeInsets.only(left: 32, bottom: 16, right: 12),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 12),
|
|
||||||
child: MessageAudioPlayer(message: message,),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class OtherMessageBubble extends StatelessWidget {
|
|
||||||
OtherMessageBubble({required this.message, super.key});
|
|
||||||
|
|
||||||
final Message message;
|
|
||||||
final DateFormat _dateFormat = DateFormat.Hm();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
switch (message.type) {
|
|
||||||
case MessageType.sessionInvite:
|
|
||||||
return Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Card(
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
),
|
|
||||||
color: Theme
|
|
||||||
.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.secondaryContainer,
|
|
||||||
margin: const EdgeInsets.only(right: 32, bottom: 16, left: 12),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
||||||
child: MessageSessionInvite(message: message,),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
case MessageType.object:
|
|
||||||
return Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Card(
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
),
|
|
||||||
color: Theme
|
|
||||||
.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.secondaryContainer,
|
|
||||||
margin: const EdgeInsets.only(right: 32, bottom: 16, left: 12),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
||||||
child: MessageAsset(message: message,),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
case MessageType.unknown:
|
|
||||||
case MessageType.text:
|
|
||||||
return Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Flexible(
|
|
||||||
child: Card(
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
),
|
|
||||||
color: Theme
|
|
||||||
.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.secondaryContainer,
|
|
||||||
margin: const EdgeInsets.only(right: 32, bottom: 16, left: 12),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
FormattedText(
|
|
||||||
message.formattedContent,
|
|
||||||
softWrap: true,
|
|
||||||
maxLines: null,
|
|
||||||
style: Theme
|
|
||||||
.of(context)
|
|
||||||
.textTheme
|
|
||||||
.bodyLarge,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 6,),
|
|
||||||
Text(
|
|
||||||
_dateFormat.format(message.sendTime.toLocal()),
|
|
||||||
style: Theme
|
|
||||||
.of(context)
|
|
||||||
.textTheme
|
|
||||||
.labelMedium
|
|
||||||
?.copyWith(color: Colors.white54),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
case MessageType.sound:
|
|
||||||
return Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Card(
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
),
|
|
||||||
color: Theme
|
|
||||||
.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.secondaryContainer,
|
|
||||||
margin: const EdgeInsets.only(right: 32, bottom: 16, left: 12),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
||||||
child: MessageAudioPlayer(message: message,),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:contacts_plus_plus/client_holder.dart';
|
|
||||||
import 'package:contacts_plus_plus/auxiliary.dart';
|
import 'package:contacts_plus_plus/auxiliary.dart';
|
||||||
import 'package:contacts_plus_plus/models/message.dart';
|
import 'package:contacts_plus_plus/models/message.dart';
|
||||||
import 'package:contacts_plus_plus/models/session.dart';
|
import 'package:contacts_plus_plus/models/session.dart';
|
||||||
|
@ -9,41 +8,37 @@ import 'package:contacts_plus_plus/widgets/generic_avatar.dart';
|
||||||
import 'package:contacts_plus_plus/widgets/messages/messages_session_header.dart';
|
import 'package:contacts_plus_plus/widgets/messages/messages_session_header.dart';
|
||||||
import 'package:contacts_plus_plus/widgets/messages/message_state_indicator.dart';
|
import 'package:contacts_plus_plus/widgets/messages/message_state_indicator.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
|
|
||||||
class MessageSessionInvite extends StatelessWidget {
|
class MessageSessionInvite extends StatelessWidget {
|
||||||
MessageSessionInvite({required this.message, super.key});
|
const MessageSessionInvite({required this.message, this.foregroundColor, super.key});
|
||||||
final DateFormat _dateFormat = DateFormat.Hm();
|
|
||||||
|
final Color? foregroundColor;
|
||||||
final Message message;
|
final Message message;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final sessionInfo = Session.fromMap(jsonDecode(message.content));
|
final sessionInfo = Session.fromMap(jsonDecode(message.content));
|
||||||
return TextButton(
|
return Container(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 300),
|
||||||
|
child: TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showDialog(context: context, builder: (context) => SessionPopup(session: sessionInfo));
|
showDialog(context: context, builder: (context) => SessionPopup(session: sessionInfo));
|
||||||
},
|
},
|
||||||
style: TextButton.styleFrom(padding: EdgeInsets.zero),
|
style: TextButton.styleFrom(padding: EdgeInsets.zero),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.only(left: 10),
|
padding: const EdgeInsets.only(left: 4),
|
||||||
constraints: const BoxConstraints(maxWidth: 300),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.max,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 4),
|
|
||||||
child: FormattedText(sessionInfo.formattedName, maxLines: null, softWrap: true, style: Theme.of(context).textTheme.titleMedium,),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
@ -51,41 +46,45 @@ class MessageSessionInvite extends StatelessWidget {
|
||||||
GenericAvatar(
|
GenericAvatar(
|
||||||
imageUri: Aux.neosDbToHttp(Aux.neosDbToHttp(sessionInfo.thumbnail)),
|
imageUri: Aux.neosDbToHttp(Aux.neosDbToHttp(sessionInfo.thumbnail)),
|
||||||
placeholderIcon: Icons.no_photography,
|
placeholderIcon: Icons.no_photography,
|
||||||
|
foregroundColor: foregroundColor,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4,),
|
const SizedBox(height: 4,),
|
||||||
Text("${sessionInfo.sessionUsers.length}/${sessionInfo.maxUsers}")
|
Text("${sessionInfo.sessionUsers.length}/${sessionInfo.maxUsers}", style: Theme
|
||||||
|
.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyMedium
|
||||||
|
?.copyWith(color: foregroundColor),)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: FormattedText(sessionInfo.formattedName, maxLines: null, softWrap: true, textAlign: TextAlign.start, style: Theme
|
||||||
|
.of(context)
|
||||||
|
.textTheme
|
||||||
|
.titleMedium?.copyWith(color: foregroundColor),),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8,),
|
const SizedBox(height: 8,),
|
||||||
Row(
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: Text("Hosted by ${sessionInfo.hostUsername}", overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.bodySmall?.copyWith(color: Colors.white60),)),
|
Text("Hosted by ${sessionInfo.hostUsername}", overflow: TextOverflow.ellipsis, style: Theme
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
|
||||||
child: Text(
|
|
||||||
_dateFormat.format(message.sendTime.toLocal()),
|
|
||||||
style: Theme
|
|
||||||
.of(context)
|
.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.labelMedium
|
.bodySmall
|
||||||
?.copyWith(color: Colors.white54),
|
?.copyWith(color: foregroundColor?.withAlpha(150)),),
|
||||||
),
|
MessageStateIndicator(message: message, foregroundColor: foregroundColor,),
|
||||||
),
|
|
||||||
const SizedBox(width: 4,),
|
|
||||||
if (message.senderId == ClientHolder.of(context).apiClient.userId) Padding(
|
|
||||||
padding: const EdgeInsets.only(right: 12.0),
|
|
||||||
child: MessageStateIndicator(messageState: message.state),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,29 +1,45 @@
|
||||||
|
import 'package:contacts_plus_plus/client_holder.dart';
|
||||||
import 'package:contacts_plus_plus/models/message.dart';
|
import 'package:contacts_plus_plus/models/message.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
class MessageStateIndicator extends StatelessWidget {
|
class MessageStateIndicator extends StatelessWidget {
|
||||||
const MessageStateIndicator({required this.messageState, super.key});
|
MessageStateIndicator({required this.message, this.foregroundColor, super.key});
|
||||||
|
|
||||||
final MessageState messageState;
|
final DateFormat _dateFormat = DateFormat.Hm();
|
||||||
|
final Message message;
|
||||||
|
final Color? foregroundColor;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
late final IconData icon;
|
final color = foregroundColor?.withAlpha(150);
|
||||||
switch (messageState) {
|
return Row(
|
||||||
case MessageState.local:
|
children: [
|
||||||
icon = Icons.alarm;
|
Padding(
|
||||||
break;
|
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||||
case MessageState.sent:
|
child: Text(
|
||||||
icon = Icons.done;
|
_dateFormat.format(message.sendTime.toLocal()),
|
||||||
break;
|
style: Theme
|
||||||
case MessageState.read:
|
.of(context)
|
||||||
icon = Icons.done_all;
|
.textTheme
|
||||||
break;
|
.labelMedium
|
||||||
}
|
?.copyWith(color: color),
|
||||||
return Icon(
|
),
|
||||||
icon,
|
),
|
||||||
|
if (message.senderId == ClientHolder
|
||||||
|
.of(context)
|
||||||
|
.apiClient
|
||||||
|
.userId)
|
||||||
|
Icon(
|
||||||
|
switch (message.state) {
|
||||||
|
MessageState.local => Icons.alarm,
|
||||||
|
MessageState.sent => Icons.done,
|
||||||
|
MessageState.read => Icons.done_all,
|
||||||
|
},
|
||||||
size: 12,
|
size: 12,
|
||||||
|
color: color,
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
41
lib/widgets/messages/message_text.dart
Normal file
41
lib/widgets/messages/message_text.dart
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import 'package:contacts_plus_plus/models/message.dart';
|
||||||
|
import 'package:contacts_plus_plus/widgets/formatted_text.dart';
|
||||||
|
import 'package:contacts_plus_plus/widgets/messages/message_state_indicator.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class MessageText extends StatelessWidget {
|
||||||
|
const MessageText({required this.message, this.foregroundColor, super.key});
|
||||||
|
|
||||||
|
final Message message;
|
||||||
|
final Color? foregroundColor;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 300),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
child: FormattedText(
|
||||||
|
message.formattedContent,
|
||||||
|
softWrap: true,
|
||||||
|
maxLines: null,
|
||||||
|
style: Theme
|
||||||
|
.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyLarge
|
||||||
|
?.copyWith(color: foregroundColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
MessageStateIndicator(message: message, foregroundColor: foregroundColor,),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -163,16 +163,13 @@ class _MessagesListState extends State<MessagesList> {
|
||||||
itemCount: cache.messages.length,
|
itemCount: cache.messages.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final entry = cache.messages[index];
|
final entry = cache.messages[index];
|
||||||
final widget = entry.senderId == apiClient.userId
|
|
||||||
? MyMessageBubble(message: entry)
|
|
||||||
: OtherMessageBubble(message: entry);
|
|
||||||
if (index == cache.messages.length - 1) {
|
if (index == cache.messages.length - 1) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(top: 12),
|
padding: const EdgeInsets.only(top: 12),
|
||||||
child: widget,
|
child: MessageBubble(message: entry,),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return widget;
|
return MessageBubble(message: entry,);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -6,10 +6,14 @@
|
||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <dynamic_color/dynamic_color_plugin.h>
|
||||||
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
||||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||||
|
|
||||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
|
g_autoptr(FlPluginRegistrar) dynamic_color_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "DynamicColorPlugin");
|
||||||
|
dynamic_color_plugin_register_with_registrar(dynamic_color_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
|
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
|
||||||
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
|
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
dynamic_color
|
||||||
flutter_secure_storage_linux
|
flutter_secure_storage_linux
|
||||||
url_launcher_linux
|
url_launcher_linux
|
||||||
)
|
)
|
||||||
|
|
10
pubspec.lock
10
pubspec.lock
|
@ -121,6 +121,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.8"
|
version: "0.7.8"
|
||||||
|
dynamic_color:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: dynamic_color
|
||||||
|
sha256: "74dff1435a695887ca64899b8990004f8d1232b0e84bfc4faa1fdda7c6f57cc1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.6.5"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -767,4 +775,4 @@ packages:
|
||||||
version: "6.3.0"
|
version: "6.3.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.0.0 <4.0.0"
|
dart: ">=3.0.0 <4.0.0"
|
||||||
flutter: ">=3.3.0"
|
flutter: ">=3.4.0-17.0.pre"
|
||||||
|
|
|
@ -55,6 +55,7 @@ dependencies:
|
||||||
provider: ^6.0.5
|
provider: ^6.0.5
|
||||||
photo_view: ^0.14.0
|
photo_view: ^0.14.0
|
||||||
color: ^3.0.0
|
color: ^3.0.0
|
||||||
|
dynamic_color: ^1.6.5
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
@ -6,10 +6,13 @@
|
||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <dynamic_color/dynamic_color_plugin_c_api.h>
|
||||||
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||||
#include <url_launcher_windows/url_launcher_windows.h>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
|
DynamicColorPluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("DynamicColorPluginCApi"));
|
||||||
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
||||||
UrlLauncherWindowsRegisterWithRegistrar(
|
UrlLauncherWindowsRegisterWithRegistrar(
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
dynamic_color
|
||||||
flutter_secure_storage_windows
|
flutter_secure_storage_windows
|
||||||
url_launcher_windows
|
url_launcher_windows
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue