Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>

<!-- Deep linking for plans-->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- App Links (HTTPS) -->
<data android:scheme="https" android:host="we-buddhist-prod.us.auth0.com" android:pathPrefix="/plans/invite" />
<!-- Custom scheme -->
<data android:scheme="weBuddhist" android:host="plans" android:pathPrefix="/invite" />
</intent-filter>
</activity>
<activity
android:name="com.auth0.android.provider.RedirectActivity"
Expand Down
36 changes: 36 additions & 0 deletions docs/server_config/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Server-Side Configuration for Deep Linking

When you get control of `webuddhist.app`, you need to host these files to enable Universal Links (iOS) and App Links (Android).

## 1. Apple Universal Links (iOS)

**File:** `apple-app-site-association` (No extension!)

**Required Change:**
- Replace `<REPLACE_WITH_TEAM_ID>` with your Apple Team ID (found in Apple Developer Portal).

**Hosting:**
- Upload to: `https://webuddhist.app/.well-known/apple-app-site-association`
- Content-Type must be `application/json` (even without extension)

## 2. Android App Links

**File:** `assetlinks.json`

**Required Change:**
- Replace `<REPLACE_WITH_SHA256_FINGERPRINT>` with your release keystore SHA256 fingerprint.
- To get SHA256: `keytool -list -v -keystore my-release-key.keystore`

**Hosting:**
- Upload to: `https://webuddhist.app/.well-known/assetlinks.json`
- Content-Type must be `application/json`

## 3. Switch App Code

Once these files are hosted and verified:

1. Go to `lib/features/plans/services/plan_share_service.dart`
2. Change the scheme back to HTTPS:
```dart
return 'https://webuddhist.app/plans/invite?planId=$planId';
```
11 changes: 11 additions & 0 deletions docs/server_config/apple-app-site-association
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"applinks": {
"apps": [],
"details": [
{
"appID": "<REPLACE_WITH_TEAM_ID>.org.pecha.app",
"paths": [ "/plans/invite", "/plans/invite/*" ]
}
]
}
}
10 changes: 10 additions & 0 deletions docs/server_config/assetlinks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "org.pecha.app",
"sha256_cert_fingerprints": [
"<REPLACE_WITH_SHA256_FINGERPRINT>"
]
}
}]
40 changes: 23 additions & 17 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
PODS:
- app_links (6.4.1):
- Flutter
- audio_service (0.0.1):
- Flutter
- FlutterMacOS
Expand Down Expand Up @@ -57,6 +59,7 @@ PODS:
- FlutterMacOS

DEPENDENCIES:
- app_links (from `.symlinks/plugins/app_links/ios`)
- audio_service (from `.symlinks/plugins/audio_service/darwin`)
- audio_session (from `.symlinks/plugins/audio_session/ios`)
- auth0_flutter (from `.symlinks/plugins/auth0_flutter/darwin`)
Expand Down Expand Up @@ -84,6 +87,8 @@ SPEC REPOS:
- SimpleKeychain

EXTERNAL SOURCES:
app_links:
:path: ".symlinks/plugins/app_links/ios"
audio_service:
:path: ".symlinks/plugins/audio_service/darwin"
audio_session:
Expand Down Expand Up @@ -122,28 +127,29 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/video_player_avfoundation/darwin"

SPEC CHECKSUMS:
audio_service: cab6c1a0eaf01b5a35b567e11fa67d3cc1956910
audio_session: 19e9480dbdd4e5f6c4543826b2e8b0e4ab6145fe
app_links: 3dbc685f76b1693c66a6d9dd1e9ab6f73d97dc0a
audio_service: aa99a6ba2ae7565996015322b0bb024e1d25c6fd
audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0
Auth0: 2876d0c36857422eda9cb580a6cc896c7d14cb36
auth0_flutter: 0f4846524696ef8441bcb96ceab7bfdbe31b8a05
connectivity_plus: 2a701ffec2c0ae28a48cf7540e279787e77c447d
auth0_flutter: 031042ca6cf84f1b21611062b33ea7ec4bf3bc52
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4
flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f
flutter_timezone: ac3da59ac941ff1c98a2e1f0293420e020120282
image_gallery_saver_plus: 782ade975fe6a4600b53e7c1983e3a2979d1e9e5
just_audio: a42c63806f16995daf5b219ae1d679deb76e6a79
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb
flutter_timezone: 7c838e17ffd4645d261e87037e5bebf6d38fe544
image_gallery_saver_plus: e597bf65a7846979417a3eae0763b71b6dfec6c3
just_audio: 4e391f57b79cad2b0674030a00453ca5ce817eed
JWTDecode: 7dae24cb9bf9b608eae61e5081029ec169bb5527
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
SimpleKeychain: 768cf43ae778b1c21816e94dddf01bb8ee96a075
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa
video_player_avfoundation: 7993f492ae0bd77edaea24d9dc051d8bb2cd7c86
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a

