TF-1068 Implement make recent list for input of URL

This commit is contained in:
dab246
2022-10-21 12:10:00 +07:00
committed by Dat H. Pham
parent 87839351f2
commit 2a39904b35
14 changed files with 225 additions and 55 deletions
@@ -0,0 +1,45 @@
import 'package:core/presentation/resources/image_paths.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:tmail_ui_user/features/login/domain/model/recent_login_url.dart';
class RecentItemTileWidget<T> extends StatelessWidget {
final T item;
final EdgeInsets? contentPadding;
final ImagePaths imagePath;
const RecentItemTileWidget(this.item, {
required this.imagePath,
Key? key,
this.contentPadding,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: contentPadding ?? const EdgeInsets.all(12),
child: Row(
children: [
SvgPicture.asset(imagePath.icClockSB),
const SizedBox(width: 8),
Expanded(
child: Text(_getTitle(item),
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.normal,
color: Colors.black)),
),
],
),
);
}
String _getTitle(T item) {
if (item is RecentLoginUrl) {
return item.url;
}
return '';
}
}
@@ -2,4 +2,6 @@ import 'package:tmail_ui_user/features/login/domain/model/recent_login_url.dart'
abstract class LoginUrlDataSource { abstract class LoginUrlDataSource {
Future<void> saveLoginUrl(RecentLoginUrl baseUrl); Future<void> saveLoginUrl(RecentLoginUrl baseUrl);
Future<List<RecentLoginUrl>> getAllRecentLoginUrlLatest({int? limit, String? pattern});
} }
@@ -1,6 +1,7 @@
import 'package:tmail_ui_user/features/caching/recent_login_url_cache_client.dart'; import 'package:tmail_ui_user/features/caching/recent_login_url_cache_client.dart';
import 'package:tmail_ui_user/features/login/data/datasource/login_url_datasource.dart'; import 'package:tmail_ui_user/features/login/data/datasource/login_url_datasource.dart';
import 'package:tmail_ui_user/features/login/data/model/recent_login_url_cache.dart'; import 'package:tmail_ui_user/features/login/data/model/recent_login_url_cache.dart';
import 'package:tmail_ui_user/features/login/domain/extensions/list_recent_login_url_extension.dart';
import 'package:tmail_ui_user/features/login/domain/model/recent_login_url.dart'; import 'package:tmail_ui_user/features/login/domain/model/recent_login_url.dart';
class LoginUrlDataSourceImpl implements LoginUrlDataSource { class LoginUrlDataSourceImpl implements LoginUrlDataSource {
@@ -25,4 +26,34 @@ class LoginUrlDataSourceImpl implements LoginUrlDataSource {
throw error; throw error;
}); });
} }
@override
Future<List<RecentLoginUrl>> getAllRecentLoginUrlLatest({int? limit, String? pattern}) {
return Future.sync(() async {
final listRecentUrlCache = await _recentLoginUrlCacheClient.getAll();
final listRecentUrl = listRecentUrlCache
.where((recentCache) => _filterRecentUrlCache(recentCache, pattern))
.map((recentCache) => recentCache.toRecentLoginUrl())
.toList();
listRecentUrl.sortByCreationDate();
final newLimit = limit ?? 5;
final newListRecentSUrl = listRecentUrl.length > newLimit
? listRecentUrl.sublist(0, newLimit)
: listRecentUrl;
return newListRecentSUrl;
}).catchError((error) {
throw error;
});
}
bool _filterRecentUrlCache(RecentLoginUrlCache recentLoginUrlCache, String? pattern) {
if (pattern == null || pattern.trim().isEmpty) {
return true;
} else {
return recentLoginUrlCache.matchUrl(pattern);
}
}
} }
@@ -31,8 +31,8 @@ extension RecentLoginUrlCacheExtension on RecentLoginUrlCache {
return RecentLoginUrl(url, creationDate); return RecentLoginUrl(url, creationDate);
} }
bool matchUrl(Uri pattern) { bool matchUrl(String pattern) {
return url.toString().toLowerCase().contains(pattern.toString().toLowerCase()); return url.toLowerCase().contains(pattern.toLowerCase());
} }
} }
@@ -11,4 +11,9 @@ class LoginUrlRepositoryImpl implements LoginUrlRepository {
Future<void> saveRecentLoginUrl(RecentLoginUrl recentLogin) { Future<void> saveRecentLoginUrl(RecentLoginUrl recentLogin) {
return loginInfoDataSource.saveLoginUrl(recentLogin); return loginInfoDataSource.saveLoginUrl(recentLogin);
} }
@override
Future<List<RecentLoginUrl>> getAllRecentLoginUrlLatest({int? limit, String? pattern}) {
return loginInfoDataSource.getAllRecentLoginUrlLatest(limit: limit, pattern: pattern);
}
} }
@@ -0,0 +1,12 @@
import 'package:jmap_dart_client/jmap/core/extensions/date_time_extension.dart';
import 'package:tmail_ui_user/features/login/domain/model/recent_login_url.dart';
extension ListRecentLoginUrlExtension on List<RecentLoginUrl> {
void sortByCreationDate() {
sort((recentUrl1, recentUrl2) {
return recentUrl1.creationDate.compareToSort(recentUrl2.creationDate, false);
});
}
}
@@ -2,4 +2,6 @@ import 'package:tmail_ui_user/features/login/domain/model/recent_login_url.dart'
abstract class LoginUrlRepository { abstract class LoginUrlRepository {
Future<void> saveRecentLoginUrl(RecentLoginUrl recentLoginUrl); Future<void> saveRecentLoginUrl(RecentLoginUrl recentLoginUrl);
Future<List<RecentLoginUrl>> getAllRecentLoginUrlLatest({int? limit, String? pattern});
} }
@@ -0,0 +1,22 @@
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:tmail_ui_user/features/login/domain/model/recent_login_url.dart';
class GetAllRecentLoginUrlLatestSuccess extends UIState {
final List<RecentLoginUrl> listRecentLoginUrl;
GetAllRecentLoginUrlLatestSuccess(this.listRecentLoginUrl);
@override
List<Object> get props => [listRecentLoginUrl];
}
class GetAllRecentLoginUrlLatestFailure extends FeatureFailure {
final dynamic exception;
GetAllRecentLoginUrlLatestFailure(this.exception);
@override
List<Object> get props => [exception];
}
@@ -0,0 +1,22 @@
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:dartz/dartz.dart';
import 'package:tmail_ui_user/features/login/domain/repository/login_url_repository.dart';
import 'package:tmail_ui_user/features/login/domain/state/get_all_recent_login_url_latest_state.dart';
class GetAllRecentLoginUrlOnMobileInteractor {
final LoginUrlRepository _loginUrlRepository;
GetAllRecentLoginUrlOnMobileInteractor(this._loginUrlRepository);
Future<Either<Failure, Success>> execute({int? limit, String? pattern}) async {
try{
final listRecentUrl = await _loginUrlRepository.getAllRecentLoginUrlLatest(
limit: limit,
pattern: pattern);
return Right(GetAllRecentLoginUrlLatestSuccess(listRecentUrl));
} catch(e) {
return Left(GetAllRecentLoginUrlLatestFailure(e));
}
}
}
@@ -28,6 +28,7 @@ import 'package:tmail_ui_user/features/login/domain/repository/login_url_reposit
import 'package:tmail_ui_user/features/login/domain/usecases/authenticate_oidc_on_browser_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/authenticate_oidc_on_browser_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/authentication_user_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/authentication_user_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/check_oidc_is_available_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/check_oidc_is_available_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/get_all_recent_login_url_on_mobile_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/get_authentication_info_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/get_authentication_info_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/get_oidc_configuration_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/get_oidc_configuration_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/get_stored_oidc_configuration_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/get_stored_oidc_configuration_interactor.dart';
@@ -52,6 +53,7 @@ class LoginBindings extends BaseBindings {
Get.find<GetAuthenticationInfoInteractor>(), Get.find<GetAuthenticationInfoInteractor>(),
Get.find<GetStoredOidcConfigurationInteractor>(), Get.find<GetStoredOidcConfigurationInteractor>(),
Get.find<SaveLoginUrlOnMobileInteractor>(), Get.find<SaveLoginUrlOnMobileInteractor>(),
Get.find<GetAllRecentLoginUrlOnMobileInteractor>(),
)); ));
} }
@@ -110,6 +112,7 @@ class LoginBindings extends BaseBindings {
Get.lazyPut(() => SaveLoginUrlOnMobileInteractor( Get.lazyPut(() => SaveLoginUrlOnMobileInteractor(
Get.find<LoginUrlRepository>(), Get.find<LoginUrlRepository>(),
)); ));
Get.lazyPut(() => GetAllRecentLoginUrlOnMobileInteractor(Get.find<LoginUrlRepository>()));
} }
@override @override
@@ -10,6 +10,7 @@ import 'package:tmail_ui_user/features/login/domain/model/recent_login_url.dart'
import 'package:tmail_ui_user/features/login/domain/state/authenticate_oidc_on_browser_state.dart'; import 'package:tmail_ui_user/features/login/domain/state/authenticate_oidc_on_browser_state.dart';
import 'package:tmail_ui_user/features/login/domain/state/authentication_user_state.dart'; import 'package:tmail_ui_user/features/login/domain/state/authentication_user_state.dart';
import 'package:tmail_ui_user/features/login/domain/state/check_oidc_is_available_state.dart'; import 'package:tmail_ui_user/features/login/domain/state/check_oidc_is_available_state.dart';
import 'package:tmail_ui_user/features/login/domain/state/get_all_recent_login_url_latest_state.dart';
import 'package:tmail_ui_user/features/login/domain/state/get_authentication_info_state.dart'; import 'package:tmail_ui_user/features/login/domain/state/get_authentication_info_state.dart';
import 'package:tmail_ui_user/features/login/domain/state/get_oidc_configuration_state.dart'; import 'package:tmail_ui_user/features/login/domain/state/get_oidc_configuration_state.dart';
import 'package:tmail_ui_user/features/login/domain/state/get_stored_oidc_configuration_state.dart'; import 'package:tmail_ui_user/features/login/domain/state/get_stored_oidc_configuration_state.dart';
@@ -17,6 +18,7 @@ import 'package:tmail_ui_user/features/login/domain/state/get_token_oidc_state.d
import 'package:tmail_ui_user/features/login/domain/usecases/authenticate_oidc_on_browser_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/authenticate_oidc_on_browser_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/authentication_user_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/authentication_user_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/check_oidc_is_available_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/check_oidc_is_available_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/get_all_recent_login_url_on_mobile_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/get_authentication_info_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/get_authentication_info_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/get_oidc_configuration_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/get_oidc_configuration_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/get_stored_oidc_configuration_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/get_stored_oidc_configuration_interactor.dart';
@@ -43,6 +45,7 @@ class LoginController extends BaseController {
final GetAuthenticationInfoInteractor _getAuthenticationInfoInteractor; final GetAuthenticationInfoInteractor _getAuthenticationInfoInteractor;
final GetStoredOidcConfigurationInteractor _getStoredOidcConfigurationInteractor; final GetStoredOidcConfigurationInteractor _getStoredOidcConfigurationInteractor;
final SaveLoginUrlOnMobileInteractor _saveLoginUrlOnMobileInteractor; final SaveLoginUrlOnMobileInteractor _saveLoginUrlOnMobileInteractor;
final GetAllRecentLoginUrlOnMobileInteractor _getAllRecentLoginUrlOnMobileInteractor;
final TextEditingController urlInputController = TextEditingController(); final TextEditingController urlInputController = TextEditingController();
@@ -60,6 +63,7 @@ class LoginController extends BaseController {
this._getAuthenticationInfoInteractor, this._getAuthenticationInfoInteractor,
this._getStoredOidcConfigurationInteractor, this._getStoredOidcConfigurationInteractor,
this._saveLoginUrlOnMobileInteractor, this._saveLoginUrlOnMobileInteractor,
this._getAllRecentLoginUrlOnMobileInteractor,
); );
var loginState = LoginState(Right(LoginInitAction())).obs; var loginState = LoginState(Right(LoginInitAction())).obs;
@@ -340,6 +344,17 @@ class LoginController extends BaseController {
} }
} }
Future<List<RecentLoginUrl>> getAllRecentLoginUrlAction(String pattern) async {
return await _getAllRecentLoginUrlOnMobileInteractor
.execute(pattern: pattern)
.then((result) => result.fold(
(failure) => <RecentLoginUrl>[],
(success) => success is GetAllRecentLoginUrlLatestSuccess
? success.listRecentLoginUrl
: <RecentLoginUrl>[]
));
}
@override @override
void onClose() { void onClose() {
urlInputController.clear(); urlInputController.clear();
+56 -43
View File
@@ -1,7 +1,10 @@
import 'package:core/core.dart'; import 'package:core/core.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:tmail_ui_user/features/base/widget/recent_item_tile_widget.dart';
import 'package:tmail_ui_user/features/login/domain/model/recent_login_url.dart';
import 'package:tmail_ui_user/features/login/presentation/base_login_view.dart'; import 'package:tmail_ui_user/features/login/presentation/base_login_view.dart';
import 'package:tmail_ui_user/features/login/presentation/login_form_type.dart'; import 'package:tmail_ui_user/features/login/presentation/login_form_type.dart';
import 'package:tmail_ui_user/features/login/presentation/state/login_state.dart'; import 'package:tmail_ui_user/features/login/presentation/state/login_state.dart';
@@ -18,38 +21,37 @@ class LoginView extends BaseLoginView {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: AppColor.primaryLightColor, backgroundColor: AppColor.primaryLightColor,
body: Stack(children: [ body: GestureDetector(
GestureDetector( onTap: () => FocusScope.of(context).unfocus(),
onTap: () => FocusScope.of(context).unfocus(), child: Container(
color: Colors.white,
child: SafeArea( child: SafeArea(
child: _supportScrollForm(context) child: _supportScrollForm(context)
? Stack( ? Stack(children: [
children: [ Center(child: SingleChildScrollView(
Center(child: SingleChildScrollView(child: _buildCenterForm(context), scrollDirection: Axis.vertical)), child: _buildCenterForm(context),
Obx(() { scrollDirection: Axis.vertical)),
if (loginController.loginFormType.value == LoginFormType.credentialForm Obx(() {
|| loginController.loginFormType.value == LoginFormType.ssoForm) { if (loginController.loginFormType.value == LoginFormType.credentialForm
return _buildBackButton(context); || loginController.loginFormType.value == LoginFormType.ssoForm) {
} return _buildBackButton(context);
return const SizedBox.shrink(); }
}) return const SizedBox.shrink();
] })
) ])
: Stack( : Stack(children: [
children: [ _buildCenterForm(context),
_buildCenterForm(context), Obx(() {
Obx(() { if (loginController.loginFormType.value == LoginFormType.credentialForm
if (loginController.loginFormType.value == LoginFormType.credentialForm || loginController.loginFormType.value == LoginFormType.ssoForm) {
|| loginController.loginFormType.value == LoginFormType.ssoForm) { return _buildBackButton(context);
return _buildBackButton(context); }
} return const SizedBox.shrink();
return const SizedBox.shrink(); })
}) ]),
]
),
), ),
), ),
])); ));
} }
Widget _buildCenterForm(BuildContext context) { Widget _buildCenterForm(BuildContext context) {
@@ -130,21 +132,32 @@ class LoginView extends BaseLoginView {
Widget _buildUrlInput(BuildContext context) { Widget _buildUrlInput(BuildContext context) {
return Padding( return Padding(
padding: const EdgeInsets.only(right: 24, left: 24, bottom: 24), padding: const EdgeInsets.only(right: 24, left: 24, bottom: 24),
child: Container( child: TypeAheadFormField<RecentLoginUrl>(
child: (TextFieldBuilder() textFieldConfiguration: TextFieldConfiguration(
..key(const Key('login_url_input')) controller: loginController.urlInputController,
..onChange((value) => loginController.formatUrl(value)) textInputAction: TextInputAction.next,
..textInputAction(TextInputAction.next) keyboardType: TextInputType.url,
..addController(loginController.urlInputController) onSubmitted: (value) => controller.handleNextInUrlInputFormPress(),
..keyboardType(TextInputType.url) decoration: (LoginInputDecorationBuilder()
..onSubmitted((value) { ..setLabelText(AppLocalizations.of(context).prefix_https)
controller.handleNextInUrlInputFormPress(); ..setPrefixText(AppLocalizations.of(context).prefix_https))
}) .build()
..textDecoration((LoginInputDecorationBuilder() ),
..setLabelText(AppLocalizations.of(context).prefix_https) debounceDuration: const Duration(milliseconds: 300),
..setPrefixText(AppLocalizations.of(context).prefix_https)) suggestionsCallback: (pattern) async {
.build())) loginController.formatUrl(pattern);
.build())); return loginController.getAllRecentLoginUrlAction(pattern);
},
itemBuilder: (context, loginUrl) =>
RecentItemTileWidget(loginUrl, imagePath: imagePaths),
onSuggestionSelected: (loginUrl) => controller.formatUrl(loginUrl.url),
suggestionsBoxDecoration: const SuggestionsBoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(14))),
noItemsFoundBuilder: (context) => const SizedBox(),
hideOnEmpty: true,
hideOnError: true,
hideOnLoading: true,
));
} }
Widget _buildExpandedButton(BuildContext context, Widget child) { Widget _buildExpandedButton(BuildContext context, Widget child) {
@@ -15,16 +15,12 @@ class LoginView extends BaseLoginView {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: AppColor.primaryLightColor, backgroundColor: AppColor.primaryLightColor,
body: Stack(children: [ body: Center(child: SingleChildScrollView(
SafeArea( child: ResponsiveWidget(
child: Center(child: SingleChildScrollView( responsiveUtils: responsiveUtils,
child: ResponsiveWidget( mobile: _buildMobileForm(context),
responsiveUtils: responsiveUtils, desktop: _buildWebForm(context),
mobile: _buildMobileForm(context), ))),
desktop: _buildWebForm(context),
)))
),
]),
); );
} }
@@ -8,6 +8,7 @@ import 'package:tmail_ui_user/features/caching/caching_manager.dart';
import 'package:tmail_ui_user/features/caching/email_cache_client.dart'; import 'package:tmail_ui_user/features/caching/email_cache_client.dart';
import 'package:tmail_ui_user/features/caching/encryption_key_cache_client.dart'; import 'package:tmail_ui_user/features/caching/encryption_key_cache_client.dart';
import 'package:tmail_ui_user/features/caching/mailbox_cache_client.dart'; import 'package:tmail_ui_user/features/caching/mailbox_cache_client.dart';
import 'package:tmail_ui_user/features/caching/recent_login_url_cache_client.dart';
import 'package:tmail_ui_user/features/caching/recent_search_cache_client.dart'; import 'package:tmail_ui_user/features/caching/recent_search_cache_client.dart';
import 'package:tmail_ui_user/features/caching/state_cache_client.dart'; import 'package:tmail_ui_user/features/caching/state_cache_client.dart';
import 'package:tmail_ui_user/features/caching/token_oidc_cache_client.dart'; import 'package:tmail_ui_user/features/caching/token_oidc_cache_client.dart';
@@ -51,6 +52,7 @@ class LocalBindings extends Bindings {
Get.put(AuthenticationInfoCacheManager(Get.find<AuthenticationInfoCacheClient>())); Get.put(AuthenticationInfoCacheManager(Get.find<AuthenticationInfoCacheClient>()));
Get.put(OidcConfigurationCacheManager(Get.find<SharedPreferences>())); Get.put(OidcConfigurationCacheManager(Get.find<SharedPreferences>()));
Get.put(LanguageCacheManager(Get.find<SharedPreferences>())); Get.put(LanguageCacheManager(Get.find<SharedPreferences>()));
Get.put(RecentLoginUrlCacheClient());
Get.put(CachingManager( Get.put(CachingManager(
Get.find<MailboxCacheClient>(), Get.find<MailboxCacheClient>(),
Get.find<StateCacheClient>(), Get.find<StateCacheClient>(),