diff --git a/assets/images/app_store.svg b/assets/images/app_store.svg new file mode 100644 index 0000000000..7aaeb5cd12 --- /dev/null +++ b/assets/images/app_store.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/google_play.svg b/assets/images/google_play.svg new file mode 100644 index 0000000000..e83dc0cf84 --- /dev/null +++ b/assets/images/google_play.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 01a399137b..95a1a60719 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -3097,5 +3097,6 @@ "disable_notifications": "Disable notifications", "logoutDialogWarning": "You will lose access to encrypted messages. We recommend that you enable chat backups before logging out", "copyNumber": "Copy number", - "callViaCarrier": "Call via Carrier" + "callViaCarrier": "Call via Carrier", + "scanQrCodeToJoin": "Installation of the mobile application will allow you to contact people from your phone's address book, your chats will be synchronised between devices" } diff --git a/config.sample.json b/config.sample.json index 27ce4416f8..e1afcdc800 100644 --- a/config.sample.json +++ b/config.sample.json @@ -13,5 +13,6 @@ "homeserver": "https://example.com/", "platform": "platform", "default_max_upload_avatar_size_in_bytes": 1000000, - "dev_mode": false + "dev_mode": false, + "qr_code_download_url": "" } diff --git a/docs/configurations/config_web_app_for_public_platform.md b/docs/configurations/config_web_app_for_public_platform.md index 09bef71ac2..024332e6c7 100644 --- a/docs/configurations/config_web_app_for_public_platform.md +++ b/docs/configurations/config_web_app_for_public_platform.md @@ -28,7 +28,8 @@ in [config.sample.json](https://github.com/linagora/twake-on-matrix/blob/main/co "homeserver": "https://example.com/", "platform": "platform" "default_max_upload_avatar_size_in_bytes": 1000000, - "dev_mode": false + "dev_mode": false, + "qr_code_download_url": "https://example.com/" } ``` @@ -46,6 +47,7 @@ in [config.sample.json](https://github.com/linagora/twake-on-matrix/blob/main/co - `homeserver`: Homeserver - `platform`: Platform, `saas` for the case of public platform - `default_max_upload_avatar_size_in_bytes`: Default max upload avatar size -- `dev_mode`: Enable to run app in IDE +- `dev_mode`: Enable to run app in IDE, +- `qr_code_download_url`: URL generate QR code to download app If you want to disable it, please change the value or remove this from `config.sample.json` \ No newline at end of file diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index 0837e4097f..9f1b712f6d 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -52,6 +52,14 @@ abstract class AppConfig { static String appTermsOfUse = 'https://twake.app/terms'; + static String qrCodeDownloadUrl = ''; + + static String twakeChatAppleStore = + 'https://apps.apple.com/us/app/twake-chat/id6473384641'; + + static String twakeChatGooglePlay = + 'https://play.google.com/store/apps/details?id=app.twake.android.chat'; + static double toolbarHeight(BuildContext context) => responsive.isMobile(context) ? 48 : 56; static const Color chatColor = primaryColor; @@ -250,5 +258,8 @@ abstract class AppConfig { if (json['dev_mode'] is bool) { devMode = json['dev_mode']; } + if (json['qr_code_download_url'] is String) { + qrCodeDownloadUrl = json['qr_code_download_url']; + } } } diff --git a/lib/pages/chat_blank/chat_blank.dart b/lib/pages/chat_blank/chat_blank.dart index 2fc9be4280..380f484253 100644 --- a/lib/pages/chat_blank/chat_blank.dart +++ b/lib/pages/chat_blank/chat_blank.dart @@ -1,5 +1,7 @@ +import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/first_column_inner_routes.dart'; import 'package:fluffychat/pages/chat_blank/chat_blank_style.dart'; +import 'package:fluffychat/pages/chat_blank/chat_qr_code.dart'; import 'package:fluffychat/presentation/mixins/go_to_group_chat_mixin.dart'; import 'package:fluffychat/resource/image_paths.dart'; import 'package:fluffychat/utils/extension/build_context_extension.dart'; @@ -13,9 +15,7 @@ import 'package:go_router/go_router.dart'; import 'package:linagora_design_flutter/linagora_design_flutter.dart'; class ChatBlank extends StatelessWidget { - final bool loading; - - const ChatBlank({this.loading = false, super.key}); + const ChatBlank({super.key}); @override Widget build(BuildContext context) { @@ -34,9 +34,15 @@ class ChatBlank extends StatelessWidget { if (value) { return const SizedBox.shrink(); } - return loading - ? _ChatBlankLoading(context: context) - : _ChatBlankNotChat(context: context); + return ValueListenableBuilder( + valueListenable: Matrix.of(context).showQrCodeDownload, + builder: (context, value, _) { + if (value && AppConfig.qrCodeDownloadUrl.isNotEmpty) { + return const ChatQrCode(); + } + return _ChatBlankNotChat(context: context); + }, + ); }, ), ); @@ -172,21 +178,3 @@ class _ChatBlankRichText extends StatelessWidget with GoToGroupChatMixin { } } } - -class _ChatBlankLoading extends StatelessWidget { - const _ChatBlankLoading({ - required this.context, - }); - - final BuildContext context; - - @override - Widget build(BuildContext context) { - return Center( - child: SizedBox( - width: ChatBlankStyle.width(context), - child: const LinearProgressIndicator(), - ), - ); - } -} diff --git a/lib/pages/chat_blank/chat_qr_code.dart b/lib/pages/chat_blank/chat_qr_code.dart new file mode 100644 index 0000000000..9b643ee810 --- /dev/null +++ b/lib/pages/chat_blank/chat_qr_code.dart @@ -0,0 +1,135 @@ +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/resource/image_paths.dart'; +import 'package:fluffychat/utils/responsive/responsive_utils.dart'; +import 'package:fluffychat/utils/url_launcher.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:linagora_design_flutter/colors/linagora_sys_colors.dart'; +import 'package:pretty_qr_code/pretty_qr_code.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +class ChatQrCode extends StatefulWidget { + const ChatQrCode({super.key}); + + @override + State createState() => _ChatQrCodeState(); +} + +class _ChatQrCodeState extends State { + @protected + late QrCode qrCode; + + @protected + late QrImage qrImage; + + @protected + late PrettyQrDecoration decoration; + + @override + void initState() { + super.initState(); + + qrCode = QrCode.fromData( + data: AppConfig.qrCodeDownloadUrl, + errorCorrectLevel: QrErrorCorrectLevel.H, + ); + + qrImage = QrImage(qrCode); + + decoration = PrettyQrDecoration( + image: PrettyQrDecorationImage( + image: AssetImage(ImagePaths.logoPng), + position: PrettyQrDecorationImagePosition.embedded, + ), + ); + } + + @override + void dispose() { + super.dispose(); + Matrix.of(context).resetFirstLogin(); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + width: double.infinity, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only( + bottom: 36, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + InkWell( + onTap: () { + UrlLauncher( + context, + url: AppConfig.twakeChatGooglePlay, + ).launchUrl(); + }, + child: SvgPicture.asset( + ImagePaths.googlePlay, + ), + ), + const SizedBox(width: 24), + InkWell( + onTap: () { + UrlLauncher( + context, + url: AppConfig.twakeChatAppleStore, + ).launchUrl(); + }, + child: SvgPicture.asset( + ImagePaths.appStore, + ), + ), + ], + ), + ), + SizedBox( + width: 200, + height: 200, + child: TweenAnimationBuilder( + tween: PrettyQrDecorationTween( + begin: decoration, + end: decoration, + ), + curve: Curves.ease, + duration: const Duration( + milliseconds: 240, + ), + builder: (context, decoration, child) { + return PrettyQrView( + qrImage: qrImage, + decoration: decoration, + ); + }, + ), + ), + Container( + constraints: const BoxConstraints( + maxWidth: ResponsiveUtils.bodyRadioWidth, + ), + padding: const EdgeInsets.only( + top: 16, + left: 24, + right: 24, + ), + child: Text( + L10n.of(context)!.scanQrCodeToJoin, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: LinagoraSysColors.material().onSurface, + ), + textAlign: TextAlign.center, + ), + ), + ], + ), + ); + } +} diff --git a/lib/resource/image_paths.dart b/lib/resource/image_paths.dart index 71091fa7d9..c6d62de316 100644 --- a/lib/resource/image_paths.dart +++ b/lib/resource/image_paths.dart @@ -53,6 +53,9 @@ class ImagePaths { static String get icPersonCheck => _getImagePath('ic_person_check.svg'); static String get icTwakeImageLogoBeta => _getImagePath('ic_twake_image_beta.svg'); + static String get logoPng => _getAssetPath('logo.png'); + static String get appStore => _getImagePath('app_store.svg'); + static String get googlePlay => _getImagePath('google_play.svg'); static String _getImagePath(String imageName) { return AssetsPaths.images + imageName; diff --git a/lib/widgets/layouts/loading_view.dart b/lib/widgets/layouts/loading_view.dart deleted file mode 100644 index 1c75e0f730..0000000000 --- a/lib/widgets/layouts/loading_view.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:fluffychat/pages/chat_blank/chat_blank.dart'; -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; - -import 'package:matrix/matrix.dart'; -import 'package:fluffychat/utils/update_checker_no_store.dart'; -import 'package:fluffychat/widgets/matrix.dart'; - -class LoadingView extends StatelessWidget { - const LoadingView({super.key}); - - @override - Widget build(BuildContext context) { - WidgetsBinding.instance.addPostFrameCallback( - (_) async { - await UpdateCheckerNoStore(context).checkUpdate(); - context.go( - Matrix.of(context).widget.clients.any( - (client) => - client.onLoginStateChanged.value == LoginState.loggedIn, - ) - ? '/rooms' - : '/home', - extra: GoRouterState.of(context).pathParameters, - ); - }, - ); - return const ChatBlank(loading: true); - } -} diff --git a/lib/widgets/matrix.dart b/lib/widgets/matrix.dart index d912db9df9..ce4234d564 100644 --- a/lib/widgets/matrix.dart +++ b/lib/widgets/matrix.dart @@ -91,6 +91,10 @@ class MatrixState extends State bool waitForFirstSync = false; + bool firstLogin = false; + + ValueNotifier showQrCodeDownload = ValueNotifier(false); + ValueNotifier showToMBootstrap = ValueNotifier(false); bool get twakeSupported { @@ -308,6 +312,7 @@ class MatrixState extends State } else { initConfigMobile().then((_) => initSettings()); } + listenShowToMBootstrap(); }); } @@ -424,6 +429,7 @@ class MatrixState extends State LoginState loginState, ) async { waitForFirstSync = false; + markFirstLogin(); await setUpToMServicesInLogin(newActiveClient); await _storePersistActiveAccount(newActiveClient); matrixState.reSyncContacts(); @@ -830,6 +836,25 @@ class MatrixState extends State _contactsManager.reSyncContacts(); } + void markFirstLogin() { + firstLogin = true; + } + + void resetFirstLogin() { + firstLogin = false; + handleShowQrCodeDownload(firstLogin); + } + + void handleShowQrCodeDownload(bool show) { + showQrCodeDownload.value = show; + } + + void listenShowToMBootstrap() { + showToMBootstrap.addListener(() { + handleShowQrCodeDownload(firstLogin); + }); + } + @override void didChangeAppLifecycleState(AppLifecycleState state) { Logs().i('didChangeAppLifecycleState: AppLifecycleState = $state'); @@ -909,7 +934,7 @@ class MatrixState extends State backgroundPush?.onRoomSync?.cancel(); showToMBootstrap.dispose(); linuxNotifications?.close(); - + showQrCodeDownload.dispose(); super.dispose(); } diff --git a/pubspec.lock b/pubspec.lock index e175271b50..87ff185715 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2332,6 +2332,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" + pretty_qr_code: + dependency: "direct main" + description: + name: pretty_qr_code + sha256: cbdb4af29da1c1fa21dd76f809646c591320ab9e435d3b0eab867492d43607d5 + url: "https://pub.dev" + source: hosted + version: "3.3.0" process: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 4d1fae9222..6c991cf796 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -212,6 +212,7 @@ dependencies: flutter_avif: 2.4.1 heif_converter: 1.0.0 pull_down_button: 0.10.2 + pretty_qr_code: 3.3.0 dev_dependencies: build_runner: 2.4.12