[mob][photos] share with placeholder and changed share link button (#1969)

This commit is contained in:
Aman Raj Singh Mourya
2024-06-06 11:44:43 +05:30
committed by GitHub
11 changed files with 815 additions and 179 deletions

View File

@@ -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);
}

View File

@@ -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,

View File

@@ -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();

View 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,
),
),
),
),
),
],
),
);
}
}

View 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(),
),
),
),
],
),
);
}
}

View File

@@ -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,
),
),
],
),
),

View File

@@ -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 {

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}

View File

@@ -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"

View File

@@ -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