-
Notifications
You must be signed in to change notification settings - Fork 40
[#2758-Part-1] TW-2759 Invite when app installed #2761
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f9157ea
64b78dc
01fbba8
db3c6c2
296dc32
0b2cab7
c0a558a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| import 'package:fluffychat/config/app_constants.dart'; | ||
| import 'package:fluffychat/generated/l10n/app_localizations.dart'; | ||
| import 'package:fluffychat/resource/image_paths.dart'; | ||
| import 'package:fluffychat/utils/platform_infos.dart'; | ||
| import 'package:fluffychat/utils/string_extension.dart'; | ||
| import 'package:fluffychat/utils/twake_snackbar.dart'; | ||
| import 'package:flutter/material.dart'; | ||
| import 'package:flutter/services.dart'; | ||
| import 'package:flutter_svg/flutter_svg.dart'; | ||
| import 'package:linagora_design_flutter/colors/linagora_sys_colors.dart'; | ||
| import 'package:matrix/matrix.dart'; | ||
| import 'package:share_plus/share_plus.dart'; | ||
|
|
||
| class SliverInviteFriendButton extends StatelessWidget { | ||
| const SliverInviteFriendButton({super.key, required this.userId}); | ||
|
|
||
| final String userId; | ||
|
|
||
| @override | ||
| Widget build(BuildContext context) { | ||
| return SliverPersistentHeader( | ||
| floating: true, | ||
| delegate: _InviteFriendButtonDelegate(userId), | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| class _InviteFriendButtonDelegate extends SliverPersistentHeaderDelegate { | ||
| const _InviteFriendButtonDelegate(this.userId); | ||
|
|
||
| final String userId; | ||
|
|
||
| @override | ||
| Widget build( | ||
| BuildContext context, | ||
| double shrinkOffset, | ||
| bool overlapsContent, | ||
| ) { | ||
| final sysColor = LinagoraSysColors.material(); | ||
| final textTheme = Theme.of(context).textTheme; | ||
| return Material( | ||
| color: sysColor.onPrimary, | ||
| child: InkWell( | ||
| onTap: () async { | ||
| // TODO: Placeholder url | ||
| const domain = AppConstants.appLinkUniversalLinkDomain; | ||
| final url = 'https://$domain/chat/#/${Uri.encodeComponent(userId)}'; | ||
| try { | ||
| if (PlatformInfos.isMobile) { | ||
| await Share.share(url); | ||
| } else if (PlatformInfos.isWeb) { | ||
| await Clipboard.setData(ClipboardData(text: url)); | ||
| TwakeSnackBar.show( | ||
| context, | ||
| L10n.of(context)!.linkCopiedToClipboard, | ||
| ); | ||
| } | ||
| } catch (e) { | ||
| Logs().e('InviteFriendButtonDelegate::onTap():', e); | ||
| } | ||
| }, | ||
| child: Container( | ||
| padding: const EdgeInsets.all(8), | ||
| decoration: BoxDecoration( | ||
| border: Border.symmetric( | ||
| horizontal: BorderSide( | ||
| color: sysColor.surfaceTint.withValues(alpha: 0.16), | ||
| ), | ||
| ), | ||
| ), | ||
| child: Row( | ||
| children: [ | ||
| Padding( | ||
| padding: const EdgeInsets.all(12), | ||
| child: SvgPicture.asset( | ||
| ImagePaths.icPersonCheck, | ||
| width: 24, | ||
| height: 24, | ||
| colorFilter: ColorFilter.mode( | ||
| sysColor.primary, | ||
| BlendMode.srcIn, | ||
| ), | ||
| ), | ||
| ), | ||
| Flexible( | ||
| child: Text( | ||
| L10n.of(context)!.inviteFriend.capitalize(context), | ||
| style: textTheme.bodyLarge?.copyWith( | ||
| fontSize: 17, | ||
| height: 24 / 17, | ||
| color: sysColor.primary, | ||
| ), | ||
| ), | ||
| ), | ||
| ], | ||
| ), | ||
| ), | ||
| ), | ||
| ); | ||
| } | ||
|
|
||
| @override | ||
| double get maxExtent => 64; | ||
|
|
||
| @override | ||
| double get minExtent => 0; | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| @override | ||
| bool shouldRebuild(covariant _InviteFriendButtonDelegate oldDelegate) => | ||
| oldDelegate.userId != userId; | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -30,7 +30,7 @@ class UrlLauncher with GoToDraftChatMixin { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final ChromeSafariBrowser? browser = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PlatformInfos.isMobile ? ChromeSafariBrowser() : null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void launchUrl() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Future<void> launchUrl({Client? client}) async { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # Search for launchUrl method calls with client parameter
rg -nP --type=dart -C3 'launchUrl\s*\(\s*client:'Repository: linagora/twake-on-matrix Length of output: 659 🏁 Script executed: # Also check the actual method body to see if client is used
rg -nP --type=dart -A 20 'Future<void> launchUrl\(\{Client\?'Repository: linagora/twake-on-matrix Length of output: 1643 🏁 Script executed: # Search for any imports or usage of Client class in the file
rg -nP --type=dart 'Client' lib/utils/url_launcher.dartRepository: linagora/twake-on-matrix Length of output: 145 🏁 Script executed: # Check the complete launchUrl method body and see all method calls
rg -nP --type=dart -A 100 'Future<void> launchUrl\(\{Client\?' lib/utils/url_launcher.dart | head -150Repository: linagora/twake-on-matrix Length of output: 4528 🏁 Script executed: # Check if openMatrixToUrl method accepts a client parameter
rg -nP --type=dart 'void openMatrixToUrl' lib/utils/url_launcher.dartRepository: linagora/twake-on-matrix Length of output: 123 🏁 Script executed: # Check if client parameter is passed to any method within launchUrl
rg -nP --type=dart 'client:' lib/utils/url_launcher.dartRepository: linagora/twake-on-matrix Length of output: 50 Remove the unused The 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (url!.toLowerCase().startsWith(AppConfig.deepLinkPrefix) || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url!.toLowerCase().startsWith(AppConfig.inviteLinkPrefix) || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {'#', '@', '!', '+', '\$'}.contains(url![0]) || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -44,9 +44,14 @@ class UrlLauncher with GoToDraftChatMixin { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (uri.host == AppConstants.appLinkUniversalLinkDomain) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final pathWithoutChatPrefix = uri.path.replaceFirst('/chat', ''); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| context.go(pathWithoutChatPrefix); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Handle links.twake.app URLs with /chat# pattern | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (uri.path.startsWith('/chat') && uri.fragment.isNotEmpty) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final matrixToUrl = url!.replaceFirst( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'https://${AppConstants.appLinkUniversalLinkDomain}', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AppConfig.inviteLinkPrefix, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return openMatrixToUrl(matrixToUrl); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it safer to add a subpath in the universal link that manages invite ? Like links.twake.app/chat/user/@aaa:linagora.com instead of links.twake.app/chat/@aaa:linagora.com to keep other routing possibilities for the future? Maybe one day links.twake.app/chat/group/ or links.twake.app/chat/settings/ that opens settings or I don't know
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://matrix.to/#/@tddang:linagora.com Twake chat is not listed in the supported apps.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did not know that Matrix scheme already manage rooms, users, messages, etc so maybe my comment is less relevant |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!{'https', 'http'}.contains(uri.scheme)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // just launch non-https / non-http uris directly | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -104,12 +109,12 @@ class UrlLauncher with GoToDraftChatMixin { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void openMatrixToUrl() async { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void openMatrixToUrl([String? customUrl]) async { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final matrix = Matrix.of(context); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final url = this.url!.replaceFirst( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AppConfig.deepLinkPrefix, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AppConfig.inviteLinkPrefix, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final url = (customUrl ?? this.url!).replaceFirst( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AppConfig.deepLinkPrefix, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AppConfig.inviteLinkPrefix, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // The identifier might be a matrix.to url and needs escaping. Or, it might have multiple | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // identifiers (room id & event id), or it might also have a query part. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -241,4 +246,28 @@ class UrlLauncher with GoToDraftChatMixin { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bool isCreatingChatFromUrl = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Future<void> _openChatWithUser( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String matrixId, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Client? client, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) async { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (client == null || isCreatingChatFromUrl) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isCreatingChatFromUrl = true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final rooms = client.rooms.where((room) => room.isDirectChat).toList(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final availableRoom = rooms.firstWhereOrNull((room) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return room.getParticipants().any((user) => user.id == matrixId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (availableRoom != null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isCreatingChatFromUrl = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| context.go('/rooms/${availableRoom.id}'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final roomId = await client.startDirectChat(matrixId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isCreatingChatFromUrl = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| context.go('/rooms/$roomId'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+250
to
+272
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Protect the guard flag with exception handling and verify The Apply this diff to ensure the flag is always reset and verify Future<void> _openChatWithUser(
String matrixId, {
Client? client,
}) async {
if (client == null || isCreatingChatFromUrl) return;
isCreatingChatFromUrl = true;
- final rooms = client.rooms.where((room) => room.isDirectChat).toList();
- final availableRoom = rooms.firstWhereOrNull((room) {
- return room.getParticipants().any((user) => user.id == matrixId);
- });
-
- if (availableRoom != null) {
- isCreatingChatFromUrl = false;
- context.go('/rooms/${availableRoom.id}');
- return;
- }
-
- final roomId = await client.startDirectChat(matrixId);
- isCreatingChatFromUrl = false;
- context.go('/rooms/$roomId');
+ try {
+ final rooms = client.rooms.where((room) => room.isDirectChat).toList();
+ final availableRoom = rooms.firstWhereOrNull((room) {
+ return room.getParticipants().any((user) => user.id == matrixId);
+ });
+
+ if (availableRoom != null) {
+ context.go('/rooms/${availableRoom.id}');
+ return;
+ }
+
+ final roomId = await client.startDirectChat(matrixId);
+ if (roomId == null) {
+ Logs().e('_openChatWithUser: startDirectChat returned null for $matrixId');
+ return;
+ }
+ context.go('/rooms/$roomId');
+ } finally {
+ isCreatingChatFromUrl = false;
+ }
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

Uh oh!
There was an error while loading. Please reload this page.