TF-524 Create RecentSearchCache and TypeAHeadQuickSearch widget

This commit is contained in:
dab246
2022-05-23 18:48:29 +07:00
committed by Dat H. Pham
parent 5ca238153a
commit 3513c363ff
23 changed files with 2349 additions and 127 deletions
@@ -0,0 +1,111 @@
import 'package:hive/hive.dart';
import 'package:tmail_ui_user/features/caching/config/hive_cache_client.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/data/model/recent_search_cache.dart';
class RecentSearchCacheClient extends HiveCacheClient<RecentSearchCache> {
@override
String get tableName => 'RecentSearchCache';
@override
Future<void> clearAllData() {
return Future.sync(() async {
final boxState = await openBox();
boxState.clear();
}).catchError((error) {
throw error;
});
}
@override
Future<void> deleteItem(String key) {
return Future.sync(() async {
final boxState = await openBox();
return boxState.delete(key);
}).catchError((error) {
throw error;
});
}
@override
Future<void> deleteMultipleItem(List<String> listKey) {
throw UnimplementedError();
}
@override
Future<List<RecentSearchCache>> getAll() {
return Future.sync(() async {
final boxState = await openBox();
return boxState.values.toList();
}).catchError((error) {
throw error;
});
}
@override
Future<RecentSearchCache?> getItem(String key) {
throw UnimplementedError();
}
@override
Future<void> insertItem(String key, RecentSearchCache newObject) {
return Future.sync(() async {
final boxState = await openBox();
boxState.put(key, newObject);
}).catchError((error) {
throw error;
});
}
@override
Future<void> insertMultipleItem(Map<String, RecentSearchCache> mapObject) {
throw UnimplementedError();
}
@override
Future<bool> isExistItem(String key) {
return Future.sync(() async {
final boxState = await openBox();
return boxState.containsKey(key);
}).catchError((error) {
throw error;
});
}
@override
Future<bool> isExistTable() {
return Future.sync(() async {
return await Hive.boxExists(tableName);
}).catchError((error) {
throw error;
});
}
@override
Future<Box<RecentSearchCache>> openBox() {
return Future.sync(() async {
if (Hive.isBoxOpen(tableName)) {
return Hive.box<RecentSearchCache>(tableName);
}
return await Hive.openBox<RecentSearchCache>(tableName);
}).catchError((error) {
throw error;
});
}
@override
Future<void> updateItem(String key, RecentSearchCache newObject) {
return Future.sync(() async {
final boxState = await openBox();
boxState.put(key, newObject);
}).catchError((error) {
throw error;
});
}
@override
Future<void> updateMultipleItem(Map<String, RecentSearchCache> mapObject) {
throw UnimplementedError();
}
}
@@ -6,4 +6,5 @@ class CachingConstants {
static const int STATE_TYPE_IDENTIFY = 4;
static const int EMAIL_CACHE_IDENTIFY = 5;
static const int EMAIL_ADDRESS_HIVE_CACHE_IDENTIFY = 6;
static const int RECENT_SEARCH_HIVE_CACHE_IDENTIFY = 7;
}
@@ -1,64 +0,0 @@
import 'package:core/core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
typedef OnNewSearchQuery = Function(String);
class SearchFormWidgetBuilder {
final _typeAheadController = TextEditingController();
final BuildContext _context;
final ImagePaths _imagePaths;
OnNewSearchQuery? _onNewSearchQuery;
SearchFormWidgetBuilder(this._context, this._imagePaths);
SearchFormWidgetBuilder onNewSearchQuery(OnNewSearchQuery onNewSearchQuery) {
_onNewSearchQuery = onNewSearchQuery;
return this;
}
Widget build() {
return Container(
key: const Key('search_folder_form'),
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
border: Border.all(color: AppColor.searchBorderColor),
color: AppColor.primaryLightColor),
child: TypeAheadFormField(
textFieldConfiguration: TextFieldConfiguration(
controller: _typeAheadController,
textInputAction: TextInputAction.done,
decoration: InputDecoration(
border: InputBorder.none,
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
contentPadding: const EdgeInsets.only(left: 0, top: 15, bottom: 15, right: 15),
hintText: AppLocalizations.of(_context).search_folder,
hintStyle: const TextStyle(color: AppColor.searchHintTextColor, fontSize: 15.0, fontWeight: FontWeight.w500),
icon: Padding(
padding: const EdgeInsets.only(left: 20),
child: SvgPicture.asset(_imagePaths.icSearch, width: 24, height: 24, fit: BoxFit.fill),
))),
debounceDuration: const Duration(milliseconds: 300),
suggestionsCallback: (pattern) async {
if (_onNewSearchQuery != null) {
_onNewSearchQuery!(pattern);
}
return [];
},
itemBuilder: (BuildContext context, itemData) => const SizedBox.shrink(),
onSuggestionSelected: (suggestion) {},
noItemsFoundBuilder: (context) => const SizedBox(),
hideOnEmpty: true,
hideOnError: true,
hideOnLoading: true,
hideSuggestionsOnKeyboardHide: true,
));
}
}
@@ -0,0 +1,21 @@
import 'package:equatable/equatable.dart';
import 'package:hive/hive.dart';
import 'package:tmail_ui_user/features/caching/utils/caching_constants.dart';
part 'recent_search_cache.g.dart';
@HiveType(typeId: CachingConstants.RECENT_SEARCH_HIVE_CACHE_IDENTIFY)
class RecentSearchCache extends HiveObject with EquatableMixin {
@HiveField(0)
final String value;
@HiveField(1)
final DateTime creationTime;
RecentSearchCache(this.value, this.creationTime);
@override
List<Object?> get props => [value, creationTime];
}
@@ -0,0 +1,12 @@
import 'package:equatable/equatable.dart';
class RecentSearch with EquatableMixin {
final String value;
final DateTime creationDate;
RecentSearch(this.value, this.creationDate);
@override
List<Object?> get props => [value, creationDate];
}
@@ -1,21 +1,24 @@
import 'package:core/core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:get/get.dart';
import 'package:model/model.dart';
import 'package:tmail_ui_user/features/base/mixin/network_connection_mixin.dart';
import 'package:tmail_ui_user/features/composer/presentation/composer_view_web.dart';
import 'package:tmail_ui_user/features/email/presentation/email_view.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/mailbox_view_web.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/model/recent_search.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/dashboard_action.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mixin/filter_email_popup_menu_mixin.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mixin/user_setting_popup_menu_mixin.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/email_quick_search_item_tile_widget.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/recent_search_item_tile_widget.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/model/app_setting.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/model/reading_pane.dart';
import 'package:tmail_ui_user/features/thread/presentation/extensions/filter_message_option_extension.dart';
import 'package:tmail_ui_user/features/thread/presentation/thread_view.dart';
import 'package:tmail_ui_user/features/thread/presentation/widgets/search_app_bar_widget.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/app_routes.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
@@ -157,7 +160,9 @@ class MailboxDashBoardView extends GetWidget<MailboxDashBoardController> with Ne
}),
const SizedBox(width: 16),
Obx(() => !controller.isSearchActive() ? const Spacer() : const SizedBox.shrink()),
Obx(() => controller.isSearchActive() ? Expanded(child: _buildSearchForm(context)) : const SizedBox.shrink()),
Obx(() => controller.isSearchActive()
? Expanded(child: _buildSearchForm(context))
: const SizedBox.shrink()),
Obx(() => !controller.isSearchActive()
? (SearchBarView(_imagePaths)
..hintTextSearch(AppLocalizations.of(context).search_emails)
@@ -396,24 +401,119 @@ class MailboxDashBoardView extends GetWidget<MailboxDashBoardController> with Ne
}
Widget _buildSearchForm(BuildContext context) {
return (SearchAppBarWidget(context, _imagePaths, _responsiveUtils,
controller.searchQuery,
controller.searchFocus,
controller.searchInputController,
suggestionSearch: controller.suggestionSearch)
..addDecoration(const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(10)),
color: AppColor.colorBgSearchBar))
..setMargin(const EdgeInsets.only(right: 10))
..setHeightSearchBar(45)
..setHintText(AppLocalizations.of(context).search_mail)
..addOnCancelSearchPressed(() {
controller.disableSearch();
controller.dispatchAction(CancelSelectionAllEmailAction());
})
..addOnClearTextSearchAction(() => controller.clearSearchText())
..addOnTextChangeSearchAction((query) => controller.addSuggestionSearch(query))
..addOnSearchTextAction((query) => controller.searchEmail(context, query)))
.build();
return Row(
children: [
buildIconWeb(
icon: SvgPicture.asset(_imagePaths.icBack, color: AppColor.colorTextButton, fit: BoxFit.fill),
onTap: () {
controller.disableSearch();
controller.dispatchAction(CancelSelectionAllEmailAction());
}),
Expanded(child: Container(
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(10)),
color: AppColor.colorBgSearchBar),
height: 45,
child: TypeAheadFormFieldQuickSearch<PresentationEmail, RecentSearch>(
textFieldConfiguration: QuickSearchTextFieldConfiguration(
controller: controller.searchInputController,
autofocus: true,
focusNode: controller.searchFocus,
textInputAction: TextInputAction.done,
decoration: InputDecoration(
border: InputBorder.none,
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
contentPadding: EdgeInsets.zero,
hintText: AppLocalizations.of(context).search_mail,
hintStyle: const TextStyle(color: AppColor.colorHintSearchBar, fontSize: 17.0),
labelStyle: const TextStyle(color: AppColor.colorHintSearchBar, fontSize: 17.0)
),
leftButton: buildIconWeb(
icon: SvgPicture.asset(_imagePaths.icSearchBar, width: 16, height: 16, fit: BoxFit.fill),
onTap: () => {}),
rightButton: buildIconWeb(
icon: SvgPicture.asset(_imagePaths.icComposerClose, width: 18, height: 18, fit: BoxFit.fill),
onTap: () {
controller.searchInputController.clear();
controller.clearSearchText();
})
),
suggestionsBoxDecoration: QuickSearchSuggestionsBoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(16)),
constraints: BoxConstraints(
maxWidth: _responsiveUtils.isDesktop(context) ? 556 : double.infinity,
maxHeight: 322),
),
actionButton: Padding(
padding: const EdgeInsets.only(left: 24, top: 24, right: 24, bottom: 20),
child: Row(
children: [
(ButtonBuilder(_imagePaths.icAttachmentSB)
..decoration(BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: AppColor.colorButtonHeaderThread))
..paddingIcon(const EdgeInsets.only(right: 5))
..padding(const EdgeInsets.symmetric(horizontal: 12, vertical: 8))
..radiusSplash(10)
..size(16)
..textStyle(const TextStyle(fontSize: 13, color: AppColor.colorTextButtonHeaderThread))
..onPressActionClick(() => {})
..text(AppLocalizations.of(context).hasAttachment, isVertical: false))
.build(),
const SizedBox(width: 8),
(ButtonBuilder(_imagePaths.icCalendarSB)
..decoration(BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: AppColor.colorButtonHeaderThread))
..paddingIcon(const EdgeInsets.only(right: 5))
..padding(const EdgeInsets.symmetric(horizontal: 12, vertical: 8))
..radiusSplash(10)
..size(16)
..textStyle(const TextStyle(fontSize: 13, color: AppColor.colorTextButtonHeaderThread))
..onPressActionClick(() => {})
..text(AppLocalizations.of(context).last7Days, isVertical: false))
.build(),
const SizedBox(width: 8),
(ButtonBuilder(_imagePaths.icUserSB)
..decoration(BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: AppColor.colorButtonHeaderThread))
..paddingIcon(const EdgeInsets.only(right: 5))
..padding(const EdgeInsets.symmetric(horizontal: 12, vertical: 8))
..radiusSplash(10)
..size(16)
..textStyle(const TextStyle(fontSize: 13, color: AppColor.colorTextButtonHeaderThread))
..onPressActionClick(() => {})
..text(AppLocalizations.of(context).fromMe, isVertical: false))
.build(),
],
),
),
titleHeaderRecent: Padding(
padding: const EdgeInsets.only(left: 24, right: 24, bottom: 8),
child: Text(AppLocalizations.of(context).recent,
style: const TextStyle(fontSize: 13.0,
color: AppColor.colorTextButtonHeaderThread,
fontWeight: FontWeight.w500))),
getRecentCallback: (pattern) async {
return [];
},
itemRecentBuilder: (context, recent) {
return RecentSearchItemTileWidget(recent);
},
suggestionsCallback: (pattern) async {
return [];
},
itemBuilder: (context, email) {
return EmailQuickSearchItemTileWidget(email, controller.selectedMailbox.value);
},
onSuggestionSelected: (autoCompleteResult) async {
}),
)),
const SizedBox(width: 16),
]
);
}
}
@@ -0,0 +1,92 @@
import 'package:core/core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:model/email/presentation_email.dart';
import 'package:model/extensions/presentation_email_extension.dart';
import 'package:model/mailbox/presentation_mailbox.dart';
class EmailQuickSearchItemTileWidget extends StatelessWidget {
final imagePath = Get.find<ImagePaths>();
final PresentationEmail _presentationEmail;
final PresentationMailbox? _presentationMailbox;
EmailQuickSearchItemTileWidget(
this._presentationEmail,
this._presentationMailbox,
{Key? key}
) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SvgPicture.asset(
_presentationEmail.hasStarred ? imagePath.icStar : imagePath.icUnStar,
width: 20, height: 20, fit: BoxFit.fill),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Text(_getInformationSender(),
maxLines: 1,
overflow: BuildUtils.isWeb ? null : TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
color: Colors.black)),
const SizedBox(width: 16),
Expanded(
child: Text(_presentationEmail.getEmailTitle(),
maxLines: 1,
overflow: BuildUtils.isWeb ? null : TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.normal,
color: Colors.black)),
),
if (_presentationEmail.hasAttachment == true)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: SvgPicture.asset(imagePath.icAttachment, width: 14, height: 14, fit: BoxFit.fill),
),
Text(_presentationEmail.getReceivedAt(Localizations.localeOf(context).toLanguageTag()),
textAlign: TextAlign.right,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.normal,
color: Colors.black))
]),
const SizedBox(height: 3),
Text(_presentationEmail.getPartialContent(),
maxLines: 1,
overflow: BuildUtils.isWeb ? null : TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.normal,
color: AppColor.colorContentEmail))
]
),
),
],
),
);
}
String _getInformationSender() {
if (_presentationMailbox?.role == PresentationMailbox.roleSent
|| _presentationMailbox?.role == PresentationMailbox.roleDrafts
|| _presentationMailbox?.role == PresentationMailbox.roleOutbox) {
return _presentationEmail.recipientsName();
}
return _presentationEmail.getSenderName();
}
}
@@ -0,0 +1,35 @@
import 'package:core/core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/model/recent_search.dart';
class RecentSearchItemTileWidget extends StatelessWidget {
final imagePath = Get.find<ImagePaths>();
final RecentSearch recentSearch;
RecentSearchItemTileWidget(this.recentSearch, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10),
child: Row(
children: [
SvgPicture.asset(imagePath.icClockSB),
const SizedBox(width: 8),
Expanded(
child: Text(recentSearch.value,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.normal,
color: Colors.black)),
),
],
),
);
}
}