diff --git a/assets/i18n/en.json b/assets/i18n/en.json index b0d7bd7..85e19bb 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -106,7 +106,7 @@ "chat_list": { "search": "Search", "start_a_conversation": "Start a conversation", - "conversations": "Conversations", + "chats": "Chats", "groups": "Groups" }, "themes": { diff --git a/lib/components/bottom_sheet_action_button.dart b/lib/components/bottom_sheet_action_button.dart index 3d1cfb2..4330d33 100644 --- a/lib/components/bottom_sheet_action_button.dart +++ b/lib/components/bottom_sheet_action_button.dart @@ -47,6 +47,8 @@ class BottomSheetActionButtonState // return _showFab ? FloatingActionButton( + elevation: 0, + hoverElevation: 0, shape: widget.shape, foregroundColor: widget.foregroundColor, backgroundColor: widget.backgroundColor, diff --git a/lib/components/chat_component.dart b/lib/components/chat_component.dart index b6d0a0f..d1cb8ff 100644 --- a/lib/components/chat_component.dart +++ b/lib/components/chat_component.dart @@ -12,8 +12,7 @@ import '../entities/identity.dart'; import '../providers/account.dart'; import '../providers/chat.dart'; import '../providers/conversation.dart'; -import '../tools/theme_service.dart'; -import '../tools/widget_helpers.dart'; +import '../tools/tools.dart'; import '../veilid_support/veilid_support.dart'; class ChatComponent extends ConsumerStatefulWidget { @@ -121,8 +120,8 @@ class ChatComponentState extends ConsumerState { Widget build(BuildContext context) { final theme = Theme.of(context); final scale = theme.extension()!; - final chatTheme = scale.toChatTheme(); final textTheme = Theme.of(context).textTheme; + final chatTheme = makeChatTheme(scale, textTheme); final contactName = widget.activeChatContact.editedProfile.name; final protoMessages = @@ -147,7 +146,7 @@ class ChatComponentState extends ConsumerState { Container( height: 48, decoration: BoxDecoration( - color: scale.primaryScale.subtleBackground, + color: scale.primaryScale.subtleBorder, ), child: Row(children: [ Align( @@ -159,7 +158,7 @@ class ChatComponentState extends ConsumerState { textAlign: TextAlign.start, style: textTheme.titleMedium), )), - Spacer(), + const Spacer(), IconButton( icon: const Icon(Icons.close), onPressed: () async { @@ -168,7 +167,7 @@ class ChatComponentState extends ConsumerState { ]), ), Expanded( - child: Container( + child: DecoratedBox( decoration: const BoxDecoration(), child: Chat( theme: chatTheme, @@ -180,8 +179,8 @@ class ChatComponentState extends ConsumerState { onSendPressed: (message) { unawaited(_handleSendPressed(message)); }, - showUserAvatars: true, - showUserNames: true, + //showUserAvatars: false, + //showUserNames: true, user: _localUser, ), ), diff --git a/lib/components/chat_single_contact_item_widget.dart b/lib/components/chat_single_contact_item_widget.dart index e717404..ed48f96 100644 --- a/lib/components/chat_single_contact_item_widget.dart +++ b/lib/components/chat_single_contact_item_widget.dart @@ -28,12 +28,12 @@ class ChatSingleContactItemWidget extends ConsumerWidget { final selected = activeChat == remoteConversationRecordKey; return Container( - margin: const EdgeInsets.fromLTRB(4, 4, 4, 0), + margin: const EdgeInsets.fromLTRB(0, 4, 0, 0), clipBehavior: Clip.antiAlias, decoration: ShapeDecoration( - color: scale.tertiaryScale.subtleBackground, + color: scale.tertiaryScale.subtleBorder, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), + borderRadius: BorderRadius.circular(8), )), child: Slidable( key: ObjectKey(contact), diff --git a/lib/components/chat_single_contact_list_widget.dart b/lib/components/chat_single_contact_list_widget.dart index 1e33bfc..7cb047c 100644 --- a/lib/components/chat_single_contact_list_widget.dart +++ b/lib/components/chat_single_contact_list_widget.dart @@ -29,54 +29,57 @@ class ChatSingleContactListWidget extends ConsumerWidget { // ignore: prefer_expression_function_bodies Widget build(BuildContext context, WidgetRef ref) { final theme = Theme.of(context); - final textTheme = theme.textTheme; + //final textTheme = theme.textTheme; final scale = theme.extension()!; - return Container( - width: double.infinity, - decoration: ShapeDecoration( - color: scale.grayScale.appBackground, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - )), - child: (chatList.isEmpty) - ? const EmptyChatListWidget() - : SearchableList( - autoFocusOnSearch: false, - initialList: chatList.toList(), - builder: (l, i, c) { - final contact = contactMap[c.remoteConversationKey]; - if (contact == null) { - return const Text('...'); - } - return ChatSingleContactItemWidget(contact: contact); - }, - filter: (value) { - final lowerValue = value.toLowerCase(); - return chatList.where((c) { - final contact = contactMap[c.remoteConversationKey]; - if (contact == null) { - return false; - } - return contact.editedProfile.name - .toLowerCase() - .contains(lowerValue) || - contact.editedProfile.title - .toLowerCase() - .contains(lowerValue); - }).toList(); - }, - inputDecoration: InputDecoration( - labelText: translate('chat_list.search'), - fillColor: Colors.white, - focusedBorder: OutlineInputBorder( - borderSide: const BorderSide( - color: Colors.blue, - ), - borderRadius: BorderRadius.circular(10), - ), - ), - ), - ).paddingLTRB(8, 8, 8, 65); + return SizedBox.expand( + child: styledTitleContainer( + context: context, + title: translate('chat_list.chats'), + child: SizedBox.expand( + child: (chatList.isEmpty) + ? const EmptyChatListWidget() + : SearchableList( + autoFocusOnSearch: false, + initialList: chatList.toList(), + builder: (l, i, c) { + final contact = + contactMap[c.remoteConversationKey]; + if (contact == null) { + return const Text('...'); + } + return ChatSingleContactItemWidget( + contact: contact); + }, + filter: (value) { + final lowerValue = value.toLowerCase(); + return chatList.where((c) { + final contact = + contactMap[c.remoteConversationKey]; + if (contact == null) { + return false; + } + return contact.editedProfile.name + .toLowerCase() + .contains(lowerValue) || + contact.editedProfile.title + .toLowerCase() + .contains(lowerValue); + }).toList(); + }, + spaceBetweenSearchAndList: 4, + inputDecoration: InputDecoration( + labelText: translate('chat_list.search'), + contentPadding: const EdgeInsets.all(2), + fillColor: scale.primaryScale.text, + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: scale.primaryScale.hoverBorder, + ), + borderRadius: BorderRadius.circular(8), + ), + ), + ).paddingAll(8)))) + .paddingLTRB(8, 8, 8, 65); } } diff --git a/lib/components/contact_invitation_item_widget.dart b/lib/components/contact_invitation_item_widget.dart index 20f4cfa..280e669 100644 --- a/lib/components/contact_invitation_item_widget.dart +++ b/lib/components/contact_invitation_item_widget.dart @@ -33,9 +33,9 @@ class ContactInvitationItemWidget extends ConsumerWidget { margin: const EdgeInsets.fromLTRB(4, 4, 4, 0), clipBehavior: Clip.antiAlias, decoration: ShapeDecoration( - color: scale.tertiaryScale.subtleBackground, + color: scale.tertiaryScale.subtleBorder, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), + borderRadius: BorderRadius.circular(8), )), child: Slidable( // Specify a key if the Slidable is dismissible. diff --git a/lib/components/contact_invitation_list_widget.dart b/lib/components/contact_invitation_list_widget.dart index 11f4b95..30033fd 100644 --- a/lib/components/contact_invitation_list_widget.dart +++ b/lib/components/contact_invitation_list_widget.dart @@ -33,11 +33,11 @@ class ContactInvitationListWidgetState return Container( width: double.infinity, + margin: const EdgeInsets.fromLTRB(4, 0, 4, 4), decoration: ShapeDecoration( - color: scale.primaryScale.subtleBorder, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - )), + borderRadius: BorderRadius.circular(16), + )), constraints: const BoxConstraints(maxHeight: 200), child: Column( mainAxisSize: MainAxisSize.min, @@ -47,9 +47,8 @@ class ContactInvitationListWidgetState decoration: ShapeDecoration( color: scale.primaryScale.subtleBackground, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - side: BorderSide( - color: scale.primaryScale.subtleBorder, width: 4))), + borderRadius: BorderRadius.circular(16), + )), child: ListView.builder( controller: _scrollController, itemCount: widget.contactInvitationRecordList.length, @@ -75,7 +74,7 @@ class ContactInvitationListWidgetState return index; }, shrinkWrap: true, - )) + ).paddingLTRB(0, 0, 0, 4)) ], ), ); diff --git a/lib/components/contact_item_widget.dart b/lib/components/contact_item_widget.dart index 222201d..44d2b46 100644 --- a/lib/components/contact_item_widget.dart +++ b/lib/components/contact_item_widget.dart @@ -27,12 +27,12 @@ class ContactItemWidget extends ConsumerWidget { proto.TypedKeyProto.fromProto(contact.remoteConversationRecordKey); return Container( - margin: const EdgeInsets.fromLTRB(4, 4, 4, 0), + margin: const EdgeInsets.fromLTRB(0, 4, 0, 0), clipBehavior: Clip.antiAlias, decoration: ShapeDecoration( - color: scale.tertiaryScale.subtleBackground, + color: scale.tertiaryScale.subtleBorder, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), + borderRadius: BorderRadius.circular(8), )), child: Slidable( key: ObjectKey(contact), diff --git a/lib/components/contact_list_widget.dart b/lib/components/contact_list_widget.dart index 722e371..ac33482 100644 --- a/lib/components/contact_list_widget.dart +++ b/lib/components/contact_list_widget.dart @@ -22,8 +22,11 @@ class ContactListWidget extends ConsumerWidget { } @override - // ignore: prefer_expression_function_bodies Widget build(BuildContext context, WidgetRef ref) { + final theme = Theme.of(context); + //final textTheme = theme.textTheme; + final scale = theme.extension()!; + return SizedBox.expand( child: styledTitleContainer( context: context, @@ -33,7 +36,6 @@ class ContactListWidget extends ConsumerWidget { ? const EmptyContactListWidget() : SearchableList( autoFocusOnSearch: false, - shrinkWrap: true, initialList: contactList.toList(), builder: (l, i, c) => ContactItemWidget(contact: c), filter: (value) { @@ -48,17 +50,19 @@ class ContactListWidget extends ConsumerWidget { .contains(lowerValue)) .toList(); }, + spaceBetweenSearchAndList: 4, inputDecoration: InputDecoration( labelText: translate('contact_list.search'), - fillColor: Colors.white, + contentPadding: const EdgeInsets.all(2), + fillColor: scale.primaryScale.text, focusedBorder: OutlineInputBorder( - borderSide: const BorderSide( - color: Colors.blue, + borderSide: BorderSide( + color: scale.primaryScale.hoverBorder, ), - borderRadius: BorderRadius.circular(10), + borderRadius: BorderRadius.circular(8), ), ), - ), + ).paddingAll(8), ))).paddingLTRB(8, 0, 8, 8); } } diff --git a/lib/components/profile_widget.dart b/lib/components/profile_widget.dart index fa1f484..983f793 100644 --- a/lib/components/profile_widget.dart +++ b/lib/components/profile_widget.dart @@ -26,11 +26,9 @@ class ProfileWidget extends ConsumerWidget { return DecoratedBox( decoration: ShapeDecoration( - color: scale.primaryScale.subtleBorder, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - side: BorderSide( - width: 0, color: scale.primaryScale.subtleBorder))), + color: scale.primaryScale.border, + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(16))), child: Column(children: [ Text( name, diff --git a/lib/pages/home.dart b/lib/pages/home.dart index 457b1a7..13be67f 100644 --- a/lib/pages/home.dart +++ b/lib/pages/home.dart @@ -114,8 +114,8 @@ class HomePageState extends ConsumerState color: scale.secondaryScale.text, constraints: const BoxConstraints.expand(height: 64, width: 64), style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - scale.secondaryScale.subtleBorder), + backgroundColor: + MaterialStateProperty.all(scale.secondaryScale.border), shape: MaterialStateProperty.all(const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(16))))), tooltip: translate('app_bar.settings_tooltip'), @@ -212,12 +212,19 @@ class HomePageState extends ConsumerState // ignore: prefer_expression_function_bodies Widget buildTablet(BuildContext context) { final w = MediaQuery.of(context).size.width; + final theme = Theme.of(context); + final scale = theme.extension()!; + final children = [ ConstrainedBox( constraints: const BoxConstraints(minWidth: 300, maxWidth: 300), child: ConstrainedBox( constraints: BoxConstraints(maxWidth: w / 2), child: buildTabletLeftPane(context))), + SizedBox( + width: 2, + height: double.infinity, + child: ColoredBox(color: scale.primaryScale.hoverBorder)), Expanded(child: buildTabletRightPane(context)), ]; @@ -247,8 +254,8 @@ class HomePageState extends ConsumerState child: GestureDetector( onTap: () => FocusScope.of(context).requestFocus(_unfocusNode), child: DecoratedBox( - decoration: - BoxDecoration(color: scale.primaryScale.elementBackground), + decoration: BoxDecoration( + color: scale.primaryScale.activeElementBackground), child: responsiveVisibility( context: context, phone: false, diff --git a/lib/pages/main_pager/account_page.dart b/lib/pages/main_pager/account_page.dart index 4a68162..f42c4d3 100644 --- a/lib/pages/main_pager/account_page.dart +++ b/lib/pages/main_pager/account_page.dart @@ -62,8 +62,8 @@ class AccountPageState extends ConsumerState { if (contactInvitationRecordList.isNotEmpty) ExpansionTile( tilePadding: EdgeInsets.fromLTRB(8, 0, 8, 0), - backgroundColor: scale.primaryScale.subtleBorder, - collapsedBackgroundColor: scale.primaryScale.subtleBorder, + backgroundColor: scale.primaryScale.border, + collapsedBackgroundColor: scale.primaryScale.border, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), diff --git a/lib/pages/main_pager/main_pager.dart b/lib/pages/main_pager/main_pager.dart index e13eb71..b788a9b 100644 --- a/lib/pages/main_pager/main_pager.dart +++ b/lib/pages/main_pager/main_pager.dart @@ -8,6 +8,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_translate/flutter_translate.dart'; +import 'package:preload_page_view/preload_page_view.dart'; import 'package:stylish_bottom_bar/model/bar_items.dart'; import 'package:stylish_bottom_bar/stylish_bottom_bar.dart'; @@ -47,7 +48,7 @@ class MainPagerState extends ConsumerState final _unfocusNode = FocusNode(); var _currentPage = 0; - final pageController = PageController(); + final pageController = PreloadPageController(); final _selectedIconList = [Icons.person, Icons.chat]; // final _unselectedIconList = [ @@ -221,14 +222,14 @@ class MainPagerState extends ConsumerState backgroundColor: Colors.transparent, body: NotificationListener( onNotification: onScrollNotification, - child: PageView( + child: PreloadPageView( controller: pageController, + preloadPagesCount: 2, onPageChanged: (index) { setState(() { _currentPage = index; }); }, - //physics: const NeverScrollableScrollPhysics(), children: [ AccountPage( localAccounts: widget.localAccounts, @@ -244,7 +245,7 @@ class MainPagerState extends ConsumerState // ), // ), bottomNavigationBar: StylishBottomBar( - backgroundColor: scale.primaryScale.background, + backgroundColor: scale.primaryScale.hoverBorder, // gradient: LinearGradient( // begin: Alignment.topCenter, // end: Alignment.bottomCenter, @@ -275,7 +276,7 @@ class MainPagerState extends ConsumerState shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(14))), //foregroundColor: scale.secondaryScale.text, - backgroundColor: scale.secondaryScale.background, + backgroundColor: scale.secondaryScale.hoverBorder, builder: (context) => Icon( _fabIconList[_currentPage], color: scale.secondaryScale.text, diff --git a/lib/tools/radix_generator.dart b/lib/tools/radix_generator.dart index 07d6f41..42f443a 100644 --- a/lib/tools/radix_generator.dart +++ b/lib/tools/radix_generator.dart @@ -1,5 +1,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_chat_ui/flutter_chat_ui.dart'; import 'package:radix_colors/radix_colors.dart'; import 'theme_service.dart'; @@ -274,7 +275,7 @@ extension ToScaleColor on RadixColor { subtleBackground: step2, elementBackground: step3, hoverElementBackground: step4, - activedElementBackground: step5, + activeElementBackground: step5, subtleBorder: step6, border: step7, hoverBorder: step8, @@ -507,13 +508,39 @@ ColorScheme _radixColorScheme(Brightness brightness, RadixScheme radix) => surfaceTint: radix.primaryAlphaScale.step4, ); +ChatTheme makeChatTheme(ScaleScheme scale, TextTheme textTheme) => + DefaultChatTheme( + primaryColor: scale.primaryScale.background, + secondaryColor: scale.secondaryScale.background, + backgroundColor: scale.grayScale.subtleBackground, + inputBackgroundColor: Colors.blue, + inputBorderRadius: BorderRadius.zero, + inputTextDecoration: InputDecoration( + filled: true, + fillColor: scale.primaryScale.elementBackground, + isDense: true, + contentPadding: const EdgeInsets.fromLTRB(8, 12, 8, 12), + border: const OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.all(Radius.circular(16))), + ), + inputContainerDecoration: BoxDecoration(color: scale.primaryScale.border), + inputPadding: const EdgeInsets.all(9), + inputTextColor: scale.primaryScale.text, + attachmentButtonIcon: Icon(Icons.attach_file), + ); + ThemeData radixGenerator(Brightness brightness, RadixThemeColor themeColor) { - TextTheme? textTheme; + final textTheme = (brightness == Brightness.light) + ? Typography.blackCupertino + : Typography.whiteCupertino; final radix = _radixScheme(brightness, themeColor); final colorScheme = _radixColorScheme(brightness, radix); + final scaleScheme = radix.toScale(); + return ThemeData.from( colorScheme: colorScheme, textTheme: textTheme, useMaterial3: true) .copyWith(extensions: >[ - radix.toScale(), + scaleScheme, ]); } diff --git a/lib/tools/theme_service.dart b/lib/tools/theme_service.dart index 798ae4a..b9d9f79 100644 --- a/lib/tools/theme_service.dart +++ b/lib/tools/theme_service.dart @@ -18,7 +18,7 @@ class ScaleColor { required this.subtleBackground, required this.elementBackground, required this.hoverElementBackground, - required this.activedElementBackground, + required this.activeElementBackground, required this.subtleBorder, required this.border, required this.hoverBorder, @@ -32,7 +32,7 @@ class ScaleColor { Color subtleBackground; Color elementBackground; Color hoverElementBackground; - Color activedElementBackground; + Color activeElementBackground; Color subtleBorder; Color border; Color hoverBorder; @@ -46,7 +46,7 @@ class ScaleColor { Color? subtleBackground, Color? elementBackground, Color? hoverElementBackground, - Color? activedElementBackground, + Color? activeElementBackground, Color? subtleBorder, Color? border, Color? hoverBorder, @@ -60,8 +60,8 @@ class ScaleColor { elementBackground: elementBackground ?? this.elementBackground, hoverElementBackground: hoverElementBackground ?? this.hoverElementBackground, - activedElementBackground: - activedElementBackground ?? this.activedElementBackground, + activeElementBackground: + activeElementBackground ?? this.activeElementBackground, subtleBorder: subtleBorder ?? this.subtleBorder, border: border ?? this.border, hoverBorder: hoverBorder ?? this.hoverBorder, @@ -84,8 +84,8 @@ class ScaleColor { hoverElementBackground: Color.lerp(a.hoverElementBackground, b.hoverElementBackground, t) ?? const Color(0x00000000), - activedElementBackground: Color.lerp( - a.activedElementBackground, b.activedElementBackground, t) ?? + activeElementBackground: Color.lerp( + a.activeElementBackground, b.activeElementBackground, t) ?? const Color(0x00000000), subtleBorder: Color.lerp(a.subtleBorder, b.subtleBorder, t) ?? const Color(0x00000000), @@ -150,28 +150,6 @@ class ScaleScheme extends ThemeExtension { errorScale: ScaleColor.lerp(errorScale, other.errorScale, t), ); } - - ChatTheme toChatTheme() => DefaultChatTheme( - primaryColor: primaryScale.background, - secondaryColor: secondaryScale.background, - backgroundColor: grayScale.appBackground, - inputBackgroundColor: grayScale.subtleBackground, - inputBorderRadius: BorderRadius.zero, - inputTextDecoration: InputDecoration( - border: OutlineInputBorder( - borderSide: BorderSide(color: primaryScale.subtleBorder), - borderRadius: const BorderRadius.all(Radius.circular(16))), - ), - inputContainerDecoration: - BoxDecoration(color: primaryScale.appBackground), - inputPadding: EdgeInsets.all(5), - inputTextStyle: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - height: 1, - ), - attachmentButtonIcon: Icon(Icons.attach_file), - ); } //////////////////////////////////////////////////////////////////////// diff --git a/lib/tools/widget_helpers.dart b/lib/tools/widget_helpers.dart index 8535f6a..0d05d27 100644 --- a/lib/tools/widget_helpers.dart +++ b/lib/tools/widget_helpers.dart @@ -76,9 +76,9 @@ Widget styledTitleContainer( final scale = theme.extension()!; final textTheme = theme.textTheme; - return Container( + return DecoratedBox( decoration: ShapeDecoration( - color: scale.primaryScale.subtleBorder, + color: scale.primaryScale.border, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), )), @@ -87,7 +87,7 @@ Widget styledTitleContainer( title, style: textTheme.titleMedium! .copyWith(color: scale.primaryScale.subtleText), - ).paddingLTRB(4, 4, 4, 0), + ).paddingLTRB(8, 8, 8, 8), DecoratedBox( decoration: ShapeDecoration( color: scale.primaryScale.subtleBackground, @@ -130,7 +130,7 @@ Future showStyledDialog( borderRadius: BorderRadius.circular(16))), child: DecoratedBox( decoration: ShapeDecoration( - color: scale.primaryScale.subtleBackground, + color: scale.primaryScale.appBackground, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12))), child: child.paddingAll(0))))); diff --git a/pubspec.lock b/pubspec.lock index b415a89..0e1708c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -925,6 +925,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" + preload_page_view: + dependency: "direct main" + description: + name: preload_page_view + sha256: "488a10c158c5c2e9ba9d77e5dbc09b1e49e37a20df2301e5ba02992eac802b7a" + url: "https://pub.dev" + source: hosted + version: "0.2.0" protobuf: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 17e8210..0a6479c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -48,6 +48,7 @@ dependencies: path: ^1.8.2 path_provider: ^2.0.11 pinput: ^3.0.1 + preload_page_view: ^0.2.0 protobuf: ^3.0.0 qr_flutter: ^4.1.0 quickalert: ^1.0.1