mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 09:28:41 +00:00
add receiver side for media
This commit is contained in:
parent
2e7b0edce3
commit
384bafe67b
13 changed files with 291 additions and 77 deletions
|
|
@ -34,6 +34,7 @@
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
||||||
<uses-permission android:name="android.permission.CAMERA"/>
|
<uses-permission android:name="android.permission.CAMERA"/>
|
||||||
|
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
|
||||||
<!-- Required to query activities that can process text, see:
|
<!-- Required to query activities that can process text, see:
|
||||||
https://developer.android.com/training/package-visibility and
|
https://developer.android.com/training/package-visibility and
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
package com.example.connect
|
package com.example.connect
|
||||||
|
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterFragmentActivity
|
||||||
|
|
||||||
class MainActivity: FlutterActivity()
|
class MainActivity: FlutterFragmentActivity()
|
||||||
|
|
|
||||||
1
assets/animations/present.lottie.json
Normal file
1
assets/animations/present.lottie.json
Normal file
File diff suppressed because one or more lines are too long
BIN
assets/animations/present.lottie.zip
Normal file
BIN
assets/animations/present.lottie.zip
Normal file
Binary file not shown.
|
|
@ -48,6 +48,8 @@
|
||||||
<key>NSCameraUsageDescription</key>
|
<key>NSCameraUsageDescription</key>
|
||||||
<string>To create photos that can be shared.</string>
|
<string>To create photos that can be shared.</string>
|
||||||
<key>NSMicrophoneUsageDescription</key>
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
<string>Explanation on why the microphone access is needed.</string>
|
<string>To create videos that can be securely shared.</string>
|
||||||
|
<key>NSFaceIDUsageDescription</key>
|
||||||
|
<string>To protect others twonlies!</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ class MessageSendStateIcon extends StatelessWidget {
|
||||||
String text = "";
|
String text = "";
|
||||||
|
|
||||||
Color color =
|
Color color =
|
||||||
message.messageKind.getColor(Theme.of(context).colorScheme.primary);
|
message.messageContent.getColor(Theme.of(context).colorScheme.primary);
|
||||||
|
|
||||||
Widget loaderIcon = Row(
|
Widget loaderIcon = Row(
|
||||||
children: [
|
children: [
|
||||||
|
|
|
||||||
|
|
@ -23,16 +23,6 @@ extension MessageKindExtension on MessageKind {
|
||||||
static MessageKind fromIndex(int index) {
|
static MessageKind fromIndex(int index) {
|
||||||
return MessageKind.values[index];
|
return MessageKind.values[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
Color getColor(Color primary) {
|
|
||||||
Color color = primary;
|
|
||||||
if (this == MessageKind.textMessage) {
|
|
||||||
color = Colors.lightBlue;
|
|
||||||
} else if (this == MessageKind.video) {
|
|
||||||
color = Colors.deepPurple;
|
|
||||||
}
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: use message as base class, remove kind and flatten content
|
// TODO: use message as base class, remove kind and flatten content
|
||||||
|
|
@ -72,6 +62,29 @@ class Message {
|
||||||
class MessageContent {
|
class MessageContent {
|
||||||
MessageContent();
|
MessageContent();
|
||||||
|
|
||||||
|
Color getColor(Color primary) {
|
||||||
|
Color color;
|
||||||
|
if (this is TextMessageContent) {
|
||||||
|
color = Colors.lightBlue;
|
||||||
|
} else {
|
||||||
|
final content = this;
|
||||||
|
if (content is MediaMessageContent) {
|
||||||
|
if (content.isRealTwonly) {
|
||||||
|
color = primary;
|
||||||
|
} else {
|
||||||
|
if (content.isVideo) {
|
||||||
|
color = Colors.deepPurple;
|
||||||
|
} else {
|
||||||
|
color = const Color.fromARGB(255, 214, 47, 47);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Colors.black; // this should not happen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
static MessageContent fromJson(Map json) {
|
static MessageContent fromJson(Map json) {
|
||||||
switch (json['type']) {
|
switch (json['type']) {
|
||||||
case 'MediaMessageContent':
|
case 'MediaMessageContent':
|
||||||
|
|
@ -92,10 +105,12 @@ class MediaMessageContent extends MessageContent {
|
||||||
final List<int> downloadToken;
|
final List<int> downloadToken;
|
||||||
final int maxShowTime;
|
final int maxShowTime;
|
||||||
final bool isRealTwonly;
|
final bool isRealTwonly;
|
||||||
|
final bool isVideo;
|
||||||
MediaMessageContent({
|
MediaMessageContent({
|
||||||
required this.downloadToken,
|
required this.downloadToken,
|
||||||
required this.maxShowTime,
|
required this.maxShowTime,
|
||||||
required this.isRealTwonly,
|
required this.isRealTwonly,
|
||||||
|
required this.isVideo,
|
||||||
});
|
});
|
||||||
|
|
||||||
static MediaMessageContent fromJson(Map json) {
|
static MediaMessageContent fromJson(Map json) {
|
||||||
|
|
@ -103,6 +118,7 @@ class MediaMessageContent extends MessageContent {
|
||||||
downloadToken: List<int>.from(json['downloadToken']),
|
downloadToken: List<int>.from(json['downloadToken']),
|
||||||
maxShowTime: json['maxShowTime'],
|
maxShowTime: json['maxShowTime'],
|
||||||
isRealTwonly: json['isRealTwonly'],
|
isRealTwonly: json['isRealTwonly'],
|
||||||
|
isVideo: json['isVideo'] ?? false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ class DbMessage {
|
||||||
int? messageOtherId;
|
int? messageOtherId;
|
||||||
int otherUserId;
|
int otherUserId;
|
||||||
MessageKind messageKind;
|
MessageKind messageKind;
|
||||||
MessageContent? messageContent;
|
MessageContent messageContent;
|
||||||
DateTime? messageOpenedAt;
|
DateTime? messageOpenedAt;
|
||||||
bool messageAcknowledgeByUser;
|
bool messageAcknowledgeByUser;
|
||||||
bool isDownloaded;
|
bool isDownloaded;
|
||||||
|
|
|
||||||
|
|
@ -152,10 +152,10 @@ Future uploadMediaFile(
|
||||||
kind: MessageKind.image,
|
kind: MessageKind.image,
|
||||||
messageId: messageId,
|
messageId: messageId,
|
||||||
content: MediaMessageContent(
|
content: MediaMessageContent(
|
||||||
downloadToken: uploadToken,
|
downloadToken: uploadToken,
|
||||||
maxShowTime: maxShowTime,
|
maxShowTime: maxShowTime,
|
||||||
isRealTwonly: isRealTwonly,
|
isRealTwonly: isRealTwonly,
|
||||||
),
|
isVideo: false),
|
||||||
timestamp: DateTime.now(),
|
timestamp: DateTime.now(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -171,10 +171,10 @@ Future encryptAndUploadMediaFile(
|
||||||
target.toInt(),
|
target.toInt(),
|
||||||
MessageKind.image,
|
MessageKind.image,
|
||||||
MediaMessageContent(
|
MediaMessageContent(
|
||||||
downloadToken: [],
|
downloadToken: [],
|
||||||
maxShowTime: maxShowTime,
|
maxShowTime: maxShowTime,
|
||||||
isRealTwonly: isRealTwonly,
|
isRealTwonly: isRealTwonly,
|
||||||
));
|
isVideo: false));
|
||||||
// isRealTwonly,
|
// isRealTwonly,
|
||||||
if (messageId == null) return;
|
if (messageId == null) return;
|
||||||
|
|
||||||
|
|
@ -252,11 +252,13 @@ Future<Uint8List?> getDownloadedMedia(
|
||||||
List<int> mediaToken, int messageOtherId) async {
|
List<int> mediaToken, int messageOtherId) async {
|
||||||
final box = await getMediaStorage();
|
final box = await getMediaStorage();
|
||||||
Uint8List? media = box.get("${mediaToken}_downloaded");
|
Uint8List? media = box.get("${mediaToken}_downloaded");
|
||||||
int fromUserId = box.get("${mediaToken}_fromUserId");
|
|
||||||
await userOpenedOtherMessage(fromUserId, messageOtherId);
|
// int fromUserId = box.get("${mediaToken}_fromUserId");
|
||||||
|
// await userOpenedOtherMessage(fromUserId, messageOtherId);
|
||||||
// box.delete(mediaToken.toString());
|
// box.delete(mediaToken.toString());
|
||||||
// box.put("${mediaToken}_downloaded", "deleted");
|
// box.put("${mediaToken}_downloaded", "deleted");
|
||||||
// box.delete("${mediaToken}_fromUserId");
|
// box.delete("${mediaToken}_fromUserId");
|
||||||
|
|
||||||
return media;
|
return media;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,8 +67,8 @@ class ChatListEntry extends StatelessWidget {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case MessageKind.image:
|
case MessageKind.image:
|
||||||
Color color =
|
Color color = message.messageContent
|
||||||
message.messageKind.getColor(Theme.of(context).colorScheme.primary);
|
.getColor(Theme.of(context).colorScheme.primary);
|
||||||
child = GestureDetector(
|
child = GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (state == MessageSendState.received && !isDownloading) {
|
if (state == MessageSendState.received && !isDownloading) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import 'dart:typed_data';
|
import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
import 'package:local_auth/local_auth.dart';
|
||||||
|
import 'package:lottie/lottie.dart';
|
||||||
import 'package:twonly/src/components/media_view_sizing.dart';
|
import 'package:twonly/src/components/media_view_sizing.dart';
|
||||||
import 'package:twonly/src/model/contacts_model.dart';
|
import 'package:twonly/src/model/contacts_model.dart';
|
||||||
import 'package:twonly/src/model/json/message.dart';
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
|
|
@ -18,75 +21,205 @@ class MediaViewerView extends StatefulWidget {
|
||||||
|
|
||||||
class _MediaViewerViewState extends State<MediaViewerView> {
|
class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
Uint8List? _imageByte;
|
Uint8List? _imageByte;
|
||||||
|
DateTime? canBeSeenUntil;
|
||||||
|
int maxShowTime = 999999;
|
||||||
|
bool isRealTwonly = false;
|
||||||
|
// DateTime opened;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_initAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future _initAsync() async {
|
|
||||||
final content = widget.message.messageContent;
|
final content = widget.message.messageContent;
|
||||||
if (content is MediaMessageContent) {
|
if (content is MediaMessageContent) {
|
||||||
|
if (content.isRealTwonly) {
|
||||||
|
isRealTwonly = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadMedia();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future loadMedia({bool force = false}) async {
|
||||||
|
final content = widget.message.messageContent;
|
||||||
|
if (content is MediaMessageContent) {
|
||||||
|
if (content.isRealTwonly) {
|
||||||
|
if (!force) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final LocalAuthentication auth = LocalAuthentication();
|
||||||
|
bool didAuthenticate = await auth.authenticate(
|
||||||
|
localizedReason: 'Please authenticate to see this twonly!',
|
||||||
|
options: const AuthenticationOptions(useErrorDialogs: false));
|
||||||
|
if (!didAuthenticate) {
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} on PlatformException catch (e) {
|
||||||
|
debugPrint(e.toString());
|
||||||
|
// these errors because of hardware not available or bio is not enrolled
|
||||||
|
// as this is just a nice gimig, do not interrupt the user experience
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<int> token = content.downloadToken;
|
List<int> token = content.downloadToken;
|
||||||
_imageByte =
|
_imageByte =
|
||||||
await getDownloadedMedia(token, widget.message.messageOtherId!);
|
await getDownloadedMedia(token, widget.message.messageOtherId!);
|
||||||
|
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startTimer() {
|
||||||
|
Future.delayed(canBeSeenUntil!.difference(DateTime.now()), () {
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaOpened() {
|
||||||
|
if (canBeSeenUntil != null) return;
|
||||||
|
final content = widget.message.messageContent;
|
||||||
|
if (content is MediaMessageContent) {
|
||||||
|
if (content.maxShowTime != 999999) {
|
||||||
|
canBeSeenUntil = DateTime.now().add(
|
||||||
|
Duration(seconds: content.maxShowTime),
|
||||||
|
);
|
||||||
|
maxShowTime = content.maxShowTime;
|
||||||
|
startTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (_imageByte == null) return Container();
|
double progress = 0;
|
||||||
|
if (canBeSeenUntil != null) {
|
||||||
|
Duration difference = canBeSeenUntil!.difference(DateTime.now());
|
||||||
|
print(difference.inMilliseconds);
|
||||||
|
// Calculate the progress as a value between 0.0 and 1.0
|
||||||
|
progress = (difference.inMilliseconds / (maxShowTime * 1000));
|
||||||
|
if (progress <= 0) {
|
||||||
|
return Scaffold();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// progress = 0.8;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Stack(
|
body: SafeArea(
|
||||||
fit: StackFit.expand,
|
child: Stack(
|
||||||
children: [
|
fit: StackFit.expand,
|
||||||
MediaViewSizing(Image.memory(
|
children: [
|
||||||
_imageByte!,
|
if (_imageByte != null)
|
||||||
fit: BoxFit.contain,
|
MediaViewSizing(
|
||||||
)),
|
Image.memory(
|
||||||
Positioned(
|
_imageByte!,
|
||||||
left: 10,
|
fit: BoxFit.contain,
|
||||||
top: 60,
|
frameBuilder:
|
||||||
child: Row(
|
((context, child, frame, wasSynchronouslyLoaded) {
|
||||||
// mainAxisAlignment: MainAxisAlignment.center,
|
if (frame != null || wasSynchronouslyLoaded) {
|
||||||
children: [
|
mediaOpened();
|
||||||
IconButton(
|
}
|
||||||
icon: Icon(Icons.close, size: 30),
|
if (wasSynchronouslyLoaded) return child;
|
||||||
color: Colors.white,
|
return AnimatedSwitcher(
|
||||||
onPressed: () async {
|
duration: const Duration(milliseconds: 200),
|
||||||
Navigator.pop(context);
|
child: frame != null
|
||||||
|
? child
|
||||||
|
: SizedBox(
|
||||||
|
height: 60,
|
||||||
|
width: 60,
|
||||||
|
child: CircularProgressIndicator(strokeWidth: 6),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (isRealTwonly && _imageByte == null)
|
||||||
|
Positioned.fill(
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
loadMedia(force: true);
|
||||||
},
|
},
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Lottie.asset(
|
||||||
|
'assets/animations/present.lottie.json'),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.only(bottom: 200),
|
||||||
|
child: Text("Tap to open your twonly!"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
|
Positioned(
|
||||||
|
left: 10,
|
||||||
|
top: 10,
|
||||||
|
child: Row(
|
||||||
|
// mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.close, size: 30),
|
||||||
|
color: Colors.white,
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
Positioned(
|
||||||
Positioned(
|
right: 20,
|
||||||
bottom: 70,
|
top: 27,
|
||||||
left: 0,
|
child: Row(
|
||||||
right: 0,
|
children: [
|
||||||
child: Row(
|
if (canBeSeenUntil != null)
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
SizedBox(
|
||||||
children: [
|
width: 20,
|
||||||
const SizedBox(width: 20),
|
height: 20,
|
||||||
FilledButton.icon(
|
child: CircularProgressIndicator(
|
||||||
icon: FaIcon(FontAwesomeIcons.solidPaperPlane),
|
value: progress,
|
||||||
onPressed: () async {},
|
strokeWidth: 2.0,
|
||||||
style: ButtonStyle(
|
)),
|
||||||
padding: WidgetStateProperty.all<EdgeInsets>(
|
],
|
||||||
EdgeInsets.symmetric(vertical: 10, horizontal: 30),
|
),
|
||||||
|
),
|
||||||
|
if (_imageByte != null)
|
||||||
|
Positioned(
|
||||||
|
bottom: 30,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
// const SizedBox(width: 20),
|
||||||
|
FilledButton.icon(
|
||||||
|
icon: FaIcon(FontAwesomeIcons.solidPaperPlane),
|
||||||
|
onPressed: () async {},
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: WidgetStateProperty.all<EdgeInsets>(
|
||||||
|
EdgeInsets.symmetric(vertical: 10, horizontal: 30),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
label: Text(
|
||||||
|
"Respond",
|
||||||
|
style: TextStyle(fontSize: 17),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
label: Text(
|
|
||||||
"Respond",
|
|
||||||
style: TextStyle(fontSize: 17),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
)
|
),
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
58
pubspec.lock
58
pubspec.lock
|
|
@ -432,6 +432,14 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_plugin_android_lifecycle:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_plugin_android_lifecycle
|
||||||
|
sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.24"
|
||||||
flutter_secure_storage:
|
flutter_secure_storage:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -674,6 +682,46 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.1.1"
|
version: "5.1.1"
|
||||||
|
local_auth:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: local_auth
|
||||||
|
sha256: "434d854cf478f17f12ab29a76a02b3067f86a63a6d6c4eb8fbfdcfe4879c1b7b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.0"
|
||||||
|
local_auth_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: local_auth_android
|
||||||
|
sha256: "6763aaf8965f21822624cb2fd3c03d2a8b3791037b5efb0fe4b13e110f5afc92"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.46"
|
||||||
|
local_auth_darwin:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: local_auth_darwin
|
||||||
|
sha256: "630996cd7b7f28f5ab92432c4b35d055dd03a747bc319e5ffbb3c4806a3e50d2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.3"
|
||||||
|
local_auth_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: local_auth_platform_interface
|
||||||
|
sha256: "1b842ff177a7068442eae093b64abe3592f816afd2a533c0ebcdbe40f9d2075a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.10"
|
||||||
|
local_auth_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: local_auth_windows
|
||||||
|
sha256: bc4e66a29b0fdf751aafbec923b5bed7ad6ed3614875d8151afe2578520b2ab5
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.11"
|
||||||
logging:
|
logging:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -682,6 +730,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.3.0"
|
||||||
|
lottie:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: lottie
|
||||||
|
sha256: c5fa04a80a620066c15cf19cc44773e19e9b38e989ff23ea32e5903ef1015950
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.3.1"
|
||||||
macros:
|
macros:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1217,4 +1273,4 @@ packages:
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.6.0 <4.0.0"
|
dart: ">=3.6.0 <4.0.0"
|
||||||
flutter: ">=3.24.0"
|
flutter: ">=3.27.0"
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,9 @@ dependencies:
|
||||||
introduction_screen: ^3.1.14
|
introduction_screen: ^3.1.14
|
||||||
json_annotation: ^4.9.0
|
json_annotation: ^4.9.0
|
||||||
libsignal_protocol_dart: ^0.7.1
|
libsignal_protocol_dart: ^0.7.1
|
||||||
|
local_auth: ^2.3.0
|
||||||
logging: ^1.3.0
|
logging: ^1.3.0
|
||||||
|
lottie: ^3.3.1
|
||||||
path: ^1.9.0
|
path: ^1.9.0
|
||||||
path_provider: ^2.1.5
|
path_provider: ^2.1.5
|
||||||
permission_handler: ^11.3.1
|
permission_handler: ^11.3.1
|
||||||
|
|
@ -72,6 +74,7 @@ flutter:
|
||||||
assets:
|
assets:
|
||||||
# Add assets from the images directory to the application.
|
# Add assets from the images directory to the application.
|
||||||
- assets/images/
|
- assets/images/
|
||||||
|
- assets/animations/present.lottie.json
|
||||||
- assets/icons/
|
- assets/icons/
|
||||||
- assets/icons/flame.png
|
- assets/icons/flame.png
|
||||||
- assets/images/onboarding/01.png
|
- assets/images/onboarding/01.png
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue