generated from ttipo/project-base
feat(flutter): 로그인 전에 받은 앱 링크를 로그인 뒤 이어서 연다
QR 을 기기 카메라로 찍어 앱이 열렸는데 로그인 전이면 인트로·로그인으로 가며 링크가 사라졌다. 링크를 기억해 두었다가 진입(인트로·로그인·온보딩)을 마치고 홈으로 갈 때 대신 연다. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,7 @@ import 'package:tma_app/features/diary/presentation/pages/diary_compose_page.dar
|
||||
import 'package:tma_app/features/diary/presentation/pages/diary_page.dart';
|
||||
import 'package:tma_app/features/diary/presentation/pages/diary_settings_page.dart';
|
||||
import 'package:tma_app/features/diary/presentation/widgets/diary_join.dart';
|
||||
import 'package:tma_app/features/entry/domain/entry_state.dart';
|
||||
import 'package:tma_app/features/entry/presentation/providers/entry_provider.dart';
|
||||
import 'package:tma_app/features/home/presentation/pages/home_page.dart';
|
||||
import 'package:tma_app/features/intro/presentation/pages/intro_page.dart';
|
||||
@@ -63,14 +64,26 @@ final appRouterProvider = Provider<GoRouter>((ref) {
|
||||
// 탭바 위에 덮는 화면(등록 등)은 최상위 내비게이터에 올린다
|
||||
final rootKey = GlobalKey<NavigatorState>();
|
||||
|
||||
// 로그인 전에 받은 앱 링크 — 진입(인트로·로그인·온보딩)을 마치고 홈으로 갈 때 대신 연다.
|
||||
// 길 찾기 중에 바꾸므로 프로바이더 상태가 아니라 이 라우터만 쓰는 값으로 둔다
|
||||
String? pendingLink;
|
||||
|
||||
final router = GoRouter(
|
||||
navigatorKey: rootKey,
|
||||
initialLocation: AppRoutes.splash,
|
||||
refreshListenable: refresh,
|
||||
redirect: (context, state) => AppRoutes.redirectFor(
|
||||
ref.read(entryProvider).stage,
|
||||
state.matchedLocation,
|
||||
),
|
||||
redirect: (context, state) {
|
||||
final stage = ref.read(entryProvider).stage;
|
||||
final location = state.matchedLocation;
|
||||
final target = AppRoutes.redirectFor(stage, location);
|
||||
if (target != null && stage != EntryStage.app && isAppLinkPath(location)) pendingLink = location;
|
||||
if (stage == EntryStage.app && pendingLink != null && (target ?? location) == AppRoutes.home) {
|
||||
final link = pendingLink;
|
||||
pendingLink = null;
|
||||
return link;
|
||||
}
|
||||
return target;
|
||||
},
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: AppRoutes.splash,
|
||||
|
||||
@@ -153,6 +153,9 @@ class _LinkLandingPageState extends ConsumerState<LinkLandingPage> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 앱 링크의 경로인지 — 로그인 전에 받은 링크를 기억할 때 (라우터가 쓴다)
|
||||
bool isAppLinkPath(String path) => AppLinkKind.values.any((k) => path.startsWith(appLinkPattern(k).replaceAll(':code', '')));
|
||||
|
||||
/// 링크의 경로 → 무엇을 열지 (라우터가 쓴다)
|
||||
String appLinkPattern(AppLinkKind kind) => switch (kind) {
|
||||
AppLinkKind.petCard => '/$kCardLinkSegment/:code',
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:tma_app/app.dart';
|
||||
import 'package:tma_app/core/config/app_config.dart';
|
||||
import 'package:tma_app/core/config/app_links.dart';
|
||||
import 'package:tma_app/core/storage/preferences_provider.dart';
|
||||
import 'package:tma_app/features/auth/presentation/pages/login_page.dart';
|
||||
import 'package:tma_app/features/diary/domain/diary_invitation.dart';
|
||||
import 'package:tma_app/features/intro/presentation/pages/intro_page.dart';
|
||||
import 'package:tma_app/features/pet_card/data/received_card_repository.dart';
|
||||
import 'package:tma_app/features/pet_card/domain/card_style.dart';
|
||||
import 'package:tma_app/features/pet_card/domain/card_view_mode.dart';
|
||||
@@ -168,4 +176,41 @@ void main() {
|
||||
expect(find.byType(ShareCalendarPage), findsOneWidget);
|
||||
expect(find.text('2/4'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('로그인 전에 받은 링크는 기억했다가 로그인을 마치면 연다', (tester) async {
|
||||
tester.view.physicalSize = const Size(402, 874);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.reset);
|
||||
// 온보딩은 봤지만 로그인하지 않은 사람 — 인트로부터 시작한다
|
||||
SharedPreferences.setMockInitialValues({'entry.onboardingSeen': true});
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final cards = _CardRepo();
|
||||
await tester.pumpWidget(ProviderScope(
|
||||
overrides: [
|
||||
sharedPreferencesProvider.overrideWithValue(prefs),
|
||||
dataModeProvider.overrideWithValue(DataMode.local),
|
||||
receivedCardRepositoryProvider.overrideWithValue(cards),
|
||||
],
|
||||
child: const RupApp(),
|
||||
));
|
||||
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 300)));
|
||||
await tester.pump(const Duration(seconds: 4));
|
||||
await settle(tester);
|
||||
expect(find.byType(IntroPage), findsOneWidget);
|
||||
|
||||
// 기기 카메라로 찍은 명함 링크 — 로그인 전이라 인트로에 머문다
|
||||
await _openLink(tester, GoRouter.of(tester.element(find.byType(IntroPage))), petCardDeepLink('0428-117'));
|
||||
expect(find.byType(IntroPage), findsOneWidget);
|
||||
expect(cards.previewed, isEmpty);
|
||||
|
||||
await tester.tap(find.text('시작하기'));
|
||||
await settle(tester);
|
||||
expect(find.byType(LoginPage), findsOneWidget);
|
||||
await tester.tap(find.text('카카오로 3초만에 시작하기'));
|
||||
await settle(tester);
|
||||
await settle(tester);
|
||||
|
||||
expect(cards.previewed, ['0428-117']);
|
||||
expect(find.byType(ReceiveCardPage), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user