Compare commits
10 Commits
896ef50b9a
...
7746784949
Author | SHA1 | Date | |
---|---|---|---|
7746784949 | |||
024bd48aba | |||
cb13c9faa4 | |||
009ec759a3 | |||
6ba16ad020 | |||
43b0b34cdd | |||
94e6eb2d10 | |||
578eea5d9f | |||
724450e049 | |||
1759baebad |
@ -73,7 +73,9 @@
|
|||||||
"fileNotEncrypted": "The chat is encrypted but the file is not encrypted"
|
"fileNotEncrypted": "The chat is encrypted but the file is not encrypted"
|
||||||
},
|
},
|
||||||
"conversation": {
|
"conversation": {
|
||||||
"audioRecordingError": "Failed to finalize audio recording"
|
"audioRecordingError": "Failed to finalize audio recording",
|
||||||
|
"openFileNoAppError": "No app found to open this file",
|
||||||
|
"openFileGenericError": "Failed to open file"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"warnings": {
|
"warnings": {
|
||||||
@ -127,7 +129,10 @@
|
|||||||
"showWarning": "Show warning",
|
"showWarning": "Show warning",
|
||||||
"addToContacts": "Add to contacts",
|
"addToContacts": "Add to contacts",
|
||||||
"addToContactsTitle": "Add ${jid} to contacts",
|
"addToContactsTitle": "Add ${jid} to contacts",
|
||||||
"addToContactsBody": "Are you sure you want to add ${jid} to your contacts?"
|
"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"
|
||||||
},
|
},
|
||||||
"addcontact": {
|
"addcontact": {
|
||||||
"title": "Add new contact",
|
"title": "Add new contact",
|
||||||
|
@ -73,7 +73,9 @@
|
|||||||
"fileNotEncrypted": "Der Chat ist verschlüsselt, aber die Datei wurde unverschlüsselt übertragen"
|
"fileNotEncrypted": "Der Chat ist verschlüsselt, aber die Datei wurde unverschlüsselt übertragen"
|
||||||
},
|
},
|
||||||
"conversation": {
|
"conversation": {
|
||||||
"audioRecordingError": "Fehler beim Fertigstellen der Audioaufnahme"
|
"audioRecordingError": "Fehler beim Fertigstellen der Audioaufnahme",
|
||||||
|
"openFileNoAppError": "Keine App vorhanden, um die Datei zu öffnen",
|
||||||
|
"openFileGenericError": "Fehler beim Öffnen der Datei"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"warnings": {
|
"warnings": {
|
||||||
@ -127,7 +129,10 @@
|
|||||||
"showWarning": "Warnung anzeigen",
|
"showWarning": "Warnung anzeigen",
|
||||||
"addToContacts": "Zu Kontaken hinzufügen",
|
"addToContacts": "Zu Kontaken hinzufügen",
|
||||||
"addToContactsTitle": "${jid} zu Kontakten hinzufügen",
|
"addToContactsTitle": "${jid} zu Kontakten hinzufügen",
|
||||||
"addToContactsBody": "Bist du dir sicher, dass du ${jid} zu deinen Kontakten hinzufügen möchtest?"
|
"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"
|
||||||
},
|
},
|
||||||
"addcontact": {
|
"addcontact": {
|
||||||
"title": "Neuen Kontakt hinzufügen",
|
"title": "Neuen Kontakt hinzufügen",
|
||||||
|
@ -276,6 +276,8 @@ class OmemoService {
|
|||||||
final keys = List<OmemoDevice>.empty(growable: true);
|
final keys = List<OmemoDevice>.empty(growable: true);
|
||||||
final tm = omemoState.trustManager as BlindTrustBeforeVerificationTrustManager;
|
final tm = omemoState.trustManager as BlindTrustBeforeVerificationTrustManager;
|
||||||
final trustMap = await tm.getDevicesTrust(jid);
|
final trustMap = await tm.getDevicesTrust(jid);
|
||||||
|
|
||||||
|
if (!_fingerprintCache.containsKey(jid)) return [];
|
||||||
for (final deviceId in _fingerprintCache[jid]!.keys) {
|
for (final deviceId in _fingerprintCache[jid]!.keys) {
|
||||||
keys.add(
|
keys.add(
|
||||||
OmemoDevice(
|
OmemoDevice(
|
||||||
@ -341,22 +343,27 @@ class OmemoService {
|
|||||||
);
|
);
|
||||||
final bareJid = ownJid.toBare().toString();
|
final bareJid = ownJid.toBare().toString();
|
||||||
|
|
||||||
// Get finger prints if we have to
|
// Get fingerprints if we have to
|
||||||
await _loadOrFetchFingerprints(ownJid);
|
await _loadOrFetchFingerprints(ownJid);
|
||||||
|
|
||||||
_fingerprintCache[bareJid]!.forEach((deviceId, fingerprint) {
|
final tm = omemoState.trustManager as BlindTrustBeforeVerificationTrustManager;
|
||||||
if (deviceId == ownId) return;
|
final trustMap = await tm.getDevicesTrust(bareJid);
|
||||||
|
|
||||||
|
for (final deviceId in _fingerprintCache[bareJid]!.keys) {
|
||||||
|
if (deviceId == ownId) continue;
|
||||||
|
|
||||||
|
final fingerprint = _fingerprintCache[bareJid]![deviceId]!;
|
||||||
keys.add(
|
keys.add(
|
||||||
OmemoDevice(
|
OmemoDevice(
|
||||||
fingerprint,
|
fingerprint,
|
||||||
false,
|
await tm.isTrusted(bareJid, deviceId),
|
||||||
false,
|
trustMap[deviceId] == BTBVTrustState.verified,
|
||||||
false,
|
await tm.isEnabled(bareJid, deviceId),
|
||||||
deviceId,
|
deviceId,
|
||||||
hasSessionWith: false,
|
hasSessionWith: false,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,11 @@ const double fontsizeBody = 15;
|
|||||||
const double fontsizeBodyOnlyEmojis = 30;
|
const double fontsizeBodyOnlyEmojis = 30;
|
||||||
const double fontsizeSubbody = 10;
|
const double fontsizeSubbody = 10;
|
||||||
|
|
||||||
|
// The color for a shared media item
|
||||||
|
final Color sharedMediaItemBackgroundColor = Colors.grey.shade500;
|
||||||
|
// The color for a shared media summary
|
||||||
|
final Color sharedMediaSummaryBackgroundColor = Colors.grey.shade500;
|
||||||
|
|
||||||
// The translucent black we use when we need to ensure good contrast, for example when
|
// The translucent black we use when we need to ensure good contrast, for example when
|
||||||
// displaying the download progress indicator.
|
// displaying the download progress indicator.
|
||||||
final backdropBlack = Colors.black.withAlpha(150);
|
final backdropBlack = Colors.black.withAlpha(150);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
import 'package:better_open_file/better_open_file.dart';
|
||||||
import 'package:cryptography/cryptography.dart';
|
import 'package:cryptography/cryptography.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -353,3 +354,24 @@ Future<void> handleUri(String uriString) async {
|
|||||||
mode: LaunchMode.externalNonBrowserApplication,
|
mode: LaunchMode.externalNonBrowserApplication,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Open the file [path] using the system native means. Shows a toast if the
|
||||||
|
/// file cannot be opened.
|
||||||
|
Future<void> openFile(String path) async {
|
||||||
|
final result = await OpenFile.open(path);
|
||||||
|
|
||||||
|
if (result.type != ResultType.done) {
|
||||||
|
String message;
|
||||||
|
if (result.type == ResultType.noAppToOpen) {
|
||||||
|
message = t.errors.conversation.openFileNoAppError;
|
||||||
|
} else {
|
||||||
|
message = t.errors.conversation.openFileGenericError;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Fluttertoast.showToast(
|
||||||
|
msg: message,
|
||||||
|
toastLength: Toast.LENGTH_SHORT,
|
||||||
|
gravity: ToastGravity.SNACKBAR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -502,27 +502,33 @@ class ConversationPageState extends State<ConversationPage> with TickerProviderS
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
Positioned(
|
BlocBuilder<ConversationBloc, ConversationState>(
|
||||||
right: 8,
|
buildWhen: (prev, next) => prev.emojiPickerVisible != next.emojiPickerVisible ||
|
||||||
bottom: 80,
|
prev.stickerPickerVisible != next.stickerPickerVisible,
|
||||||
child: Material(
|
builder: (context, state) => Positioned(
|
||||||
color: const Color.fromRGBO(0, 0, 0, 0),
|
right: 8,
|
||||||
child: ScaleTransition(
|
bottom: state.emojiPickerVisible || state.stickerPickerVisible ?
|
||||||
scale: _scrollToBottom,
|
330 /* 80 + 250 */ :
|
||||||
alignment: FractionalOffset.center,
|
80,
|
||||||
child: SizedBox(
|
child: Material(
|
||||||
width: 45,
|
color: const Color.fromRGBO(0, 0, 0, 0),
|
||||||
height: 45,
|
child: ScaleTransition(
|
||||||
child: FloatingActionButton(
|
scale: _scrollToBottom,
|
||||||
heroTag: 'fabScrollDown',
|
alignment: FractionalOffset.center,
|
||||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
child: SizedBox(
|
||||||
onPressed: () {
|
width: 45,
|
||||||
_scrollController.jumpTo(0);
|
height: 45,
|
||||||
},
|
child: FloatingActionButton(
|
||||||
child: const Icon(
|
heroTag: 'fabScrollDown',
|
||||||
Icons.arrow_downward,
|
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
// TODO(Unknown): Theme dependent
|
onPressed: () {
|
||||||
color: Colors.white,
|
_scrollController.jumpTo(0);
|
||||||
|
},
|
||||||
|
child: const Icon(
|
||||||
|
Icons.arrow_downward,
|
||||||
|
// TODO(Unknown): Theme dependent
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -95,18 +95,13 @@ class ConversationProfileHeader extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
SharedMediaContainer(
|
SharedMediaContainer(
|
||||||
ClipRRect(
|
Icon(
|
||||||
borderRadius: BorderRadius.circular(10),
|
conversation.muted ?
|
||||||
child: ColoredBox(
|
Icons.do_not_disturb_on :
|
||||||
color: getTileColor(context),
|
Icons.do_not_disturb_off,
|
||||||
child: Icon(
|
size: 32,
|
||||||
conversation.muted ?
|
|
||||||
Icons.do_not_disturb_on :
|
|
||||||
Icons.do_not_disturb_off,
|
|
||||||
size: 32,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
color: getTileColor(context),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
GetIt.I.get<ProfileBloc>().add(
|
GetIt.I.get<ProfileBloc>().add(
|
||||||
MuteStateSetEvent(
|
MuteStateSetEvent(
|
||||||
@ -134,16 +129,11 @@ class ConversationProfileHeader extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
SharedMediaContainer(
|
SharedMediaContainer(
|
||||||
ClipRRect(
|
const Icon(
|
||||||
borderRadius: BorderRadius.circular(10),
|
Icons.security_outlined,
|
||||||
child: ColoredBox(
|
size: 32,
|
||||||
color: getTileColor(context),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.security_outlined,
|
|
||||||
size: 32,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
color: getTileColor(context),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
GetIt.I.get<DevicesBloc>().add(DevicesRequestedEvent(conversation.jid));
|
GetIt.I.get<DevicesBloc>().add(DevicesRequestedEvent(conversation.jid));
|
||||||
},
|
},
|
||||||
|
@ -90,16 +90,11 @@ class SelfProfileHeader extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
SharedMediaContainer(
|
SharedMediaContainer(
|
||||||
ClipRRect(
|
const Icon(
|
||||||
borderRadius: BorderRadius.circular(10),
|
Icons.security_outlined,
|
||||||
child: ColoredBox(
|
size: 32,
|
||||||
color: getTileColor(context),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.security_outlined,
|
|
||||||
size: 32,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
color: getTileColor(context),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
GetIt.I.get<OwnDevicesBloc>().add(OwnDevicesRequestedEvent());
|
GetIt.I.get<OwnDevicesBloc>().add(OwnDevicesRequestedEvent());
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,16 @@
|
|||||||
|
import 'package:auto_size_text/auto_size_text.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:moxxyv2/ui/constants.dart';
|
import 'package:moxxyv2/ui/constants.dart';
|
||||||
|
|
||||||
|
String _formatHalfFingerprint(String half) {
|
||||||
|
final p1 = half.substring(0, 8);
|
||||||
|
final p2 = half.substring(8, 16);
|
||||||
|
final p3 = half.substring(16, 24);
|
||||||
|
final p4 = half.substring(24, 32);
|
||||||
|
|
||||||
|
return '$p1 $p2 $p3 $p4';
|
||||||
|
}
|
||||||
|
|
||||||
class FingerprintListItem extends StatelessWidget {
|
class FingerprintListItem extends StatelessWidget {
|
||||||
const FingerprintListItem(
|
const FingerprintListItem(
|
||||||
this.fingerprint,
|
this.fingerprint,
|
||||||
@ -26,14 +36,8 @@ class FingerprintListItem extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final parts = List<String>.empty(growable: true);
|
|
||||||
for (var i = 0; i < 8; i++) {
|
|
||||||
final part = fingerprint.substring(i*8, (i+1)*8);
|
|
||||||
parts.add(part);
|
|
||||||
}
|
|
||||||
|
|
||||||
final width = MediaQuery.of(context).size.width;
|
final width = MediaQuery.of(context).size.width;
|
||||||
final fontSize = width * 0.04;
|
final fontSize = width * 0.1;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
child: Card(
|
child: Card(
|
||||||
@ -47,17 +51,25 @@ class FingerprintListItem extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Wrap(
|
AutoSizeText(
|
||||||
spacing: 6,
|
_formatHalfFingerprint(fingerprint.substring(0, 32)),
|
||||||
children: parts
|
style: TextStyle(
|
||||||
.map((part_) => Text(
|
fontFamily: 'RobotoMono',
|
||||||
part_,
|
fontSize: fontSize,
|
||||||
style: TextStyle(
|
),
|
||||||
fontFamily: 'RobotoMono',
|
textAlign: TextAlign.center,
|
||||||
fontSize: fontSize,
|
maxLines: 1,
|
||||||
),
|
|
||||||
),).toList(),
|
|
||||||
),
|
),
|
||||||
|
AutoSizeText(
|
||||||
|
_formatHalfFingerprint(fingerprint.substring(32)),
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'RobotoMono',
|
||||||
|
fontSize: fontSize,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
maxLines: 1,
|
||||||
|
),
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
|
@ -101,6 +101,7 @@ class SendFilesPage extends StatelessWidget {
|
|||||||
? const Icon(Icons.delete, size: 32)
|
? const Icon(Icons.delete, size: 32)
|
||||||
: const Icon(Icons.file_present),
|
: const Icon(Icons.file_present),
|
||||||
),
|
),
|
||||||
|
color: sharedMediaItemBackgroundColor,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (selected) {
|
if (selected) {
|
||||||
// The trash can icon has been tapped
|
// The trash can icon has been tapped
|
||||||
@ -199,14 +200,11 @@ class SendFilesPage extends StatelessWidget {
|
|||||||
return _renderPreview(context, item, index == state.index, index);
|
return _renderPreview(context, item, index == state.index, index);
|
||||||
} else {
|
} else {
|
||||||
return SharedMediaContainer(
|
return SharedMediaContainer(
|
||||||
DecoratedBox(
|
const Icon(Icons.attach_file),
|
||||||
decoration: BoxDecoration(
|
color: sharedMediaItemBackgroundColor,
|
||||||
borderRadius: BorderRadius.circular(10),
|
onTap: () => context.read<SendFilesBloc>().add(
|
||||||
color: Colors.grey,
|
AddFilesRequestedEvent(),
|
||||||
),
|
|
||||||
child: const Icon(Icons.attach_file),
|
|
||||||
),
|
),
|
||||||
onTap: () => context.read<SendFilesBloc>().add(AddFilesRequestedEvent()),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -124,13 +124,8 @@ class StickerPackPage extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return SharedMediaContainer(
|
return SharedMediaContainer(
|
||||||
ClipRRect(
|
child,
|
||||||
borderRadius: BorderRadius.circular(10),
|
color: color,
|
||||||
child: ColoredBox(
|
|
||||||
color: color,
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (state.stickerPack!.local) {
|
if (state.stickerPack!.local) {
|
||||||
_onDeletePressed(context, state);
|
_onDeletePressed(context, state);
|
||||||
|
@ -57,8 +57,8 @@ class RawChatBubble extends StatelessWidget {
|
|||||||
return message.stickerPackId != null && message.stickerHashKey != null;
|
return message.stickerPackId != null && message.stickerHashKey != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Color? _getBubbleColor(BuildContext context) {
|
Color _getBubbleColor(BuildContext context) {
|
||||||
if (_shouldNotColorBubble()) return null;
|
if (_shouldNotColorBubble()) return Colors.transparent;
|
||||||
|
|
||||||
// Color the bubble red if it should be encrypted but is not.
|
// Color the bubble red if it should be encrypted but is not.
|
||||||
if (chatEncrypted && !message.encrypted) {
|
if (chatEncrypted && !message.encrypted) {
|
||||||
@ -83,24 +83,24 @@ class RawChatBubble extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final borderRadius = getBorderRadius(sentBySelf, start, between, end);
|
final borderRadius = getBorderRadius(sentBySelf, start, between, end);
|
||||||
return Container(
|
return ConstrainedBox(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
maxWidth: maxWidth,
|
maxWidth: maxWidth,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
child: Material(
|
||||||
color: _getBubbleColor(context),
|
color: _getBubbleColor(context),
|
||||||
borderRadius: borderRadius,
|
borderRadius: borderRadius,
|
||||||
),
|
child: Padding(
|
||||||
child: Padding(
|
// NOTE: Images don't work well with padding here
|
||||||
// NOTE: Images don't work well with padding here
|
padding: message.isMedia || message.quotes != null ?
|
||||||
padding: message.isMedia || message.quotes != null ?
|
EdgeInsets.zero :
|
||||||
EdgeInsets.zero :
|
const EdgeInsets.all(8),
|
||||||
const EdgeInsets.all(8),
|
child: buildMessageWidget(
|
||||||
child: buildMessageWidget(
|
message,
|
||||||
message,
|
maxWidth,
|
||||||
maxWidth,
|
borderRadius,
|
||||||
borderRadius,
|
sentBySelf,
|
||||||
sentBySelf,
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -262,6 +262,7 @@ class AudioChatState extends State<AudioChatWidget> {
|
|||||||
(widget.message.filename ?? '') :
|
(widget.message.filename ?? '') :
|
||||||
filenameFromUrl(widget.message.srcUrl!),
|
filenameFromUrl(widget.message.srcUrl!),
|
||||||
widget.radius,
|
widget.radius,
|
||||||
|
widget.maxWidth,
|
||||||
widget.sent,
|
widget.sent,
|
||||||
extra: DownloadButton(
|
extra: DownloadButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
@ -33,8 +33,13 @@ class MediaBaseChatWidget extends StatelessWidget {
|
|||||||
borderRadius: radius,
|
borderRadius: radius,
|
||||||
child: background,
|
child: background,
|
||||||
),
|
),
|
||||||
...gradient ? [BottomGradient(radius)] : [],
|
|
||||||
...extra != null ? [ extra! ] : [],
|
if (gradient)
|
||||||
|
BottomGradient(radius),
|
||||||
|
|
||||||
|
if (extra != null)
|
||||||
|
extra!,
|
||||||
|
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
@ -47,17 +52,13 @@ class MediaBaseChatWidget extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (onTap != null) {
|
return IntrinsicWidth(
|
||||||
return IntrinsicWidth(
|
child: onTap != null ?
|
||||||
child: InkResponse(
|
InkWell(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
child: content,
|
child: content,
|
||||||
),
|
) :
|
||||||
);
|
content,
|
||||||
} else {
|
);
|
||||||
return IntrinsicWidth(
|
|
||||||
child: content,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import 'dart:core';
|
import 'dart:core';
|
||||||
|
import 'package:auto_size_text/auto_size_text.dart';
|
||||||
import 'package:better_open_file/better_open_file.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:moxplatform/moxplatform.dart';
|
import 'package:moxplatform/moxplatform.dart';
|
||||||
import 'package:moxxyv2/shared/commands.dart';
|
import 'package:moxxyv2/shared/commands.dart';
|
||||||
import 'package:moxxyv2/shared/helpers.dart';
|
import 'package:moxxyv2/shared/helpers.dart';
|
||||||
import 'package:moxxyv2/shared/models/message.dart';
|
import 'package:moxxyv2/shared/models/message.dart';
|
||||||
|
import 'package:moxxyv2/ui/helpers.dart';
|
||||||
import 'package:moxxyv2/ui/widgets/chat/bottom.dart';
|
import 'package:moxxyv2/ui/widgets/chat/bottom.dart';
|
||||||
import 'package:moxxyv2/ui/widgets/chat/downloadbutton.dart';
|
import 'package:moxxyv2/ui/widgets/chat/downloadbutton.dart';
|
||||||
import 'package:moxxyv2/ui/widgets/chat/media/base.dart';
|
import 'package:moxxyv2/ui/widgets/chat/media/base.dart';
|
||||||
@ -18,6 +18,7 @@ class FileChatBaseWidget extends StatelessWidget {
|
|||||||
this.icon,
|
this.icon,
|
||||||
this.filename,
|
this.filename,
|
||||||
this.radius,
|
this.radius,
|
||||||
|
this.maxWidth,
|
||||||
this.sent,
|
this.sent,
|
||||||
{
|
{
|
||||||
this.extra,
|
this.extra,
|
||||||
@ -29,35 +30,43 @@ class FileChatBaseWidget extends StatelessWidget {
|
|||||||
final IconData icon;
|
final IconData icon;
|
||||||
final String filename;
|
final String filename;
|
||||||
final BorderRadius radius;
|
final BorderRadius radius;
|
||||||
|
final double maxWidth;
|
||||||
final Widget? extra;
|
final Widget? extra;
|
||||||
final bool sent;
|
final bool sent;
|
||||||
final void Function()? onTap;
|
final void Function()? onTap;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MediaBaseChatWidget(
|
return SizedBox(
|
||||||
Padding(
|
width: maxWidth,
|
||||||
padding: const EdgeInsets.all(16),
|
child: MediaBaseChatWidget(
|
||||||
child: Column(
|
Padding(
|
||||||
mainAxisSize: MainAxisSize.min,
|
padding: const EdgeInsets.all(16),
|
||||||
children: [
|
child: Row(
|
||||||
Icon(
|
mainAxisSize: MainAxisSize.min,
|
||||||
icon,
|
children: [
|
||||||
size: 128,
|
Icon(
|
||||||
),
|
icon,
|
||||||
|
size: 48,
|
||||||
|
),
|
||||||
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 8),
|
padding: const EdgeInsets.only(left: 6),
|
||||||
child: Text(filename),
|
child: AutoSizeText(
|
||||||
),
|
filename,
|
||||||
],
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
MessageBubbleBottom(message, sent),
|
||||||
|
radius,
|
||||||
|
gradient: false,
|
||||||
|
extra: extra,
|
||||||
|
onTap: onTap,
|
||||||
),
|
),
|
||||||
MessageBubbleBottom(message, sent),
|
|
||||||
radius,
|
|
||||||
gradient: false,
|
|
||||||
extra: extra,
|
|
||||||
onTap: onTap,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -68,6 +77,7 @@ class FileChatWidget extends StatelessWidget {
|
|||||||
const FileChatWidget(
|
const FileChatWidget(
|
||||||
this.message,
|
this.message,
|
||||||
this.radius,
|
this.radius,
|
||||||
|
this.maxWidth,
|
||||||
this.sent,
|
this.sent,
|
||||||
{
|
{
|
||||||
this.extra,
|
this.extra,
|
||||||
@ -77,6 +87,7 @@ class FileChatWidget extends StatelessWidget {
|
|||||||
final Message message;
|
final Message message;
|
||||||
final BorderRadius radius;
|
final BorderRadius radius;
|
||||||
final bool sent;
|
final bool sent;
|
||||||
|
final double maxWidth;
|
||||||
final Widget? extra;
|
final Widget? extra;
|
||||||
|
|
||||||
Widget _buildNonDownloaded() {
|
Widget _buildNonDownloaded() {
|
||||||
@ -85,6 +96,7 @@ class FileChatWidget extends StatelessWidget {
|
|||||||
Icons.file_present,
|
Icons.file_present,
|
||||||
message.isFileUploadNotification ? (message.filename ?? '') : filenameFromUrl(message.srcUrl!),
|
message.isFileUploadNotification ? (message.filename ?? '') : filenameFromUrl(message.srcUrl!),
|
||||||
radius,
|
radius,
|
||||||
|
maxWidth,
|
||||||
sent,
|
sent,
|
||||||
extra: DownloadButton(
|
extra: DownloadButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@ -103,6 +115,7 @@ class FileChatWidget extends StatelessWidget {
|
|||||||
Icons.file_present,
|
Icons.file_present,
|
||||||
message.isFileUploadNotification ? (message.filename ?? '') : filenameFromUrl(message.srcUrl!),
|
message.isFileUploadNotification ? (message.filename ?? '') : filenameFromUrl(message.srcUrl!),
|
||||||
radius,
|
radius,
|
||||||
|
maxWidth,
|
||||||
sent,
|
sent,
|
||||||
extra: ProgressWidget(id: message.id),
|
extra: ProgressWidget(id: message.id),
|
||||||
);
|
);
|
||||||
@ -114,27 +127,19 @@ class FileChatWidget extends StatelessWidget {
|
|||||||
Icons.file_present,
|
Icons.file_present,
|
||||||
message.isFileUploadNotification ? (message.filename ?? '') : filenameFromUrl(message.srcUrl!),
|
message.isFileUploadNotification ? (message.filename ?? '') : filenameFromUrl(message.srcUrl!),
|
||||||
radius,
|
radius,
|
||||||
|
maxWidth,
|
||||||
sent,
|
sent,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
OpenFile.open(message.mediaUrl);
|
openFile(message.mediaUrl!);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildWrapper() {
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
if (!message.isDownloading && message.mediaUrl != null) return _buildInner();
|
if (!message.isDownloading && message.mediaUrl != null) return _buildInner();
|
||||||
if (message.isFileUploadNotification || message.isDownloading) return _buildDownloading();
|
if (message.isFileUploadNotification || message.isDownloading) return _buildDownloading();
|
||||||
|
|
||||||
return _buildNonDownloaded();
|
return _buildNonDownloaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return IntrinsicWidth(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
child: _buildWrapper(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:better_open_file/better_open_file.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_blurhash/flutter_blurhash.dart';
|
import 'package:flutter_blurhash/flutter_blurhash.dart';
|
||||||
import 'package:moxplatform/moxplatform.dart';
|
import 'package:moxplatform/moxplatform.dart';
|
||||||
import 'package:moxxyv2/shared/commands.dart';
|
import 'package:moxxyv2/shared/commands.dart';
|
||||||
import 'package:moxxyv2/shared/helpers.dart';
|
import 'package:moxxyv2/shared/helpers.dart';
|
||||||
import 'package:moxxyv2/shared/models/message.dart';
|
import 'package:moxxyv2/shared/models/message.dart';
|
||||||
|
import 'package:moxxyv2/ui/helpers.dart';
|
||||||
import 'package:moxxyv2/ui/widgets/chat/bottom.dart';
|
import 'package:moxxyv2/ui/widgets/chat/bottom.dart';
|
||||||
import 'package:moxxyv2/ui/widgets/chat/downloadbutton.dart';
|
import 'package:moxxyv2/ui/widgets/chat/downloadbutton.dart';
|
||||||
import 'package:moxxyv2/ui/widgets/chat/helpers.dart';
|
import 'package:moxxyv2/ui/widgets/chat/helpers.dart';
|
||||||
@ -61,6 +61,7 @@ class ImageChatWidget extends StatelessWidget {
|
|||||||
Icons.image,
|
Icons.image,
|
||||||
message.isFileUploadNotification ? (message.filename ?? '') : filenameFromUrl(message.srcUrl!),
|
message.isFileUploadNotification ? (message.filename ?? '') : filenameFromUrl(message.srcUrl!),
|
||||||
radius,
|
radius,
|
||||||
|
maxWidth,
|
||||||
sent,
|
sent,
|
||||||
extra: ProgressWidget(id: message.id),
|
extra: ProgressWidget(id: message.id),
|
||||||
);
|
);
|
||||||
@ -86,9 +87,7 @@ class ImageChatWidget extends StatelessWidget {
|
|||||||
image,
|
image,
|
||||||
MessageBubbleBottom(message, sent),
|
MessageBubbleBottom(message, sent),
|
||||||
radius,
|
radius,
|
||||||
onTap: () {
|
onTap: () => openFile(message.mediaUrl!),
|
||||||
OpenFile.open(message.mediaUrl);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,6 +117,7 @@ class ImageChatWidget extends StatelessWidget {
|
|||||||
Icons.image,
|
Icons.image,
|
||||||
message.isFileUploadNotification ? (message.filename ?? '') : filenameFromUrl(message.srcUrl!),
|
message.isFileUploadNotification ? (message.filename ?? '') : filenameFromUrl(message.srcUrl!),
|
||||||
radius,
|
radius,
|
||||||
|
maxWidth,
|
||||||
sent,
|
sent,
|
||||||
extra: DownloadButton(
|
extra: DownloadButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:better_open_file/better_open_file.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:moxxyv2/shared/models/media.dart';
|
import 'package:moxxyv2/shared/models/media.dart';
|
||||||
import 'package:moxxyv2/shared/models/message.dart';
|
import 'package:moxxyv2/shared/models/message.dart';
|
||||||
|
import 'package:moxxyv2/ui/helpers.dart';
|
||||||
import 'package:moxxyv2/ui/widgets/chat/media/audio.dart';
|
import 'package:moxxyv2/ui/widgets/chat/media/audio.dart';
|
||||||
import 'package:moxxyv2/ui/widgets/chat/media/file.dart';
|
import 'package:moxxyv2/ui/widgets/chat/media/file.dart';
|
||||||
import 'package:moxxyv2/ui/widgets/chat/media/image.dart';
|
import 'package:moxxyv2/ui/widgets/chat/media/image.dart';
|
||||||
@ -84,7 +84,7 @@ Widget buildMessageWidget(Message message, double maxWidth, BorderRadius radius,
|
|||||||
case MessageType.audio:
|
case MessageType.audio:
|
||||||
return AudioChatWidget(message, radius, maxWidth, sent);
|
return AudioChatWidget(message, radius, maxWidth, sent);
|
||||||
case MessageType.file: {
|
case MessageType.file: {
|
||||||
return FileChatWidget(message, radius, sent);
|
return FileChatWidget(message, radius, maxWidth, sent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,25 +116,25 @@ Widget buildSharedMediaWidget(SharedMedium medium, String conversationJid) {
|
|||||||
if (medium.mime!.startsWith('image/')) {
|
if (medium.mime!.startsWith('image/')) {
|
||||||
return SharedImageWidget(
|
return SharedImageWidget(
|
||||||
medium.path,
|
medium.path,
|
||||||
onTap: () => OpenFile.open(medium.path),
|
onTap: () => openFile(medium.path),
|
||||||
);
|
);
|
||||||
} else if (medium.mime!.startsWith('video/')) {
|
} else if (medium.mime!.startsWith('video/')) {
|
||||||
return SharedVideoWidget(
|
return SharedVideoWidget(
|
||||||
medium.path,
|
medium.path,
|
||||||
conversationJid,
|
conversationJid,
|
||||||
medium.mime!,
|
medium.mime!,
|
||||||
onTap: () => OpenFile.open(medium.path),
|
onTap: () => openFile(medium.path),
|
||||||
child: const PlayButton(size: 32),
|
child: const PlayButton(size: 32),
|
||||||
);
|
);
|
||||||
} else if (medium.mime!.startsWith('audio/')) {
|
} else if (medium.mime!.startsWith('audio/')) {
|
||||||
return SharedAudioWidget(
|
return SharedAudioWidget(
|
||||||
medium.path,
|
medium.path,
|
||||||
onTap: () => OpenFile.open(medium.path),
|
onTap: () => openFile(medium.path),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return SharedFileWidget(
|
return SharedFileWidget(
|
||||||
medium.path,
|
medium.path,
|
||||||
onTap: () => OpenFile.open(medium.path),
|
onTap: () => openFile(medium.path),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:better_open_file/better_open_file.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_blurhash/flutter_blurhash.dart';
|
import 'package:flutter_blurhash/flutter_blurhash.dart';
|
||||||
import 'package:moxplatform/moxplatform.dart';
|
import 'package:moxplatform/moxplatform.dart';
|
||||||
import 'package:moxxyv2/shared/commands.dart';
|
import 'package:moxxyv2/shared/commands.dart';
|
||||||
import 'package:moxxyv2/shared/helpers.dart';
|
import 'package:moxxyv2/shared/helpers.dart';
|
||||||
import 'package:moxxyv2/shared/models/message.dart';
|
import 'package:moxxyv2/shared/models/message.dart';
|
||||||
|
import 'package:moxxyv2/ui/helpers.dart';
|
||||||
import 'package:moxxyv2/ui/widgets/chat/bottom.dart';
|
import 'package:moxxyv2/ui/widgets/chat/bottom.dart';
|
||||||
import 'package:moxxyv2/ui/widgets/chat/downloadbutton.dart';
|
import 'package:moxxyv2/ui/widgets/chat/downloadbutton.dart';
|
||||||
import 'package:moxxyv2/ui/widgets/chat/helpers.dart';
|
import 'package:moxxyv2/ui/widgets/chat/helpers.dart';
|
||||||
@ -72,6 +72,7 @@ class VideoChatWidget extends StatelessWidget {
|
|||||||
Icons.video_file_outlined,
|
Icons.video_file_outlined,
|
||||||
message.isFileUploadNotification ? (message.filename ?? '') : filenameFromUrl(message.srcUrl!),
|
message.isFileUploadNotification ? (message.filename ?? '') : filenameFromUrl(message.srcUrl!),
|
||||||
radius,
|
radius,
|
||||||
|
maxWidth,
|
||||||
sent,
|
sent,
|
||||||
extra: ProgressWidget(id: message.id),
|
extra: ProgressWidget(id: message.id),
|
||||||
);
|
);
|
||||||
@ -93,9 +94,7 @@ class VideoChatWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
MessageBubbleBottom(message, sent),
|
MessageBubbleBottom(message, sent),
|
||||||
radius,
|
radius,
|
||||||
onTap: () {
|
onTap: () => openFile(message.mediaUrl!),
|
||||||
OpenFile.open(message.mediaUrl);
|
|
||||||
},
|
|
||||||
extra: const PlayButton(),
|
extra: const PlayButton(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -126,6 +125,7 @@ class VideoChatWidget extends StatelessWidget {
|
|||||||
Icons.video_file_outlined,
|
Icons.video_file_outlined,
|
||||||
message.isFileUploadNotification ? (message.filename ?? '') : filenameFromUrl(message.srcUrl!),
|
message.isFileUploadNotification ? (message.filename ?? '') : filenameFromUrl(message.srcUrl!),
|
||||||
radius,
|
radius,
|
||||||
|
maxWidth,
|
||||||
sent,
|
sent,
|
||||||
extra: DownloadButton(
|
extra: DownloadButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
@ -45,11 +45,9 @@ class QuoteBaseWidget extends StatelessWidget {
|
|||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
child: Container(
|
child: Material(
|
||||||
decoration: BoxDecoration(
|
color: _getColor(),
|
||||||
color: _getColor(),
|
borderRadius: const BorderRadius.all(radiusLarge),
|
||||||
borderRadius: const BorderRadius.all(radiusLarge),
|
|
||||||
),
|
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
@ -62,19 +60,19 @@ class QuoteBaseWidget extends StatelessWidget {
|
|||||||
width: quoteLeftBorderWidth,
|
width: quoteLeftBorderWidth,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
...resetQuotedMessage != null ? [
|
|
||||||
|
if (resetQuotedMessage != null)
|
||||||
Positioned(
|
Positioned(
|
||||||
right: 3,
|
right: 3,
|
||||||
top: 3,
|
top: 3,
|
||||||
child: InkWell(
|
child: IconButton(
|
||||||
onTap: resetQuotedMessage,
|
onPressed: resetQuotedMessage,
|
||||||
child: const Icon(
|
icon: const Icon(
|
||||||
Icons.close,
|
Icons.close,
|
||||||
size: 24,
|
size: 24,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
] : [],
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: padding,
|
padding: padding,
|
||||||
child: child,
|
child: child,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:moxxyv2/ui/constants.dart';
|
||||||
import 'package:moxxyv2/ui/widgets/chat/shared/base.dart';
|
import 'package:moxxyv2/ui/widgets/chat/shared/base.dart';
|
||||||
|
|
||||||
class SharedAudioWidget extends StatelessWidget {
|
class SharedAudioWidget extends StatelessWidget {
|
||||||
@ -23,7 +24,6 @@ class SharedAudioWidget extends StatelessWidget {
|
|||||||
Container(
|
Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(borderRadius),
|
borderRadius: BorderRadius.circular(borderRadius),
|
||||||
color: Colors.white60,
|
|
||||||
border: borderColor != null ? Border.all(
|
border: borderColor != null ? Border.all(
|
||||||
color: borderColor!,
|
color: borderColor!,
|
||||||
width: 4,
|
width: 4,
|
||||||
@ -35,6 +35,7 @@ class SharedAudioWidget extends StatelessWidget {
|
|||||||
size: size * 2/3,
|
size: size * 2/3,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
color: sharedMediaItemBackgroundColor,
|
||||||
size: size,
|
size: size,
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
);
|
);
|
||||||
|
@ -24,14 +24,17 @@ const sharedMediaContainerDimension = 75.0;
|
|||||||
/// A widget to show a message that was sent within a chat or is about to be sent.
|
/// A widget to show a message that was sent within a chat or is about to be sent.
|
||||||
class SharedMediaContainer extends StatelessWidget {
|
class SharedMediaContainer extends StatelessWidget {
|
||||||
const SharedMediaContainer(this.child, {
|
const SharedMediaContainer(this.child, {
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.size = sharedMediaContainerDimension,
|
this.size = sharedMediaContainerDimension,
|
||||||
super.key,
|
this.borderRadius = 10,
|
||||||
}
|
required this.color,
|
||||||
);
|
super.key,
|
||||||
|
});
|
||||||
|
final double borderRadius;
|
||||||
final Widget? child;
|
final Widget? child;
|
||||||
final void Function()? onTap;
|
final void Function()? onTap;
|
||||||
final double size;
|
final double size;
|
||||||
|
final Color color;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -44,13 +47,17 @@ class SharedMediaContainer extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (onTap != null) {
|
return ClipRRect(
|
||||||
return InkWell(
|
borderRadius: BorderRadius.circular(borderRadius),
|
||||||
onTap: onTap,
|
child: Material(
|
||||||
child: childWidget,
|
color: color,
|
||||||
);
|
child: onTap != null ?
|
||||||
}
|
InkWell(
|
||||||
|
onTap: onTap,
|
||||||
return childWidget;
|
child: childWidget,
|
||||||
|
) :
|
||||||
|
childWidget,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:moxxyv2/ui/constants.dart';
|
||||||
import 'package:moxxyv2/ui/widgets/chat/shared/base.dart';
|
import 'package:moxxyv2/ui/widgets/chat/shared/base.dart';
|
||||||
|
|
||||||
class SharedFileWidget extends StatelessWidget {
|
class SharedFileWidget extends StatelessWidget {
|
||||||
@ -18,16 +19,12 @@ class SharedFileWidget extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SharedMediaContainer(
|
return SharedMediaContainer(
|
||||||
DecoratedBox(
|
Icon(
|
||||||
decoration: BoxDecoration(
|
Icons.file_present,
|
||||||
borderRadius: BorderRadius.circular(borderRadius),
|
size: size * 2/3,
|
||||||
color: Colors.white60,
|
|
||||||
),
|
|
||||||
child: Icon(
|
|
||||||
Icons.file_present,
|
|
||||||
size: size * 2/3,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
color: sharedMediaItemBackgroundColor,
|
||||||
|
borderRadius: borderRadius,
|
||||||
size: size,
|
size: size,
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
);
|
);
|
||||||
|
@ -38,6 +38,8 @@ class SharedImageWidget extends StatelessWidget {
|
|||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
|
borderRadius: borderRadius,
|
||||||
|
color: Colors.transparent,
|
||||||
size: size,
|
size: size,
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
);
|
);
|
||||||
|
@ -11,20 +11,15 @@ class SharedSummaryWidget extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final number = min(notShown, 99);
|
final number = min(notShown, 99);
|
||||||
return SharedMediaContainer(
|
return SharedMediaContainer(
|
||||||
ClipRRect(
|
Center(
|
||||||
borderRadius: BorderRadius.circular(10),
|
child: Text(
|
||||||
child: ColoredBox(
|
'+$number',
|
||||||
color: Colors.black38,
|
style: const TextStyle(
|
||||||
child: Center(
|
fontSize: 30,
|
||||||
child: Text(
|
|
||||||
'+$number',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 30,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
color: sharedMediaSummaryBackgroundColor,
|
||||||
onTap: () => Navigator.of(context).pushNamed(sharedMediaRoute),
|
onTap: () => Navigator.of(context).pushNamed(sharedMediaRoute),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,8 @@ class SharedVideoWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(borderRadius),
|
borderRadius: BorderRadius.circular(borderRadius),
|
||||||
),
|
),
|
||||||
|
borderRadius: borderRadius,
|
||||||
|
color: Colors.transparent,
|
||||||
size: size,
|
size: size,
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
);
|
);
|
||||||
|
0
lib/ui/widgets/omemo_device_card.dart
Normal file
0
lib/ui/widgets/omemo_device_card.dart
Normal file
@ -1,9 +1,14 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:flutter_vibrate/flutter_vibrate.dart';
|
||||||
|
import 'package:moxxyv2/i18n/strings.g.dart';
|
||||||
import 'package:moxxyv2/shared/models/sticker.dart';
|
import 'package:moxxyv2/shared/models/sticker.dart';
|
||||||
import 'package:moxxyv2/shared/models/sticker_pack.dart';
|
import 'package:moxxyv2/shared/models/sticker_pack.dart';
|
||||||
|
import 'package:moxxyv2/ui/bloc/navigation_bloc.dart' as nav;
|
||||||
|
import 'package:moxxyv2/ui/bloc/sticker_pack_bloc.dart';
|
||||||
import 'package:moxxyv2/ui/bloc/stickers_bloc.dart';
|
import 'package:moxxyv2/ui/bloc/stickers_bloc.dart';
|
||||||
|
import 'package:moxxyv2/ui/constants.dart';
|
||||||
|
|
||||||
class StickerPicker extends StatelessWidget {
|
class StickerPicker extends StatelessWidget {
|
||||||
StickerPicker({
|
StickerPicker({
|
||||||
@ -18,13 +23,114 @@ class StickerPicker extends StatelessWidget {
|
|||||||
late final double _itemSize;
|
late final double _itemSize;
|
||||||
final void Function(Sticker, StickerPack) onStickerTapped;
|
final void Function(Sticker, StickerPack) onStickerTapped;
|
||||||
|
|
||||||
|
Widget _buildList(BuildContext context, StickersState state) {
|
||||||
|
// TODO(PapaTutuWawa): Solve this somewhere else
|
||||||
|
final stickerPacks = state.stickerPacks
|
||||||
|
.where((pack) => !pack.restricted)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (stickerPacks.isEmpty) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Align(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'${t.pages.conversation.stickerPickerNoStickersLine1}\n${t.pages.conversation.stickerPickerNoStickersLine2}',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
context.read<nav.NavigationBloc>().add(
|
||||||
|
nav.PushedNamedEvent(
|
||||||
|
const nav.NavigationDestination(stickersRoute),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Text(t.pages.conversation.stickerSettings),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: stickerPacks.length * 2,
|
||||||
|
itemBuilder: (_, si) {
|
||||||
|
if (si.isEven) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 15),
|
||||||
|
child: Text(
|
||||||
|
stickerPacks[si ~/ 2].name,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final sindex = (si - 1) ~/ 2;
|
||||||
|
return ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemCount: (stickerPacks[sindex].stickers.length / 4).ceil(),
|
||||||
|
itemBuilder: (_, index) {
|
||||||
|
final stickersLength = stickerPacks[sindex].stickers.length - index * 4;
|
||||||
|
return SizedBox(
|
||||||
|
width: width,
|
||||||
|
child: Row(
|
||||||
|
children: List<int>.generate(
|
||||||
|
stickersLength >= 4 ?
|
||||||
|
4 :
|
||||||
|
stickersLength,
|
||||||
|
(i) => i,
|
||||||
|
).map((rowIndex) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(15),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
onStickerTapped(
|
||||||
|
stickerPacks[sindex].stickers[index * 4 + rowIndex],
|
||||||
|
stickerPacks[sindex],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onLongPress: () {
|
||||||
|
Vibrate.feedback(FeedbackType.medium);
|
||||||
|
|
||||||
|
context.read<StickerPackBloc>().add(
|
||||||
|
LocallyAvailableStickerPackRequested(
|
||||||
|
stickerPacks[sindex].id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Image.file(
|
||||||
|
File(
|
||||||
|
stickerPacks[sindex].stickers[index * 4 + rowIndex].path,
|
||||||
|
),
|
||||||
|
key: ValueKey('${state.stickerPacks[sindex].id}_${index * 4 + rowIndex}'),
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
width: _itemSize,
|
||||||
|
height: _itemSize,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<StickersBloc, StickersState>(
|
return BlocBuilder<StickersBloc, StickersState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final stickerPacks = state.stickerPacks
|
|
||||||
.where((pack) => !pack.restricted)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: 250,
|
height: 250,
|
||||||
@ -33,66 +139,7 @@ class StickerPicker extends StatelessWidget {
|
|||||||
color: Theme.of(context).scaffoldBackgroundColor,
|
color: Theme.of(context).scaffoldBackgroundColor,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(top: 16),
|
padding: const EdgeInsets.only(top: 16),
|
||||||
child: ListView.builder(
|
child: _buildList(context, state),
|
||||||
itemCount: stickerPacks.length * 2,
|
|
||||||
itemBuilder: (_, si) {
|
|
||||||
if (si.isEven) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 15),
|
|
||||||
child: Text(
|
|
||||||
stickerPacks[si ~/ 2].name,
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 20,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final sindex = (si - 1) ~/ 2;
|
|
||||||
return ListView.builder(
|
|
||||||
shrinkWrap: true,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
itemCount: (stickerPacks[sindex].stickers.length / 4).ceil(),
|
|
||||||
itemBuilder: (_, index) {
|
|
||||||
final stickersLength = stickerPacks[sindex].stickers.length - index * 4;
|
|
||||||
return SizedBox(
|
|
||||||
width: width,
|
|
||||||
child: Row(
|
|
||||||
children: List<int>.generate(
|
|
||||||
stickersLength >= 4 ?
|
|
||||||
4 :
|
|
||||||
stickersLength,
|
|
||||||
(i) => i,
|
|
||||||
).map((rowIndex) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.all(15),
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () {
|
|
||||||
onStickerTapped(
|
|
||||||
stickerPacks[sindex].stickers[index * 4 + rowIndex],
|
|
||||||
stickerPacks[sindex],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Image.file(
|
|
||||||
File(
|
|
||||||
stickerPacks[sindex].stickers[index * 4 + rowIndex].path,
|
|
||||||
),
|
|
||||||
key: ValueKey('${state.stickerPacks[sindex].id}_${index * 4 + rowIndex}'),
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
width: _itemSize,
|
|
||||||
height: _itemSize,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -43,6 +43,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.1"
|
||||||
|
auto_size_text:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: auto_size_text
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
awesome_notifications:
|
awesome_notifications:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -12,6 +12,7 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
archive: 3.3.0
|
archive: 3.3.0
|
||||||
audiofileplayer: 2.1.1
|
audiofileplayer: 2.1.1
|
||||||
|
auto_size_text: 3.0.0
|
||||||
awesome_notifications: 0.7.4+1
|
awesome_notifications: 0.7.4+1
|
||||||
badges: 2.0.3
|
badges: 2.0.3
|
||||||
better_open_file: 3.6.3
|
better_open_file: 3.6.3
|
||||||
|
Loading…
Reference in New Issue
Block a user