diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 905aac9a..ec4d67a1 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -10,7 +10,7 @@
+
+
+
+
+
+
diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements
index 80b5221d..e815340b 100644
--- a/ios/Runner/Runner.entitlements
+++ b/ios/Runner/Runner.entitlements
@@ -8,5 +8,9 @@
Default
+ com.apple.developer.associated-domains
+
+ applinks:www.grimity.com
+
diff --git a/lib/app/config/app_analytics.dart b/lib/app/config/app_analytics.dart
new file mode 100644
index 00000000..1ab9bc41
--- /dev/null
+++ b/lib/app/config/app_analytics.dart
@@ -0,0 +1,6 @@
+import 'package:firebase_analytics/firebase_analytics.dart';
+
+abstract final class AppAnalytics {
+ static final FirebaseAnalytics _analytics = FirebaseAnalytics.instance;
+ static final FirebaseAnalyticsObserver observer = FirebaseAnalyticsObserver(analytics: _analytics);
+}
diff --git a/lib/app/config/app_router.dart b/lib/app/config/app_router.dart
index 10aa7d18..3c69752a 100644
--- a/lib/app/config/app_router.dart
+++ b/lib/app/config/app_router.dart
@@ -1,10 +1,11 @@
-import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
+import 'package:grimity/app/config/app_analytics.dart';
import 'package:grimity/app/enum/report.enum.dart';
import 'package:grimity/app/linking/external_link.dart';
import 'package:grimity/app/linking/external_link_parser.dart';
+import 'package:grimity/app/linking/initialize_app_provider.dart';
import 'package:grimity/domain/entity/album.dart';
import 'package:grimity/domain/entity/feed.dart';
import 'package:grimity/domain/entity/post.dart';
@@ -40,21 +41,22 @@ import 'package:grimity/presentation/sign_in/sign_in_page.dart';
import 'package:grimity/presentation/sign_up/sign_up_page.dart';
import 'package:grimity/presentation/splash/splash_page.dart';
import 'package:grimity/presentation/storage/storage_page.dart';
+import 'package:grimity/app/linking/pending_deep_link_provider.dart';
+import 'package:grimity/presentation/common/provider/user_auth_provider.dart';
+import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'app_router.g.dart';
final rootNavigatorKey = GlobalKey();
final shellNavigatorKey = GlobalKey();
-abstract final class AppRouter {
- static final FirebaseAnalytics _analytics = FirebaseAnalytics.instance;
- static final FirebaseAnalyticsObserver _observer = FirebaseAnalyticsObserver(analytics: _analytics);
-
- static final GoRouter _router = GoRouter(
+@riverpod
+GoRouter router(Ref ref) {
+ return GoRouter(
navigatorKey: rootNavigatorKey,
initialLocation: SplashRoute.path,
routes: $appRoutes,
- observers: [_observer],
+ observers: [AppAnalytics.observer],
// FIX: 카카오톡 App 로그인 시의 Routing 관련 문제 수정
// Ref: https://github.com/kakao/kakao_flutter_sdk/issues/200
redirect: (context, state) {
@@ -64,29 +66,47 @@ abstract final class AppRouter {
return SignInRoute.path;
}
+ final isWebLink = uri.scheme == 'http' || uri.scheme == 'https';
+
+ // 웹 링크(딥링크)인 경우 redirect 처리
+ if (isWebLink) {
+ final parsed = ExternalLinkParser.parse(uri.toString());
+ final isLogin = ref.read(userAuthProvider) != null;
+ final initializeApp = ref.read(initializeAppProvider);
+ final pendingDeepLinkNotifier = ref.read(pendingDeepLinkProvider.notifier);
+
+ /// [ColdStart]인 경우
+ if (initializeApp == false) {
+ // 유효하지 않은 경로는 딥링크 세팅 없이 스플래시 페이지 이동 처리
+ if (parsed.type == ExternalLinkType.unknown) {
+ return SplashRoute.path;
+ }
+
+ // 유효한 경로는 딥링크 세팅 후 스플래시 페이지 이동 처리
+ Future.microtask(() => pendingDeepLinkNotifier.setLink(parsed.location));
+ return SplashRoute.path;
+ }
+ /// [WarmStart]인 경우
+ else {
+ // 유효하지 않은 경로는 딥링크 세팅 없이 로그인 여부에 따라
+ if (parsed.type == ExternalLinkType.unknown) {
+ return isLogin ? HomeRoute.path : SignInRoute.path;
+ }
+
+ if (isLogin) {
+ // 로그인 된 사용자의 경우 딥링크 세팅 후 홈 페이지로 이동
+ pendingDeepLinkNotifier.setLink(parsed.location);
+ return HomeRoute.path;
+ } else {
+ // 로그인되지 않은 사용자의 경우 딥링크 세팅 없이 회원가입 페이지로 이동
+ return SignInRoute.path;
+ }
+ }
+ }
+
return null;
},
);
-
- static GoRouter router(WidgetRef ref) => _router;
-
- // URL을 내부 라우팅으로 이동
- static void handleServerUrl(BuildContext context, String url, {String? myUrl}) {
- final parsed = ExternalLinkParser.parse(url);
- switch (parsed.type) {
- case ExternalLinkType.profile:
- ProfileRoute(url: parsed.url!).push(context);
- break;
- case ExternalLinkType.post:
- context.push('/posts/${parsed.id}');
- break;
- case ExternalLinkType.feed:
- context.push('/feeds/${parsed.id}');
- break;
- case ExternalLinkType.unknown:
- break;
- }
- }
}
@TypedStatefulShellRoute(
@@ -250,6 +270,8 @@ class ProfileRoute extends GoRouteData {
static const String path = '/profile/:url';
static const String name = 'userProfile';
+ static String makePath(String url) => '/profile/$url';
+
@override
Widget build(BuildContext context, GoRouterState state) => ProfilePage(url: url);
}
@@ -415,6 +437,8 @@ class FeedDetailRoute extends GoRouteData {
static const String path = '/feeds/:id';
static const String name = 'feed-detail';
+ static String makePath(String id) => '/feeds/$id';
+
@override
Widget build(BuildContext context, GoRouterState state) {
return FeedDetailPage(feedId: id);
@@ -447,6 +471,8 @@ class PostDetailRoute extends GoRouteData {
static const String path = '/posts/:id';
static const String name = 'post-detail';
+ static String makePath(String id) => '/posts/$id';
+
@override
Widget build(BuildContext context, GoRouterState state) {
return PostDetailPage(postId: id);
diff --git a/lib/app/extension/string_extension.dart b/lib/app/extension/string_extension.dart
index 2f3e43c5..a6d6d73c 100644
--- a/lib/app/extension/string_extension.dart
+++ b/lib/app/extension/string_extension.dart
@@ -1,6 +1,7 @@
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/widgets.dart';
import 'package:grimity/app/environment/flavor.dart';
+import 'package:grimity/app/util/validator_util.dart';
extension StringExtension on String {
/// 리사이즈를 지원하는 이미지 크기(너비 기준) 목록.
@@ -58,4 +59,16 @@ extension StringExtension on String {
return Size(width.toDouble(), height.toDouble());
}
+
+ String? getUrlCheckMessage() {
+ if (!ValidatorUtil.isValidUrl(this)) {
+ return '숫자, 영문(소문자), 언더바(_)만 입력 가능합니다.';
+ }
+
+ if (ValidatorUtil.isForbiddenUrl(this)) {
+ return '사용할 수 없는 URL입니다.';
+ }
+
+ return null;
+ }
}
diff --git a/lib/app/linking/external_link.dart b/lib/app/linking/external_link.dart
index f3fc35e3..85fb1178 100644
--- a/lib/app/linking/external_link.dart
+++ b/lib/app/linking/external_link.dart
@@ -1,9 +1,27 @@
-enum ExternalLinkType { profile, post, feed, unknown }
+import 'package:grimity/app/config/app_router.dart';
+
+enum ExternalLinkType {
+ profile,
+ post,
+ feed,
+ unknown,
+}
class ExternalLink {
- const ExternalLink(this.type, {this.url, this.id});
+ const ExternalLink(
+ this.type, {
+ this.url,
+ this.id,
+ });
final ExternalLinkType type;
final String? url;
final String? id;
+
+ String get location => switch (type) {
+ ExternalLinkType.profile => ProfileRoute.makePath(url!),
+ ExternalLinkType.post => PostDetailRoute.makePath(id!),
+ ExternalLinkType.feed => FeedDetailRoute.makePath(id!),
+ ExternalLinkType.unknown => throw UnimplementedError('unknown은 location을 사용할 수 없습니다.'),
+ };
}
diff --git a/lib/app/linking/external_link_parser.dart b/lib/app/linking/external_link_parser.dart
index afd460a9..19381af3 100644
--- a/lib/app/linking/external_link_parser.dart
+++ b/lib/app/linking/external_link_parser.dart
@@ -1,4 +1,5 @@
import 'package:grimity/app/config/app_config.dart';
+import 'package:grimity/app/util/validator_util.dart';
import 'external_link.dart';
@@ -34,8 +35,7 @@ class ExternalLinkParser {
// /:userPath (예약어 충돌 방지)
if (segs.length == 1) {
final url = segs.first;
- const reserved = {'posts', 'feeds'};
- if (!reserved.contains(url)) {
+ if (!ValidatorUtil.forbiddenUrls.contains(url)) {
return ExternalLink(ExternalLinkType.profile, url: url);
}
}
diff --git a/lib/app/linking/initialize_app_provider.dart b/lib/app/linking/initialize_app_provider.dart
new file mode 100644
index 00000000..07542113
--- /dev/null
+++ b/lib/app/linking/initialize_app_provider.dart
@@ -0,0 +1,15 @@
+import 'package:riverpod_annotation/riverpod_annotation.dart';
+
+part 'initialize_app_provider.g.dart';
+
+/// 앱 초기화 여부를 확인하기 위한 Provider.
+/// 딥 링크로 앱이 켜졌을 때 ColdStart/WramStart를 구분하기 위해 사용합니다.
+@Riverpod(keepAlive: true)
+class InitializeApp extends _$InitializeApp {
+ @override
+ bool build() => false;
+
+ void set(bool value) {
+ state = value;
+ }
+}
diff --git a/lib/app/linking/pending_deep_link_provider.dart b/lib/app/linking/pending_deep_link_provider.dart
new file mode 100644
index 00000000..61c4a44b
--- /dev/null
+++ b/lib/app/linking/pending_deep_link_provider.dart
@@ -0,0 +1,19 @@
+import 'package:riverpod_annotation/riverpod_annotation.dart';
+
+part 'pending_deep_link_provider.g.dart';
+
+@Riverpod(keepAlive: true)
+class PendingDeepLink extends _$PendingDeepLink {
+ @override
+ String? build() => null;
+
+ void setLink(String link) {
+ state = link;
+ }
+
+ String? consume() {
+ final link = state;
+ state = null;
+ return link;
+ }
+}
diff --git a/lib/app/linking/url_handler.dart b/lib/app/linking/url_handler.dart
new file mode 100644
index 00000000..6358c9da
--- /dev/null
+++ b/lib/app/linking/url_handler.dart
@@ -0,0 +1,21 @@
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+import 'package:grimity/app/linking/external_link.dart';
+import 'package:grimity/app/linking/external_link_parser.dart';
+
+abstract class UrlHandler {
+ // URL을 내부 라우팅으로 이동
+ static void handleServerUrl(BuildContext context, String url) {
+ final parsed = ExternalLinkParser.parse(url);
+
+ switch (parsed.type) {
+ case ExternalLinkType.profile:
+ case ExternalLinkType.post:
+ case ExternalLinkType.feed:
+ context.push(parsed.location);
+ break;
+ case ExternalLinkType.unknown:
+ break;
+ }
+ }
+}
diff --git a/lib/app/util/validator_util.dart b/lib/app/util/validator_util.dart
index c260993f..a625578a 100644
--- a/lib/app/util/validator_util.dart
+++ b/lib/app/util/validator_util.dart
@@ -1,4 +1,47 @@
class ValidatorUtil {
+ static List forbiddenUrls = [
+ "popular",
+ "board",
+ "following",
+ "search",
+ "write",
+ "posts",
+ "feeds",
+ "mypage",
+ "ranking",
+ "direct",
+ "admin",
+ "home",
+ "sign-in",
+ "sign-up",
+ "splash",
+ "setting",
+ "notification",
+ "report",
+ "storage",
+ "follow",
+ "album-edit",
+ "profile-edit",
+ "crop-image",
+ "feed-upload",
+ "post-upload",
+ "photo-select",
+ "image-viewer",
+ "album-organize",
+ "blocked-users",
+ "chatMessage",
+ "newChat",
+ "boardSearch",
+ ];
+
+ static bool isAvailableUrl(String url) {
+ return isValidUrl(url) && !isForbiddenUrl(url);
+ }
+
+ static bool isForbiddenUrl(String url) {
+ return forbiddenUrls.contains(url);
+ }
+
static bool isValidUrl(String url) {
// 숫자, 영문(소문자), 언더바(_), 2 ~ 12자
return RegExp(r'^[a-z0-9_]{2,12}$').hasMatch(url);
diff --git a/lib/presentation/app.dart b/lib/presentation/app.dart
index 34c3c1df..8d9af5ce 100644
--- a/lib/presentation/app.dart
+++ b/lib/presentation/app.dart
@@ -5,6 +5,8 @@ import 'package:fluttertoast/fluttertoast.dart';
import 'package:grimity/app/config/app_router.dart';
import 'package:grimity/app/config/app_theme.dart';
import 'package:grimity/app/environment/flavor.dart';
+import 'package:grimity/app/linking/initialize_app_provider.dart';
+import 'package:grimity/app/linking/pending_deep_link_provider.dart';
import 'package:grimity/app/static/push_notification.dart';
import 'package:grimity/presentation/common/provider/user_auth_provider.dart';
import 'package:talker_flutter/talker_flutter.dart';
@@ -79,6 +81,21 @@ class _AppState extends ConsumerState with WidgetsBindingObserver {
@override
Widget build(BuildContext context) {
+ // [WarmStart] 딥링크 이벤트 처리
+ ref.listen(pendingDeepLinkProvider, (previous, next) {
+ // initializeApp 플래그를 통해 [ColdStart] 처리는 여기서 하지 않음
+ final initializeApp = ref.read(initializeAppProvider);
+
+ if (next != null && initializeApp == true) {
+ final link = ref.read(pendingDeepLinkProvider.notifier).consume();
+ if (link != null) {
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ ref.read(routerProvider).push(link);
+ });
+ }
+ }
+ });
+
return MaterialApp.router(
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
@@ -86,7 +103,7 @@ class _AppState extends ConsumerState with WidgetsBindingObserver {
GlobalWidgetsLocalizations.delegate,
FlutterQuillLocalizations.delegate,
],
- routerConfig: AppRouter.router(ref),
+ routerConfig: ref.watch(routerProvider),
theme: AppTheme.appTheme,
builder: routerBuilder,
);
diff --git a/lib/presentation/feed_detail/widget/feed_detail_delete_dialog.dart b/lib/presentation/feed_detail/widget/feed_detail_delete_dialog.dart
index 970fdb4f..d9cc4218 100644
--- a/lib/presentation/feed_detail/widget/feed_detail_delete_dialog.dart
+++ b/lib/presentation/feed_detail/widget/feed_detail_delete_dialog.dart
@@ -6,7 +6,7 @@ import 'package:grimity/presentation/common/widget/alert/grimity_dialog.dart';
import 'package:grimity/presentation/feed_detail/provider/feed_detail_data_provider.dart';
void showDeleteFeedDialog(String feedId, BuildContext context, WidgetRef ref) {
- final router = AppRouter.router(ref);
+ final router = ref.read(routerProvider);
showDialog(
context: context,
diff --git a/lib/presentation/notification/widget/notification_widget.dart b/lib/presentation/notification/widget/notification_widget.dart
index 2e950a0b..064768dd 100644
--- a/lib/presentation/notification/widget/notification_widget.dart
+++ b/lib/presentation/notification/widget/notification_widget.dart
@@ -2,11 +2,10 @@ import 'package:flutter/material.dart' hide Notification;
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:gap/gap.dart';
import 'package:grimity/app/config/app_color.dart';
-import 'package:grimity/app/config/app_router.dart';
import 'package:grimity/app/config/app_typeface.dart';
import 'package:grimity/app/extension/date_time_extension.dart';
+import 'package:grimity/app/linking/url_handler.dart';
import 'package:grimity/gen/assets.gen.dart';
-import 'package:grimity/presentation/common/provider/user_auth_provider.dart';
import 'package:grimity/presentation/common/widget/grimity_gesture.dart';
import 'package:grimity/presentation/common/widget/system/profile/grimity_user_profile.dart';
import 'package:grimity/presentation/notification/provider/notification_data_provider.dart';
@@ -23,12 +22,11 @@ class NotificationWidget extends ConsumerWidget {
return InkWell(
onTap: () {
- final myUrl = ref.read(userAuthProvider)?.url;
if (notification.isRead == false) {
notifier.markNotificationAsRead(notification.id);
}
- AppRouter.handleServerUrl(context, notification.link, myUrl: myUrl);
+ UrlHandler.handleServerUrl(context, notification.link);
},
child: Container(
padding: EdgeInsets.all(16),
diff --git a/lib/presentation/post_detail/widget/post_detail_delete_dialog.dart b/lib/presentation/post_detail/widget/post_detail_delete_dialog.dart
index 6014a0c2..537a9728 100644
--- a/lib/presentation/post_detail/widget/post_detail_delete_dialog.dart
+++ b/lib/presentation/post_detail/widget/post_detail_delete_dialog.dart
@@ -6,7 +6,7 @@ import 'package:grimity/presentation/common/widget/alert/grimity_dialog.dart';
import 'package:grimity/presentation/post_detail/provider/post_detail_data_provider.dart';
void showDeletePostDialog(String postId, BuildContext context, WidgetRef ref) {
- final router = AppRouter.router(ref);
+ final router = ref.read(routerProvider);
showDialog(
context: context,
diff --git a/lib/presentation/profile/profile_view.dart b/lib/presentation/profile/profile_view.dart
index dcd4bd4a..84ca4274 100644
--- a/lib/presentation/profile/profile_view.dart
+++ b/lib/presentation/profile/profile_view.dart
@@ -29,7 +29,8 @@ class ProfileView extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final nameOpacity = useState(0.0);
- final tabController = useTabController(initialLength: postTabView == null ? 1 : 2);
+ final tabLength = postTabView == null ? 1 : 2;
+ final tabController = useTabController(initialLength: tabLength, keys: [tabLength]);
return Scaffold(
endDrawer: MainAppDrawer(),
diff --git a/lib/presentation/profile_edit/provider/profile_edit_provider.dart b/lib/presentation/profile_edit/provider/profile_edit_provider.dart
index 4adc426a..12c10a3a 100644
--- a/lib/presentation/profile_edit/provider/profile_edit_provider.dart
+++ b/lib/presentation/profile_edit/provider/profile_edit_provider.dart
@@ -1,5 +1,6 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:grimity/app/enum/grimity.enum.dart';
+import 'package:grimity/app/extension/string_extension.dart';
import 'package:grimity/app/service/toast_service.dart';
import 'package:grimity/app/util/validator_util.dart';
import 'package:grimity/domain/dto/me_request_params.dart';
@@ -10,6 +11,7 @@ import 'package:grimity/presentation/common/provider/user_auth_provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'profile_edit_provider.freezed.dart';
+
part 'profile_edit_provider.g.dart';
/// 프로필 수정 상태를 관리하는 프로바이더
@@ -166,11 +168,11 @@ class ProfileEdit extends _$ProfileEdit {
/// URL 유효성 검증
Future checkUrlValidity() async {
- if (!ValidatorUtil.isValidUrl(state.url) || state.isUrlChecking) {
+ if (!ValidatorUtil.isAvailableUrl(state.url) || state.isUrlChecking) {
state = state.copyWith(
isUrlChecking: false,
urlState: GrimityTextFieldState.error,
- urlCheckMessage: '숫자, 영문(소문자), 언더바(_)만 입력 가능합니다.',
+ urlCheckMessage: state.url.getUrlCheckMessage(),
);
return;
}
@@ -178,13 +180,13 @@ class ProfileEdit extends _$ProfileEdit {
state = state.copyWith(isUrlChecking: true);
try {
- final bool isAvailable = ValidatorUtil.isValidUrl(state.url);
+ final bool isAvailable = ValidatorUtil.isAvailableUrl(state.url);
if (!isAvailable) {
state = state.copyWith(
isUrlChecking: false,
urlState: GrimityTextFieldState.error,
- urlCheckMessage: '숫자, 영문(소문자), 언더바(_)만 입력 가능합니다.',
+ urlCheckMessage: state.url.getUrlCheckMessage(),
);
return;
}
diff --git a/lib/presentation/profile_edit/widget/profile_edit_save_button.dart b/lib/presentation/profile_edit/widget/profile_edit_save_button.dart
index 0f84bd4e..c7b965b7 100644
--- a/lib/presentation/profile_edit/widget/profile_edit_save_button.dart
+++ b/lib/presentation/profile_edit/widget/profile_edit_save_button.dart
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
+import 'package:grimity/app/config/app_router.dart';
import 'package:grimity/presentation/common/widget/button/grimity_button.dart';
import 'package:grimity/presentation/profile/provider/profile_data_provider.dart';
import 'package:grimity/presentation/profile_edit/provider/profile_edit_provider.dart';
@@ -20,10 +21,23 @@ class ProfileEditSaveButton extends ConsumerWidget {
child: GrimityButton.large(
text: '변경 내용 저장',
onTap: () async {
+ final router = ref.read(routerProvider);
+
await ref.read(profileEditProvider.notifier).updateUser();
if (context.mounted && ref.read(profileEditProvider).isSaved == true) {
+ final newUrl = ref.read(profileEditProvider).url;
+ // ProfileEdit 페이지 Pop
+ if (router.canPop()) {
+ router.pop();
+ }
+
+ // 기존 Profile 페이지에서 사용하는 데이터 무효화
ref.invalidate(profileDataProvider);
- Navigator.of(context).pop();
+
+ // 변경된 URL 기준으로 프로필 페이지 pushReplacement
+ WidgetsBinding.instance.addPostFrameCallback(
+ (timeStamp) => router.pushReplacement(ProfileRoute.makePath(newUrl)),
+ );
}
},
),
diff --git a/lib/presentation/sign_in/provider/sign_in_provider.dart b/lib/presentation/sign_in/provider/sign_in_provider.dart
index ea8b537c..2b228383 100644
--- a/lib/presentation/sign_in/provider/sign_in_provider.dart
+++ b/lib/presentation/sign_in/provider/sign_in_provider.dart
@@ -29,7 +29,7 @@ class SignIn extends _$SignIn {
Future login(WidgetRef widgetRef, LoginProvider provider) async {
try {
// 비동기 통신 이후 widgetRef가 dispose 될 수 있어 라우터 참조
- final router = AppRouter.router(widgetRef);
+ final router = widgetRef.read(routerProvider);
// login 시도
await ref.read(userAuthProvider.notifier).login(provider);
diff --git a/lib/presentation/sign_up/provider/sign_up_provider.dart b/lib/presentation/sign_up/provider/sign_up_provider.dart
index b383b5a1..06a3195b 100644
--- a/lib/presentation/sign_up/provider/sign_up_provider.dart
+++ b/lib/presentation/sign_up/provider/sign_up_provider.dart
@@ -1,5 +1,6 @@
import 'package:grimity/app/base/result.dart';
import 'package:grimity/app/enum/grimity.enum.dart';
+import 'package:grimity/app/extension/string_extension.dart';
import 'package:grimity/app/util/device_info_util.dart';
import 'package:grimity/app/util/validator_util.dart';
import 'package:grimity/domain/dto/auth_request_params.dart';
@@ -65,18 +66,18 @@ class SignUp extends _$SignUp {
/// URL 유효성 검증
Future checkUrlValidity() async {
- if (!ValidatorUtil.isValidUrl(state.url) || state.isUrlChecking) return;
+ if (!ValidatorUtil.isAvailableUrl(state.url) || state.isUrlChecking) return;
state = state.copyWith(isUrlChecking: true);
try {
- final bool isAvailable = ValidatorUtil.isValidUrl(state.url);
+ final bool isAvailable = ValidatorUtil.isAvailableUrl(state.url);
if (!isAvailable) {
state = state.copyWith(
isUrlChecking: false,
urlState: GrimityTextFieldState.error,
- urlCheckMessage: '숫자, 영문(소문자), 언더바(_)만 입력 가능합니다.',
+ urlCheckMessage: state.url.getUrlCheckMessage(),
);
return;
}
@@ -115,7 +116,9 @@ class SignUp extends _$SignUp {
/// 유효성 검사
bool isInformationValid() {
- return ValidatorUtil.isValidNickname(state.nickname) && ValidatorUtil.isValidUrl(state.url) && state.isTermsAgreed;
+ return ValidatorUtil.isValidNickname(state.nickname) &&
+ ValidatorUtil.isAvailableUrl(state.url) &&
+ state.isTermsAgreed;
}
}
diff --git a/lib/presentation/sign_up/widget/sign_up_check_url_button.dart b/lib/presentation/sign_up/widget/sign_up_check_url_button.dart
index 110835ee..48241d88 100644
--- a/lib/presentation/sign_up/widget/sign_up_check_url_button.dart
+++ b/lib/presentation/sign_up/widget/sign_up_check_url_button.dart
@@ -14,7 +14,7 @@ class SignUpCheckUrlButton extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final isEnabled =
- ValidatorUtil.isValidUrl(ref.watch(signUpProvider).url) &&
+ ValidatorUtil.isAvailableUrl(ref.watch(signUpProvider).url) &&
ref.watch(signUpProvider).urlState != GrimityTextFieldState.error;
return Padding(
diff --git a/lib/presentation/splash/provider/splash_provider.dart b/lib/presentation/splash/provider/splash_provider.dart
index d55ffe19..d8d4f0dd 100644
--- a/lib/presentation/splash/provider/splash_provider.dart
+++ b/lib/presentation/splash/provider/splash_provider.dart
@@ -1,5 +1,7 @@
+import 'package:flutter/material.dart';
import 'package:grimity/app/config/app_router.dart';
import 'package:grimity/app/environment/flavor.dart';
+import 'package:grimity/app/linking/pending_deep_link_provider.dart';
import 'package:grimity/app/update/version.dart';
import 'package:grimity/domain/usecase/system_usecases.dart';
import 'package:grimity/presentation/common/provider/user_auth_provider.dart';
@@ -19,6 +21,7 @@ class Splash extends _$Splash {
Future checkUserAndRoute(WidgetRef ref) async {
// 앱 업데이트 필요 여부.
final needUpdate = await checkNeedUpdate();
+ final pendingLink = ref.read(pendingDeepLinkProvider.notifier).consume();
// 유저 정보 조회 시도
// 조회 실패 시 로그인 화면으로 이동
@@ -36,6 +39,14 @@ class Splash extends _$Splash {
// 유저 정보 로그인 시도 후 구독 여부 조회
ref.read(userSubscribeProvider.notifier).getSubscription();
HomeRoute().go(ref.context);
+
+ /// [ColdStart] 딥링크 처리
+ if (pendingLink != null) {
+ WidgetsBinding.instance.addPostFrameCallback(
+ (_) => ref.read(routerProvider).push(pendingLink),
+ );
+ }
+
return needUpdate;
}
diff --git a/lib/presentation/splash/splash_page.dart b/lib/presentation/splash/splash_page.dart
index df8a9066..55a28387 100644
--- a/lib/presentation/splash/splash_page.dart
+++ b/lib/presentation/splash/splash_page.dart
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:grimity/app/config/app_router.dart';
+import 'package:grimity/app/linking/initialize_app_provider.dart';
import 'package:grimity/presentation/app_update/show_app_update_dialog.dart';
import 'package:grimity/presentation/splash/provider/splash_provider.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -14,6 +15,9 @@ class SplashPage extends HookConsumerWidget {
useEffect(() {
ref.read(splashProvider.notifier).checkUserAndRoute(ref).then(
(needUpdate) {
+ // 앱 초기화 완료 여부 설정
+ ref.read(initializeAppProvider.notifier).set(true);
+
if (needUpdate) {
// 화면 이동 이후 UpdateDialog 표시.
WidgetsBinding.instance.addPostFrameCallback((_) {