[mob][photos] share with placeholder and changed share link button (#1969)
This commit is contained in:
@@ -110,12 +110,6 @@ class CollectionActions {
|
||||
BuildContext context,
|
||||
List<EnteFile> files,
|
||||
) async {
|
||||
final dialog = createProgressDialog(
|
||||
context,
|
||||
S.of(context).creatingLink,
|
||||
isDismissible: true,
|
||||
);
|
||||
await dialog.show();
|
||||
try {
|
||||
// create album with emptyName, use collectionCreationTime on UI to
|
||||
// show name
|
||||
@@ -143,10 +137,8 @@ class CollectionActions {
|
||||
await collectionsService.addOrCopyToCollection(collection.id, files);
|
||||
logger.finest("creating public link for the newly created album");
|
||||
await CollectionsService.instance.createShareUrl(collection);
|
||||
await dialog.hide();
|
||||
return collection;
|
||||
} catch (e, s) {
|
||||
await dialog.hide();
|
||||
await showGenericErrorDialog(context: context, error: e);
|
||||
logger.severe("Failing to create link for selected files", e, s);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import "package:flutter/material.dart";
|
||||
import "package:photos/theme/ente_theme.dart";
|
||||
|
||||
@@ -89,11 +91,41 @@ class __BodyState extends State<_Body> {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
widget.icon,
|
||||
size: 24,
|
||||
color: getEnteColorScheme(context).textMuted,
|
||||
),
|
||||
if (widget.icon == Icons.navigation_rounded)
|
||||
Transform.rotate(
|
||||
angle: math.pi / 2,
|
||||
child: Icon(
|
||||
widget.icon,
|
||||
size: 24,
|
||||
color: getEnteColorScheme(context).primary300,
|
||||
shadows: const [
|
||||
BoxShadow(
|
||||
color: Color.fromARGB(12, 0, 179, 60),
|
||||
offset: Offset(0, 2.51),
|
||||
blurRadius: 5.02,
|
||||
spreadRadius: 0,
|
||||
),
|
||||
BoxShadow(
|
||||
color: Color.fromARGB(24, 0, 179, 60),
|
||||
offset: Offset(0, 1.25),
|
||||
blurRadius: 3.76,
|
||||
spreadRadius: 0,
|
||||
),
|
||||
BoxShadow(
|
||||
color: Color.fromARGB(24, 0, 179, 60),
|
||||
offset: Offset(0, 0.63),
|
||||
blurRadius: 1.88,
|
||||
spreadRadius: 0,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
else
|
||||
Icon(
|
||||
widget.icon,
|
||||
size: 24,
|
||||
color: getEnteColorScheme(context).textMuted,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
widget.labelText,
|
||||
|
||||
@@ -6,11 +6,13 @@ class TitleBarTitleWidget extends StatelessWidget {
|
||||
final bool isTitleH2;
|
||||
final IconData? icon;
|
||||
final VoidCallback? onTap;
|
||||
final String? heroTag;
|
||||
const TitleBarTitleWidget({
|
||||
this.title,
|
||||
this.isTitleH2 = false,
|
||||
this.icon,
|
||||
this.onTap,
|
||||
this.heroTag,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@@ -51,7 +53,10 @@ class TitleBarTitleWidget extends StatelessWidget {
|
||||
maxLines: 1,
|
||||
);
|
||||
}
|
||||
return GestureDetector(onTap: onTap, child: widget);
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: heroTag != null ? Hero(tag: heroTag!, child: widget) : widget,
|
||||
);
|
||||
}
|
||||
|
||||
return const SizedBox.shrink();
|
||||
|
||||
401
mobile/lib/ui/sharing/show_images_prevew.dart
Normal file
401
mobile/lib/ui/sharing/show_images_prevew.dart
Normal file
@@ -0,0 +1,401 @@
|
||||
import 'dart:math' as math;
|
||||
import "dart:ui";
|
||||
|
||||
import "package:figma_squircle/figma_squircle.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:photos/core/constants.dart";
|
||||
import "package:photos/models/file/file.dart";
|
||||
import "package:photos/theme/ente_theme.dart";
|
||||
import "package:photos/ui/viewer/file/thumbnail_widget.dart";
|
||||
|
||||
class LinkPlaceholder extends StatelessWidget {
|
||||
const LinkPlaceholder({
|
||||
required this.files,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final List<EnteFile> files;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final int length = files.length;
|
||||
Widget placeholderWidget = const SizedBox(
|
||||
height: 300,
|
||||
width: 300,
|
||||
);
|
||||
|
||||
if (length == 1) {
|
||||
placeholderWidget = _BackDrop(
|
||||
backDropImage: files[0],
|
||||
children: [
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final imageHeight = constraints.maxHeight * 0.9;
|
||||
return Center(
|
||||
child: _CustomImage(
|
||||
width: imageHeight,
|
||||
height: imageHeight,
|
||||
file: files[0],
|
||||
zIndex: 0,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
} else if (length == 2) {
|
||||
placeholderWidget = _BackDrop(
|
||||
backDropImage: files[0],
|
||||
children: [
|
||||
LayoutBuilder(
|
||||
builder: ((context, constraints) {
|
||||
final imageHeight = constraints.maxHeight * 0.52;
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: 145,
|
||||
left: 180,
|
||||
child: _CustomImage(
|
||||
height: imageHeight,
|
||||
width: imageHeight,
|
||||
file: files[1],
|
||||
zIndex: 10 * math.pi / 180,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 45,
|
||||
left: 3.2,
|
||||
child: _CustomImage(
|
||||
height: imageHeight,
|
||||
width: imageHeight,
|
||||
file: files[0],
|
||||
zIndex: -(10 * math.pi / 180),
|
||||
imageShadow: const [
|
||||
BoxShadow(
|
||||
offset: Offset(0, 0),
|
||||
blurRadius: 0.84,
|
||||
color: Color.fromRGBO(0, 0, 0, 0.11),
|
||||
),
|
||||
BoxShadow(
|
||||
offset: Offset(0.84, 0.84),
|
||||
blurRadius: 1.68,
|
||||
color: Color.fromRGBO(0, 0, 0, 0.09),
|
||||
),
|
||||
BoxShadow(
|
||||
offset: Offset(2.53, 2.53),
|
||||
blurRadius: 2.53,
|
||||
color: Color.fromRGBO(0, 0, 0, 0.05),
|
||||
),
|
||||
BoxShadow(
|
||||
offset: Offset(5.05, 4.21),
|
||||
blurRadius: 2.53,
|
||||
color: Color.fromRGBO(0, 0, 0, 0.02),
|
||||
),
|
||||
BoxShadow(
|
||||
offset: Offset(7.58, 6.74),
|
||||
blurRadius: 2.53,
|
||||
color: Color.fromRGBO(0, 0, 0, 0.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else if (length == 3) {
|
||||
placeholderWidget = _BackDrop(
|
||||
backDropImage: files[0],
|
||||
children: [
|
||||
LayoutBuilder(
|
||||
builder: (context, constraint) {
|
||||
final imageHeightSmall = constraint.maxHeight * 0.43;
|
||||
final imageHeightLarge = constraint.maxHeight * 0.50;
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: 55,
|
||||
child: _CustomImage(
|
||||
height: imageHeightSmall,
|
||||
width: imageHeightSmall,
|
||||
file: files[1],
|
||||
zIndex: -(20 * math.pi / 180),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 50,
|
||||
right: -10,
|
||||
child: _CustomImage(
|
||||
height: imageHeightSmall,
|
||||
width: imageHeightSmall,
|
||||
file: files[2],
|
||||
zIndex: 20 * math.pi / 180,
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: _CustomImage(
|
||||
height: imageHeightLarge,
|
||||
width: imageHeightLarge,
|
||||
file: files[0],
|
||||
zIndex: 0.0,
|
||||
imageShadow: const [
|
||||
BoxShadow(
|
||||
offset: Offset(0, 1.02),
|
||||
blurRadius: 2.04,
|
||||
color: Color.fromRGBO(0, 0, 0, 0.23),
|
||||
),
|
||||
BoxShadow(
|
||||
offset: Offset(0, 3.06),
|
||||
blurRadius: 3.06,
|
||||
color: Color.fromRGBO(0, 0, 0, 0.2),
|
||||
),
|
||||
BoxShadow(
|
||||
offset: Offset(0, 6.12),
|
||||
blurRadius: 4.08,
|
||||
color: Color.fromRGBO(0, 0, 0, 0.12),
|
||||
),
|
||||
BoxShadow(
|
||||
offset: Offset(0, 11.22),
|
||||
blurRadius: 5.1,
|
||||
color: Color.fromRGBO(0, 0, 0, 0.04),
|
||||
),
|
||||
BoxShadow(
|
||||
offset: Offset(0, 18.36),
|
||||
blurRadius: 5.1,
|
||||
color: Color.fromRGBO(0, 0, 0, 0.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
} else if (length > 3) {
|
||||
placeholderWidget = _BackDrop(
|
||||
backDropImage: files[0],
|
||||
children: [
|
||||
LayoutBuilder(
|
||||
builder: (context, constraint) {
|
||||
final imageHeightSmall = constraint.maxHeight * 0.43;
|
||||
final imageHeightLarge = constraint.maxHeight * 0.50;
|
||||
final boxHeight = constraint.maxHeight * 0.15;
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: 30,
|
||||
left: 25,
|
||||
child: _CustomImage(
|
||||
height: imageHeightSmall,
|
||||
width: imageHeightSmall,
|
||||
file: files[1],
|
||||
zIndex: 0.0,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 202,
|
||||
left: 50,
|
||||
child: _CustomImage(
|
||||
height: imageHeightSmall,
|
||||
width: imageHeightSmall,
|
||||
file: files[2],
|
||||
zIndex: 0.0,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 75,
|
||||
right: 25,
|
||||
child: _CustomImage(
|
||||
height: imageHeightLarge,
|
||||
width: imageHeightLarge,
|
||||
file: files[0],
|
||||
zIndex: 0.0,
|
||||
imageShadow: const [
|
||||
BoxShadow(
|
||||
offset: Offset(0, 1.02),
|
||||
blurRadius: 2.04,
|
||||
color: Color.fromRGBO(0, 0, 0, 0.23),
|
||||
),
|
||||
BoxShadow(
|
||||
offset: Offset(0, 3.06),
|
||||
blurRadius: 3.06,
|
||||
color: Color.fromRGBO(0, 0, 0, 0.2),
|
||||
),
|
||||
BoxShadow(
|
||||
offset: Offset(0, 6.12),
|
||||
blurRadius: 4.08,
|
||||
color: Color.fromRGBO(0, 0, 0, 0.12),
|
||||
),
|
||||
BoxShadow(
|
||||
offset: Offset(0, 11.22),
|
||||
blurRadius: 5.1,
|
||||
color: Color.fromRGBO(0, 0, 0, 0.04),
|
||||
),
|
||||
BoxShadow(
|
||||
offset: Offset(0, 18.36),
|
||||
blurRadius: 5.1,
|
||||
color: Color.fromRGBO(0, 0, 0, 0.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 290,
|
||||
left: 270,
|
||||
child: Stack(
|
||||
children: [
|
||||
Center(
|
||||
child: Container(
|
||||
height: boxHeight + 1,
|
||||
width: boxHeight + 1,
|
||||
decoration: ShapeDecoration(
|
||||
color: const Color.fromRGBO(129, 129, 129, 0.1),
|
||||
shape: SmoothRectangleBorder(
|
||||
borderRadius: SmoothBorderRadius(
|
||||
cornerRadius: 12.5,
|
||||
cornerSmoothing: 1.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: ClipSmoothRect(
|
||||
radius: SmoothBorderRadius(
|
||||
cornerRadius: 12,
|
||||
cornerSmoothing: 1.0,
|
||||
),
|
||||
child: Container(
|
||||
height: boxHeight,
|
||||
width: boxHeight,
|
||||
color: const Color.fromRGBO(255, 255, 255, 1),
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Center(
|
||||
child: FittedBox(
|
||||
child: Text(
|
||||
"+" "${length - 3}",
|
||||
style: getEnteTextTheme(context).h3Bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return placeholderWidget;
|
||||
}
|
||||
}
|
||||
|
||||
class _BackDrop extends StatelessWidget {
|
||||
const _BackDrop({
|
||||
required this.backDropImage,
|
||||
required this.children,
|
||||
});
|
||||
|
||||
final List<Widget> children;
|
||||
final EnteFile backDropImage;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: Stack(
|
||||
children: [
|
||||
ThumbnailWidget(
|
||||
backDropImage,
|
||||
shouldShowSyncStatus: false,
|
||||
shouldShowFavoriteIcon: false,
|
||||
thumbnailSize: thumbnailLargeSize,
|
||||
),
|
||||
BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 12, sigmaY: 12),
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
),
|
||||
...children,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CustomImage extends StatelessWidget {
|
||||
const _CustomImage({
|
||||
required this.width,
|
||||
required this.height,
|
||||
required this.file,
|
||||
required this.zIndex,
|
||||
this.imageShadow,
|
||||
});
|
||||
final List<BoxShadow>? imageShadow;
|
||||
final EnteFile file;
|
||||
final double zIndex;
|
||||
final double height;
|
||||
final double width;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
transform: Matrix4.rotationZ(zIndex),
|
||||
height: height,
|
||||
width: width,
|
||||
child: Stack(
|
||||
children: [
|
||||
Center(
|
||||
child: Container(
|
||||
height: height,
|
||||
width: width,
|
||||
decoration: ShapeDecoration(
|
||||
color: const Color.fromRGBO(129, 129, 129, 0.1),
|
||||
shadows: imageShadow,
|
||||
shape: SmoothRectangleBorder(
|
||||
borderRadius: SmoothBorderRadius(
|
||||
cornerRadius: 21.0,
|
||||
cornerSmoothing: 1.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: SizedBox(
|
||||
height: height - 2,
|
||||
width: width - 2,
|
||||
child: ClipSmoothRect(
|
||||
radius: SmoothBorderRadius(
|
||||
cornerRadius: 20.0,
|
||||
cornerSmoothing: 1,
|
||||
),
|
||||
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(boxShadow: imageShadow),
|
||||
child: ThumbnailWidget(
|
||||
file,
|
||||
shouldShowSyncStatus: false,
|
||||
shouldShowFavoriteIcon: false,
|
||||
thumbnailSize: thumbnailLargeSize,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
69
mobile/lib/ui/tabs/shared/all_quick_links_page.dart
Normal file
69
mobile/lib/ui/tabs/shared/all_quick_links_page.dart
Normal file
@@ -0,0 +1,69 @@
|
||||
import "package:flutter/material.dart";
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import "package:photos/models/collection/collection.dart";
|
||||
import "package:photos/ui/components/title_bar_title_widget.dart";
|
||||
import "package:photos/ui/tabs/shared/quick_link_album_item.dart";
|
||||
|
||||
class AllQuickLinksPage extends StatelessWidget {
|
||||
final List<Collection> quickLinks;
|
||||
final String titleHeroTag;
|
||||
const AllQuickLinksPage({
|
||||
required this.quickLinks,
|
||||
required this.titleHeroTag,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
toolbarHeight: 48,
|
||||
leadingWidth: 48,
|
||||
leading: GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Icon(
|
||||
Icons.arrow_back_outlined,
|
||||
),
|
||||
),
|
||||
),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TitleBarTitleWidget(
|
||||
title: S.of(context).quickLinks,
|
||||
heroTag: titleHeroTag,
|
||||
),
|
||||
Text(quickLinks.length.toString()),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 20,
|
||||
horizontal: 16,
|
||||
),
|
||||
child: ListView.separated(
|
||||
itemBuilder: (context, index) {
|
||||
return QuickLinkAlbumItem(c: quickLinks[index]);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return const SizedBox(height: 10);
|
||||
},
|
||||
itemCount: quickLinks.length,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,9 @@ import 'package:photos/models/collection/collection.dart';
|
||||
import 'package:photos/models/collection/collection_items.dart';
|
||||
import 'package:photos/models/file/file.dart';
|
||||
import "package:photos/services/collections_service.dart";
|
||||
import "package:photos/theme/colors.dart";
|
||||
import "package:photos/theme/ente_theme.dart";
|
||||
import "package:photos/ui/common/loading_widget.dart";
|
||||
import "package:photos/ui/components/buttons/icon_button_widget.dart";
|
||||
import "package:photos/ui/viewer/file/no_thumbnail_widget.dart";
|
||||
import "package:photos/ui/viewer/file/thumbnail_widget.dart";
|
||||
import "package:photos/ui/viewer/gallery/collection_page.dart";
|
||||
@@ -20,96 +20,123 @@ class QuickLinkAlbumItem extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
final textTheme = getEnteTextTheme(context);
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: colorScheme.strokeFainter),
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(2),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(1),
|
||||
child: SizedBox(
|
||||
height: 60,
|
||||
width: 60,
|
||||
child: FutureBuilder<EnteFile?>(
|
||||
future: CollectionsService.instance.getCover(c),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
final String heroTag = heroTagPrefix + snapshot.data!.tag;
|
||||
return Hero(
|
||||
tag: heroTag,
|
||||
child: ThumbnailWidget(
|
||||
snapshot.data!,
|
||||
key: ValueKey(heroTag),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const NoThumbnailWidget();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const Padding(padding: EdgeInsets.all(8)),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 6,
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
c.displayName,
|
||||
style: getEnteTextTheme(context).body,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(0, 4, 0, 0),
|
||||
child: FutureBuilder<int>(
|
||||
future: CollectionsService.instance.getFileCount(c),
|
||||
SizedBox(
|
||||
width: 60,
|
||||
height: 60,
|
||||
child: FutureBuilder<EnteFile?>(
|
||||
future: CollectionsService.instance.getCover(c),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasError) {
|
||||
// final String textCount = NumberFormat().format(snapshot.data);
|
||||
return Row(
|
||||
children: [
|
||||
(!snapshot.hasData)
|
||||
? const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
),
|
||||
child: EnteLoadingWidget(size: 10),
|
||||
)
|
||||
: Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(right: 8.0),
|
||||
child: Text(
|
||||
S.of(context).itemCount(snapshot.data!),
|
||||
style: getEnteTextTheme(context)
|
||||
.smallMuted,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
c.hasLink
|
||||
? (c.publicURLs!.first!.isExpired
|
||||
? const Icon(
|
||||
Icons.link_outlined,
|
||||
color: warning500,
|
||||
)
|
||||
: Icon(
|
||||
Icons.link_outlined,
|
||||
color: getEnteColorScheme(context)
|
||||
.strokeMuted,
|
||||
))
|
||||
: const SizedBox.shrink(),
|
||||
],
|
||||
if (snapshot.hasData) {
|
||||
final String heroTag =
|
||||
heroTagPrefix + snapshot.data!.tag;
|
||||
return Hero(
|
||||
tag: heroTag,
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.horizontal(
|
||||
left: Radius.circular(2),
|
||||
),
|
||||
child: ThumbnailWidget(
|
||||
snapshot.data!,
|
||||
key: ValueKey(heroTag),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else if (snapshot.hasError) {
|
||||
return Text(S.of(context).somethingWentWrong);
|
||||
} else {
|
||||
return const EnteLoadingWidget(size: 10);
|
||||
return const NoThumbnailWidget();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
c.displayName,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
FutureBuilder<int>(
|
||||
future: CollectionsService.instance.getFileCount(c),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasError) {
|
||||
if (!snapshot.hasData) {
|
||||
return Row(
|
||||
children: [
|
||||
EnteLoadingWidget(
|
||||
size: 10,
|
||||
color: colorScheme.strokeMuted,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
final noOfMemories = snapshot.data;
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Text(
|
||||
noOfMemories.toString() + " \u2022 ",
|
||||
style: textTheme.smallMuted,
|
||||
),
|
||||
c.hasLink
|
||||
? (c.publicURLs!.first!.isExpired
|
||||
? Icon(
|
||||
Icons.link_outlined,
|
||||
color: colorScheme.warning500,
|
||||
size: 22,
|
||||
)
|
||||
: Icon(
|
||||
Icons.link_outlined,
|
||||
color: colorScheme.strokeMuted,
|
||||
size: 22,
|
||||
))
|
||||
: const SizedBox.shrink(),
|
||||
],
|
||||
);
|
||||
} else if (snapshot.hasError) {
|
||||
return Text(S.of(context).somethingWentWrong);
|
||||
} else {
|
||||
return const EnteLoadingWidget(size: 10);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Flexible(
|
||||
flex: 1,
|
||||
child: IconButtonWidget(
|
||||
icon: Icons.chevron_right_outlined,
|
||||
iconButtonType: IconButtonType.secondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import "dart:math";
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
@@ -17,6 +18,7 @@ import "package:photos/ui/components/buttons/icon_button_widget.dart";
|
||||
import "package:photos/ui/components/divider_widget.dart";
|
||||
import "package:photos/ui/components/models/button_type.dart";
|
||||
import 'package:photos/ui/tabs/section_title.dart';
|
||||
import "package:photos/ui/tabs/shared/all_quick_links_page.dart";
|
||||
import "package:photos/ui/tabs/shared/empty_state.dart";
|
||||
import "package:photos/ui/tabs/shared/quick_link_album_item.dart";
|
||||
import "package:photos/utils/debouncer.dart";
|
||||
@@ -97,7 +99,9 @@ class _SharedCollectionsTabState extends State<SharedCollectionsTab>
|
||||
|
||||
Widget _getSharedCollectionsGallery(SharedCollections collections) {
|
||||
const maxThumbnailWidth = 160.0;
|
||||
final bool hasQuickLinks = collections.quickLinks.isNotEmpty;
|
||||
const maxQuickLinks = 6;
|
||||
final numberOfQuickLinks = collections.quickLinks.length;
|
||||
const quickLinkTitleHeroTag = "quick_link_title";
|
||||
final SectionTitle sharedWithYou =
|
||||
SectionTitle(title: S.of(context).sharedWithYou);
|
||||
final SectionTitle sharedByYou =
|
||||
@@ -216,25 +220,56 @@ class _SharedCollectionsTabState extends State<SharedCollectionsTab>
|
||||
],
|
||||
),
|
||||
),
|
||||
hasQuickLinks
|
||||
numberOfQuickLinks > 0
|
||||
? Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
SectionOptions(
|
||||
SectionTitle(title: S.of(context).quickLinks),
|
||||
Hero(
|
||||
tag: quickLinkTitleHeroTag,
|
||||
child: SectionTitle(
|
||||
title: S.of(context).quickLinks,
|
||||
),
|
||||
),
|
||||
trailingWidget: numberOfQuickLinks > maxQuickLinks
|
||||
? IconButtonWidget(
|
||||
icon: Icons.chevron_right,
|
||||
iconButtonType: IconButtonType.secondary,
|
||||
onTap: () {
|
||||
unawaited(
|
||||
routeToPage(
|
||||
context,
|
||||
AllQuickLinksPage(
|
||||
titleHeroTag: quickLinkTitleHeroTag,
|
||||
quickLinks: collections.quickLinks,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
ListView.builder(
|
||||
ListView.separated(
|
||||
shrinkWrap: true,
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 12,
|
||||
left: 12,
|
||||
right: 12,
|
||||
),
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return QuickLinkAlbumItem(
|
||||
c: collections.quickLinks[index],
|
||||
);
|
||||
},
|
||||
itemCount: collections.quickLinks.length,
|
||||
separatorBuilder: (context, index) {
|
||||
return const SizedBox(height: 4);
|
||||
},
|
||||
itemCount: min(numberOfQuickLinks, maxQuickLinks),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -248,10 +283,10 @@ class _SharedCollectionsTabState extends State<SharedCollectionsTab>
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 0, 24, 12),
|
||||
child: ButtonWidget(
|
||||
buttonType:
|
||||
!hasQuickLinks && collections.outgoing.isEmpty
|
||||
? ButtonType.trailingIconSecondary
|
||||
: ButtonType.trailingIconPrimary,
|
||||
buttonType: numberOfQuickLinks == 0 &&
|
||||
collections.outgoing.isEmpty
|
||||
? ButtonType.trailingIconSecondary
|
||||
: ButtonType.trailingIconPrimary,
|
||||
labelText: S.of(context).inviteYourFriendsToEnte,
|
||||
icon: Icons.ios_share_outlined,
|
||||
onTap: () async {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import "dart:async";
|
||||
import "dart:io";
|
||||
|
||||
import 'package:fast_base58/fast_base58.dart';
|
||||
import "package:flutter/cupertino.dart";
|
||||
@@ -6,6 +7,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import "package:logging/logging.dart";
|
||||
import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
|
||||
import "package:path_provider/path_provider.dart";
|
||||
import 'package:photos/core/configuration.dart';
|
||||
import "package:photos/core/event_bus.dart";
|
||||
import "package:photos/events/people_changed_event.dart";
|
||||
@@ -32,7 +34,8 @@ import 'package:photos/ui/components/action_sheet_widget.dart';
|
||||
import "package:photos/ui/components/bottom_action_bar/selection_action_button_widget.dart";
|
||||
import 'package:photos/ui/components/buttons/button_widget.dart';
|
||||
import 'package:photos/ui/components/models/button_type.dart';
|
||||
import 'package:photos/ui/sharing/manage_links_widget.dart';
|
||||
// import 'package:photos/ui/sharing/manage_links_widget.dart';
|
||||
import "package:photos/ui/sharing/show_images_prevew.dart";
|
||||
import "package:photos/ui/tools/collage/collage_creator_page.dart";
|
||||
import "package:photos/ui/viewer/location/update_location_data_widget.dart";
|
||||
import 'package:photos/utils/delete_file_util.dart';
|
||||
@@ -42,6 +45,7 @@ import 'package:photos/utils/magic_util.dart';
|
||||
import 'package:photos/utils/navigation_util.dart';
|
||||
import "package:photos/utils/share_util.dart";
|
||||
import 'package:photos/utils/toast_util.dart';
|
||||
import "package:screenshot/screenshot.dart";
|
||||
|
||||
class FileSelectionActionsWidget extends StatefulWidget {
|
||||
final GalleryType type;
|
||||
@@ -73,12 +77,14 @@ class _FileSelectionActionsWidgetState
|
||||
late FilesSplit split;
|
||||
late CollectionActions collectionActions;
|
||||
late bool isCollectionOwner;
|
||||
|
||||
final ScreenshotController screenshotController = ScreenshotController();
|
||||
late String? placeholderPath;
|
||||
// _cachedCollectionForSharedLink is primarily used to avoid creating duplicate
|
||||
// links if user keeps on creating Create link button after selecting
|
||||
// few files. This link is reset on any selection changed;
|
||||
Collection? _cachedCollectionForSharedLink;
|
||||
final GlobalKey shareButtonKey = GlobalKey();
|
||||
final GlobalKey sendLinkButtonKey = GlobalKey();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -157,16 +163,17 @@ class _FileSelectionActionsWidgetState
|
||||
SelectionActionButton(
|
||||
icon: Icons.copy_outlined,
|
||||
labelText: S.of(context).copyLink,
|
||||
onTap: anyUploadedFiles ? _copyLink : null,
|
||||
onTap: anyUploadedFiles ? _sendLink : null,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
items.add(
|
||||
SelectionActionButton(
|
||||
icon: Icons.link_outlined,
|
||||
labelText: S.of(context).shareLink,
|
||||
onTap: anyUploadedFiles ? _onCreatedSharedLinkClicked : null,
|
||||
icon: Icons.navigation_rounded,
|
||||
labelText: S.of(context).sendLink,
|
||||
onTap: anyUploadedFiles ? _onSendLinkTapped : null,
|
||||
shouldShow: ownedFilesCount > 0,
|
||||
key: sendLinkButtonKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -409,6 +416,7 @@ class _FileSelectionActionsWidgetState
|
||||
SelectionActionButton(
|
||||
labelText: S.of(context).share,
|
||||
icon: Icons.adaptive.share_outlined,
|
||||
key: shareButtonKey,
|
||||
onTap: () => shareSelected(
|
||||
context,
|
||||
shareButtonKey,
|
||||
@@ -602,7 +610,43 @@ class _FileSelectionActionsWidgetState
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onCreatedSharedLinkClicked() async {
|
||||
Future<String> saveImage(Uint8List bytes) async {
|
||||
String path = "";
|
||||
try {
|
||||
final Directory root = await getTemporaryDirectory();
|
||||
final String directoryPath = '${root.path}/enteTempFiles';
|
||||
final DateTime timeStamp = DateTime.now();
|
||||
await Directory(directoryPath).create(recursive: true);
|
||||
final String filePath = '$directoryPath/$timeStamp.jpg';
|
||||
final file = await File(filePath).writeAsBytes(bytes);
|
||||
path = file.path;
|
||||
} catch (e) {
|
||||
_logger.severe("Failed to save placeholder image", e);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
Future<String?> _createPlaceholder(
|
||||
List<EnteFile> ownedSelectedFiles,
|
||||
) async {
|
||||
final Widget imageWidget = LinkPlaceholder(
|
||||
files: ownedSelectedFiles,
|
||||
);
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
final double pixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||
final bytesOfImageToWidget = await screenshotController.captureFromWidget(
|
||||
imageWidget,
|
||||
pixelRatio: pixelRatio,
|
||||
targetSize: MediaQuery.sizeOf(context),
|
||||
delay: const Duration(milliseconds: 100),
|
||||
);
|
||||
|
||||
final String onCreatedPlaceholderPath =
|
||||
await saveImage(bytesOfImageToWidget);
|
||||
return onCreatedPlaceholderPath;
|
||||
}
|
||||
|
||||
Future<void> _onSendLinkTapped() async {
|
||||
if (split.ownedByCurrentUser.isEmpty) {
|
||||
showShortToast(
|
||||
context,
|
||||
@@ -610,51 +654,19 @@ class _FileSelectionActionsWidgetState
|
||||
);
|
||||
return;
|
||||
}
|
||||
final dialog = createProgressDialog(
|
||||
context,
|
||||
S.of(context).creatingLink,
|
||||
isDismissible: true,
|
||||
);
|
||||
await dialog.show();
|
||||
_cachedCollectionForSharedLink ??= await collectionActions
|
||||
.createSharedCollectionLink(context, split.ownedByCurrentUser);
|
||||
final actionResult = await showActionSheet(
|
||||
context: context,
|
||||
buttons: [
|
||||
ButtonWidget(
|
||||
labelText: S.of(context).copyLink,
|
||||
buttonType: ButtonType.neutral,
|
||||
buttonSize: ButtonSize.large,
|
||||
shouldStickToDarkTheme: true,
|
||||
buttonAction: ButtonAction.first,
|
||||
isInAlert: true,
|
||||
),
|
||||
ButtonWidget(
|
||||
labelText: S.of(context).manageLink,
|
||||
buttonType: ButtonType.secondary,
|
||||
buttonSize: ButtonSize.large,
|
||||
buttonAction: ButtonAction.second,
|
||||
shouldStickToDarkTheme: true,
|
||||
isInAlert: true,
|
||||
),
|
||||
ButtonWidget(
|
||||
labelText: S.of(context).done,
|
||||
buttonType: ButtonType.secondary,
|
||||
buttonSize: ButtonSize.large,
|
||||
buttonAction: ButtonAction.third,
|
||||
shouldStickToDarkTheme: true,
|
||||
isInAlert: true,
|
||||
),
|
||||
],
|
||||
title: S.of(context).publicLinkCreated,
|
||||
body: S.of(context).youCanManageYourLinksInTheShareTab,
|
||||
actionSheetType: ActionSheetType.defaultActionSheet,
|
||||
);
|
||||
if (actionResult?.action != null) {
|
||||
if (actionResult!.action == ButtonAction.first) {
|
||||
await _copyLink();
|
||||
}
|
||||
if (actionResult.action == ButtonAction.second) {
|
||||
await routeToPage(
|
||||
context,
|
||||
ManageSharedLinkWidget(collection: _cachedCollectionForSharedLink),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final List<EnteFile> ownedSelectedFiles = split.ownedByCurrentUser;
|
||||
placeholderPath = await _createPlaceholder(ownedSelectedFiles);
|
||||
await dialog.hide();
|
||||
await _sendLink();
|
||||
widget.selectedFiles.clearAll();
|
||||
if (mounted) {
|
||||
setState(() => {});
|
||||
@@ -756,7 +768,7 @@ class _FileSelectionActionsWidgetState
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _copyLink() async {
|
||||
Future<void> _sendLink() async {
|
||||
if (_cachedCollectionForSharedLink != null) {
|
||||
final String collectionKey = Base58Encode(
|
||||
CollectionsService.instance
|
||||
@@ -764,8 +776,25 @@ class _FileSelectionActionsWidgetState
|
||||
);
|
||||
final String url =
|
||||
"${_cachedCollectionForSharedLink!.publicURLs?.first?.url}#$collectionKey";
|
||||
await Clipboard.setData(ClipboardData(text: url));
|
||||
showShortToast(context, S.of(context).linkCopiedToClipboard);
|
||||
unawaited(Clipboard.setData(ClipboardData(text: url)));
|
||||
await shareImageAndUrl(
|
||||
placeholderPath!,
|
||||
url,
|
||||
context: context,
|
||||
key: sendLinkButtonKey,
|
||||
);
|
||||
if (placeholderPath != null) {
|
||||
final file = File(placeholderPath!);
|
||||
try {
|
||||
if (file.existsSync()) {
|
||||
file.deleteSync();
|
||||
}
|
||||
} catch (e) {
|
||||
_logger.warning("Failed to delete the file: $e");
|
||||
} finally {
|
||||
placeholderPath = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,19 +18,8 @@ import 'package:share_plus/share_plus.dart';
|
||||
import "package:uuid/uuid.dart";
|
||||
|
||||
final _logger = Logger("ShareUtil");
|
||||
// Set of possible image extensions
|
||||
final _imageExtension = {"jpg", "jpeg", "png", "heic", "heif", "webp", ".gif"};
|
||||
final _videoExtension = {
|
||||
"mp4",
|
||||
"mov",
|
||||
"avi",
|
||||
"mkv",
|
||||
"webm",
|
||||
"wmv",
|
||||
"flv",
|
||||
"3gp",
|
||||
};
|
||||
// share is used to share media/files from ente to other apps
|
||||
|
||||
/// share is used to share media/files from ente to other apps
|
||||
Future<void> share(
|
||||
BuildContext context,
|
||||
List<EnteFile> files, {
|
||||
@@ -62,9 +51,13 @@ Future<void> share(
|
||||
final paths = await Future.wait(pathFutures);
|
||||
await dialog.hide();
|
||||
paths.removeWhere((element) => element == null);
|
||||
final List<String> nonNullPaths = paths.map((element) => element!).toList();
|
||||
return Share.shareFiles(
|
||||
nonNullPaths,
|
||||
final xFiles = <XFile>[];
|
||||
for (String? path in paths) {
|
||||
if (path == null) continue;
|
||||
xFiles.add(XFile(path));
|
||||
}
|
||||
await Share.shareXFiles(
|
||||
xFiles,
|
||||
// required for ipad https://github.com/flutter/flutter/issues/47220#issuecomment-608453383
|
||||
sharePositionOrigin: shareButtonRect(context, shareButtonKey),
|
||||
);
|
||||
@@ -79,8 +72,10 @@ Future<void> share(
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the rect of button if context and key are not null
|
||||
/// If key is null, returned rect will be at the center of the screen
|
||||
Rect shareButtonRect(BuildContext context, GlobalKey? shareButtonKey) {
|
||||
Size size = MediaQuery.of(context).size;
|
||||
Size size = MediaQuery.sizeOf(context);
|
||||
final RenderObject? renderObject =
|
||||
shareButtonKey?.currentContext?.findRenderObject();
|
||||
RenderBox? renderBox;
|
||||
@@ -99,8 +94,21 @@ Rect shareButtonRect(BuildContext context, GlobalKey? shareButtonKey) {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> shareText(String text) async {
|
||||
return Share.share(text);
|
||||
Future<ShareResult> shareText(
|
||||
String text, {
|
||||
BuildContext? context,
|
||||
GlobalKey? key,
|
||||
}) async {
|
||||
try {
|
||||
final sharePosOrigin = _sharePosOrigin(context, key);
|
||||
return Share.share(
|
||||
text,
|
||||
sharePositionOrigin: sharePosOrigin,
|
||||
);
|
||||
} catch (e, s) {
|
||||
_logger.severe("failed to share text", e, s);
|
||||
return ShareResult.unavailable;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<EnteFile>> convertIncomingSharedMediaToFile(
|
||||
@@ -218,3 +226,32 @@ void shareSelected(
|
||||
shareButtonKey: shareButtonKey,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> shareImageAndUrl(
|
||||
String imagePath,
|
||||
String url, {
|
||||
BuildContext? context,
|
||||
GlobalKey? key,
|
||||
}) async {
|
||||
final sharePosOrigin = _sharePosOrigin(context, key);
|
||||
|
||||
await Share.shareXFiles(
|
||||
[XFile(imagePath)],
|
||||
text: url,
|
||||
sharePositionOrigin: sharePosOrigin,
|
||||
);
|
||||
}
|
||||
|
||||
/// required for ipad https://github.com/flutter/flutter/issues/47220#issuecomment-608453383
|
||||
/// This returns the position of the share button if context and key are not null
|
||||
/// and if not, it returns a default position so that the share sheet on iPad has
|
||||
/// some position to show up.
|
||||
Rect _sharePosOrigin(BuildContext? context, GlobalKey? key) {
|
||||
late final Rect rect;
|
||||
if (context != null) {
|
||||
rect = shareButtonRect(context, key);
|
||||
} else {
|
||||
rect = const Offset(20.0, 20.0) & const Size(10, 10);
|
||||
}
|
||||
return rect;
|
||||
}
|
||||
|
||||
@@ -1871,6 +1871,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.3"
|
||||
screenshot:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: screenshot
|
||||
sha256: "63817697a7835e6ce82add4228e15d233b74d42975c143ad8cfe07009fab866b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
scrollable_positioned_list:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1899,18 +1907,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: share_plus
|
||||
sha256: "3ef39599b00059db0990ca2e30fca0a29d8b37aae924d60063f8e0184cf20900"
|
||||
sha256: ef3489a969683c4f3d0239010cc8b7a2a46543a8d139e111c06c558875083544
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.2.2"
|
||||
version: "9.0.0"
|
||||
share_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus_platform_interface
|
||||
sha256: "251eb156a8b5fa9ce033747d73535bf53911071f8d3b6f4f0b578505ce0d4496"
|
||||
sha256: "0f9e4418835d1b2c3ae78fdb918251959106cefdbc4dd43526e182f80e82f6d4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.4.0"
|
||||
version: "4.0.0"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -2599,4 +2607,4 @@ packages:
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.3.0 <4.0.0"
|
||||
flutter: ">=3.20.0-1.2.pre"
|
||||
flutter: ">=3.22.0"
|
||||
|
||||
@@ -141,10 +141,11 @@ dependencies:
|
||||
provider: ^6.0.0
|
||||
quiver: ^3.0.1
|
||||
receive_sharing_intent: ^1.7.0
|
||||
screenshot: ^3.0.0
|
||||
scrollable_positioned_list: ^0.3.5
|
||||
sentry: ^7.9.0
|
||||
sentry_flutter: ^7.9.0
|
||||
share_plus: 7.2.2
|
||||
share_plus: ^9.0.0
|
||||
shared_preferences: ^2.0.5
|
||||
simple_cluster: ^0.3.0
|
||||
sqflite: ^2.3.0
|
||||
|
||||
Reference in New Issue
Block a user