mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 09:28:41 +00:00
uploads of files does work
This commit is contained in:
parent
e42b68e8ee
commit
2ab0ad3daa
13 changed files with 324 additions and 107 deletions
|
|
@ -88,7 +88,7 @@ class TextContent extends MessageContent {
|
|||
|
||||
@JsonSerializable()
|
||||
class ImageContent extends MessageContent {
|
||||
final String imageToken;
|
||||
final List<int> imageToken;
|
||||
|
||||
ImageContent(this.imageToken);
|
||||
|
||||
|
|
|
|||
|
|
@ -47,7 +47,9 @@ Map<String, dynamic> _$TextContentToJson(TextContent instance) =>
|
|||
};
|
||||
|
||||
ImageContent _$ImageContentFromJson(Map<String, dynamic> json) => ImageContent(
|
||||
json['imageToken'] as String,
|
||||
(json['imageToken'] as List<dynamic>)
|
||||
.map((e) => (e as num).toInt())
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ImageContentToJson(ImageContent instance) =>
|
||||
|
|
|
|||
|
|
@ -178,6 +178,8 @@ class ApiProvider {
|
|||
DbContacts.acceptUser(fromUserId.toInt());
|
||||
updateNotifier();
|
||||
break;
|
||||
case MessageKind.image:
|
||||
log.info("Got image: ${message.content}");
|
||||
default:
|
||||
log.shout("Got unknown MessageKind $message");
|
||||
}
|
||||
|
|
@ -360,6 +362,43 @@ class ApiProvider {
|
|||
return _asResult(resp);
|
||||
}
|
||||
|
||||
Future<Result> getUploadToken(int size) async {
|
||||
var get = ApplicationData_GetUploadToken()..len = size;
|
||||
var appData = ApplicationData()..getuploadtoken = get;
|
||||
var req = createClientToServerFromApplicationData(appData);
|
||||
final resp = await _sendRequestV0(req);
|
||||
if (resp == null) {
|
||||
return Result.error(ErrorCode.InternalError);
|
||||
}
|
||||
return _asResult(resp);
|
||||
}
|
||||
|
||||
Future<List<int>?> uploadData(Uint8List data) async {
|
||||
Result res = await getUploadToken(data.length);
|
||||
|
||||
if (res.isError || !res.value.hasUploadtoken()) {
|
||||
Logger("api.dart").shout("Error getting upload token!");
|
||||
return null;
|
||||
}
|
||||
List<int> uploadToken = res.value.uploadtoken;
|
||||
log.info("Got token: $uploadToken");
|
||||
|
||||
log.shout("fragmentate the data");
|
||||
|
||||
var get = ApplicationData_UploadData()
|
||||
..uploadToken = uploadToken
|
||||
..data = data
|
||||
..offset = 0;
|
||||
|
||||
var appData = ApplicationData()..uploaddata = get;
|
||||
var req = createClientToServerFromApplicationData(appData);
|
||||
final resp = await _sendRequestV0(req);
|
||||
if (resp == null) {
|
||||
return null;
|
||||
}
|
||||
return _asResult(resp).isSuccess ? uploadToken : null;
|
||||
}
|
||||
|
||||
Future<Result> getUserData(String username) async {
|
||||
var get = ApplicationData_GetUserByUsername()..username = username;
|
||||
var appData = ApplicationData()..getuserbyusername = get;
|
||||
|
|
|
|||
|
|
@ -4,11 +4,29 @@ import 'package:twonly/src/model/contacts_model.dart';
|
|||
/// Mix-in [DiagnosticableTreeMixin] to have access to [debugFillProperties] for the devtool
|
||||
// ignore: prefer_mixin
|
||||
class NotifyProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||
// The page index of the HomeView widget
|
||||
int _activePageIdx = 0;
|
||||
|
||||
int _newContactRequests = 0;
|
||||
List<Contact> _allContacts = [];
|
||||
|
||||
List<Contact> _sendingCurrentlyTo = [];
|
||||
|
||||
int get newContactRequests => _newContactRequests;
|
||||
List<Contact> get allContacts => _allContacts;
|
||||
List<Contact> get sendingCurrentlyTo => _sendingCurrentlyTo;
|
||||
|
||||
int get activePageIdx => _activePageIdx;
|
||||
|
||||
void setActivePageIdx(int idx) {
|
||||
_activePageIdx = idx;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void addSendingTo(List<Contact> users) {
|
||||
_sendingCurrentlyTo.addAll(users);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void update() async {
|
||||
_allContacts = await DbContacts.getUsers();
|
||||
|
|
|
|||
|
|
@ -1,12 +1,16 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:twonly/main.dart';
|
||||
import 'package:twonly/src/model/contacts_model.dart';
|
||||
import 'package:twonly/src/model/json/message.dart';
|
||||
import 'package:twonly/src/proto/api/error.pb.dart';
|
||||
import 'package:twonly/src/providers/api_provider.dart';
|
||||
import 'package:twonly/src/providers/notify_provider.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
// ignore: library_prefixes
|
||||
import 'package:twonly/src/utils/signal.dart' as SignalHelper;
|
||||
|
|
@ -55,6 +59,9 @@ Future<Result> encryptAndSendMessage(Int64 userId, Message msg) async {
|
|||
|
||||
Result resp = await apiProvider.sendTextMessage(userId, bytes);
|
||||
|
||||
Logger("encryptAndSendMessage")
|
||||
.shout("handle errors here and store them in the database");
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
|
|
@ -86,3 +93,43 @@ Future<Result> createNewUser(String username, String inviteCode) async {
|
|||
|
||||
return res;
|
||||
}
|
||||
|
||||
Future sendImage(
|
||||
BuildContext context, List<Contact> users, String imagePath) async {
|
||||
// 1. set notifier provider
|
||||
|
||||
context.read<NotifyProvider>().addSendingTo(users);
|
||||
|
||||
File imageFile = File(imagePath);
|
||||
|
||||
Uint8List? imageBytes = await getCompressedImage(imageFile);
|
||||
if (imageBytes == null) {
|
||||
Logger("api.dart").shout("Error compressing image!");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < users.length; i++) {
|
||||
Int64 target = users[i].userId;
|
||||
Uint8List? encryptedImage =
|
||||
await SignalHelper.encryptBytes(imageBytes, target);
|
||||
if (encryptedImage == null) {
|
||||
Logger("api.dart").shout("Error encrypting image!");
|
||||
continue;
|
||||
}
|
||||
|
||||
List<int>? imageToken = await apiProvider.uploadData(encryptedImage);
|
||||
if (imageToken == null) {
|
||||
Logger("api.dart").shout("handle error uploading like saving...");
|
||||
continue;
|
||||
}
|
||||
|
||||
Message msg = Message(
|
||||
kind: MessageKind.image,
|
||||
content: ImageContent(imageToken),
|
||||
timestamp: DateTime.timestamp(),
|
||||
);
|
||||
|
||||
print("Send image to $target");
|
||||
encryptAndSendMessage(target, msg);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
||||
import 'package:gal/gal.dart';
|
||||
import 'package:image/image.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
|
|
@ -118,3 +121,13 @@ InputDecoration getInputDecoration(context, hintText) {
|
|||
contentPadding: EdgeInsets.symmetric(vertical: 15.0, horizontal: 20.0),
|
||||
);
|
||||
}
|
||||
|
||||
Future<Uint8List?> getCompressedImage(File file) async {
|
||||
var result = await FlutterImageCompress.compressWithFile(
|
||||
file.absolute.path,
|
||||
quality: 90,
|
||||
);
|
||||
print(file.lengthSync());
|
||||
print(result!.length);
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -190,6 +190,27 @@ List<Uint8List>? removeLastFourBytes(Uint8List original) {
|
|||
return [newList, lastFourBytes];
|
||||
}
|
||||
|
||||
Future<Uint8List?> encryptBytes(Uint8List bytes, Int64 target) async {
|
||||
try {
|
||||
ConnectSignalProtocolStore signalStore = (await getSignalStore())!;
|
||||
|
||||
SessionCipher session = SessionCipher.fromStore(
|
||||
signalStore, SignalProtocolAddress(target.toString(), defaultDeviceId));
|
||||
|
||||
final ciphertext =
|
||||
await session.encrypt(Uint8List.fromList(gzip.encode(bytes)));
|
||||
|
||||
var b = BytesBuilder();
|
||||
b.add(ciphertext.serialize());
|
||||
b.add(intToBytes(ciphertext.getType()));
|
||||
|
||||
return b.takeBytes();
|
||||
} catch (e) {
|
||||
Logger("utils/signal").shout(e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Uint8List?> encryptMessage(Message msg, Int64 target) async {
|
||||
try {
|
||||
ConnectSignalProtocolStore signalStore = (await getSignalStore())!;
|
||||
|
|
|
|||
|
|
@ -77,17 +77,17 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
Navigator.push(
|
||||
context,
|
||||
PageRouteBuilder(
|
||||
opaque: false,
|
||||
pageBuilder: (context, a1, a2) =>
|
||||
ShareImageEditorView(image: path),
|
||||
transitionsBuilder:
|
||||
(context, animation, secondaryAnimation, child) {
|
||||
return child;
|
||||
},
|
||||
transitionDuration: Duration.zero,
|
||||
reverseTransitionDuration: Duration.zero),
|
||||
opaque: false,
|
||||
pageBuilder: (context, a1, a2) =>
|
||||
ShareImageEditorView(image: path),
|
||||
transitionsBuilder:
|
||||
(context, animation, secondaryAnimation, child) {
|
||||
return child;
|
||||
},
|
||||
transitionDuration: Duration.zero,
|
||||
reverseTransitionDuration: Duration.zero,
|
||||
),
|
||||
);
|
||||
debugPrint('Picture saved: ${path}');
|
||||
},
|
||||
multiple: (multiple) {
|
||||
multiple.fileBySensor.forEach((key, value) {
|
||||
|
|
|
|||
|
|
@ -66,35 +66,69 @@ class _ChatListViewState extends State<ChatListView> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<Contact> sendingCurrentlyTo =
|
||||
context.watch<NotifyProvider>().sendingCurrentlyTo;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(context)!.chatsTitle),
|
||||
actions: [
|
||||
NotificationBadge(
|
||||
count: context.watch<NotifyProvider>().newContactRequests,
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.person_add),
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => SearchUsernameView(),
|
||||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(context)!.chatsTitle),
|
||||
actions: [
|
||||
NotificationBadge(
|
||||
count: context.watch<NotifyProvider>().newContactRequests,
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.person_add),
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => SearchUsernameView(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
if (sendingCurrentlyTo.isNotEmpty)
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 10),
|
||||
child: ListTile(
|
||||
leading: Stack(
|
||||
// child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
strokeWidth: 1,
|
||||
),
|
||||
Icon(
|
||||
Icons.send, // Replace with your desired icon
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
size: 20, // Adjust the size as needed
|
||||
),
|
||||
],
|
||||
// ),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
body: ListView.builder(
|
||||
restorationId: 'chat_list_view',
|
||||
itemCount: _activeUsers.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final user = _activeUsers[index];
|
||||
return UserListItem(user: user, secondsSinceOpen: _secondsSinceOpen);
|
||||
},
|
||||
),
|
||||
);
|
||||
title: Text(sendingCurrentlyTo
|
||||
.map((e) => e.displayName)
|
||||
.toList()
|
||||
.join(", ")),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
restorationId: 'chat_list_view',
|
||||
itemCount: _activeUsers.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final user = _activeUsers[index];
|
||||
return UserListItem(
|
||||
user: user, secondsSinceOpen: _secondsSinceOpen);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import 'package:pie_menu/pie_menu.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:twonly/src/providers/notify_provider.dart';
|
||||
|
||||
import 'camera_preview_view.dart';
|
||||
import 'chat_list_view.dart';
|
||||
|
|
@ -6,6 +8,8 @@ import 'profile_view.dart';
|
|||
import '../settings/settings_controller.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
final PageController homeViewPageController = PageController(initialPage: 0);
|
||||
|
||||
class HomeView extends StatefulWidget {
|
||||
const HomeView({super.key, required this.settingsController});
|
||||
final SettingsController settingsController;
|
||||
|
|
@ -15,8 +19,6 @@ class HomeView extends StatefulWidget {
|
|||
}
|
||||
|
||||
class HomeViewState extends State<HomeView> {
|
||||
int _activePageIdx = 0;
|
||||
final PageController _pageController = PageController(initialPage: 0);
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PieCanvas(
|
||||
|
|
@ -43,11 +45,9 @@ class HomeViewState extends State<HomeView> {
|
|||
),
|
||||
child: Scaffold(
|
||||
body: PageView(
|
||||
controller: _pageController,
|
||||
controller: homeViewPageController,
|
||||
onPageChanged: (index) {
|
||||
setState(() {
|
||||
_activePageIdx = index;
|
||||
});
|
||||
context.read<NotifyProvider>().setActivePageIdx(index);
|
||||
},
|
||||
children: [
|
||||
ChatListView(),
|
||||
|
|
@ -69,14 +69,14 @@ class HomeViewState extends State<HomeView> {
|
|||
BottomNavigationBarItem(icon: Icon(Icons.verified_user), label: ""),
|
||||
],
|
||||
onTap: (int index) {
|
||||
context.read<NotifyProvider>().setActivePageIdx(index);
|
||||
setState(() {
|
||||
_activePageIdx = index;
|
||||
_pageController.animateToPage(_activePageIdx,
|
||||
homeViewPageController.animateToPage(index,
|
||||
duration: const Duration(milliseconds: 100),
|
||||
curve: Curves.bounceIn);
|
||||
});
|
||||
},
|
||||
currentIndex: _activePageIdx,
|
||||
currentIndex: context.watch<NotifyProvider>().activePageIdx,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
import 'dart:collection';
|
||||
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:twonly/src/components/best_friends_selector.dart';
|
||||
import 'package:twonly/src/components/headline.dart';
|
||||
import 'package:twonly/src/components/initialsavatar.dart';
|
||||
import 'package:twonly/src/model/contacts_model.dart';
|
||||
import 'package:twonly/src/providers/notify_provider.dart';
|
||||
import 'package:twonly/src/utils/api.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/views/home_view.dart';
|
||||
|
||||
class ShareImageView extends StatefulWidget {
|
||||
const ShareImageView({super.key, required this.image});
|
||||
|
|
@ -86,9 +89,14 @@ class _ShareImageView extends State<ShareImageView> {
|
|||
},
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
HeadLineComponent(AppLocalizations.of(context)!.shareImageAllUsers),
|
||||
if (_usersFiltered.isNotEmpty)
|
||||
HeadLineComponent(
|
||||
AppLocalizations.of(context)!.shareImageAllUsers),
|
||||
Expanded(
|
||||
child: UserList(_usersFiltered),
|
||||
child: UserList(
|
||||
List.from(_usersFiltered),
|
||||
selectedUserIds: _selectedUserIds,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
|
|
@ -103,13 +111,17 @@ class _ShareImageView extends State<ShareImageView> {
|
|||
FilledButton.icon(
|
||||
icon: Icon(Icons.send),
|
||||
onPressed: () async {
|
||||
print(_selectedUserIds);
|
||||
// Navigator.push(
|
||||
// context,
|
||||
// MaterialPageRoute(
|
||||
// builder: (context) =>
|
||||
// ShareImageView(image: widget.image)),
|
||||
// );
|
||||
sendImage(
|
||||
context,
|
||||
_users
|
||||
.where((c) => _selectedUserIds.contains(c.userId))
|
||||
.toList(),
|
||||
widget.image);
|
||||
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(context);
|
||||
context.read<NotifyProvider>().setActivePageIdx(0);
|
||||
homeViewPageController.jumpToPage(0);
|
||||
},
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all<EdgeInsets>(
|
||||
|
|
@ -130,62 +142,44 @@ class _ShareImageView extends State<ShareImageView> {
|
|||
}
|
||||
|
||||
class UserList extends StatelessWidget {
|
||||
const UserList(this._knownUsers, {super.key});
|
||||
final List<Contact> _knownUsers;
|
||||
const UserList(this.users, {super.key, required this.selectedUserIds});
|
||||
final List<Contact> users;
|
||||
final HashSet<Int64> selectedUserIds;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Step 1: Sort the users alphabetically
|
||||
_knownUsers.sort((a, b) => a.displayName.compareTo(b.displayName));
|
||||
|
||||
// Step 2: Group users by their initials
|
||||
Map<String, List<String>> groupedUsers = {};
|
||||
for (var user in _knownUsers) {
|
||||
String initial = user.displayName[0].toUpperCase();
|
||||
if (!groupedUsers.containsKey(initial)) {
|
||||
groupedUsers[initial] = [];
|
||||
}
|
||||
groupedUsers[initial]!.add(user.displayName);
|
||||
}
|
||||
|
||||
// Step 3: Create a list of sections
|
||||
List<MapEntry<String, List<String>>> sections =
|
||||
groupedUsers.entries.toList();
|
||||
users.sort((a, b) => a.displayName.compareTo(b.displayName));
|
||||
|
||||
return ListView.builder(
|
||||
restorationId: 'new_message_users_list',
|
||||
itemCount: sections.length,
|
||||
itemBuilder: (BuildContext context, int sectionIndex) {
|
||||
final section = sections[sectionIndex];
|
||||
final initial = section.key;
|
||||
final users = section.value;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header for the initial
|
||||
// Padding(
|
||||
// padding:
|
||||
// const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
|
||||
// child: Text(
|
||||
// initial,
|
||||
// style: TextStyle(fontWeight: FontWeight.normal, fontSize: 18),
|
||||
// ),
|
||||
// ),
|
||||
// List of users under this initial
|
||||
...users.map((username) {
|
||||
return ListTile(
|
||||
title: Text(username),
|
||||
leading: InitialsAvatar(
|
||||
displayName: username,
|
||||
fontSize: 15,
|
||||
),
|
||||
onTap: () {
|
||||
// Handle tap
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
],
|
||||
itemCount: users.length,
|
||||
itemBuilder: (BuildContext context, int i) {
|
||||
Contact user = users[i];
|
||||
return ListTile(
|
||||
title: Text(user.displayName),
|
||||
leading: InitialsAvatar(
|
||||
displayName: user.displayName,
|
||||
fontSize: 15,
|
||||
),
|
||||
trailing: Checkbox(
|
||||
value: selectedUserIds.contains(user.userId),
|
||||
onChanged: (bool? value) {
|
||||
if (value == null) return;
|
||||
if (value) {
|
||||
selectedUserIds.add(user.userId);
|
||||
} else {
|
||||
selectedUserIds.remove(user.userId);
|
||||
}
|
||||
},
|
||||
),
|
||||
onTap: () {
|
||||
if (!selectedUserIds.contains(user.userId)) {
|
||||
selectedUserIds.add(user.userId);
|
||||
} else {
|
||||
selectedUserIds.remove(user.userId);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
48
pubspec.lock
48
pubspec.lock
|
|
@ -315,6 +315,54 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_image_compress:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_image_compress
|
||||
sha256: "51d23be39efc2185e72e290042a0da41aed70b14ef97db362a6b5368d0523b27"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
flutter_image_compress_common:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_image_compress_common
|
||||
sha256: "7f79bc6c8a363063620b4e372fa86bc691e1cb28e58048cd38e030692fbd99ee"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
flutter_image_compress_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_image_compress_macos
|
||||
sha256: "20019719b71b743aba0ef874ed29c50747461e5e8438980dfa5c2031898f7337"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
flutter_image_compress_ohos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_image_compress_ohos
|
||||
sha256: e76b92bbc830ee08f5b05962fc78a532011fcd2041f620b5400a593e96da3f51
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.3"
|
||||
flutter_image_compress_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_image_compress_platform_interface
|
||||
sha256: "579cb3947fd4309103afe6442a01ca01e1e6f93dc53bb4cbd090e8ce34a41889"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
flutter_image_compress_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_image_compress_web
|
||||
sha256: b9b141ac7c686a2ce7bb9a98176321e1182c9074650e47bb140741a44b6f5a96
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.5"
|
||||
flutter_keyboard_visibility:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ dependencies:
|
|||
fixnum: ^1.1.1
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_image_compress: ^2.4.0
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
flutter_secure_storage: ^9.2.2
|
||||
|
|
|
|||
Loading…
Reference in a new issue