PODFILE CHECKSUM: e30f02f9d1c72c47bb6344a0a748c9d268180865

Expand Down
5 changes: 5 additions & 0 deletions ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<key>CFBundleURLSchemes</key>
<array>
<string>org.pecha.app</string>
<string>weBuddhist</string>
</array>
</dict>
</array>
Expand Down Expand Up @@ -64,5 +65,9 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:webuddhist.app</string>
</array>
</dict>
</plist>
96 changes: 95 additions & 1 deletion lib/core/config/router/go_router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import 'package:flutter_pecha/features/notifications/presentation/notification_s
import 'package:flutter_pecha/features/plans/models/plans_model.dart';
import 'package:flutter_pecha/features/plans/presentation/plan_details.dart';
import 'package:flutter_pecha/features/plans/presentation/plan_info.dart';
import 'package:flutter_pecha/features/plans/presentation/plan_invite_handler.dart';
import 'package:flutter_pecha/features/prayer_of_the_day/presentation/prayer_of_the_day_screen.dart';
import 'package:flutter_pecha/features/story_view/presentation/widgets/image_story.dart';
import 'package:flutter_pecha/features/story_view/presentation/widgets/text_story.dart';
Expand Down Expand Up @@ -47,6 +48,7 @@ import 'package:story_view/story_view.dart';
import 'package:flutter_story_presenter/flutter_story_presenter.dart' as fsp;
import 'route_config.dart';
import 'package:flutter_pecha/features/onboarding/data/providers/onboarding_datasource_providers.dart';
import 'package:flutter_pecha/core/l10n/generated/app_localizations.dart';

final _logger = AppLogger('GoRouter');

Expand Down Expand Up @@ -624,6 +626,22 @@ final goRouterProvider = Provider<GoRouter>((ref) {
},
),
// all plan tab routes
GoRoute(
path: '/plans/invite',
builder: (context, state) {
// Handle deep link invitation to a plan
final planId = state.uri.queryParameters['planId'];

if (planId == null || planId.isEmpty) {
return const Scaffold(
body: Center(child: Text('Invalid plan invitation link')),
);
}

// Show handler that fetches plan data and navigates
return PlanInviteHandler(planId: planId);
},
),
GoRoute(
path: '/plans/info',
builder: (context, state) {
Expand All @@ -638,7 +656,7 @@ final goRouterProvider = Provider<GoRouter>((ref) {
}
return PlanInfo(
plan: extra['plan'] as PlansModel,
author: extra['author'] as AuthorDtoModel,
author: extra['author'] as AuthorDtoModel?,
);
},
),
Expand Down Expand Up @@ -770,6 +788,82 @@ final goRouterProvider = Provider<GoRouter>((ref) {
// 6. No redirect needed
return null;
},
errorBuilder: (context, state) {
_logger.warning('Route not found: ${state.uri}');
_logger.debug('Full path: ${state.fullPath}, Matched location: ${state.matchedLocation}, URI path: ${state.uri.path}, URI host: ${state.uri.host}');

// If this is a plan invite deep link that failed, try to redirect manually
if (state.uri.path.contains('invite') || (state.uri.host == 'plans' && state.uri.path.contains('invite'))) {
final planId = state.uri.queryParameters['planId'];
if (planId != null && planId.isNotEmpty) {
_logger.info('Attempting to redirect plan invite deep link manually');
WidgetsBinding.instance.addPostFrameCallback((_) {
try {
final router = GoRouter.of(context);
router.go('/plans/invite?planId=$planId');
} catch (e) {
_logger.error('Error redirecting plan invite', e);
}
});
// Return a loading widget while redirecting
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
}

// Don't modify providers during build - delay it
WidgetsBinding.instance.addPostFrameCallback((_) {
try {
final container = ProviderScope.containerOf(context);
container.read(bottomNavIndexProvider.notifier).state = 0;
} catch (e) {
_logger.error('Error resetting bottom nav index', e);
}
});

return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context)?.nav_home ?? 'Home'),
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.error_outline,
size: 64,
color: Colors.red,
),
const SizedBox(height: 16),
Text(
'Page not found',
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 8),
Text(
'The requested page could not be found.',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 24),
FilledButton(
onPressed: () {
// Reset bottom nav index to 0 (texts tab) and navigate home
final container = ProviderScope.containerOf(context);
container.read(bottomNavIndexProvider.notifier).state = 0;
context.go(RouteConfig.home);
},
child: Text(AppLocalizations.of(context)?.nav_home ?? 'Home'),
),
],
),
),
),
);
},
);
});

Expand Down
Loading