Minor message view improvements
This commit is contained in:
parent
c12748de6c
commit
1b7af5f4a7
9 changed files with 278 additions and 134 deletions
|
@ -1,5 +1,4 @@
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
import 'dart:io' show Platform;
|
|
||||||
|
|
||||||
import 'package:contacts_plus_plus/apis/github_api.dart';
|
import 'package:contacts_plus_plus/apis/github_api.dart';
|
||||||
import 'package:contacts_plus_plus/client_holder.dart';
|
import 'package:contacts_plus_plus/client_holder.dart';
|
||||||
|
|
|
@ -49,7 +49,7 @@ class Message implements Comparable {
|
||||||
final MessageState state;
|
final MessageState state;
|
||||||
|
|
||||||
Message({required this.id, required this.recipientId, required this.senderId, required this.type,
|
Message({required this.id, required this.recipientId, required this.senderId, required this.type,
|
||||||
required this.content, required DateTime sendTime, this.state=MessageState.local})
|
required this.content, required DateTime sendTime, required this.state})
|
||||||
: formattedContent = FormatNode.fromText(content), sendTime = sendTime.toUtc();
|
: formattedContent = FormatNode.fromText(content), sendTime = sendTime.toUtc();
|
||||||
|
|
||||||
factory Message.fromMap(Map map, {MessageState? withState}) {
|
factory Message.fromMap(Map map, {MessageState? withState}) {
|
||||||
|
@ -65,7 +65,7 @@ class Message implements Comparable {
|
||||||
type: type,
|
type: type,
|
||||||
content: map["content"],
|
content: map["content"],
|
||||||
sendTime: DateTime.parse(map["sendTime"]),
|
sendTime: DateTime.parse(map["sendTime"]),
|
||||||
state: withState ?? (map["readTime"] != null ? MessageState.read : MessageState.local)
|
state: withState ?? (map["readTime"] != null ? MessageState.read : MessageState.sent)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ 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';
|
||||||
import 'package:contacts_plus_plus/clients/messaging_client.dart';
|
import 'package:contacts_plus_plus/clients/messaging_client.dart';
|
||||||
import 'package:contacts_plus_plus/models/friend.dart';
|
import 'package:contacts_plus_plus/models/friend.dart';
|
||||||
import 'package:contacts_plus_plus/models/personal_profile.dart';
|
|
||||||
import 'package:contacts_plus_plus/widgets/default_error_widget.dart';
|
import 'package:contacts_plus_plus/widgets/default_error_widget.dart';
|
||||||
import 'package:contacts_plus_plus/widgets/friends/expanding_input_fab.dart';
|
import 'package:contacts_plus_plus/widgets/friends/expanding_input_fab.dart';
|
||||||
import 'package:contacts_plus_plus/widgets/friends/friend_list_tile.dart';
|
import 'package:contacts_plus_plus/widgets/friends/friend_list_tile.dart';
|
||||||
|
@ -42,7 +41,6 @@ class _FriendsListState extends State<FriendsList> {
|
||||||
final clientHolder = ClientHolder.of(context);
|
final clientHolder = ClientHolder.of(context);
|
||||||
if (_clientHolder != clientHolder) {
|
if (_clientHolder != clientHolder) {
|
||||||
_clientHolder = clientHolder;
|
_clientHolder = clientHolder;
|
||||||
final apiClient = _clientHolder!.apiClient;
|
|
||||||
_refreshUserStatus();
|
_refreshUserStatus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,8 +53,12 @@ class _MessageAttachmentListState extends State<MessageAttachmentList> {
|
||||||
return LinearGradient(
|
return LinearGradient(
|
||||||
begin: Alignment.centerLeft,
|
begin: Alignment.centerLeft,
|
||||||
end: Alignment.centerRight,
|
end: Alignment.centerRight,
|
||||||
colors: [Colors.transparent, Colors.transparent, Colors.transparent, Theme.of(context).colorScheme.background],
|
colors: [Colors.transparent, Colors.transparent, Colors.transparent, Theme
|
||||||
stops: [0.0, 0.0, _showShadow ? 0.96 : 1.0, 1.0], // 10% purple, 80% transparent, 10% purple
|
.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.background
|
||||||
|
],
|
||||||
|
stops: [0.0, 0.0, _showShadow ? 0.90 : 1.0, 1.0], // 10% purple, 80% transparent, 10% purple
|
||||||
).createShader(bounds);
|
).createShader(bounds);
|
||||||
},
|
},
|
||||||
blendMode: BlendMode.dstOut,
|
blendMode: BlendMode.dstOut,
|
||||||
|
@ -66,50 +70,50 @@ class _MessageAttachmentListState extends State<MessageAttachmentList> {
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 4.0, right: 4.0, top: 4.0),
|
padding: const EdgeInsets.only(left: 4.0, right: 4.0, top: 4.0),
|
||||||
child: TextButton.icon(
|
child: TextButton.icon(
|
||||||
onPressed: widget.disabled ? null : () {
|
onPressed: widget.disabled ? null : () {
|
||||||
showDialog(context: context, builder: (context) =>
|
showDialog(context: context, builder: (context) =>
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
title: const Text("Remove attachment"),
|
title: const Text("Remove attachment"),
|
||||||
content: Text(
|
content: Text(
|
||||||
"This will remove attachment '${basename(
|
"This will remove attachment '${basename(
|
||||||
file.$2.path)}', are you sure?"),
|
file.$2.path)}', are you sure?"),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
child: const Text("No"),
|
child: const Text("No"),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
_loadedFiles.remove(file);
|
_loadedFiles.remove(file);
|
||||||
await widget.onChange(_loadedFiles);
|
await widget.onChange(_loadedFiles);
|
||||||
},
|
},
|
||||||
child: const Text("Yes"),
|
child: const Text("Yes"),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
foregroundColor: Theme
|
foregroundColor: Theme
|
||||||
.of(context)
|
.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
.onBackground,
|
.onBackground,
|
||||||
side: BorderSide(
|
side: BorderSide(
|
||||||
color: Theme
|
color: Theme
|
||||||
.of(context)
|
.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
.primary,
|
.primary,
|
||||||
width: 1
|
width: 1
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
label: Text(basename(file.$2.path)),
|
||||||
label: Text(basename(file.$2.path)),
|
icon: switch (file.$1) {
|
||||||
icon: switch (file.$1) {
|
FileType.image => const Icon(Icons.image),
|
||||||
FileType.image => const Icon(Icons.image),
|
_ => const Icon(Icons.attach_file)
|
||||||
_ => const Icon(Icons.attach_file)
|
}
|
||||||
}
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
).toList()
|
).toList()
|
||||||
|
@ -117,43 +121,189 @@ class _MessageAttachmentListState extends State<MessageAttachmentList> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
PopupMenuButton<DocumentType>(
|
||||||
onPressed: widget.disabled ? null : () async {
|
offset: const Offset(0, -64),
|
||||||
final result = await FilePicker.platform.pickFiles(type: FileType.image, allowMultiple: true);
|
constraints: const BoxConstraints.tightFor(width: 48 * 3, height: 64),
|
||||||
if (result != null) {
|
shadowColor: Colors.transparent,
|
||||||
setState(() {
|
position: PopupMenuPosition.over,
|
||||||
_loadedFiles.addAll(
|
color: Colors.transparent,
|
||||||
result.files.map((e) => e.path != null ? (FileType.image, File(e.path!)) : null)
|
enableFeedback: true,
|
||||||
.whereNotNull());
|
padding: EdgeInsets.zero,
|
||||||
});
|
surfaceTintColor: Colors.transparent,
|
||||||
}
|
iconSize: 24,
|
||||||
},
|
itemBuilder: (context) =>
|
||||||
icon: const Icon(Icons.add_photo_alternate),
|
[
|
||||||
),
|
PopupMenuItem(
|
||||||
IconButton(
|
padding: EdgeInsets.zero,
|
||||||
onPressed: widget.disabled ? null : () async {
|
child: Row(
|
||||||
final picture = await Navigator.of(context).push(
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
MaterialPageRoute(builder: (context) => const MessageCameraView()));
|
children: [
|
||||||
if (picture != null) {
|
IconButton(
|
||||||
_loadedFiles.add(picture);
|
iconSize: 24,
|
||||||
await widget.onChange(_loadedFiles);
|
style: IconButton.styleFrom(
|
||||||
}
|
backgroundColor: Theme
|
||||||
},
|
.of(context)
|
||||||
icon: const Icon(Icons.add_a_photo),
|
.colorScheme
|
||||||
),
|
.surface,
|
||||||
IconButton(
|
foregroundColor: Theme
|
||||||
onPressed: widget.disabled ? null : () async {
|
.of(context)
|
||||||
final result = await FilePicker.platform.pickFiles(type: FileType.any, allowMultiple: true);
|
.colorScheme
|
||||||
if (result != null) {
|
.onSurface,
|
||||||
setState(() {
|
side: BorderSide(
|
||||||
_loadedFiles.addAll(
|
width: 1,
|
||||||
result.files.map((e) => e.path != null ? (FileType.any, File(e.path!)) : null).whereNotNull());
|
color: Theme
|
||||||
});
|
.of(context)
|
||||||
}
|
.colorScheme
|
||||||
},
|
.secondary,
|
||||||
icon: const Icon(Icons.file_present_rounded),
|
)
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
onPressed: () async {
|
||||||
|
final result = await FilePicker.platform.pickFiles(type: FileType.image, allowMultiple: true);
|
||||||
|
if (result != null) {
|
||||||
|
setState(() {
|
||||||
|
_loadedFiles.addAll(
|
||||||
|
result.files.map((e) => e.path != null ? (FileType.image, File(e.path!)) : null)
|
||||||
|
.whereNotNull());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.image,),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
iconSize: 24,
|
||||||
|
style: IconButton.styleFrom(
|
||||||
|
backgroundColor: Theme
|
||||||
|
.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.surface,
|
||||||
|
foregroundColor: Theme
|
||||||
|
.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSurface,
|
||||||
|
side: BorderSide(
|
||||||
|
width: 1,
|
||||||
|
color: Theme
|
||||||
|
.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.secondary,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
onPressed: () async {
|
||||||
|
final picture = await Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(builder: (context) => const MessageCameraView())) as File?;
|
||||||
|
if (picture != null) {
|
||||||
|
_loadedFiles.add((FileType.image, picture));
|
||||||
|
await widget.onChange(_loadedFiles);
|
||||||
|
}
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.camera,),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
iconSize: 24,
|
||||||
|
style: IconButton.styleFrom(
|
||||||
|
backgroundColor: Theme
|
||||||
|
.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.surface,
|
||||||
|
foregroundColor: Theme
|
||||||
|
.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSurface,
|
||||||
|
side: BorderSide(
|
||||||
|
width: 1,
|
||||||
|
color: Theme
|
||||||
|
.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.secondary,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
onPressed: () async {
|
||||||
|
final result = await FilePicker.platform.pickFiles(type: FileType.any, allowMultiple: true);
|
||||||
|
if (result != null) {
|
||||||
|
setState(() {
|
||||||
|
_loadedFiles.addAll(
|
||||||
|
result.files.map((e) => e.path != null ? (FileType.any, File(e.path!)) : null)
|
||||||
|
.whereNotNull());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.file_present_rounded,),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
icon: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(64),
|
||||||
|
border: Border.all(
|
||||||
|
color: Theme
|
||||||
|
.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.add,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum DocumentType {
|
||||||
|
gallery,
|
||||||
|
camera,
|
||||||
|
rawFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PopupMenuIcon<T> extends PopupMenuEntry<T> {
|
||||||
|
const PopupMenuIcon({this.radius=24, this.value, required this.icon, this.onPressed, super.key});
|
||||||
|
|
||||||
|
final T? value;
|
||||||
|
final double radius;
|
||||||
|
final Widget icon;
|
||||||
|
final void Function()? onPressed;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => _PopupMenuIconState();
|
||||||
|
|
||||||
|
@override
|
||||||
|
double get height => radius;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool represents(T? value) => this.value == value;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PopupMenuIconState extends State<PopupMenuIcon> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(128),
|
||||||
|
child: Container(
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 2),
|
||||||
|
margin: const EdgeInsets.all(1),
|
||||||
|
child: InkWell(
|
||||||
|
child: widget.icon,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -88,8 +88,26 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer> with WidgetsBin
|
||||||
return FutureBuilder(
|
return FutureBuilder(
|
||||||
future: _audioFileFuture,
|
future: _audioFileFuture,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasData) {
|
if (snapshot.hasError) {
|
||||||
return IntrinsicWidth(
|
return SizedBox(
|
||||||
|
width: 300,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.volume_off),
|
||||||
|
const SizedBox(width: 8,),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
"Failed to load voice message: ${snapshot.error}",
|
||||||
|
maxLines: 4,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
softWrap: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return IntrinsicWidth(
|
||||||
child: StreamBuilder<PlayerState>(
|
child: StreamBuilder<PlayerState>(
|
||||||
stream: _audioPlayer.playerStateStream,
|
stream: _audioPlayer.playerStateStream,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
|
@ -103,7 +121,7 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer> with WidgetsBin
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
snapshot.hasData ? IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
switch (playerState.processingState) {
|
switch (playerState.processingState) {
|
||||||
case ProcessingState.idle:
|
case ProcessingState.idle:
|
||||||
|
@ -124,16 +142,15 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer> with WidgetsBin
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
color: widget.foregroundColor,
|
color: widget.foregroundColor,
|
||||||
icon: SizedBox(
|
icon: SizedBox.square(
|
||||||
width: 24,
|
dimension: 24,
|
||||||
height: 24,
|
|
||||||
child: playerState.processingState == ProcessingState.loading
|
child: playerState.processingState == ProcessingState.loading
|
||||||
? const Center(child: CircularProgressIndicator(),)
|
? const Center(child: CircularProgressIndicator(),)
|
||||||
: Icon(((_audioPlayer.duration ?? Duration.zero) - _audioPlayer.position).inMilliseconds <
|
: Icon(((_audioPlayer.duration ?? Duration.zero) - _audioPlayer.position).inMilliseconds <
|
||||||
10 ? Icons.replay
|
10 ? Icons.replay
|
||||||
: (playerState.playing ? Icons.pause : Icons.play_arrow)),
|
: (playerState.playing ? Icons.pause : Icons.play_arrow)),
|
||||||
),
|
),
|
||||||
),
|
) : const SizedBox.square(dimension: 24, child: CircularProgressIndicator(),),
|
||||||
StreamBuilder(
|
StreamBuilder(
|
||||||
stream: _audioPlayer.positionStream,
|
stream: _audioPlayer.positionStream,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
|
@ -200,36 +217,6 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer> with WidgetsBin
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else if (snapshot.hasError) {
|
|
||||||
return SizedBox(
|
|
||||||
width: 300,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.volume_off),
|
|
||||||
const SizedBox(width: 8,),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
"Failed to load voice message: ${snapshot.error}",
|
|
||||||
maxLines: 4,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
softWrap: true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return const Padding(
|
|
||||||
padding: EdgeInsets.all(8.0),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Icon(Icons.volume_up),
|
|
||||||
SizedBox(width: 8,),
|
|
||||||
Center(child: CircularProgressIndicator()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ class _MessageRecordButtonState extends State<MessageRecordButton> {
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTapDown: widget.disabled ? null : (_) async {
|
onTapDown: widget.disabled ? null : (_) async {
|
||||||
HapticFeedback.vibrate();
|
HapticFeedback.vibrate();
|
||||||
|
/*
|
||||||
widget.onRecordStart?.call();
|
widget.onRecordStart?.call();
|
||||||
final dir = await getTemporaryDirectory();
|
final dir = await getTemporaryDirectory();
|
||||||
await _recorder.start(
|
await _recorder.start(
|
||||||
|
@ -41,16 +42,19 @@ class _MessageRecordButtonState extends State<MessageRecordButton> {
|
||||||
encoder: AudioEncoder.opus,
|
encoder: AudioEncoder.opus,
|
||||||
samplingRate: 44100,
|
samplingRate: 44100,
|
||||||
);
|
);
|
||||||
|
*/
|
||||||
},
|
},
|
||||||
onTapUp: (_) async {
|
onLongPressUp: () async {
|
||||||
|
/*
|
||||||
if (await _recorder.isRecording()) {
|
if (await _recorder.isRecording()) {
|
||||||
final recording = await _recorder.stop();
|
final recording = await _recorder.stop();
|
||||||
widget.onRecordEnd?.call(recording == null ? null : File(recording));
|
widget.onRecordEnd?.call(recording == null ? null : File(recording));
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
},
|
},
|
||||||
child: const Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8),
|
||||||
child: Icon(Icons.mic_outlined),
|
child: Icon(Icons.mic_outlined, size: 28, color: Theme.of(context).colorScheme.onSurface,),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -98,6 +98,7 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
|
||||||
type: MessageType.text,
|
type: MessageType.text,
|
||||||
content: content,
|
content: content,
|
||||||
sendTime: DateTime.now().toUtc(),
|
sendTime: DateTime.now().toUtc(),
|
||||||
|
state: MessageState.local,
|
||||||
);
|
);
|
||||||
mClient.sendMessage(message);
|
mClient.sendMessage(message);
|
||||||
_messageTextController.clear();
|
_messageTextController.clear();
|
||||||
|
@ -119,6 +120,7 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
|
||||||
type: MessageType.object,
|
type: MessageType.object,
|
||||||
content: jsonEncode(record.toMap()),
|
content: jsonEncode(record.toMap()),
|
||||||
sendTime: DateTime.now().toUtc(),
|
sendTime: DateTime.now().toUtc(),
|
||||||
|
state: MessageState.local
|
||||||
);
|
);
|
||||||
mClient.sendMessage(message);
|
mClient.sendMessage(message);
|
||||||
_messageTextController.clear();
|
_messageTextController.clear();
|
||||||
|
@ -140,6 +142,7 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
|
||||||
type: MessageType.sound,
|
type: MessageType.sound,
|
||||||
content: jsonEncode(record.toMap()),
|
content: jsonEncode(record.toMap()),
|
||||||
sendTime: DateTime.now().toUtc(),
|
sendTime: DateTime.now().toUtc(),
|
||||||
|
state: MessageState.local,
|
||||||
);
|
);
|
||||||
mClient.sendMessage(message);
|
mClient.sendMessage(message);
|
||||||
_messageTextController.clear();
|
_messageTextController.clear();
|
||||||
|
@ -161,6 +164,7 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
|
||||||
type: MessageType.object,
|
type: MessageType.object,
|
||||||
content: jsonEncode(record.toMap()),
|
content: jsonEncode(record.toMap()),
|
||||||
sendTime: DateTime.now().toUtc(),
|
sendTime: DateTime.now().toUtc(),
|
||||||
|
state: MessageState.local,
|
||||||
);
|
);
|
||||||
mClient.sendMessage(message);
|
mClient.sendMessage(message);
|
||||||
_messageTextController.clear();
|
_messageTextController.clear();
|
||||||
|
@ -195,7 +199,8 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
|
||||||
.of(context)
|
.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
.onSecondaryContainer
|
.onSecondaryContainer
|
||||||
.withAlpha(150),),
|
.withAlpha(150),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -452,7 +457,7 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
|
||||||
_attachmentPickerOpen = true;
|
_attachmentPickerOpen = true;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.attach_file),
|
icon: const Icon(Icons.attach_file, size: 28,),
|
||||||
) :
|
) :
|
||||||
IconButton(
|
IconButton(
|
||||||
key: const ValueKey("remove-attachment-icon"),
|
key: const ValueKey("remove-attachment-icon"),
|
||||||
|
@ -487,12 +492,12 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.close),
|
icon: const Icon(Icons.close, size: 28,),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4),
|
||||||
child: TextField(
|
child: TextField(
|
||||||
enabled: cache != null && cache.error == null && !_isSending,
|
enabled: cache != null && cache.error == null && !_isSending,
|
||||||
autocorrect: true,
|
autocorrect: true,
|
||||||
|
@ -518,13 +523,13 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(24)
|
borderRadius: BorderRadius.circular(24)
|
||||||
)
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 8, right: 4.0),
|
padding: const EdgeInsets.all(4),
|
||||||
child: AnimatedSwitcher(
|
child: AnimatedSwitcher(
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
transitionBuilder: (Widget child, Animation<double> animation) =>
|
transitionBuilder: (Widget child, Animation<double> animation) =>
|
||||||
|
@ -533,6 +538,7 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
|
||||||
child: _hasText || _loadedFiles.isNotEmpty ? IconButton(
|
child: _hasText || _loadedFiles.isNotEmpty ? IconButton(
|
||||||
key: const ValueKey("send-button"),
|
key: const ValueKey("send-button"),
|
||||||
splashRadius: 24,
|
splashRadius: 24,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
onPressed: _isSending ? null : () async {
|
onPressed: _isSending ? null : () async {
|
||||||
final sMsgnr = ScaffoldMessenger.of(context);
|
final sMsgnr = ScaffoldMessenger.of(context);
|
||||||
final settings = ClientHolder
|
final settings = ClientHolder
|
||||||
|
@ -584,7 +590,6 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
|
||||||
_sendProgress = null;
|
_sendProgress = null;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
iconSize: 28,
|
|
||||||
icon: const Icon(Icons.send),
|
icon: const Icon(Icons.send),
|
||||||
) : MessageRecordButton(
|
) : MessageRecordButton(
|
||||||
key: const ValueKey("mic-button"),
|
key: const ValueKey("mic-button"),
|
||||||
|
|
|
@ -138,7 +138,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.3+4"
|
version: "0.3.3+4"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: crypto
|
name: crypto
|
||||||
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
|
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
|
||||||
|
|
|
@ -60,6 +60,7 @@ dependencies:
|
||||||
record: ^4.4.4
|
record: ^4.4.4
|
||||||
camera: ^0.10.5
|
camera: ^0.10.5
|
||||||
path_provider: ^2.0.15
|
path_provider: ^2.0.15
|
||||||
|
crypto: ^3.0.3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Reference in a new issue