mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 09:28:41 +00:00
Merge pull request #315 from twonlyapp/dev
Some checks failed
Publish on Github / build_and_publish (push) Has been cancelled
Some checks failed
Publish on Github / build_and_publish (push) Has been cancelled
- UI Performance improvements - Multiple Bug fixes
This commit is contained in:
commit
a99a52cdb0
37 changed files with 147 additions and 140 deletions
1
assets/images/build/.last_build_id
Normal file
1
assets/images/build/.last_build_id
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
5cde0a78d118f9e043e7443ceca0f306
|
||||||
BIN
assets/images/default_avatar.svg.vec
Normal file
BIN
assets/images/default_avatar.svg.vec
Normal file
Binary file not shown.
|
|
@ -34,3 +34,6 @@ Map<String, VoidCallback> globalUserDataChangedCallBack = {};
|
||||||
|
|
||||||
bool globalIsAppInBackground = true;
|
bool globalIsAppInBackground = true;
|
||||||
bool globalAllowErrorTrackingViaSentry = false;
|
bool globalAllowErrorTrackingViaSentry = false;
|
||||||
|
|
||||||
|
late String globalApplicationCacheDirectory;
|
||||||
|
late String globalApplicationSupportDirectory;
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,7 @@
|
||||||
// ignore_for_file: unused_import
|
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:camera/camera.dart';
|
import 'package:camera/camera.dart';
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:path/path.dart';
|
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||||
|
|
@ -25,21 +18,13 @@ import 'package:twonly/src/services/fcm.service.dart';
|
||||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||||
import 'package:twonly/src/services/notifications/setup.notifications.dart';
|
import 'package:twonly/src/services/notifications/setup.notifications.dart';
|
||||||
import 'package:twonly/src/services/twonly_safe/create_backup.twonly_safe.dart';
|
import 'package:twonly/src/services/twonly_safe/create_backup.twonly_safe.dart';
|
||||||
|
import 'package:twonly/src/utils/avatars.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
SentryWidgetsFlutterBinding.ensureInitialized();
|
SentryWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
// try {
|
|
||||||
// File(join((await getApplicationSupportDirectory()).path, 'twonly.sqlite'))
|
|
||||||
// .deleteSync();
|
|
||||||
// } catch (e) {}
|
|
||||||
// await updateUserdata((u) {
|
|
||||||
// u.appVersion = 0;
|
|
||||||
// return u;
|
|
||||||
// });
|
|
||||||
|
|
||||||
final user = await getUser();
|
final user = await getUser();
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
gUser = user;
|
gUser = user;
|
||||||
|
|
@ -60,6 +45,10 @@ void main() async {
|
||||||
|
|
||||||
await initFCMService();
|
await initFCMService();
|
||||||
|
|
||||||
|
globalApplicationCacheDirectory = (await getApplicationCacheDirectory()).path;
|
||||||
|
globalApplicationSupportDirectory =
|
||||||
|
(await getApplicationSupportDirectory()).path;
|
||||||
|
|
||||||
initLogger();
|
initLogger();
|
||||||
|
|
||||||
final settingsController = SettingsChangeProvider();
|
final settingsController = SettingsChangeProvider();
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
|
||||||
import 'package:twonly/src/services/api/messages.dart';
|
import 'package:twonly/src/services/api/messages.dart';
|
||||||
import 'package:twonly/src/services/api/utils.dart';
|
import 'package:twonly/src/services/api/utils.dart';
|
||||||
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
||||||
import 'package:twonly/src/services/notifications/setup.notifications.dart';
|
import 'package:twonly/src/utils/avatars.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,7 @@ Future<void> handleMediaUpdate(
|
||||||
);
|
);
|
||||||
case EncryptedContent_MediaUpdate_Type.STORED:
|
case EncryptedContent_MediaUpdate_Type.STORED:
|
||||||
Log.info('Got media file stored ${mediaFile.mediaId}');
|
Log.info('Got media file stored ${mediaFile.mediaId}');
|
||||||
final mediaService = await MediaFileService.fromMedia(mediaFile);
|
final mediaService = MediaFileService(mediaFile);
|
||||||
await mediaService.storeMediaFile();
|
await mediaService.storeMediaFile();
|
||||||
await twonlyDB.messagesDao.updateMessageId(
|
await twonlyDB.messagesDao.updateMessageId(
|
||||||
message.messageId,
|
message.messageId,
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,9 @@ Map<String, List<String>> defaultAutoDownloadOptions = {
|
||||||
};
|
};
|
||||||
|
|
||||||
Future<bool> isAllowedToDownload(MediaType type) async {
|
Future<bool> isAllowedToDownload(MediaType type) async {
|
||||||
|
if (type == MediaType.audio) {
|
||||||
|
return true; // always download audio files
|
||||||
|
}
|
||||||
final connectivityResult = await Connectivity().checkConnectivity();
|
final connectivityResult = await Connectivity().checkConnectivity();
|
||||||
|
|
||||||
final options = gUser.autoDownloadOptions ?? defaultAutoDownloadOptions;
|
final options = gUser.autoDownloadOptions ?? defaultAutoDownloadOptions;
|
||||||
|
|
@ -117,7 +120,7 @@ Future<void> handleDownloadStatusUpdate(TaskStatusUpdate update) async {
|
||||||
Mutex protectDownload = Mutex();
|
Mutex protectDownload = Mutex();
|
||||||
|
|
||||||
Future<void> startDownloadMedia(MediaFile media, bool force) async {
|
Future<void> startDownloadMedia(MediaFile media, bool force) async {
|
||||||
final mediaService = await MediaFileService.fromMedia(media);
|
final mediaService = MediaFileService(media);
|
||||||
|
|
||||||
if (mediaService.encryptedPath.existsSync()) {
|
if (mediaService.encryptedPath.existsSync()) {
|
||||||
await handleEncryptedFile(media.mediaId);
|
await handleEncryptedFile(media.mediaId);
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@ Future<void> handleUploadStatusUpdate(TaskStatusUpdate update) async {
|
||||||
Log.error(
|
Log.error(
|
||||||
'Background upload failed for $mediaId with status ${update.status} and ${update.responseStatusCode}. ',
|
'Background upload failed for $mediaId with status ${update.status} and ${update.responseStatusCode}. ',
|
||||||
);
|
);
|
||||||
final mediaService = await MediaFileService.fromMedia(media);
|
final mediaService = MediaFileService(media);
|
||||||
|
|
||||||
await mediaService.setUploadState(UploadState.uploaded);
|
await mediaService.setUploadState(UploadState.uploaded);
|
||||||
// In all other cases just try the upload again...
|
// In all other cases just try the upload again...
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ Future<void> finishStartedPreprocessing() async {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
final service = await MediaFileService.fromMedia(mediaFile);
|
final service = MediaFileService(mediaFile);
|
||||||
if (!service.originalPath.existsSync() &&
|
if (!service.originalPath.existsSync() &&
|
||||||
!service.uploadRequestPath.existsSync()) {
|
!service.uploadRequestPath.existsSync()) {
|
||||||
if (service.storedPath.existsSync()) {
|
if (service.storedPath.existsSync()) {
|
||||||
|
|
@ -78,7 +78,7 @@ Future<MediaFileService?> initializeMediaUpload(
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (mediaFile == null) return null;
|
if (mediaFile == null) return null;
|
||||||
return MediaFileService.fromMedia(mediaFile);
|
return MediaFileService(mediaFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> insertMediaFileInMessagesTable(
|
Future<void> insertMediaFileInMessagesTable(
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
|
|
@ -11,31 +10,21 @@ import 'package:twonly/src/services/mediafiles/thumbnail.service.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
class MediaFileService {
|
class MediaFileService {
|
||||||
MediaFileService(this.mediaFile, {required this.applicationSupportDirectory});
|
MediaFileService(this.mediaFile);
|
||||||
MediaFile mediaFile;
|
MediaFile mediaFile;
|
||||||
|
|
||||||
final Directory applicationSupportDirectory;
|
|
||||||
|
|
||||||
static Future<MediaFileService> fromMedia(MediaFile media) async {
|
|
||||||
return MediaFileService(
|
|
||||||
media,
|
|
||||||
applicationSupportDirectory: await getApplicationSupportDirectory(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<MediaFileService?> fromMediaId(String mediaId) async {
|
static Future<MediaFileService?> fromMediaId(String mediaId) async {
|
||||||
final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById(mediaId);
|
final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById(mediaId);
|
||||||
if (mediaFile == null) return null;
|
if (mediaFile == null) return null;
|
||||||
return MediaFileService(
|
return MediaFileService(
|
||||||
mediaFile,
|
mediaFile,
|
||||||
applicationSupportDirectory: await getApplicationSupportDirectory(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> purgeTempFolder() async {
|
static Future<void> purgeTempFolder() async {
|
||||||
final tempDirectory = MediaFileService.buildDirectoryPath(
|
final tempDirectory = MediaFileService.buildDirectoryPath(
|
||||||
'tmp',
|
'tmp',
|
||||||
await getApplicationSupportDirectory(),
|
globalApplicationSupportDirectory,
|
||||||
);
|
);
|
||||||
|
|
||||||
final files = tempDirectory.listSync();
|
final files = tempDirectory.listSync();
|
||||||
|
|
@ -241,11 +230,11 @@ class MediaFileService {
|
||||||
|
|
||||||
static Directory buildDirectoryPath(
|
static Directory buildDirectoryPath(
|
||||||
String directory,
|
String directory,
|
||||||
Directory applicationSupportDirectory,
|
String applicationSupportDirectory,
|
||||||
) {
|
) {
|
||||||
final mediaBaseDir = Directory(
|
final mediaBaseDir = Directory(
|
||||||
join(
|
join(
|
||||||
applicationSupportDirectory.path,
|
applicationSupportDirectory,
|
||||||
'mediafiles',
|
'mediafiles',
|
||||||
directory,
|
directory,
|
||||||
),
|
),
|
||||||
|
|
@ -275,7 +264,7 @@ class MediaFileService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final mediaBaseDir =
|
final mediaBaseDir =
|
||||||
buildDirectoryPath(directory, applicationSupportDirectory);
|
buildDirectoryPath(directory, globalApplicationSupportDirectory);
|
||||||
return File(
|
return File(
|
||||||
join(mediaBaseDir.path, '${mediaFile.mediaId}$namePrefix.$extension'),
|
join(mediaBaseDir.path, '${mediaFile.mediaId}$namePrefix.$extension'),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,7 @@
|
||||||
// ignore_for_file: unreachable_from_main
|
// ignore_for_file: unreachable_from_main
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:ui' as ui;
|
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
import 'package:twonly/globals.dart';
|
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
|
||||||
|
|
||||||
final StreamController<NotificationResponse> selectNotificationStream =
|
final StreamController<NotificationResponse> selectNotificationStream =
|
||||||
StreamController<NotificationResponse>.broadcast();
|
StreamController<NotificationResponse>.broadcast();
|
||||||
|
|
@ -54,35 +48,3 @@ Future<void> setupPushNotification() async {
|
||||||
onDidReceiveBackgroundNotificationResponse: notificationTapBackground,
|
onDidReceiveBackgroundNotificationResponse: notificationTapBackground,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> createPushAvatars() async {
|
|
||||||
if (!Platform.isAndroid) {
|
|
||||||
return; // avatars currently only shown in Android...
|
|
||||||
}
|
|
||||||
final contacts = await twonlyDB.contactsDao.getAllNotBlockedContacts();
|
|
||||||
|
|
||||||
for (final contact in contacts) {
|
|
||||||
if (contact.avatarSvgCompressed == null) continue;
|
|
||||||
|
|
||||||
final avatarSvg = getAvatarSvg(contact.avatarSvgCompressed!);
|
|
||||||
|
|
||||||
final pictureInfo = await vg.loadPicture(SvgStringLoader(avatarSvg), null);
|
|
||||||
|
|
||||||
final image = await pictureInfo.picture.toImage(300, 300);
|
|
||||||
|
|
||||||
final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
|
|
||||||
final pngBytes = byteData!.buffer.asUint8List();
|
|
||||||
|
|
||||||
// Get the directory to save the image
|
|
||||||
final directory = await getApplicationCacheDirectory();
|
|
||||||
final avatarsDirectory = Directory('${directory.path}/avatars');
|
|
||||||
|
|
||||||
// Create the avatars directory if it does not exist
|
|
||||||
if (!avatarsDirectory.existsSync()) {
|
|
||||||
await avatarsDirectory.create(recursive: true);
|
|
||||||
}
|
|
||||||
final filePath = '${avatarsDirectory.path}/${contact.userId}.png';
|
|
||||||
await File(filePath).writeAsBytes(pngBytes);
|
|
||||||
pictureInfo.picture.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
36
lib/src/utils/avatars.dart
Normal file
36
lib/src/utils/avatars.dart
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:twonly/globals.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
|
Future<void> createPushAvatars() async {
|
||||||
|
final contacts = await twonlyDB.contactsDao.getAllNotBlockedContacts();
|
||||||
|
|
||||||
|
for (final contact in contacts) {
|
||||||
|
if (contact.avatarSvgCompressed == null) continue;
|
||||||
|
|
||||||
|
final avatarSvg = getAvatarSvg(contact.avatarSvgCompressed!);
|
||||||
|
|
||||||
|
final pictureInfo = await vg.loadPicture(SvgStringLoader(avatarSvg), null);
|
||||||
|
|
||||||
|
final image = await pictureInfo.picture.toImage(300, 300);
|
||||||
|
|
||||||
|
final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
|
||||||
|
final pngBytes = byteData!.buffer.asUint8List();
|
||||||
|
|
||||||
|
await avatarPNGFile(contact.userId).writeAsBytes(pngBytes);
|
||||||
|
pictureInfo.picture.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File avatarPNGFile(int contactId) {
|
||||||
|
final avatarsDirectory =
|
||||||
|
Directory('$globalApplicationCacheDirectory/avatars');
|
||||||
|
|
||||||
|
if (!avatarsDirectory.existsSync()) {
|
||||||
|
avatarsDirectory.createSync(recursive: true);
|
||||||
|
}
|
||||||
|
return File('${avatarsDirectory.path}/$contactId.png');
|
||||||
|
}
|
||||||
|
|
@ -20,7 +20,7 @@ class HomeViewCameraPreview extends StatelessWidget {
|
||||||
}
|
}
|
||||||
return Positioned.fill(
|
return Positioned.fill(
|
||||||
child: MediaViewSizing(
|
child: MediaViewSizing(
|
||||||
requiredHeight: 90,
|
requiredHeight: 80,
|
||||||
bottomNavigation: Container(),
|
bottomNavigation: Container(),
|
||||||
child: Screenshot(
|
child: Screenshot(
|
||||||
controller: screenshotController,
|
controller: screenshotController,
|
||||||
|
|
@ -60,7 +60,7 @@ class SendToCameraPreview extends StatelessWidget {
|
||||||
}
|
}
|
||||||
return Positioned.fill(
|
return Positioned.fill(
|
||||||
child: MediaViewSizing(
|
child: MediaViewSizing(
|
||||||
requiredHeight: 90,
|
requiredHeight: 80,
|
||||||
bottomNavigation: Container(),
|
bottomNavigation: Container(),
|
||||||
child: Screenshot(
|
child: Screenshot(
|
||||||
controller: screenshotController,
|
controller: screenshotController,
|
||||||
|
|
|
||||||
|
|
@ -598,7 +598,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
return Container();
|
return Container();
|
||||||
}
|
}
|
||||||
return MediaViewSizing(
|
return MediaViewSizing(
|
||||||
requiredHeight: 90,
|
requiredHeight: 80,
|
||||||
bottomNavigation: Container(),
|
bottomNavigation: Container(),
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onPanStart: (details) async {
|
onPanStart: (details) async {
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ class BestFriendsSelector extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: UserCheckbox(
|
child: UserCheckbox(
|
||||||
|
key: ValueKey(groups[firstUserIndex]),
|
||||||
isChecked: selectedGroupIds
|
isChecked: selectedGroupIds
|
||||||
.contains(groups[firstUserIndex].groupId),
|
.contains(groups[firstUserIndex].groupId),
|
||||||
group: groups[firstUserIndex],
|
group: groups[firstUserIndex],
|
||||||
|
|
@ -83,6 +84,7 @@ class BestFriendsSelector extends StatelessWidget {
|
||||||
if (secondUserIndex < groups.length)
|
if (secondUserIndex < groups.length)
|
||||||
Expanded(
|
Expanded(
|
||||||
child: UserCheckbox(
|
child: UserCheckbox(
|
||||||
|
key: ValueKey(groups[secondUserIndex]),
|
||||||
isChecked: selectedGroupIds
|
isChecked: selectedGroupIds
|
||||||
.contains(groups[secondUserIndex].groupId),
|
.contains(groups[secondUserIndex].groupId),
|
||||||
group: groups[secondUserIndex],
|
group: groups[secondUserIndex],
|
||||||
|
|
|
||||||
|
|
@ -524,7 +524,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
child: MediaViewSizing(
|
child: MediaViewSizing(
|
||||||
requiredHeight: 90,
|
requiredHeight: 80,
|
||||||
bottomNavigation: ColoredBox(
|
bottomNavigation: ColoredBox(
|
||||||
color: Theme.of(context).colorScheme.surface,
|
color: Theme.of(context).colorScheme.surface,
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|
|
||||||
|
|
@ -323,6 +323,7 @@ class UserList extends StatelessWidget {
|
||||||
itemBuilder: (BuildContext context, int i) {
|
itemBuilder: (BuildContext context, int i) {
|
||||||
final group = groups[i];
|
final group = groups[i];
|
||||||
return ListTile(
|
return ListTile(
|
||||||
|
key: ValueKey(group.groupId),
|
||||||
title: Row(
|
title: Row(
|
||||||
children: [
|
children: [
|
||||||
Text(substringBy(group.groupName, 12)),
|
Text(substringBy(group.groupName, 12)),
|
||||||
|
|
|
||||||
|
|
@ -278,6 +278,7 @@ class ContactsListView extends StatelessWidget {
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final contact = contacts[index];
|
final contact = contacts[index];
|
||||||
return ListTile(
|
return ListTile(
|
||||||
|
key: ValueKey(contact.userId),
|
||||||
title: Text(substringBy(contact.username, 25)),
|
title: Text(substringBy(contact.username, 25)),
|
||||||
leading: AvatarIcon(contactId: contact.userId),
|
leading: AvatarIcon(contactId: contact.userId),
|
||||||
trailing: Row(
|
trailing: Row(
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,7 @@ class _AllReactionsViewState extends State<AllReactionsView> {
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: reactionsUsers.map((entry) {
|
children: reactionsUsers.map((entry) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
|
key: ValueKey(entry),
|
||||||
onTap: (entry.$2 != null)
|
onTap: (entry.$2 != null)
|
||||||
? null
|
? null
|
||||||
: () {
|
: () {
|
||||||
|
|
|
||||||
|
|
@ -71,9 +71,9 @@ class _ChatListEntryState extends State<ChatListEntry> {
|
||||||
if (widget.message.mediaId != null) {
|
if (widget.message.mediaId != null) {
|
||||||
final mediaFileStream =
|
final mediaFileStream =
|
||||||
twonlyDB.mediaFilesDao.watchMedia(widget.message.mediaId!);
|
twonlyDB.mediaFilesDao.watchMedia(widget.message.mediaId!);
|
||||||
mediaFileSub = mediaFileStream.listen((mediaFiles) async {
|
mediaFileSub = mediaFileStream.listen((mediaFiles) {
|
||||||
if (mediaFiles != null) {
|
if (mediaFiles != null) {
|
||||||
mediaService = await MediaFileService.fromMedia(mediaFiles);
|
mediaService = MediaFileService(mediaFiles);
|
||||||
if (mounted) setState(() {});
|
if (mounted) setState(() {});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -513,7 +513,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
},
|
},
|
||||||
child: MediaViewSizing(
|
child: MediaViewSizing(
|
||||||
bottomNavigation: bottomNavigation(),
|
bottomNavigation: bottomNavigation(),
|
||||||
requiredHeight: 90,
|
requiredHeight: 80,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
if (videoController != null)
|
if (videoController != null)
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,7 @@ class _MessageInfoViewState extends State<MessageInfoView> {
|
||||||
|
|
||||||
columns.add(
|
columns.add(
|
||||||
Padding(
|
Padding(
|
||||||
|
key: ValueKey(groupMember.$1.contactId),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
|
|
||||||
|
|
@ -219,7 +219,7 @@ class _StartNewChatView extends State<StartNewChatView> {
|
||||||
|
|
||||||
if (i < filteredContacts.length) {
|
if (i < filteredContacts.length) {
|
||||||
return UserContextMenu(
|
return UserContextMenu(
|
||||||
key: Key(filteredContacts[i].userId.toString()),
|
key: ValueKey(filteredContacts[i].userId),
|
||||||
contact: filteredContacts[i],
|
contact: filteredContacts[i],
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
title: Row(
|
title: Row(
|
||||||
|
|
@ -259,7 +259,7 @@ class _StartNewChatView extends State<StartNewChatView> {
|
||||||
|
|
||||||
if (i < filteredGroups.length) {
|
if (i < filteredGroups.length) {
|
||||||
return GroupContextMenu(
|
return GroupContextMenu(
|
||||||
key: Key(filteredGroups[i].groupId),
|
key: ValueKey(filteredGroups[i].groupId),
|
||||||
group: filteredGroups[i],
|
group: filteredGroups[i],
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
|
import 'package:twonly/src/utils/avatars.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
import 'package:vector_graphics/vector_graphics.dart';
|
||||||
|
|
||||||
class AvatarIcon extends StatefulWidget {
|
class AvatarIcon extends StatefulWidget {
|
||||||
const AvatarIcon({
|
const AvatarIcon({
|
||||||
|
|
@ -25,8 +27,9 @@ class AvatarIcon extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AvatarIconState extends State<AvatarIcon> {
|
class _AvatarIconState extends State<AvatarIcon> {
|
||||||
List<String> _avatarSVGs = [];
|
List<Contact> _avatarContacts = [];
|
||||||
String? _globalUserDataCallBackId;
|
String? _globalUserDataCallBackId;
|
||||||
|
String? _avatarSvg;
|
||||||
|
|
||||||
StreamSubscription<List<Contact>>? groupStream;
|
StreamSubscription<List<Contact>>? groupStream;
|
||||||
StreamSubscription<List<Contact>>? contactsStream;
|
StreamSubscription<List<Contact>>? contactsStream;
|
||||||
|
|
@ -49,20 +52,44 @@ class _AvatarIconState extends State<AvatarIcon> {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ignore: strict_top_level_inference
|
||||||
|
Widget errorBuilder(_, __, ___) {
|
||||||
|
return const SvgPicture(
|
||||||
|
AssetBytesLoader('assets/images/default_avatar.svg.vec'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget getAvatarForContact(Contact contact) {
|
||||||
|
final avatarFile = avatarPNGFile(contact.userId);
|
||||||
|
if (avatarFile.existsSync()) {
|
||||||
|
return Image.file(
|
||||||
|
avatarFile,
|
||||||
|
errorBuilder: errorBuilder,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (contact.avatarSvgCompressed != null) {
|
||||||
|
return SvgPicture.string(
|
||||||
|
getAvatarSvg(contact.avatarSvgCompressed!),
|
||||||
|
errorBuilder: errorBuilder,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return errorBuilder(null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
if (widget.group != null) {
|
if (widget.group != null) {
|
||||||
groupStream = twonlyDB.groupsDao
|
groupStream = twonlyDB.groupsDao
|
||||||
.watchGroupContact(widget.group!.groupId)
|
.watchGroupContact(widget.group!.groupId)
|
||||||
.listen((contacts) {
|
.listen((contacts) {
|
||||||
_avatarSVGs = [];
|
_avatarContacts = [];
|
||||||
if (contacts.length == 1) {
|
if (contacts.length == 1) {
|
||||||
if (contacts.first.avatarSvgCompressed != null) {
|
if (contacts.first.avatarSvgCompressed != null) {
|
||||||
_avatarSVGs.add(getAvatarSvg(contacts.first.avatarSvgCompressed!));
|
_avatarContacts.add(contacts.first);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (final contact in contacts) {
|
for (final contact in contacts) {
|
||||||
if (contact.avatarSvgCompressed != null) {
|
if (contact.avatarSvgCompressed != null) {
|
||||||
_avatarSVGs.add(getAvatarSvg(contact.avatarSvgCompressed!));
|
_avatarContacts.add(contact);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -73,21 +100,21 @@ class _AvatarIconState extends State<AvatarIcon> {
|
||||||
globalUserDataChangedCallBack[_globalUserDataCallBackId!] = () {
|
globalUserDataChangedCallBack[_globalUserDataCallBackId!] = () {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (gUser.avatarSvg != null) {
|
if (gUser.avatarSvg != null) {
|
||||||
_avatarSVGs = [gUser.avatarSvg!];
|
_avatarSvg = gUser.avatarSvg;
|
||||||
} else {
|
} else {
|
||||||
_avatarSVGs = [];
|
_avatarContacts = [];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
if (gUser.avatarSvg != null) {
|
if (gUser.avatarSvg != null) {
|
||||||
_avatarSVGs = [gUser.avatarSvg!];
|
_avatarSvg = gUser.avatarSvg;
|
||||||
}
|
}
|
||||||
} else if (widget.contactId != null) {
|
} else if (widget.contactId != null) {
|
||||||
contactStream = twonlyDB.contactsDao
|
contactStream = twonlyDB.contactsDao
|
||||||
.watchContact(widget.contactId!)
|
.watchContact(widget.contactId!)
|
||||||
.listen((contact) {
|
.listen((contact) {
|
||||||
if (contact != null && contact.avatarSvgCompressed != null) {
|
if (contact != null && contact.avatarSvgCompressed != null) {
|
||||||
_avatarSVGs = [getAvatarSvg(contact.avatarSvgCompressed!)];
|
_avatarContacts = [contact];
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -99,27 +126,20 @@ class _AvatarIconState extends State<AvatarIcon> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final proSize = (widget.fontSize == null) ? 40 : (widget.fontSize! * 2);
|
final proSize = (widget.fontSize == null) ? 40 : (widget.fontSize! * 2);
|
||||||
|
|
||||||
Widget avatars = SvgPicture.asset('assets/images/default_avatar.svg');
|
Widget avatars = Container();
|
||||||
|
|
||||||
if (_avatarSVGs.length == 1) {
|
if (_avatarSvg != null) {
|
||||||
avatars = SvgPicture.string(
|
avatars = SvgPicture.string(
|
||||||
_avatarSVGs.first,
|
_avatarSvg!,
|
||||||
errorBuilder: (a, b, c) => avatars,
|
errorBuilder: errorBuilder,
|
||||||
);
|
);
|
||||||
} else if (_avatarSVGs.length >= 2) {
|
} else if (_avatarContacts.length == 1) {
|
||||||
final a = SvgPicture.string(
|
avatars = getAvatarForContact(_avatarContacts.first);
|
||||||
_avatarSVGs.first,
|
} else if (_avatarContacts.length >= 2) {
|
||||||
errorBuilder: (a, b, c) => avatars,
|
final a = getAvatarForContact(_avatarContacts.first);
|
||||||
);
|
final b = getAvatarForContact(_avatarContacts[1]);
|
||||||
final b = SvgPicture.string(
|
if (_avatarContacts.length >= 3) {
|
||||||
_avatarSVGs[1],
|
final c = getAvatarForContact(_avatarContacts[2]);
|
||||||
errorBuilder: (a, b, c) => avatars,
|
|
||||||
);
|
|
||||||
if (_avatarSVGs.length >= 3) {
|
|
||||||
final c = SvgPicture.string(
|
|
||||||
_avatarSVGs[2],
|
|
||||||
errorBuilder: (a, b, c) => avatars,
|
|
||||||
);
|
|
||||||
avatars = Stack(
|
avatars = Stack(
|
||||||
children: [
|
children: [
|
||||||
Transform.translate(
|
Transform.translate(
|
||||||
|
|
@ -153,6 +173,10 @@ class _AvatarIconState extends State<AvatarIcon> {
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
avatars = const SvgPicture(
|
||||||
|
AssetBytesLoader('assets/images/default_avatar.svg.vec'),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,7 @@ class _ContactViewState extends State<ContactView> {
|
||||||
}
|
}
|
||||||
final contact = snapshot.data!;
|
final contact = snapshot.data!;
|
||||||
return ListView(
|
return ListView(
|
||||||
|
key: ValueKey(contact.userId),
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(10),
|
||||||
|
|
|
||||||
|
|
@ -228,6 +228,7 @@ class _GroupViewState extends State<GroupView> {
|
||||||
),
|
),
|
||||||
...members.map((member) {
|
...members.map((member) {
|
||||||
return GroupMemberContextMenu(
|
return GroupMemberContextMenu(
|
||||||
|
key: ValueKey(member.$1.userId),
|
||||||
group: group,
|
group: group,
|
||||||
contact: member.$1,
|
contact: member.$1,
|
||||||
member: member.$2,
|
member: member.$2,
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,7 @@ class _GroupCreateSelectGroupNameViewState
|
||||||
itemBuilder: (BuildContext context, int i) {
|
itemBuilder: (BuildContext context, int i) {
|
||||||
final user = widget.selectedUsers[i];
|
final user = widget.selectedUsers[i];
|
||||||
return UserContextMenu(
|
return UserContextMenu(
|
||||||
|
key: ValueKey(user.userId),
|
||||||
contact: user,
|
contact: user,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
title: Row(
|
title: Row(
|
||||||
|
|
|
||||||
|
|
@ -188,8 +188,7 @@ class _StartNewChatView extends State<GroupCreateSelectMembersView> {
|
||||||
}
|
}
|
||||||
final user = contacts[i];
|
final user = contacts[i];
|
||||||
return UserContextMenu(
|
return UserContextMenu(
|
||||||
// when this is not set, then the avatar is not updated in the list when searching :/
|
key: ValueKey(user.userId),
|
||||||
key: Key(user.userId.toString()),
|
|
||||||
contact: user,
|
contact: user,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
title: Row(
|
title: Row(
|
||||||
|
|
|
||||||
|
|
@ -151,13 +151,12 @@ class HomeViewState extends State<HomeView> {
|
||||||
|
|
||||||
final draftMedia = await twonlyDB.mediaFilesDao.getDraftMediaFile();
|
final draftMedia = await twonlyDB.mediaFilesDao.getDraftMediaFile();
|
||||||
if (draftMedia != null) {
|
if (draftMedia != null) {
|
||||||
final service = await MediaFileService.fromMedia(draftMedia);
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
await Navigator.push(
|
await Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => ShareImageEditorView(
|
builder: (context) => ShareImageEditorView(
|
||||||
mediaFileService: service,
|
mediaFileService: MediaFileService(draftMedia),
|
||||||
sharedFromGallery: true,
|
sharedFromGallery: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
|
|
@ -47,13 +46,8 @@ class MemoriesViewState extends State<MemoriesView> {
|
||||||
months = [];
|
months = [];
|
||||||
var lastMonth = '';
|
var lastMonth = '';
|
||||||
galleryItems = [];
|
galleryItems = [];
|
||||||
final applicationSupportDirectory =
|
|
||||||
await getApplicationSupportDirectory();
|
|
||||||
for (final mediaFile in mediaFiles) {
|
for (final mediaFile in mediaFiles) {
|
||||||
final mediaService = MediaFileService(
|
final mediaService = MediaFileService(mediaFile);
|
||||||
mediaFile,
|
|
||||||
applicationSupportDirectory: applicationSupportDirectory,
|
|
||||||
);
|
|
||||||
if (!mediaService.imagePreviewAvailable) continue;
|
if (!mediaService.imagePreviewAvailable) continue;
|
||||||
if (mediaService.mediaFile.type == MediaType.video) {
|
if (mediaService.mediaFile.type == MediaType.video) {
|
||||||
if (!mediaService.thumbnailPath.existsSync()) {
|
if (!mediaService.thumbnailPath.existsSync()) {
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,9 @@ class _DataAndStorageViewState extends State<DataAndStorageView> {
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(context.lang.settingsStorageDataAutoDownMobile),
|
title: Text(context.lang.settingsStorageDataAutoDownMobile),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
autoDownloadOptions[ConnectivityResult.mobile.name]!.join(', '),
|
autoDownloadOptions[ConnectivityResult.mobile.name]!
|
||||||
|
.where((e) => e != 'audio')
|
||||||
|
.join(', '),
|
||||||
style: const TextStyle(color: Colors.grey),
|
style: const TextStyle(color: Colors.grey),
|
||||||
),
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
|
|
@ -117,7 +119,9 @@ class _DataAndStorageViewState extends State<DataAndStorageView> {
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(context.lang.settingsStorageDataAutoDownWifi),
|
title: Text(context.lang.settingsStorageDataAutoDownWifi),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
autoDownloadOptions[ConnectivityResult.wifi.name]!.join(', '),
|
autoDownloadOptions[ConnectivityResult.wifi.name]!
|
||||||
|
.where((e) => e != 'audio')
|
||||||
|
.join(', '),
|
||||||
style: const TextStyle(color: Colors.grey),
|
style: const TextStyle(color: Colors.grey),
|
||||||
),
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
|
|
@ -178,14 +182,6 @@ class _AutoDownloadOptionsDialogState extends State<AutoDownloadOptionsDialog> {
|
||||||
await _updateAutoDownloadSetting(DownloadMediaTypes.video, value);
|
await _updateAutoDownloadSetting(DownloadMediaTypes.video, value);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
CheckboxListTile(
|
|
||||||
title: const Text('Audio'),
|
|
||||||
value: autoDownloadOptions[widget.connectionMode.name]!
|
|
||||||
.contains(DownloadMediaTypes.audio.name),
|
|
||||||
onChanged: (bool? value) async {
|
|
||||||
await _updateAutoDownloadSetting(DownloadMediaTypes.audio, value);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
|
|
@ -44,12 +45,12 @@ class _ExportMediaViewState extends State<ExportMediaView> {
|
||||||
bool _zipSaved = false;
|
bool _zipSaved = false;
|
||||||
bool _isStoring = false;
|
bool _isStoring = false;
|
||||||
|
|
||||||
Future<Directory> _mediaFolder() async {
|
Directory _mediaFolder() {
|
||||||
final dir = MediaFileService.buildDirectoryPath(
|
final dir = MediaFileService.buildDirectoryPath(
|
||||||
'stored',
|
'stored',
|
||||||
await getApplicationSupportDirectory(),
|
globalApplicationSupportDirectory,
|
||||||
);
|
);
|
||||||
if (!dir.existsSync()) await dir.create(recursive: true);
|
if (!dir.existsSync()) dir.createSync(recursive: true);
|
||||||
return dir;
|
return dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,7 +63,7 @@ class _ExportMediaViewState extends State<ExportMediaView> {
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final folder = await _mediaFolder();
|
final folder = _mediaFolder();
|
||||||
final allFiles =
|
final allFiles =
|
||||||
folder.listSync(recursive: true).whereType<File>().toList();
|
folder.listSync(recursive: true).whereType<File>().toList();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ class _ImportMediaViewState extends State<ImportMediaView> {
|
||||||
stored: const Value(true),
|
stored: const Value(true),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
final mediaService = await MediaFileService.fromMedia(mediaFile!);
|
final mediaService = MediaFileService(mediaFile!);
|
||||||
await mediaService.storedPath.writeAsBytes(file.content);
|
await mediaService.storedPath.writeAsBytes(file.content);
|
||||||
|
|
||||||
processed++;
|
processed++;
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,7 @@ class UserList extends StatelessWidget {
|
||||||
itemBuilder: (BuildContext context, int i) {
|
itemBuilder: (BuildContext context, int i) {
|
||||||
final user = users[i];
|
final user = users[i];
|
||||||
return UserContextMenu(
|
return UserContextMenu(
|
||||||
|
key: ValueKey(user.userId),
|
||||||
contact: user,
|
contact: user,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
title: Row(
|
title: Row(
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ class _DatabaseMigrationViewState extends State<DatabaseMigrationView> {
|
||||||
stored: const Value(true),
|
stored: const Value(true),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
final mediaService = await MediaFileService.fromMedia(mediaFile!);
|
final mediaService = MediaFileService(mediaFile!);
|
||||||
File(file.path).copySync(mediaService.storedPath.path);
|
File(file.path).copySync(mediaService.storedPath.path);
|
||||||
setState(() {
|
setState(() {
|
||||||
_storedMediaFiles += 1;
|
_storedMediaFiles += 1;
|
||||||
|
|
|
||||||
|
|
@ -1822,7 +1822,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.5.2"
|
version: "4.5.2"
|
||||||
vector_graphics:
|
vector_graphics:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: vector_graphics
|
name: vector_graphics
|
||||||
sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6
|
sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ description: "twonly, a privacy-friendly way to connect with friends through sec
|
||||||
|
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
version: 0.0.70+70
|
version: 0.0.71+71
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.6.0
|
sdk: ^3.6.0
|
||||||
|
|
@ -75,6 +75,7 @@ dependencies:
|
||||||
share_plus: ^12.0.0
|
share_plus: ^12.0.0
|
||||||
tutorial_coach_mark: ^1.3.0
|
tutorial_coach_mark: ^1.3.0
|
||||||
url_launcher: ^6.3.1
|
url_launcher: ^6.3.1
|
||||||
|
vector_graphics: ^1.1.19
|
||||||
video_player: ^2.9.5
|
video_player: ^2.9.5
|
||||||
web_socket_channel: ^3.0.1
|
web_socket_channel: ^3.0.1
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue