Compare commits
3 Commits
fd3c9190de
...
d2e42d0a3c
Author | SHA1 | Date | |
---|---|---|---|
d2e42d0a3c | |||
842cf5aaaa | |||
c8f727e982 |
@ -132,7 +132,8 @@
|
||||
"addToContactsBody": "Are you sure you want to add ${jid} to your contacts?",
|
||||
"stickerPickerNoStickersLine1": "You have no sticker packs installed.",
|
||||
"stickerPickerNoStickersLine2": "They can be installed in the sticker settings.",
|
||||
"stickerSettings": "Sticker settings"
|
||||
"stickerSettings": "Sticker settings",
|
||||
"newDeviceMessage": "${title} added a new encryption device"
|
||||
},
|
||||
"addcontact": {
|
||||
"title": "Add new contact",
|
||||
|
@ -132,7 +132,8 @@
|
||||
"addToContactsBody": "Bist du dir sicher, dass du ${jid} zu deinen Kontakten hinzufügen möchtest?",
|
||||
"stickerPickerNoStickersLine1": "Du hast keine Stickerpacks installiert.",
|
||||
"stickerPickerNoStickersLine2": "Diese können in den Stickereinstellungen installiert werden.",
|
||||
"stickerSettings": "Stickereinstellungen"
|
||||
"stickerSettings": "Stickereinstellungen",
|
||||
"newDeviceMessage": "${title} hat ein neues Verschlüsselungsgerät hinzugefügt"
|
||||
},
|
||||
"addcontact": {
|
||||
"title": "Neuen Kontakt hinzufügen",
|
||||
|
@ -4,7 +4,6 @@ import 'package:cryptography/cryptography.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:hex/hex.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:moxxyv2/service/conversation.dart';
|
||||
import 'package:moxxyv2/service/preferences.dart';
|
||||
@ -28,14 +27,22 @@ String _cleanBase64String(String original) {
|
||||
|
||||
class _AvatarData {
|
||||
const _AvatarData(this.data, this.id);
|
||||
final String data;
|
||||
final List<int> data;
|
||||
final String id;
|
||||
}
|
||||
|
||||
class AvatarService {
|
||||
final Logger _log = Logger('AvatarService');
|
||||
|
||||
Future<void> updateAvatarForJid(String jid, String hash, String base64) async {
|
||||
Future<void> handleAvatarUpdate(AvatarUpdatedEvent event) async {
|
||||
await updateAvatarForJid(
|
||||
event.jid,
|
||||
event.hash,
|
||||
base64Decode(_cleanBase64String(event.base64)),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> updateAvatarForJid(String jid, String hash, List<int> data) async {
|
||||
final cs = GetIt.I.get<ConversationService>();
|
||||
final rs = GetIt.I.get<RosterService>();
|
||||
final originalConversation = await cs.getConversationByJid(jid);
|
||||
@ -43,10 +50,9 @@ class AvatarService {
|
||||
|
||||
// Clean the raw data. Since this may arrive by chunks, those chunks may contain
|
||||
// weird data pieces.
|
||||
final base64Data = base64Decode(_cleanBase64String(base64));
|
||||
if (originalConversation != null) {
|
||||
final avatarPath = await saveAvatarInCache(
|
||||
base64Data,
|
||||
data,
|
||||
hash,
|
||||
jid,
|
||||
originalConversation.avatarUrl,
|
||||
@ -69,7 +75,7 @@ class AvatarService {
|
||||
avatarPath = await getAvatarPath(jid, hash);
|
||||
} else {
|
||||
avatarPath = await saveAvatarInCache(
|
||||
base64Data,
|
||||
data,
|
||||
hash,
|
||||
jid,
|
||||
originalRoster.avatarUrl,
|
||||
@ -105,7 +111,7 @@ class AvatarService {
|
||||
final avatar = avatarResult.get<UserAvatar>();
|
||||
|
||||
return _AvatarData(
|
||||
avatar.base64,
|
||||
base64Decode(_cleanBase64String(avatar.base64)),
|
||||
avatar.hash,
|
||||
);
|
||||
}
|
||||
@ -119,14 +125,15 @@ class AvatarService {
|
||||
|
||||
final binval = vcardResult.get<VCard>().photo?.binval;
|
||||
if (binval == null) return null;
|
||||
|
||||
final rawHash = await Sha1().hash(base64Decode(binval));
|
||||
|
||||
final data = base64Decode(_cleanBase64String(binval));
|
||||
final rawHash = await Sha1().hash(data);
|
||||
final hash = HEX.encode(rawHash.bytes);
|
||||
|
||||
vm.setLastHash(jid, hash);
|
||||
|
||||
return _AvatarData(
|
||||
binval,
|
||||
data,
|
||||
hash,
|
||||
);
|
||||
}
|
||||
|
@ -57,6 +57,8 @@ Future<void> createDatabase(Database db, int version) async {
|
||||
containsNoStore INTEGER NOT NULL,
|
||||
stickerPackId TEXT,
|
||||
stickerHashKey TEXT,
|
||||
pseudoMessageType INTEGER,
|
||||
pseudoMessageData TEXT,
|
||||
CONSTRAINT fk_quote FOREIGN KEY (quote_id) REFERENCES $messagesTable (id),
|
||||
)''',
|
||||
);
|
||||
|
@ -18,6 +18,7 @@ import 'package:moxxyv2/service/database/migrations/0000_conversations3.dart';
|
||||
import 'package:moxxyv2/service/database/migrations/0000_language.dart';
|
||||
import 'package:moxxyv2/service/database/migrations/0000_lmc.dart';
|
||||
import 'package:moxxyv2/service/database/migrations/0000_omemo_fingerprint_cache.dart';
|
||||
import 'package:moxxyv2/service/database/migrations/0000_pseudo_messages.dart';
|
||||
import 'package:moxxyv2/service/database/migrations/0000_reactions.dart';
|
||||
import 'package:moxxyv2/service/database/migrations/0000_reactions_store_hint.dart';
|
||||
import 'package:moxxyv2/service/database/migrations/0000_retraction.dart';
|
||||
@ -80,7 +81,7 @@ class DatabaseService {
|
||||
_db = await openDatabase(
|
||||
dbPath,
|
||||
password: key,
|
||||
version: 23,
|
||||
version: 24,
|
||||
onCreate: createDatabase,
|
||||
onConfigure: (db) async {
|
||||
// In order to do schema changes during database upgrades, we disable foreign
|
||||
@ -181,6 +182,10 @@ class DatabaseService {
|
||||
_log.finest('Running migration for database version 23');
|
||||
await upgradeFromV22ToV23(db);
|
||||
}
|
||||
if (oldVersion < 24) {
|
||||
_log.finest('Running migration for database version 24');
|
||||
await upgradeFromV23ToV24(db);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@ -428,6 +433,8 @@ class DatabaseService {
|
||||
int? mediaSize,
|
||||
String? stickerPackId,
|
||||
String? stickerHashKey,
|
||||
int? pseudoMessageType,
|
||||
Map<String, dynamic>? pseudoMessageData,
|
||||
}
|
||||
) async {
|
||||
var m = Message(
|
||||
@ -464,6 +471,8 @@ class DatabaseService {
|
||||
mediaSize: mediaSize,
|
||||
stickerPackId: stickerPackId,
|
||||
stickerHashKey: stickerHashKey,
|
||||
pseudoMessageType: pseudoMessageType,
|
||||
pseudoMessageData: pseudoMessageData,
|
||||
);
|
||||
|
||||
if (quoteId != null) {
|
||||
|
12
lib/service/database/migrations/0000_pseudo_messages.dart
Normal file
12
lib/service/database/migrations/0000_pseudo_messages.dart
Normal file
@ -0,0 +1,12 @@
|
||||
import 'package:moxxyv2/service/database/constants.dart';
|
||||
import 'package:moxxyv2/shared/models/preference.dart';
|
||||
import 'package:sqflite_sqlcipher/sqflite.dart';
|
||||
|
||||
Future<void> upgradeFromV23ToV24(Database db) async {
|
||||
await db.execute(
|
||||
'ALTER TABLE $messagesTable ADD COLUMN pseudoMessageType INTEGER;',
|
||||
);
|
||||
await db.execute(
|
||||
'ALTER TABLE $messagesTable ADD COLUMN pseudoMessageData TEXT;',
|
||||
);
|
||||
}
|
@ -66,6 +66,8 @@ class MessageService {
|
||||
int? mediaSize,
|
||||
String? stickerPackId,
|
||||
String? stickerHashKey,
|
||||
int? pseudoMessageType,
|
||||
Map<String, dynamic>? pseudoMessageData,
|
||||
}
|
||||
) async {
|
||||
final msg = await GetIt.I.get<DatabaseService>().addMessageFromData(
|
||||
@ -99,6 +101,8 @@ class MessageService {
|
||||
mediaSize: mediaSize,
|
||||
stickerPackId: stickerPackId,
|
||||
stickerHashKey: stickerHashKey,
|
||||
pseudoMessageType: pseudoMessageType,
|
||||
pseudoMessageData: pseudoMessageData,
|
||||
);
|
||||
|
||||
// Only update the cache if the conversation already has been loaded. This prevents
|
||||
|
@ -6,9 +6,14 @@ import 'package:hex/hex.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:moxxmpp/moxxmpp.dart' as moxxmpp;
|
||||
import 'package:moxxyv2/service/database/database.dart';
|
||||
import 'package:moxxyv2/service/message.dart';
|
||||
import 'package:moxxyv2/service/moxxmpp/omemo.dart';
|
||||
import 'package:moxxyv2/service/omemo/implementations.dart';
|
||||
import 'package:moxxyv2/service/omemo/types.dart';
|
||||
import 'package:moxxyv2/service/service.dart';
|
||||
import 'package:moxxyv2/service/xmpp.dart';
|
||||
import 'package:moxxyv2/shared/events.dart';
|
||||
import 'package:moxxyv2/shared/models/message.dart';
|
||||
import 'package:moxxyv2/shared/models/omemo_device.dart' as model;
|
||||
import 'package:omemo_dart/omemo_dart.dart';
|
||||
import 'package:synchronized/synchronized.dart';
|
||||
@ -90,6 +95,8 @@ class OmemoService {
|
||||
if (_fingerprintCache.containsKey(event.jid)) {
|
||||
_fingerprintCache[event.jid]![event.deviceId] = fingerprint;
|
||||
}
|
||||
|
||||
await addNewDeviceMessage(event.jid, event.deviceId);
|
||||
}
|
||||
} else if (event is DeviceListModifiedEvent) {
|
||||
await commitDeviceMap(event.list);
|
||||
@ -113,6 +120,37 @@ class OmemoService {
|
||||
});
|
||||
}
|
||||
|
||||
/// Adds a pseudo message saying that [jid] added a new device with id [deviceId].
|
||||
/// If, however, [jid] is our own JID, then nothing is done.
|
||||
Future<void> addNewDeviceMessage(String jid, int deviceId) async {
|
||||
// Add a pseudo message if it is not about our own devices
|
||||
final xmppState = await GetIt.I.get<XmppService>().getXmppState();
|
||||
if (jid == xmppState.jid) return;
|
||||
|
||||
final ms = GetIt.I.get<MessageService>();
|
||||
final message = await ms.addMessageFromData(
|
||||
'',
|
||||
DateTime.now().millisecondsSinceEpoch,
|
||||
'',
|
||||
jid,
|
||||
false,
|
||||
'',
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
pseudoMessageType: pseudoMessageTypeNewDevice,
|
||||
pseudoMessageData: <String, dynamic>{
|
||||
'deviceId': deviceId,
|
||||
'jid': jid,
|
||||
},
|
||||
);
|
||||
sendEvent(
|
||||
MessageAddedEvent(
|
||||
message: message,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<model.OmemoDevice> regenerateDevice(String jid) async {
|
||||
// Prevent access to the session manager as it is (mostly) guarded ensureInitialized
|
||||
await _lock.synchronized(() {
|
||||
|
@ -1405,11 +1405,7 @@ class XmppService {
|
||||
}
|
||||
|
||||
Future<void> _onAvatarUpdated(AvatarUpdatedEvent event, { dynamic extra }) async {
|
||||
await GetIt.I.get<AvatarService>().updateAvatarForJid(
|
||||
event.jid,
|
||||
event.hash,
|
||||
event.base64,
|
||||
);
|
||||
await GetIt.I.get<AvatarService>().handleAvatarUpdate(event);
|
||||
}
|
||||
|
||||
Future<void> _onStanzaAcked(StanzaAckedEvent event, { dynamic extra }) async {
|
||||
|
@ -9,18 +9,33 @@ import 'package:moxxyv2/shared/warning_types.dart';
|
||||
part 'message.freezed.dart';
|
||||
part 'message.g.dart';
|
||||
|
||||
const pseudoMessageTypeNewDevice = 1;
|
||||
|
||||
Map<String, String>? _optionalJsonDecode(String? data) {
|
||||
if (data == null) return null;
|
||||
|
||||
return (jsonDecode(data) as Map<dynamic, dynamic>).cast<String, String>();
|
||||
}
|
||||
|
||||
String? _optionalJsonEncode(Map<String, String>? data) {
|
||||
Map<String, dynamic> _optionalJsonDecodeWithFallback(String? data) {
|
||||
if (data == null) return <String, dynamic>{};
|
||||
|
||||
return (jsonDecode(data) as Map<dynamic, dynamic>).cast<String, dynamic>();
|
||||
}
|
||||
|
||||
String? _optionalJsonEncode(Map<String, dynamic>? data) {
|
||||
if (data == null) return null;
|
||||
|
||||
return jsonEncode(data);
|
||||
}
|
||||
|
||||
String? _optionalJsonEncodeWithFallback(Map<String, dynamic>? data) {
|
||||
if (data == null) return null;
|
||||
if (data.isEmpty) return null;
|
||||
|
||||
return jsonEncode(data);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class Message with _$Message {
|
||||
factory Message(
|
||||
@ -66,6 +81,8 @@ class Message with _$Message {
|
||||
@Default([]) List<Reaction> reactions,
|
||||
String? stickerPackId,
|
||||
String? stickerHashKey,
|
||||
int? pseudoMessageType,
|
||||
Map<String, dynamic>? pseudoMessageData,
|
||||
}
|
||||
) = _Message;
|
||||
|
||||
@ -91,6 +108,7 @@ class Message with _$Message {
|
||||
'isEdited': intToBool(json['isEdited']! as int),
|
||||
'containsNoStore': intToBool(json['containsNoStore']! as int),
|
||||
'reactions': <Map<String, dynamic>>[],
|
||||
'pseudoMessageData': _optionalJsonDecodeWithFallback(json['pseudoMessageData'] as String?)
|
||||
}).copyWith(
|
||||
quotes: quotes,
|
||||
reactions: (jsonDecode(json['reactions']! as String) as List<dynamic>)
|
||||
@ -104,7 +122,8 @@ class Message with _$Message {
|
||||
final map = toJson()
|
||||
..remove('id')
|
||||
..remove('quotes')
|
||||
..remove('reactions');
|
||||
..remove('reactions')
|
||||
..remove('pseudoMessageData');
|
||||
|
||||
return {
|
||||
...map,
|
||||
@ -128,6 +147,7 @@ class Message with _$Message {
|
||||
.map((r) => r.toJson())
|
||||
.toList(),
|
||||
),
|
||||
'pseudoMessageData': _optionalJsonEncodeWithFallback(pseudoMessageData),
|
||||
};
|
||||
}
|
||||
|
||||
@ -143,27 +163,30 @@ class Message with _$Message {
|
||||
return mimeTypeToEmoji(mediaType, addTypeName: false);
|
||||
}
|
||||
|
||||
/// True if the message is a pseudo message.
|
||||
bool get isPseudoMessage => pseudoMessageType != null && pseudoMessageData != null;
|
||||
|
||||
/// Returns true if the message can be quoted. False if not.
|
||||
bool get isQuotable => !hasError && !isRetracted && !isFileUploadNotification && !isUploading && !isDownloading;
|
||||
bool get isQuotable => !hasError && !isRetracted && !isFileUploadNotification && !isUploading && !isDownloading && !isPseudoMessage;
|
||||
|
||||
/// Returns true if the message can be retracted. False if not.
|
||||
/// [sentBySelf] asks whether or not the message was sent by us (the current Jid).
|
||||
bool canRetract(bool sentBySelf) {
|
||||
return originId != null && sentBySelf && !isFileUploadNotification && !isUploading && !isDownloading;
|
||||
return originId != null && sentBySelf && !isFileUploadNotification && !isUploading && !isDownloading && !isPseudoMessage;
|
||||
}
|
||||
|
||||
/// Returns true if we can send a reaction for this message.
|
||||
bool get isReactable => !hasError && !isRetracted && !isFileUploadNotification && !isUploading && !isDownloading;
|
||||
bool get isReactable => !hasError && !isRetracted && !isFileUploadNotification && !isUploading && !isDownloading && !isPseudoMessage;
|
||||
|
||||
/// Returns true if the message can be edited. False if not.
|
||||
/// [sentBySelf] asks whether or not the message was sent by us (the current Jid).
|
||||
bool canEdit(bool sentBySelf) {
|
||||
return sentBySelf && !isMedia && !isFileUploadNotification && !isUploading && !isDownloading;
|
||||
return sentBySelf && !isMedia && !isFileUploadNotification && !isUploading && !isDownloading && !isPseudoMessage;
|
||||
}
|
||||
|
||||
/// Returns true if the message can open the selection menu by longpressing. False if
|
||||
/// not.
|
||||
bool get isLongpressable => !isRetracted;
|
||||
bool get isLongpressable => !isRetracted && !isPseudoMessage;
|
||||
|
||||
/// Returns true if the menu item to show the error should be shown in the
|
||||
/// longpress menu.
|
||||
@ -176,14 +199,14 @@ class Message with _$Message {
|
||||
|
||||
/// Returns true if the message contains media that can be thumbnailed, i.e. videos or
|
||||
/// images.
|
||||
bool get isThumbnailable => isMedia && mediaType != null && (
|
||||
bool get isThumbnailable => !isPseudoMessage && isMedia && mediaType != null && (
|
||||
mediaType!.startsWith('image/') ||
|
||||
mediaType!.startsWith('video/')
|
||||
);
|
||||
|
||||
/// Returns true if the message can be copied to the clipboard.
|
||||
bool get isCopyable => !isMedia && body.isNotEmpty;
|
||||
bool get isCopyable => !isMedia && body.isNotEmpty && !isPseudoMessage;
|
||||
|
||||
/// Returns true if the message is a sticker
|
||||
bool get isSticker => isMedia && stickerPackId != null && stickerHashKey != null;
|
||||
bool get isSticker => isMedia && stickerPackId != null && stickerHashKey != null && !isPseudoMessage;
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ part 'devices_event.dart';
|
||||
part 'devices_state.dart';
|
||||
|
||||
class DevicesBloc extends Bloc<DevicesEvent, DevicesState> {
|
||||
|
||||
DevicesBloc() : super(DevicesState()) {
|
||||
on<DevicesRequestedEvent>(_onRequested);
|
||||
on<DeviceEnabledSetEvent>(_onDeviceEnabledSet);
|
||||
|
@ -4,14 +4,12 @@ abstract class DevicesEvent {}
|
||||
|
||||
/// Triggered when the user requested the key page
|
||||
class DevicesRequestedEvent extends DevicesEvent {
|
||||
|
||||
DevicesRequestedEvent(this.jid);
|
||||
final String jid;
|
||||
}
|
||||
|
||||
/// Triggered by the UI when we want to enable or disable a key
|
||||
class DeviceEnabledSetEvent extends DevicesEvent {
|
||||
|
||||
DeviceEnabledSetEvent(this.deviceId, this.enabled);
|
||||
final int deviceId;
|
||||
final bool enabled;
|
||||
|
@ -19,6 +19,7 @@ import 'package:moxxyv2/ui/pages/conversation/helpers.dart';
|
||||
import 'package:moxxyv2/ui/pages/conversation/topbar.dart';
|
||||
import 'package:moxxyv2/ui/widgets/chat/chatbubble.dart';
|
||||
import 'package:moxxyv2/ui/widgets/chat/datebubble.dart';
|
||||
import 'package:moxxyv2/ui/widgets/chat/media/new_device.dart';
|
||||
import 'package:moxxyv2/ui/widgets/overview_menu.dart';
|
||||
|
||||
class ConversationPage extends StatefulWidget {
|
||||
@ -136,11 +137,28 @@ class ConversationPageState extends State<ConversationPage> with TickerProviderS
|
||||
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
|
||||
// TODO(Unknown): Since we reverse the list: Fix start, end and between
|
||||
final index = state.messages.length - 1 - (_index - 1) ~/ 2;
|
||||
final item = state.messages[index];
|
||||
|
||||
if (item.isPseudoMessage) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: maxWidth,
|
||||
),
|
||||
child: NewDeviceBubble(
|
||||
data: item.pseudoMessageData!,
|
||||
title: state.conversation!.title,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
final start = index - 1 < 0 ?
|
||||
true :
|
||||
isSent(state.messages[index - 1], jid) != isSent(item, jid);
|
||||
|
@ -46,6 +46,11 @@ class RawChatBubble extends StatelessWidget {
|
||||
isInlinedWidget = message.mediaType!.startsWith('image/');
|
||||
}
|
||||
|
||||
// Check if it is a pseudo message
|
||||
if (message.isPseudoMessage) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if it is an embedded file
|
||||
if (message.isMedia && message.mediaUrl != null && isInlinedWidget) {
|
||||
return true;
|
||||
|
@ -26,7 +26,7 @@ enum MessageType {
|
||||
video,
|
||||
audio,
|
||||
file,
|
||||
sticker
|
||||
sticker,
|
||||
}
|
||||
|
||||
/// Deduce the type of message we are dealing with to pick the correct
|
||||
@ -72,7 +72,9 @@ Widget buildMessageWidget(Message message, double maxWidth, BorderRadius radius,
|
||||
return TextChatWidget(
|
||||
message,
|
||||
sent,
|
||||
topWidget: message.quotes != null ? buildQuoteMessageWidget(message.quotes!, sent) : null,
|
||||
topWidget: message.quotes != null ?
|
||||
buildQuoteMessageWidget(message.quotes!, sent) :
|
||||
null,
|
||||
);
|
||||
}
|
||||
case MessageType.image:
|
||||
@ -83,13 +85,8 @@ Widget buildMessageWidget(Message message, double maxWidth, BorderRadius radius,
|
||||
return StickerChatWidget(message, radius, maxWidth, sent);
|
||||
case MessageType.audio:
|
||||
return AudioChatWidget(message, radius, maxWidth, sent);
|
||||
case MessageType.file: {
|
||||
case MessageType.file:
|
||||
return FileChatWidget(message, radius, maxWidth, sent);
|
||||
/*return TextChatWidget(
|
||||
message,
|
||||
sent,
|
||||
);*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
47
lib/ui/widgets/chat/media/new_device.dart
Normal file
47
lib/ui/widgets/chat/media/new_device.dart
Normal file
@ -0,0 +1,47 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:moxxyv2/i18n/strings.g.dart';
|
||||
import 'package:moxxyv2/ui/bloc/devices_bloc.dart';
|
||||
|
||||
class NewDeviceBubble extends StatelessWidget {
|
||||
const NewDeviceBubble({
|
||||
required this.data,
|
||||
required this.title,
|
||||
super.key,
|
||||
});
|
||||
final Map<String, dynamic> data;
|
||||
final String title;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
context.read<DevicesBloc>().add(
|
||||
DevicesRequestedEvent(data['jid']! as String),
|
||||
);
|
||||
},
|
||||
child: ColoredBox(
|
||||
color: const Color(0xffeee8d5),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 6,
|
||||
),
|
||||
child: Text(
|
||||
t.pages.conversation.newDeviceMessage(title: title),
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -888,7 +888,7 @@ packages:
|
||||
name: omemo_dart
|
||||
url: "https://git.polynom.me/api/packages/PapaTutuWawa/pub/"
|
||||
source: hosted
|
||||
version: "0.4.0"
|
||||
version: "0.4.1"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -75,7 +75,7 @@ dependencies:
|
||||
native_imaging: 0.1.0
|
||||
omemo_dart:
|
||||
hosted: https://git.polynom.me/api/packages/PapaTutuWawa/pub
|
||||
version: 0.4.0
|
||||
version: 0.4.1
|
||||
page_transition: 2.0.9
|
||||
path: 1.8.2
|
||||
path_provider: 2.0.11
|
||||
@ -140,7 +140,7 @@ dependency_overrides:
|
||||
moxxmpp:
|
||||
git:
|
||||
url: https://git.polynom.me/Moxxy/moxxmpp.git
|
||||
rev: 62001c1e29b644fcf7fe12618d77571853fd073e
|
||||
rev: 596693c2067bc3fe73250f07cd88e7040a285537
|
||||
path: packages/moxxmpp
|
||||
|
||||
extra_licenses:
|
||||
|
Loading…
Reference in New Issue
Block a user