Merge branch 'main' into flutter-3.22
This commit is contained in:
commit
d9edf9f05b
17 changed files with 403 additions and 336 deletions
|
|
@ -610,7 +610,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}
|
||||
}
|
||||
|
||||
if (await Record().hasPermission() == false) return;
|
||||
if (await AudioRecorder().hasPermission() == false) return;
|
||||
final result = await showDialog<RecordingResult>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
|
|
|
|||
|
|
@ -150,7 +150,8 @@ class ChatView extends StatelessWidget {
|
|||
}
|
||||
},
|
||||
child: StreamBuilder(
|
||||
stream: controller.room.onUpdate.stream
|
||||
stream: controller.room.client.onRoomState.stream
|
||||
.where((update) => update.roomId == controller.room.id)
|
||||
.rateLimit(const Duration(seconds: 1)),
|
||||
builder: (context, snapshot) => FutureBuilder(
|
||||
future: controller.loadTimelineFuture,
|
||||
|
|
|
|||
|
|
@ -144,255 +144,276 @@ class Message extends StatelessWidget {
|
|||
setState(resetAnimateIn);
|
||||
});
|
||||
}
|
||||
return AnimatedSlide(
|
||||
offset: Offset(0, animateIn ? 1 : 0),
|
||||
return AnimatedSize(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: InkWell(
|
||||
onTap: () => onSelect(event),
|
||||
onLongPress: () => onSelect(event),
|
||||
borderRadius:
|
||||
BorderRadius.circular(AppConfig.borderRadius / 2),
|
||||
child: Material(
|
||||
borderRadius:
|
||||
BorderRadius.circular(AppConfig.borderRadius / 2),
|
||||
color: selected
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.secondaryContainer
|
||||
.withAlpha(100)
|
||||
: highlightMarker
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.tertiaryContainer
|
||||
.withAlpha(100)
|
||||
: Colors.transparent,
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: rowMainAxisAlignment,
|
||||
children: [
|
||||
if (longPressSelect)
|
||||
SizedBox(
|
||||
height: 32,
|
||||
width: Avatar.defaultSize,
|
||||
child: Checkbox.adaptive(
|
||||
value: selected,
|
||||
shape: const CircleBorder(),
|
||||
onChanged: (_) => onSelect(event),
|
||||
),
|
||||
)
|
||||
else if (nextEventSameSender || ownMessage)
|
||||
SizedBox(
|
||||
width: Avatar.defaultSize,
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: event.status == EventStatus.error
|
||||
? const Icon(Icons.error, color: Colors.red)
|
||||
: event.fileSendingStatus != null
|
||||
? const CircularProgressIndicator.adaptive(
|
||||
strokeWidth: 1,
|
||||
)
|
||||
: null,
|
||||
clipBehavior: Clip.none,
|
||||
alignment: ownMessage ? Alignment.bottomRight : Alignment.bottomLeft,
|
||||
child: animateIn
|
||||
? const SizedBox(height: 0, width: double.infinity)
|
||||
: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: InkWell(
|
||||
onTap: () => onSelect(event),
|
||||
onLongPress: () => onSelect(event),
|
||||
borderRadius:
|
||||
BorderRadius.circular(AppConfig.borderRadius / 2),
|
||||
child: Material(
|
||||
borderRadius:
|
||||
BorderRadius.circular(AppConfig.borderRadius / 2),
|
||||
color: selected
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.secondaryContainer
|
||||
.withAlpha(100)
|
||||
: highlightMarker
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.tertiaryContainer
|
||||
.withAlpha(100)
|
||||
: Colors.transparent,
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
FutureBuilder<User?>(
|
||||
future: event.fetchSenderUser(),
|
||||
builder: (context, snapshot) {
|
||||
final user =
|
||||
snapshot.data ?? event.senderFromMemoryOrFallback;
|
||||
return Avatar(
|
||||
mxContent: user.avatarUrl,
|
||||
name: user.calcDisplayname(),
|
||||
presenceUserId: user.stateKey,
|
||||
presenceBackgroundColor:
|
||||
avatarPresenceBackgroundColor,
|
||||
onTap: () => onAvatarTab(event),
|
||||
);
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: rowMainAxisAlignment,
|
||||
children: [
|
||||
if (!nextEventSameSender)
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(left: 8.0, bottom: 4),
|
||||
child: ownMessage || event.room.isDirectChat
|
||||
? const SizedBox(height: 12)
|
||||
: FutureBuilder<User?>(
|
||||
future: event.fetchSenderUser(),
|
||||
builder: (context, snapshot) {
|
||||
final displayname =
|
||||
snapshot.data?.calcDisplayname() ??
|
||||
event.senderFromMemoryOrFallback
|
||||
.calcDisplayname();
|
||||
return Text(
|
||||
displayname,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color:
|
||||
(Theme.of(context).brightness ==
|
||||
Brightness.light
|
||||
? displayname.color
|
||||
: displayname.lightColorText),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (longPressSelect)
|
||||
SizedBox(
|
||||
height: 32,
|
||||
width: Avatar.defaultSize,
|
||||
child: Checkbox.adaptive(
|
||||
value: selected,
|
||||
shape: const CircleBorder(),
|
||||
onChanged: (_) => onSelect(event),
|
||||
),
|
||||
)
|
||||
else if (nextEventSameSender || ownMessage)
|
||||
SizedBox(
|
||||
width: Avatar.defaultSize,
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: event.status == EventStatus.error
|
||||
? const Icon(Icons.error, color: Colors.red)
|
||||
: event.fileSendingStatus != null
|
||||
? const CircularProgressIndicator
|
||||
.adaptive(
|
||||
strokeWidth: 1,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
FutureBuilder<User?>(
|
||||
future: event.fetchSenderUser(),
|
||||
builder: (context, snapshot) {
|
||||
final user = snapshot.data ??
|
||||
event.senderFromMemoryOrFallback;
|
||||
return Avatar(
|
||||
mxContent: user.avatarUrl,
|
||||
name: user.calcDisplayname(),
|
||||
presenceUserId: user.stateKey,
|
||||
presenceBackgroundColor:
|
||||
avatarPresenceBackgroundColor,
|
||||
onTap: () => onAvatarTab(event),
|
||||
);
|
||||
},
|
||||
),
|
||||
Container(
|
||||
alignment: alignment,
|
||||
padding: const EdgeInsets.only(left: 8),
|
||||
child: GestureDetector(
|
||||
onLongPress: longPressSelect
|
||||
? null
|
||||
: () {
|
||||
HapticFeedback.heavyImpact();
|
||||
onSelect(event);
|
||||
},
|
||||
child: AnimatedOpacity(
|
||||
opacity: animateIn
|
||||
? 0
|
||||
: event.redacted ||
|
||||
event.messageType ==
|
||||
MessageTypes.BadEncrypted ||
|
||||
event.status.isSending
|
||||
? 0.5
|
||||
: 1,
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
child: Material(
|
||||
color: noBubble ? Colors.transparent : color,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppConfig.borderRadius,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (!nextEventSameSender)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 8.0,
|
||||
bottom: 4,
|
||||
),
|
||||
padding: noBubble || noPadding
|
||||
? EdgeInsets.zero
|
||||
: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: FluffyThemes.columnWidth * 1.5,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
if (event.relationshipType ==
|
||||
RelationshipTypes.reply)
|
||||
FutureBuilder<Event?>(
|
||||
future: event.getReplyEvent(timeline),
|
||||
builder:
|
||||
(BuildContext context, snapshot) {
|
||||
final replyEvent = snapshot.hasData
|
||||
? snapshot.data!
|
||||
: Event(
|
||||
eventId: event
|
||||
.relationshipEventId!,
|
||||
content: {
|
||||
'msgtype': 'm.text',
|
||||
'body': '...',
|
||||
},
|
||||
senderId: event.senderId,
|
||||
type: 'm.room.message',
|
||||
room: event.room,
|
||||
status: EventStatus.sent,
|
||||
originServerTs:
|
||||
DateTime.now(),
|
||||
);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 4.0,
|
||||
),
|
||||
child: InkWell(
|
||||
borderRadius:
|
||||
ReplyContent.borderRadius,
|
||||
onTap: () => scrollToEventId(
|
||||
replyEvent.eventId,
|
||||
),
|
||||
child: AbsorbPointer(
|
||||
child: ReplyContent(
|
||||
replyEvent,
|
||||
ownMessage: ownMessage,
|
||||
timeline: timeline,
|
||||
),
|
||||
),
|
||||
child: ownMessage || event.room.isDirectChat
|
||||
? const SizedBox(height: 12)
|
||||
: FutureBuilder<User?>(
|
||||
future: event.fetchSenderUser(),
|
||||
builder: (context, snapshot) {
|
||||
final displayname = snapshot.data
|
||||
?.calcDisplayname() ??
|
||||
event.senderFromMemoryOrFallback
|
||||
.calcDisplayname();
|
||||
return Text(
|
||||
displayname,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: (Theme.of(context)
|
||||
.brightness ==
|
||||
Brightness.light
|
||||
? displayname.color
|
||||
: displayname
|
||||
.lightColorText),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
MessageContent(
|
||||
displayEvent,
|
||||
textColor: textColor,
|
||||
onInfoTab: onInfoTab,
|
||||
),
|
||||
Container(
|
||||
alignment: alignment,
|
||||
padding: const EdgeInsets.only(left: 8),
|
||||
child: GestureDetector(
|
||||
onLongPress: longPressSelect
|
||||
? null
|
||||
: () {
|
||||
HapticFeedback.heavyImpact();
|
||||
onSelect(event);
|
||||
},
|
||||
child: AnimatedOpacity(
|
||||
opacity: animateIn
|
||||
? 0
|
||||
: event.redacted ||
|
||||
event.messageType ==
|
||||
MessageTypes.BadEncrypted ||
|
||||
event.status.isSending
|
||||
? 0.5
|
||||
: 1,
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
child: Material(
|
||||
color:
|
||||
noBubble ? Colors.transparent : color,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
if (event.hasAggregatedEvents(
|
||||
timeline,
|
||||
RelationshipTypes.edit,
|
||||
))
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 4.0,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.edit_outlined,
|
||||
color: textColor.withAlpha(164),
|
||||
size: 14,
|
||||
),
|
||||
Text(
|
||||
' - ${displayEvent.originServerTs.localizedTimeShort(context)}',
|
||||
style: TextStyle(
|
||||
color:
|
||||
textColor.withAlpha(164),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppConfig.borderRadius,
|
||||
),
|
||||
),
|
||||
],
|
||||
padding: noBubble || noPadding
|
||||
? EdgeInsets.zero
|
||||
: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth:
|
||||
FluffyThemes.columnWidth * 1.5,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
if (event.relationshipType ==
|
||||
RelationshipTypes.reply)
|
||||
FutureBuilder<Event?>(
|
||||
future: event
|
||||
.getReplyEvent(timeline),
|
||||
builder: (
|
||||
BuildContext context,
|
||||
snapshot,
|
||||
) {
|
||||
final replyEvent = snapshot
|
||||
.hasData
|
||||
? snapshot.data!
|
||||
: Event(
|
||||
eventId: event
|
||||
.relationshipEventId!,
|
||||
content: {
|
||||
'msgtype': 'm.text',
|
||||
'body': '...',
|
||||
},
|
||||
senderId:
|
||||
event.senderId,
|
||||
type:
|
||||
'm.room.message',
|
||||
room: event.room,
|
||||
status:
|
||||
EventStatus.sent,
|
||||
originServerTs:
|
||||
DateTime.now(),
|
||||
);
|
||||
return Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(
|
||||
bottom: 4.0,
|
||||
),
|
||||
child: InkWell(
|
||||
borderRadius: ReplyContent
|
||||
.borderRadius,
|
||||
onTap: () =>
|
||||
scrollToEventId(
|
||||
replyEvent.eventId,
|
||||
),
|
||||
child: AbsorbPointer(
|
||||
child: ReplyContent(
|
||||
replyEvent,
|
||||
ownMessage:
|
||||
ownMessage,
|
||||
timeline: timeline,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
MessageContent(
|
||||
displayEvent,
|
||||
textColor: textColor,
|
||||
onInfoTab: onInfoTab,
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
if (event.hasAggregatedEvents(
|
||||
timeline,
|
||||
RelationshipTypes.edit,
|
||||
))
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 4.0,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize:
|
||||
MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.edit_outlined,
|
||||
color: textColor
|
||||
.withAlpha(164),
|
||||
size: 14,
|
||||
),
|
||||
Text(
|
||||
' - ${displayEvent.originServerTs.localizedTimeShort(context)}',
|
||||
style: TextStyle(
|
||||
color: textColor
|
||||
.withAlpha(164),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import 'package:pasteboard/pasteboard.dart';
|
|||
import 'package:slugify/slugify.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_file_extension.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/widgets/mxc_image.dart';
|
||||
import '../../widgets/avatar.dart';
|
||||
|
|
@ -465,8 +464,11 @@ class InputBar extends StatelessWidget {
|
|||
mimeType: content.mimeType,
|
||||
bytes: data,
|
||||
name: content.uri.split('/').last,
|
||||
).detectFileType;
|
||||
room.sendFileEvent(file, shrinkImageMaxDimension: 1600);
|
||||
);
|
||||
room.sendFileEvent(
|
||||
file,
|
||||
shrinkImageMaxDimension: 1600,
|
||||
);
|
||||
},
|
||||
),
|
||||
minLines: minLines,
|
||||
|
|
|
|||
|
|
@ -28,16 +28,16 @@ class RecordingDialogState extends State<RecordingDialog> {
|
|||
|
||||
bool error = false;
|
||||
String? _recordedPath;
|
||||
final _audioRecorder = Record();
|
||||
final _audioRecorder = AudioRecorder();
|
||||
final List<double> amplitudeTimeline = [];
|
||||
|
||||
static const int bitRate = 64000;
|
||||
static const int samplingRate = 22050;
|
||||
static const int samplingRate = 44100;
|
||||
|
||||
Future<void> startRecording() async {
|
||||
try {
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
_recordedPath =
|
||||
final path = _recordedPath =
|
||||
'${tempDir.path}/recording${DateTime.now().microsecondsSinceEpoch}.${RecordingDialog.recordingFileType}';
|
||||
|
||||
final result = await _audioRecorder.hasPermission();
|
||||
|
|
@ -47,9 +47,15 @@ class RecordingDialogState extends State<RecordingDialog> {
|
|||
}
|
||||
await WakelockPlus.enable();
|
||||
await _audioRecorder.start(
|
||||
path: _recordedPath,
|
||||
bitRate: bitRate,
|
||||
samplingRate: samplingRate,
|
||||
const RecordConfig(
|
||||
bitRate: bitRate,
|
||||
sampleRate: samplingRate,
|
||||
numChannels: 1,
|
||||
autoGain: true,
|
||||
echoCancel: true,
|
||||
noiseSuppress: true,
|
||||
),
|
||||
path: path,
|
||||
);
|
||||
setState(() => _duration = Duration.zero);
|
||||
_recorderSubscription?.cancel();
|
||||
|
|
|
|||
|
|
@ -14,80 +14,90 @@ class TypingIndicators extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final typingUsers = controller.room.typingUsers
|
||||
..removeWhere((u) => u.stateKey == Matrix.of(context).client.userID);
|
||||
const topPadding = 20.0;
|
||||
const bottomPadding = 4.0;
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
alignment: Alignment.center,
|
||||
child: AnimatedContainer(
|
||||
constraints:
|
||||
const BoxConstraints(maxWidth: FluffyThemes.columnWidth * 2.5),
|
||||
height: typingUsers.isEmpty ? 0 : Avatar.defaultSize + bottomPadding,
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
alignment: controller.timeline!.events.isNotEmpty &&
|
||||
controller.timeline!.events.first.senderId ==
|
||||
Matrix.of(context).client.userID
|
||||
? Alignment.topRight
|
||||
: Alignment.topLeft,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: const BoxDecoration(),
|
||||
padding: const EdgeInsets.only(
|
||||
left: 8.0,
|
||||
bottom: bottomPadding,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: Avatar.defaultSize,
|
||||
width: typingUsers.length < 2
|
||||
? Avatar.defaultSize
|
||||
: Avatar.defaultSize + 16,
|
||||
child: Stack(
|
||||
children: [
|
||||
if (typingUsers.isNotEmpty)
|
||||
Avatar(
|
||||
mxContent: typingUsers.first.avatarUrl,
|
||||
name: typingUsers.first.calcDisplayname(),
|
||||
),
|
||||
if (typingUsers.length == 2)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16),
|
||||
child: Avatar(
|
||||
mxContent: typingUsers.length == 2
|
||||
? typingUsers.last.avatarUrl
|
||||
: null,
|
||||
name: typingUsers.length == 2
|
||||
? typingUsers.last.calcDisplayname()
|
||||
: '+${typingUsers.length - 1}',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: topPadding),
|
||||
child: Material(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(2),
|
||||
topRight: Radius.circular(AppConfig.borderRadius),
|
||||
bottomLeft: Radius.circular(AppConfig.borderRadius),
|
||||
bottomRight: Radius.circular(AppConfig.borderRadius),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: typingUsers.isEmpty ? null : const _TypingDots(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
return StreamBuilder<Object>(
|
||||
stream: controller.room.client.onSync.stream.where(
|
||||
(syncUpdate) =>
|
||||
syncUpdate.rooms?.join?[controller.room.id]?.ephemeral
|
||||
?.any((ephemeral) => ephemeral.type == 'm.typing') ??
|
||||
false,
|
||||
),
|
||||
builder: (context, _) {
|
||||
final typingUsers = controller.room.typingUsers
|
||||
..removeWhere((u) => u.stateKey == Matrix.of(context).client.userID);
|
||||
const topPadding = 20.0;
|
||||
const bottomPadding = 4.0;
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
alignment: Alignment.center,
|
||||
child: AnimatedContainer(
|
||||
constraints:
|
||||
const BoxConstraints(maxWidth: FluffyThemes.columnWidth * 2.5),
|
||||
height:
|
||||
typingUsers.isEmpty ? 0 : Avatar.defaultSize + bottomPadding,
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
alignment: controller.timeline!.events.isNotEmpty &&
|
||||
controller.timeline!.events.first.senderId ==
|
||||
Matrix.of(context).client.userID
|
||||
? Alignment.topRight
|
||||
: Alignment.topLeft,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: const BoxDecoration(),
|
||||
padding: const EdgeInsets.only(
|
||||
left: 8.0,
|
||||
bottom: bottomPadding,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: Avatar.defaultSize,
|
||||
width: typingUsers.length < 2
|
||||
? Avatar.defaultSize
|
||||
: Avatar.defaultSize + 16,
|
||||
child: Stack(
|
||||
children: [
|
||||
if (typingUsers.isNotEmpty)
|
||||
Avatar(
|
||||
mxContent: typingUsers.first.avatarUrl,
|
||||
name: typingUsers.first.calcDisplayname(),
|
||||
),
|
||||
if (typingUsers.length == 2)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16),
|
||||
child: Avatar(
|
||||
mxContent: typingUsers.length == 2
|
||||
? typingUsers.last.avatarUrl
|
||||
: null,
|
||||
name: typingUsers.length == 2
|
||||
? typingUsers.last.calcDisplayname()
|
||||
: '+${typingUsers.length - 1}',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: topPadding),
|
||||
child: Material(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(2),
|
||||
topRight: Radius.circular(AppConfig.borderRadius),
|
||||
bottomLeft: Radius.circular(AppConfig.borderRadius),
|
||||
bottomRight: Radius.circular(AppConfig.borderRadius),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: typingUsers.isEmpty ? null : const _TypingDots(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ class ChatAccessSettingsPageView extends StatelessWidget {
|
|||
),
|
||||
body: MaxWidthBody(
|
||||
child: StreamBuilder<Object>(
|
||||
stream: room.onUpdate.stream,
|
||||
stream: room.client.onRoomState.stream
|
||||
.where((update) => update.roomId == controller.room.id),
|
||||
builder: (context, snapshot) {
|
||||
final canonicalAlias = room.canonicalAlias;
|
||||
final altAliases = room
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@ class ChatDetailsView extends StatelessWidget {
|
|||
}
|
||||
|
||||
return StreamBuilder(
|
||||
stream: room.onUpdate.stream,
|
||||
stream: room.client.onRoomState.stream
|
||||
.where((update) => update.roomId == room.id),
|
||||
builder: (context, snapshot) {
|
||||
var members = room.getParticipants().toList()
|
||||
..sort((b, a) => a.powerLevel.compareTo(b.powerLevel));
|
||||
|
|
|
|||
|
|
@ -82,7 +82,8 @@ class ChatEncryptionSettingsView extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
StreamBuilder(
|
||||
stream: room.onUpdate.stream,
|
||||
stream: room.client.onRoomState.stream
|
||||
.where((update) => update.roomId == controller.room.id),
|
||||
builder: (context, snapshot) =>
|
||||
FutureBuilder<List<DeviceKeys>>(
|
||||
future: room.getUserDeviceKeys(),
|
||||
|
|
|
|||
|
|
@ -64,7 +64,8 @@ class InvitationSelectionView extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
StreamBuilder<Object>(
|
||||
stream: room.onUpdate.stream,
|
||||
stream: room.client.onRoomState.stream
|
||||
.where((update) => update.roomId == room.id),
|
||||
builder: (context, snapshot) {
|
||||
final participants =
|
||||
room.getParticipants().map((user) => user.id).toSet();
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ class MultipleEmotesSettingsView extends StatelessWidget {
|
|||
title: Text(L10n.of(context)!.emotePacks),
|
||||
),
|
||||
body: StreamBuilder(
|
||||
stream: room.onUpdate.stream,
|
||||
stream: room.client.onRoomState.stream
|
||||
.where((update) => update.roomId == room.id),
|
||||
builder: (context, snapshot) {
|
||||
final packStateEvents = room.states['im.ponies.room_emotes'];
|
||||
// we need to manually convert the map using Map.of, otherwise assigning null will throw a type error.
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ abstract class ClientManager {
|
|||
},
|
||||
nativeImplementations: nativeImplementations,
|
||||
customImageResizer: PlatformInfos.isMobile ? customImageResizer : null,
|
||||
defaultNetworkRequestTimeout: const Duration(minutes: 5),
|
||||
enableDehydratedDevices: true,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,12 @@ Future<DatabaseApi> flutterMatrixSdkDatabaseBuilder(Client client) async {
|
|||
),
|
||||
);
|
||||
|
||||
// Delete database file:
|
||||
if (database == null && !kIsWeb) {
|
||||
final dbFile = File(await _getDatabasePath(client.clientName));
|
||||
if (await dbFile.exists()) await dbFile.delete();
|
||||
}
|
||||
|
||||
try {
|
||||
// Send error notification:
|
||||
final l10n = lookupL10n(PlatformDispatcher.instance.locale);
|
||||
|
|
@ -61,9 +67,6 @@ Future<MatrixSdkDatabase> _constructDatabase(Client client) async {
|
|||
|
||||
final cipher = await getDatabaseCipher();
|
||||
|
||||
final databaseDirectory = PlatformInfos.isIOS || PlatformInfos.isMacOS
|
||||
? await getLibraryDirectory()
|
||||
: await getApplicationSupportDirectory();
|
||||
Directory? fileStorageLocation;
|
||||
try {
|
||||
fileStorageLocation = await getTemporaryDirectory();
|
||||
|
|
@ -73,7 +76,7 @@ Future<MatrixSdkDatabase> _constructDatabase(Client client) async {
|
|||
);
|
||||
}
|
||||
|
||||
final path = join(databaseDirectory.path, '${client.clientName}.sqlite');
|
||||
final path = await _getDatabasePath(client.clientName);
|
||||
|
||||
// fix dlopen for old Android
|
||||
await applyWorkaroundToOpenSqlCipherOnOldAndroidVersions();
|
||||
|
|
@ -118,6 +121,14 @@ Future<MatrixSdkDatabase> _constructDatabase(Client client) async {
|
|||
);
|
||||
}
|
||||
|
||||
Future<String> _getDatabasePath(String clientName) async {
|
||||
final databaseDirectory = PlatformInfos.isIOS || PlatformInfos.isMacOS
|
||||
? await getLibraryDirectory()
|
||||
: await getApplicationSupportDirectory();
|
||||
|
||||
return join(databaseDirectory.path, '$clientName.sqlite');
|
||||
}
|
||||
|
||||
Future<void> _migrateLegacyLocation(
|
||||
String sqlFilePath,
|
||||
String clientName,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue