kasy-cli 1.20.0 → 1.21.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -3
- package/lib/commands/docs.js +0 -10
- package/lib/commands/ios.js +3 -2
- package/lib/commands/new.js +98 -58
- package/lib/commands/run.js +7 -0
- package/lib/scaffold/CHANGELOG.json +14 -0
- package/lib/scaffold/backends/api/patch/lib/core/data/api/meta_ads_api.dart +1 -1
- package/lib/scaffold/backends/api/patch/lib/core/data/api/storage_api.dart +1 -1
- package/lib/scaffold/backends/api/patch/lib/core/data/entities/user_entity.dart +1 -1
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +4 -5
- package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/feature_request_api.dart +1 -1
- package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/feature_vote_api.dart +1 -1
- package/lib/scaffold/backends/api/patch/lib/features/llm_chat/api/llm_chat_api.dart +1 -1
- package/lib/scaffold/backends/api/patch/lib/features/llm_chat/providers/llm_chat_notifier.dart +317 -0
- package/lib/scaffold/backends/api/patch/lib/features/notifications/api/device_api.dart +40 -1
- package/lib/scaffold/backends/api/patch/lib/features/notifications/api/entities/notifications_entity.dart +2 -0
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/api/entities/user_info_entity.dart +1 -1
- package/lib/scaffold/backends/api/patch/lib/features/subscription/api/entities/subscription_entity.dart +1 -1
- package/lib/scaffold/backends/api/patch/lib/features/subscription/shared/maybeshow_premium.dart +0 -2
- package/lib/scaffold/backends/api/pubspec.yaml.tpl +2 -0
- package/lib/scaffold/backends/firebase/enable-auth-via-cli.js +11 -8
- package/lib/scaffold/backends/firebase/setup-from-scratch.js +10 -8
- package/lib/scaffold/backends/supabase/deploy.js +56 -3
- package/lib/scaffold/backends/supabase/patch/lib/core/data/api/storage_api.dart +5 -11
- package/lib/scaffold/backends/supabase/patch/lib/core/data/entities/user_entity.dart +2 -2
- package/lib/scaffold/backends/supabase/patch/lib/features/notifications/api/device_api.dart +31 -1
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/api/entities/user_info_entity.dart +1 -1
- package/lib/scaffold/backends/supabase/patch/lib/features/subscription/api/entities/subscription_entity.dart +1 -1
- package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +2 -0
- package/lib/scaffold/catalog.js +2 -2
- package/lib/scaffold/generate.js +19 -3
- package/lib/scaffold/shared/generator-utils.js +265 -55
- package/lib/scaffold/shared/post-build.js +22 -6
- package/lib/utils/apple-release.js +1 -10
- package/lib/utils/browser.js +61 -0
- package/lib/utils/checks.js +189 -69
- package/lib/utils/env-tools.js +101 -0
- package/lib/utils/i18n/messages-en.js +13 -1
- package/lib/utils/i18n/messages-es.js +13 -1
- package/lib/utils/i18n/messages-pt.js +13 -1
- package/package.json +1 -1
- package/templates/firebase/lib/components/kasy_sidebar_pro.dart +8 -14
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +38 -128
- package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +6 -125
- package/templates/firebase/lib/core/states/user_state_notifier.dart +8 -10
- package/templates/firebase/lib/core/widgets/kasy_hover.dart +9 -1
- package/templates/firebase/lib/features/home/home_components_page.dart +8 -14
- package/templates/firebase/lib/features/home/home_page.dart +7 -8
- package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +1 -0
- package/templates/firebase/lib/router.dart +60 -0
- package/templates/firebase/test/core/bottom_menu/detail_route_menu_test.dart +57 -0
- package/lib/scaffold/backends/api/patch/lib/core/rating/widgets/review_popup.dart +0 -211
- package/lib/scaffold/backends/api/patch/lib/features/notifications/providers/models/notification.dart +0 -185
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +0 -73
- package/lib/scaffold/backends/api/patch/lib/main.dart +0 -275
- package/lib/scaffold/backends/api/patch/lib/router.dart +0 -133
- package/lib/scaffold/backends/supabase/patch/lib/core/rating/widgets/review_popup.dart +0 -211
- package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/ui/component/add_feature_form.dart +0 -199
- package/lib/scaffold/backends/supabase/patch/lib/features/notifications/providers/models/notification.dart +0 -174
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +0 -73
- package/lib/scaffold/backends/supabase/patch/lib/main.dart +0 -307
- package/lib/scaffold/backends/supabase/patch/lib/router.dart +0 -133
|
@@ -99,12 +99,18 @@ class FirebaseDeviceApi implements DeviceApi {
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
final token = await _messaging.getToken();
|
|
102
|
+
if (token == null) {
|
|
103
|
+
throw ApiError(
|
|
104
|
+
code: 0,
|
|
105
|
+
message: 'FCM token is null — check Firebase setup and notification permissions',
|
|
106
|
+
);
|
|
107
|
+
}
|
|
102
108
|
final os = Platform.isAndroid
|
|
103
109
|
? OperatingSystem.android //
|
|
104
110
|
: OperatingSystem.ios;
|
|
105
111
|
return DeviceEntity(
|
|
106
112
|
installationId: installationId,
|
|
107
|
-
token: token
|
|
113
|
+
token: token,
|
|
108
114
|
operatingSystem: os,
|
|
109
115
|
creationDate: DateTime.now(),
|
|
110
116
|
lastUpdateDate: DateTime.now(),
|
|
@@ -289,6 +295,7 @@ class FirebaseDeviceApi implements DeviceApi {
|
|
|
289
295
|
}
|
|
290
296
|
|
|
291
297
|
Future<String?> getIpAddress() async {
|
|
298
|
+
if (kIsWeb) return null;
|
|
292
299
|
try {
|
|
293
300
|
// First, try to find a public IP in network interfaces
|
|
294
301
|
final interfaces = await io.NetworkInterface.list();
|
|
@@ -333,6 +340,29 @@ class FirebaseDeviceApi implements DeviceApi {
|
|
|
333
340
|
/// Returns a map with all device information
|
|
334
341
|
@override
|
|
335
342
|
Future<Map<String, String>> fetchDeviceProperties() async {
|
|
343
|
+
// On web there is no native device layer (no NetworkInterface, no Platform):
|
|
344
|
+
// return static values so the app works as a PWA without throwing at runtime.
|
|
345
|
+
if (kIsWeb) {
|
|
346
|
+
final webLocale = PlatformDispatcher.instance.locale.toLanguageTag().replaceAll('-', '_');
|
|
347
|
+
return {
|
|
348
|
+
'appLongVersion': '',
|
|
349
|
+
'osVersion': 'web',
|
|
350
|
+
'deviceModel': 'browser',
|
|
351
|
+
'deviceLocale': webLocale,
|
|
352
|
+
'timezone': '',
|
|
353
|
+
'carrier': '',
|
|
354
|
+
'screenWidth': '',
|
|
355
|
+
'screenHeight': '',
|
|
356
|
+
'screenDensity': '',
|
|
357
|
+
'cpuCores': '',
|
|
358
|
+
'storageSize': '',
|
|
359
|
+
'freeStorage': '',
|
|
360
|
+
'deviceTimezone': '',
|
|
361
|
+
'mobileAdvertiserId': '',
|
|
362
|
+
'anonymousFbId': '',
|
|
363
|
+
'clientIpAddress': '',
|
|
364
|
+
};
|
|
365
|
+
}
|
|
336
366
|
try {
|
|
337
367
|
final deviceInfo = DeviceInfoPlugin();
|
|
338
368
|
final packageInfo = await PackageInfo.fromPlatform();
|
|
@@ -18,7 +18,7 @@ sealed class UserInfoEntity with _$UserInfoEntity {
|
|
|
18
18
|
@JsonKey(name: 'user_id') required String userId,
|
|
19
19
|
@JsonKey(name: 'info_key') required String key,
|
|
20
20
|
@JsonKey(name: 'info_value') required String value,
|
|
21
|
-
}) =
|
|
21
|
+
}) = UserInfoEntityData;
|
|
22
22
|
|
|
23
23
|
factory UserInfoEntity.fromJson(Map<String, Object?> json) =>
|
|
24
24
|
_$UserInfoEntityFromJson(json);
|
|
@@ -21,7 +21,7 @@ sealed class SubscriptionEntity with _$SubscriptionEntity {
|
|
|
21
21
|
const factory SubscriptionEntity({
|
|
22
22
|
@JsonKey(includeIfNull: false) String? id,
|
|
23
23
|
@JsonKey(name: 'user_id') String? userId,
|
|
24
|
-
@JsonKey(name: 'offer_id')
|
|
24
|
+
@JsonKey(name: 'offer_id') String? offerId,
|
|
25
25
|
@JsonKey(name: 'sku_id') required String skuId,
|
|
26
26
|
@JsonKey(name: 'creation_date') DateTime? creationDate,
|
|
27
27
|
@JsonKey(name: 'last_update_date') DateTime? lastUpdateDate,
|
|
@@ -52,6 +52,7 @@ dependencies:
|
|
|
52
52
|
intl: ^0.20.2
|
|
53
53
|
jiffy: ^6.4.4
|
|
54
54
|
json_annotation: ^4.9.0
|
|
55
|
+
local_auth: ^3.0.1
|
|
55
56
|
logger: ^2.6.2
|
|
56
57
|
lucide_icons_flutter: ^3.1.10
|
|
57
58
|
mixpanel_flutter: ^2.5.0
|
|
@@ -73,6 +74,7 @@ dependencies:
|
|
|
73
74
|
universal_html: ^2.3.0
|
|
74
75
|
universal_io: ^2.3.1
|
|
75
76
|
url_launcher: ^6.3.2
|
|
77
|
+
web: ^1.1.1
|
|
76
78
|
|
|
77
79
|
dev_dependencies:
|
|
78
80
|
build_runner: ^2.11.1
|
package/lib/scaffold/catalog.js
CHANGED
|
@@ -50,12 +50,12 @@ const FEATURE_CATALOG = [
|
|
|
50
50
|
{ id: 'revenuecat', displayName: 'RevenueCat', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['saas', 'full'], enhances: 'subscription' },
|
|
51
51
|
// features
|
|
52
52
|
{ id: 'onboarding', displayName: 'Onboarding', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['starter', 'saas', 'content', 'full'] },
|
|
53
|
-
{ id: 'web', displayName: 'Web Support (PWA)', status: 'public', availableIn: ['firebase'
|
|
53
|
+
{ id: 'web', displayName: 'Web Support (PWA)', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['full'] },
|
|
54
54
|
{ id: 'widget', displayName: 'Home Widget', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['full'] },
|
|
55
55
|
{ id: 'llm_chat', displayName: 'AI Chat', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['content', 'full'] },
|
|
56
56
|
{ id: 'local_notifications', displayName: 'Local Reminders', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: [] },
|
|
57
57
|
// feedback (Firebase/Supabase only)
|
|
58
|
-
{ id: 'feedback', displayName: 'Feature Requests', status: 'public', availableIn: ['firebase', 'supabase'],
|
|
58
|
+
{ id: 'feedback', displayName: 'Feature Requests', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['saas', 'full'], tag: 'requiresDb' },
|
|
59
59
|
// ci/cd
|
|
60
60
|
{ id: 'ci', displayName: 'CI/CD', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['full'] },
|
|
61
61
|
];
|
package/lib/scaffold/generate.js
CHANGED
|
@@ -58,6 +58,7 @@ const {
|
|
|
58
58
|
removeFacebookSigninFromAuthPages,
|
|
59
59
|
removeAndroidFacebookMetadata,
|
|
60
60
|
writeNoOpAdminHomeWidgets,
|
|
61
|
+
patchLanguageSwitcherNoWidget,
|
|
61
62
|
writeNoOpFeatureRequestRepository,
|
|
62
63
|
writeNoOpSubscriptionStubs,
|
|
63
64
|
writeNoOpSentryUsages,
|
|
@@ -67,7 +68,7 @@ const {
|
|
|
67
68
|
removeDevelopmentTeam,
|
|
68
69
|
localizeReleaseDocs,
|
|
69
70
|
} = require('./shared/generator-utils');
|
|
70
|
-
const { pubGet, slangGenerate, buildRunner, dartFix, flutterfireConfigure, writeGoogleAuthOptions, writeGoogleIosUrlScheme, patchFirebaseServiceWorker } = require('./shared/post-build');
|
|
71
|
+
const { pubGet, slangGenerate, buildRunner, dartFix, flutterfireConfigure, writeGoogleAuthOptions, writeGoogleIosUrlScheme, patchFirebaseServiceWorker, deployFirestoreRules } = require('./shared/post-build');
|
|
71
72
|
const { FIREBASE_SOURCE_DIR } = require('./shared/backend-config');
|
|
72
73
|
|
|
73
74
|
/**
|
|
@@ -264,6 +265,9 @@ async function generateProject(targetDir, backend, options, hooks = {}) {
|
|
|
264
265
|
// (imports home_widget_mywidget_service which only exists when widget is selected)
|
|
265
266
|
if (!modules.includes('widget')) {
|
|
266
267
|
await writeNoOpAdminHomeWidgets(targetDir);
|
|
268
|
+
// The language switcher pushes locale changes to the home widget — strip
|
|
269
|
+
// that hook so it doesn't reference the (now absent) widget provider.
|
|
270
|
+
await patchLanguageSwitcherNoWidget(targetDir);
|
|
267
271
|
// Remove Android native widget files and unregister the receiver from the manifest
|
|
268
272
|
// so the widget does not appear in the Android widget picker when not selected
|
|
269
273
|
await removeAndroidWidgetArtifacts(targetDir, bundleId);
|
|
@@ -341,8 +345,10 @@ async function generateProject(targetDir, backend, options, hooks = {}) {
|
|
|
341
345
|
});
|
|
342
346
|
}
|
|
343
347
|
|
|
344
|
-
// iOS: register REVERSED_CLIENT_ID as a URL scheme in ios/Runner/Info.plist
|
|
345
|
-
|
|
348
|
+
// iOS: register REVERSED_CLIENT_ID as a URL scheme in ios/Runner/Info.plist.
|
|
349
|
+
// Skip for Supabase — REVERSED_CLIENT_ID isn't present until Google Sign-In is
|
|
350
|
+
// enabled later in new.js; that flow calls writeGoogleIosUrlSchemeFromClientId instead.
|
|
351
|
+
if (backend !== 'supabase' && !deferGoogleAuthPatches) {
|
|
346
352
|
const iosSchemeResult = await writeGoogleIosUrlScheme(targetDir);
|
|
347
353
|
steps.push({
|
|
348
354
|
name: 'google-ios-url-scheme',
|
|
@@ -367,6 +373,16 @@ async function generateProject(targetDir, backend, options, hooks = {}) {
|
|
|
367
373
|
// (the dead lib/firebase_options.dart we no longer ship).
|
|
368
374
|
await cleanFirebaseJsonKitRefs(targetDir);
|
|
369
375
|
|
|
376
|
+
// Deploy Firestore security rules so the app works immediately after
|
|
377
|
+
// `kasy new` without needing `kasy deploy` first. Fast (<30s), billing-free.
|
|
378
|
+
// Without this the project gets Firebase's default deny-all rules, causing
|
|
379
|
+
// every Firestore read to throw permission-denied and the app to log the user out.
|
|
380
|
+
if (firebaseProjectId) {
|
|
381
|
+
onProgress('firestore-rules');
|
|
382
|
+
const rulesResult = await deployFirestoreRules(targetDir, firebaseProjectId);
|
|
383
|
+
steps.push({ name: 'firestore-rules', ok: rulesResult.ok, detail: rulesResult.ok ? null : rulesResult.error });
|
|
384
|
+
}
|
|
385
|
+
|
|
370
386
|
// ── 3. Post-build específico do backend ────────────────────────────────────
|
|
371
387
|
if (postBuild) {
|
|
372
388
|
try {
|
|
@@ -318,12 +318,14 @@ async function writeRouter(projectDir, modules, packageName, defaultPaywall = 'b
|
|
|
318
318
|
const withOnboarding = modules.includes('onboarding');
|
|
319
319
|
const withRevenuecat = modules.includes('revenuecat');
|
|
320
320
|
const withAnalytics = modules.includes('analytics');
|
|
321
|
+
const withWidget = modules.includes('widget');
|
|
321
322
|
|
|
322
323
|
const fallback = withOnboarding ? '/onboarding' : '/signin';
|
|
323
324
|
|
|
324
325
|
const lines = [];
|
|
325
326
|
|
|
326
327
|
// ── Imports ────────────────────────────────────────────────────────────────
|
|
328
|
+
lines.push(`import 'package:flutter/foundation.dart';`);
|
|
327
329
|
lines.push(`import 'package:flutter/material.dart';`);
|
|
328
330
|
lines.push(`import 'package:flutter_riverpod/flutter_riverpod.dart';`);
|
|
329
331
|
lines.push(`import 'package:go_router/go_router.dart';`);
|
|
@@ -340,6 +342,12 @@ async function writeRouter(projectDir, modules, packageName, defaultPaywall = 'b
|
|
|
340
342
|
if (withFeedback) {
|
|
341
343
|
lines.push(`import 'package:${pkg}/features/feedbacks/ui/feedback_page.dart';`);
|
|
342
344
|
}
|
|
345
|
+
// Home showcase detail screens — always present (not module-gated).
|
|
346
|
+
lines.push(`import 'package:${pkg}/features/home/design_system_page.dart';`);
|
|
347
|
+
lines.push(`import 'package:${pkg}/features/home/home_components_page.dart';`);
|
|
348
|
+
lines.push(`import 'package:${pkg}/features/home/home_components_preview_page.dart';`);
|
|
349
|
+
lines.push(`import 'package:${pkg}/features/home/home_components_preview_registry.dart';`);
|
|
350
|
+
lines.push(`import 'package:${pkg}/features/home/home_features_page.dart';`);
|
|
343
351
|
if (withLlmChat) {
|
|
344
352
|
lines.push(`import 'package:${pkg}/features/llm_chat/llm_chat_page.dart';`);
|
|
345
353
|
}
|
|
@@ -349,10 +357,22 @@ async function writeRouter(projectDir, modules, packageName, defaultPaywall = 'b
|
|
|
349
357
|
if (withOnboarding) {
|
|
350
358
|
lines.push(`import 'package:${pkg}/features/onboarding/ui/onboarding_page.dart';`);
|
|
351
359
|
}
|
|
360
|
+
// Admin tools: send-push is always available; paywalls/home-widgets only
|
|
361
|
+
// when their module exists. The screens themselves are gated by kDebugMode.
|
|
362
|
+
// Kept in alphabetical order to satisfy directives_ordering.
|
|
363
|
+
if (withWidget) {
|
|
364
|
+
lines.push(`import 'package:${pkg}/features/settings/ui/components/admin/admin_home_widgets.dart';`);
|
|
365
|
+
}
|
|
366
|
+
if (withRevenuecat) {
|
|
367
|
+
lines.push(`import 'package:${pkg}/features/settings/ui/components/admin/admin_paywalls.dart';`);
|
|
368
|
+
}
|
|
369
|
+
lines.push(`import 'package:${pkg}/features/settings/ui/components/admin/admin_routes.dart';`);
|
|
370
|
+
lines.push(`import 'package:${pkg}/features/settings/ui/components/admin/send_push_notification_page.dart';`);
|
|
352
371
|
if (withRevenuecat) {
|
|
353
372
|
lines.push(`import 'package:${pkg}/features/subscription/ui/component/premium_page_factory.dart';`);
|
|
354
373
|
lines.push(`import 'package:${pkg}/features/subscription/ui/premium_page.dart';`);
|
|
355
374
|
}
|
|
375
|
+
lines.push(`import 'package:logger/logger.dart';`);
|
|
356
376
|
|
|
357
377
|
// ── Provider ──────────────────────────────────────────────────────────────
|
|
358
378
|
lines.push('');
|
|
@@ -374,7 +394,16 @@ async function writeRouter(projectDir, modules, packageName, defaultPaywall = 'b
|
|
|
374
394
|
lines.push(` return GoRouter(`);
|
|
375
395
|
lines.push(` initialLocation: '/',`);
|
|
376
396
|
lines.push(` navigatorKey: navigatorKey,`);
|
|
377
|
-
|
|
397
|
+
// Unknown routes (e.g. a stale deep link) redirect home instead of dead-ending
|
|
398
|
+
// on a 404. The /404 GoRoute below still serves explicit navigation to it.
|
|
399
|
+
lines.push(` onException: (context, state, router) {`);
|
|
400
|
+
lines.push(` Logger().w(`);
|
|
401
|
+
lines.push(` 'GoRouter caught unknown route → "\${state.uri}" '`);
|
|
402
|
+
lines.push(` '(matched: "\${state.matchedLocation}", error: \${state.error}). '`);
|
|
403
|
+
lines.push(` 'Redirecting to "/".',`);
|
|
404
|
+
lines.push(` );`);
|
|
405
|
+
lines.push(` router.go('/');`);
|
|
406
|
+
lines.push(` },`);
|
|
378
407
|
lines.push(` observers: [`);
|
|
379
408
|
if (withAnalytics) {
|
|
380
409
|
lines.push(` AnalyticsObserver(analyticsApi: MixpanelAnalyticsApi.instance()),`);
|
|
@@ -393,6 +422,42 @@ async function writeRouter(projectDir, modules, packageName, defaultPaywall = 'b
|
|
|
393
422
|
lines.push(` ),`);
|
|
394
423
|
lines.push(` ),`);
|
|
395
424
|
|
|
425
|
+
// Home showcase detail screens. TOP-LEVEL routes (siblings of '/', not children
|
|
426
|
+
// of the BottomMenu shell), so go_router renders them on the root navigator:
|
|
427
|
+
// full-screen, above the bottom bar, URL-addressable. Returning pops back to
|
|
428
|
+
// the tab with its menu intact.
|
|
429
|
+
lines.push(` GoRoute(`);
|
|
430
|
+
lines.push(` name: 'features',`);
|
|
431
|
+
lines.push(` path: '/features',`);
|
|
432
|
+
lines.push(` builder: (context, state) => const HomeFeaturesPage(),`);
|
|
433
|
+
lines.push(` ),`);
|
|
434
|
+
lines.push(` GoRoute(`);
|
|
435
|
+
lines.push(` name: 'design_system',`);
|
|
436
|
+
lines.push(` path: '/design-system',`);
|
|
437
|
+
lines.push(` builder: (context, state) => const DesignSystemPage(),`);
|
|
438
|
+
lines.push(` ),`);
|
|
439
|
+
lines.push(` GoRoute(`);
|
|
440
|
+
lines.push(` name: 'components',`);
|
|
441
|
+
lines.push(` path: '/components',`);
|
|
442
|
+
lines.push(` builder: (context, state) => const HomeComponentsPage(),`);
|
|
443
|
+
lines.push(` routes: [`);
|
|
444
|
+
lines.push(` GoRoute(`);
|
|
445
|
+
lines.push(` name: 'component_preview',`);
|
|
446
|
+
lines.push(` path: ':name',`);
|
|
447
|
+
lines.push(` builder: (context, state) {`);
|
|
448
|
+
lines.push(` final definition = getComponentPreviewDefinition(`);
|
|
449
|
+
lines.push(` state.pathParameters['name'] ?? '',`);
|
|
450
|
+
lines.push(` );`);
|
|
451
|
+
lines.push(` if (definition == null) return const PageNotFound();`);
|
|
452
|
+
lines.push(` return HomeComponentsPreviewPage(`);
|
|
453
|
+
lines.push(` title: definition.title,`);
|
|
454
|
+
lines.push(` variants: definition.variants,`);
|
|
455
|
+
lines.push(` );`);
|
|
456
|
+
lines.push(` },`);
|
|
457
|
+
lines.push(` ),`);
|
|
458
|
+
lines.push(` ],`);
|
|
459
|
+
lines.push(` ),`);
|
|
460
|
+
|
|
396
461
|
// Onboarding
|
|
397
462
|
if (withOnboarding) {
|
|
398
463
|
lines.push(` GoRoute(`);
|
|
@@ -458,6 +523,64 @@ async function writeRouter(projectDir, modules, packageName, defaultPaywall = 'b
|
|
|
458
523
|
lines.push(` ),`);
|
|
459
524
|
}
|
|
460
525
|
|
|
526
|
+
// Notifications + Settings: open the BottomMenu shell on the right tab.
|
|
527
|
+
// These also back the deep links from the settings/admin UI.
|
|
528
|
+
lines.push(` GoRoute(`);
|
|
529
|
+
lines.push(` name: 'notifications',`);
|
|
530
|
+
lines.push(` path: '/notifications',`);
|
|
531
|
+
lines.push(` builder: (context, state) => const UserInfosGuard(`);
|
|
532
|
+
lines.push(` fallbackRoute: '${fallback}',`);
|
|
533
|
+
lines.push(` child: BottomMenu(initialRoute: 'notifications'),`);
|
|
534
|
+
lines.push(` ),`);
|
|
535
|
+
lines.push(` ),`);
|
|
536
|
+
lines.push(` GoRoute(`);
|
|
537
|
+
lines.push(` name: 'settings',`);
|
|
538
|
+
lines.push(` path: '/settings',`);
|
|
539
|
+
lines.push(` builder: (context, state) => const UserInfosGuard(`);
|
|
540
|
+
lines.push(` fallbackRoute: '${fallback}',`);
|
|
541
|
+
lines.push(` child: BottomMenu(initialRoute: 'settings'),`);
|
|
542
|
+
lines.push(` ),`);
|
|
543
|
+
lines.push(` ),`);
|
|
544
|
+
|
|
545
|
+
// Send push notification (admin tool — always routable; the entry point in
|
|
546
|
+
// the settings admin sheet is itself debug-gated).
|
|
547
|
+
lines.push(` GoRoute(`);
|
|
548
|
+
lines.push(` name: 'send_push',`);
|
|
549
|
+
lines.push(` path: adminRouteSendPush,`);
|
|
550
|
+
lines.push(` builder: (context, state) => const SendPushNotificationPage(),`);
|
|
551
|
+
lines.push(` ),`);
|
|
552
|
+
|
|
553
|
+
// Debug-only admin screens (paywall preview / home widgets preview).
|
|
554
|
+
if (withRevenuecat || withWidget) {
|
|
555
|
+
lines.push(` if (kDebugMode) ...[`);
|
|
556
|
+
if (withRevenuecat) {
|
|
557
|
+
lines.push(` GoRoute(`);
|
|
558
|
+
lines.push(` name: 'admin_paywalls',`);
|
|
559
|
+
lines.push(` path: adminRoutePaywalls,`);
|
|
560
|
+
lines.push(` builder: (context, state) => const AdminPaywalls(),`);
|
|
561
|
+
lines.push(` ),`);
|
|
562
|
+
}
|
|
563
|
+
if (withWidget) {
|
|
564
|
+
lines.push(` GoRoute(`);
|
|
565
|
+
lines.push(` name: 'admin_home_widgets',`);
|
|
566
|
+
lines.push(` path: adminRouteHomeWidgets,`);
|
|
567
|
+
lines.push(` builder: (context, state) => const AdminHomeWidgets(),`);
|
|
568
|
+
lines.push(` ),`);
|
|
569
|
+
}
|
|
570
|
+
if (withRevenuecat) {
|
|
571
|
+
lines.push(` GoRoute(`);
|
|
572
|
+
lines.push(` name: 'admin_premium_preview',`);
|
|
573
|
+
lines.push(` path: '/admin/premium/:variant',`);
|
|
574
|
+
lines.push(` builder: (context, state) {`);
|
|
575
|
+
lines.push(` final paywall = paywallFactoryFromAdminRoute(state.pathParameters['variant']);`);
|
|
576
|
+
lines.push(` if (paywall == null) return const PageNotFound();`);
|
|
577
|
+
lines.push(` return PremiumPage(paywall: paywall);`);
|
|
578
|
+
lines.push(` },`);
|
|
579
|
+
lines.push(` ),`);
|
|
580
|
+
}
|
|
581
|
+
lines.push(` ],`);
|
|
582
|
+
}
|
|
583
|
+
|
|
461
584
|
// Recover password + 404 (always)
|
|
462
585
|
lines.push(` GoRoute(`);
|
|
463
586
|
lines.push(` name: 'recover_password',`);
|
|
@@ -750,19 +873,28 @@ async function writeMainDart(projectDir, backend, modules, packageName, options
|
|
|
750
873
|
const withWidget = modules.includes('widget');
|
|
751
874
|
const withRevenuecat = modules.includes('revenuecat');
|
|
752
875
|
const withSupabase = backend === 'supabase';
|
|
753
|
-
|
|
876
|
+
// Firebase powers push notifications (FCM) and remote config in EVERY backend
|
|
877
|
+
// (Supabase additionally uses it for Google Sign-In). Every backend runs
|
|
878
|
+
// `flutterfire configure` during generation (see generate.js), so
|
|
879
|
+
// firebase_options_dev.dart and the google-services files always exist, which
|
|
880
|
+
// makes Firebase.initializeApp safe to call in all of them.
|
|
881
|
+
const withFirebase = true;
|
|
754
882
|
|
|
755
883
|
const lines = [];
|
|
756
884
|
|
|
757
885
|
// ── Imports ────────────────────────────────────────────────────────────────
|
|
886
|
+
lines.push(`import 'package:device_preview/device_preview.dart';`);
|
|
758
887
|
if (withFirebase) {
|
|
759
888
|
lines.push(`import 'package:firebase_core/firebase_core.dart';`);
|
|
889
|
+
lines.push(`import 'package:firebase_messaging/firebase_messaging.dart';`);
|
|
760
890
|
}
|
|
761
891
|
lines.push(`import 'package:flutter/foundation.dart';`);
|
|
762
892
|
lines.push(`import 'package:flutter/material.dart';`);
|
|
763
893
|
lines.push(`import 'package:flutter/services.dart';`);
|
|
764
894
|
lines.push(`import 'package:flutter_localizations/flutter_localizations.dart';`);
|
|
895
|
+
lines.push(`import 'package:flutter_native_splash/flutter_native_splash.dart';`);
|
|
765
896
|
lines.push(`import 'package:flutter_riverpod/flutter_riverpod.dart';`);
|
|
897
|
+
lines.push(`import 'package:jiffy/jiffy.dart';`);
|
|
766
898
|
lines.push(`import 'package:logger/logger.dart';`);
|
|
767
899
|
lines.push(`import 'package:${pkg}/core/config/app_env.dart';`);
|
|
768
900
|
lines.push(`import 'package:${pkg}/core/data/api/analytics_api.dart';`);
|
|
@@ -772,9 +904,12 @@ async function writeMainDart(projectDir, backend, modules, packageName, options
|
|
|
772
904
|
lines.push(`import 'package:${pkg}/core/home_widgets/home_widget_service.dart';`);
|
|
773
905
|
}
|
|
774
906
|
lines.push(`import 'package:${pkg}/core/initializer/onstart_widget.dart';`);
|
|
907
|
+
lines.push(`import 'package:${pkg}/core/keyboard_fix/keyboard_flicker_fix.dart';`);
|
|
775
908
|
lines.push(`import 'package:${pkg}/core/shared_preferences/shared_preferences.dart';`);
|
|
776
909
|
lines.push(`import 'package:${pkg}/core/states/user_state_notifier.dart';`);
|
|
910
|
+
lines.push(`import 'package:${pkg}/core/dev_inspector/dev_inspector.dart';`);
|
|
777
911
|
lines.push(`import 'package:${pkg}/core/theme/theme.dart';`);
|
|
912
|
+
lines.push(`import 'package:${pkg}/core/web_device_preview/web_device_preview.dart';`);
|
|
778
913
|
lines.push(`import 'package:${pkg}/environnements.dart';`);
|
|
779
914
|
if (withFirebase) {
|
|
780
915
|
lines.push(`import 'package:${pkg}/firebase_options_dev.dart' as firebase_dev;`);
|
|
@@ -782,6 +917,9 @@ async function writeMainDart(projectDir, backend, modules, packageName, options
|
|
|
782
917
|
lines.push(`import 'package:${pkg}/i18n/translations.g.dart';`);
|
|
783
918
|
lines.push(`import 'package:${pkg}/features/authentication/api/authentication_api.dart';`);
|
|
784
919
|
lines.push(`import 'package:${pkg}/features/notifications/api/local_notifier.dart';`);
|
|
920
|
+
if (withFirebase) {
|
|
921
|
+
lines.push(`import 'package:${pkg}/features/notifications/repositories/background_notification_handler.dart';`);
|
|
922
|
+
}
|
|
785
923
|
lines.push(`import 'package:${pkg}/features/notifications/repositories/notifications_repository.dart';`);
|
|
786
924
|
if (withRevenuecat) {
|
|
787
925
|
lines.push(`import 'package:${pkg}/features/subscription/repositories/subscription_repository.dart';`);
|
|
@@ -798,7 +936,8 @@ async function writeMainDart(projectDir, backend, modules, packageName, options
|
|
|
798
936
|
|
|
799
937
|
// ── main() ────────────────────────────────────────────────────────────────
|
|
800
938
|
lines.push(`void main() async {`);
|
|
801
|
-
lines.push(`
|
|
939
|
+
lines.push(` final widgetsBinding = KasyBinding.ensureInitialized();`);
|
|
940
|
+
lines.push(` FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);`);
|
|
802
941
|
lines.push(` await AppEnv.load();`);
|
|
803
942
|
lines.push(` final env = Environment.fromEnv();`);
|
|
804
943
|
lines.push(` final logger = Logger();`);
|
|
@@ -853,6 +992,13 @@ async function writeMainDart(projectDir, backend, modules, packageName, options
|
|
|
853
992
|
lines.push(` ),`);
|
|
854
993
|
lines.push(` };`);
|
|
855
994
|
lines.push('');
|
|
995
|
+
lines.push(` // Jiffy locale must be set AFTER Firebase init because Firebase resets`);
|
|
996
|
+
lines.push(` // Intl.defaultLocale internally, which would override our setting.`);
|
|
997
|
+
lines.push(` await Jiffy.setLocale(LocaleSettings.instance.currentLocale.languageCode);`);
|
|
998
|
+
lines.push('');
|
|
999
|
+
lines.push(` // MUST be registered at the top-level BEFORE runApp().`);
|
|
1000
|
+
lines.push(` FirebaseMessaging.onBackgroundMessage(onBackgroundMessage);`);
|
|
1001
|
+
lines.push('');
|
|
856
1002
|
}
|
|
857
1003
|
|
|
858
1004
|
if (withSentry) {
|
|
@@ -895,73 +1041,102 @@ async function writeMainDart(projectDir, backend, modules, packageName, options
|
|
|
895
1041
|
lines.push(`// mode: ThemeMode.dark,`);
|
|
896
1042
|
lines.push(`// ),`);
|
|
897
1043
|
lines.push(`// See ./docs/theme.md for more details`);
|
|
898
|
-
lines.push(`class MyApp extends
|
|
1044
|
+
lines.push(`class MyApp extends ConsumerStatefulWidget {`);
|
|
899
1045
|
lines.push(` final SharedPreferences sharedPreferences;`);
|
|
900
1046
|
lines.push('');
|
|
901
1047
|
lines.push(` const MyApp({super.key, required this.sharedPreferences});`);
|
|
902
1048
|
lines.push('');
|
|
1049
|
+
lines.push(` @override`);
|
|
1050
|
+
lines.push(` ConsumerState<MyApp> createState() => _MyAppState();`);
|
|
1051
|
+
lines.push(`}`);
|
|
1052
|
+
lines.push('');
|
|
1053
|
+
lines.push(`class _MyAppState extends ConsumerState<MyApp> {`);
|
|
1054
|
+
lines.push(` late final AppTheme _appTheme;`);
|
|
1055
|
+
lines.push('');
|
|
1056
|
+
lines.push(` @override`);
|
|
1057
|
+
lines.push(` void initState() {`);
|
|
1058
|
+
lines.push(` super.initState();`);
|
|
1059
|
+
lines.push(` _appTheme = AppTheme.uniform(`);
|
|
1060
|
+
lines.push(` sharedPreferences: widget.sharedPreferences,`);
|
|
1061
|
+
lines.push(` themeFactory: const UniversalThemeFactory(),`);
|
|
1062
|
+
lines.push(` lightColors: KasyColors.light(),`);
|
|
1063
|
+
lines.push(` darkColors: KasyColors.dark(),`);
|
|
1064
|
+
lines.push(` textTheme: KasyTextTheme.build(),`);
|
|
1065
|
+
lines.push(` defaultMode: ThemeMode.system,`);
|
|
1066
|
+
lines.push(` );`);
|
|
1067
|
+
lines.push(` }`);
|
|
1068
|
+
lines.push('');
|
|
1069
|
+
lines.push(` @override`);
|
|
1070
|
+
lines.push(` void dispose() {`);
|
|
1071
|
+
lines.push(` _appTheme.dispose();`);
|
|
1072
|
+
lines.push(` super.dispose();`);
|
|
1073
|
+
lines.push(` }`);
|
|
1074
|
+
lines.push('');
|
|
903
1075
|
lines.push(` // This widget is the root of your application.`);
|
|
904
1076
|
lines.push(` @override`);
|
|
905
|
-
lines.push(` Widget build(BuildContext context
|
|
1077
|
+
lines.push(` Widget build(BuildContext context) {`);
|
|
906
1078
|
lines.push(` ErrorWidget.builder = (FlutterErrorDetails details) {`);
|
|
907
1079
|
lines.push(` return AppErrorWidget(error: details);`);
|
|
908
1080
|
lines.push(` };`);
|
|
909
1081
|
lines.push(` final goRouter = ref.watch(goRouterProvider);`);
|
|
910
1082
|
lines.push('');
|
|
911
1083
|
lines.push(` return ThemeProvider(`);
|
|
912
|
-
lines.push(` notifier:
|
|
913
|
-
lines.push(` sharedPreferences: sharedPreferences,`);
|
|
914
|
-
lines.push(` themeFactory: const UniversalThemeFactory(),`);
|
|
915
|
-
lines.push(` lightColors: KasyColors.light(),`);
|
|
916
|
-
lines.push(` darkColors: KasyColors.dark(),`);
|
|
917
|
-
lines.push(` textTheme: KasyTextTheme.build(),`);
|
|
918
|
-
lines.push(` defaultMode: ThemeMode.light,`);
|
|
919
|
-
lines.push(` ),`);
|
|
1084
|
+
lines.push(` notifier: _appTheme,`);
|
|
920
1085
|
lines.push(` child: Builder(`);
|
|
921
1086
|
lines.push(` builder: (context) {`);
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
lines.push(`
|
|
927
|
-
lines.push(`
|
|
928
|
-
lines.push(`
|
|
929
|
-
lines.push(`
|
|
930
|
-
lines.push(`
|
|
931
|
-
lines.push(`
|
|
932
|
-
lines.push(`
|
|
933
|
-
lines.push(`
|
|
934
|
-
lines.push(`
|
|
935
|
-
lines.push(`
|
|
936
|
-
lines.push(`
|
|
937
|
-
lines.push(`
|
|
938
|
-
lines.push(`
|
|
939
|
-
lines.push(`
|
|
940
|
-
lines.push(`
|
|
941
|
-
lines.push(`
|
|
942
|
-
lines.push(`
|
|
943
|
-
lines.push(` //
|
|
944
|
-
lines.push(`
|
|
945
|
-
lines.push(`
|
|
946
|
-
lines.push(`
|
|
1087
|
+
// WebDevicePreview + DevInspector wrap the app so the in-app dev tools work:
|
|
1088
|
+
// • Cmd/Ctrl + Shift + P → widget inspector
|
|
1089
|
+
// • Cmd/Ctrl + Shift + D → device preview (web)
|
|
1090
|
+
// Both are no-ops in release builds.
|
|
1091
|
+
lines.push(` return WebDevicePreview.wrap(`);
|
|
1092
|
+
lines.push(` child: DevInspector.wrap(`);
|
|
1093
|
+
lines.push(` child: MaterialApp.router(`);
|
|
1094
|
+
lines.push(` title: 'Kasy',`);
|
|
1095
|
+
lines.push(` scaffoldMessengerKey: devInspectorRootScaffoldMessengerKey,`);
|
|
1096
|
+
lines.push(` theme: ThemeProvider.of(context).light,`);
|
|
1097
|
+
lines.push(` darkTheme: ThemeProvider.of(context).dark,`);
|
|
1098
|
+
lines.push(` themeMode: ThemeProvider.of(context).mode,`);
|
|
1099
|
+
lines.push(` themeAnimationDuration: Duration.zero,`);
|
|
1100
|
+
lines.push(` routerConfig: goRouter,`);
|
|
1101
|
+
lines.push(` localizationsDelegates: const [`);
|
|
1102
|
+
lines.push(` GlobalMaterialLocalizations.delegate,`);
|
|
1103
|
+
lines.push(` GlobalWidgetsLocalizations.delegate,`);
|
|
1104
|
+
lines.push(` GlobalCupertinoLocalizations.delegate,`);
|
|
1105
|
+
lines.push(` ],`);
|
|
1106
|
+
lines.push(` locale: TranslationProvider.of(context).flutterLocale,`);
|
|
1107
|
+
lines.push(` supportedLocales: AppLocaleUtils.supportedLocales,`);
|
|
1108
|
+
lines.push(` // Initializer is a widget that allows us to run some code before the app is ready`);
|
|
1109
|
+
lines.push(` builder: (context, child) => Initializer(`);
|
|
1110
|
+
lines.push(` services: [`);
|
|
1111
|
+
lines.push(` authenticationApiProvider,`);
|
|
1112
|
+
lines.push(` // shared preferences must be loaded`);
|
|
1113
|
+
lines.push(` sharedPreferencesProvider,`);
|
|
1114
|
+
lines.push(` // remote config api`);
|
|
1115
|
+
lines.push(` remoteConfigApiProvider,`);
|
|
1116
|
+
lines.push(` // notifications`);
|
|
1117
|
+
lines.push(` notificationsSettingsProvider,`);
|
|
1118
|
+
lines.push(` notificationRepositoryProvider,`);
|
|
1119
|
+
lines.push(` // user state`);
|
|
947
1120
|
if (withRevenuecat) {
|
|
948
|
-
lines.push(`
|
|
1121
|
+
lines.push(` subscriptionRepositoryProvider,`);
|
|
949
1122
|
}
|
|
950
|
-
lines.push(`
|
|
1123
|
+
lines.push(` userStateNotifierProvider.notifier,`);
|
|
951
1124
|
if (withWidget) {
|
|
952
|
-
lines.push(`
|
|
1125
|
+
lines.push(` homeWidgetsManagerProvider,`);
|
|
953
1126
|
}
|
|
954
|
-
lines.push(`
|
|
955
|
-
lines.push(`
|
|
956
|
-
lines.push(`
|
|
957
|
-
lines.push(`
|
|
958
|
-
lines.push(`
|
|
959
|
-
lines.push(`
|
|
960
|
-
lines.push(`
|
|
961
|
-
lines.push(`
|
|
962
|
-
lines.push(`
|
|
963
|
-
lines.push(`
|
|
964
|
-
lines.push(`
|
|
1127
|
+
lines.push(` // analytics`);
|
|
1128
|
+
lines.push(` analyticsApiProvider,`);
|
|
1129
|
+
lines.push(` facebookEventApiProvider,`);
|
|
1130
|
+
lines.push(` ],`);
|
|
1131
|
+
lines.push(` onReady: DevicePreview.appBuilder(context, child),`);
|
|
1132
|
+
lines.push(` onError: (_, error) => InitializationErrorPage(error: error),`);
|
|
1133
|
+
lines.push(` onLoading: Scaffold(`);
|
|
1134
|
+
lines.push(` body: Center(`);
|
|
1135
|
+
lines.push(` child: CircularProgressIndicator.adaptive(`);
|
|
1136
|
+
lines.push(` valueColor: AlwaysStoppedAnimation<Color>(`);
|
|
1137
|
+
lines.push(` context.colors.primary,`);
|
|
1138
|
+
lines.push(` ),`);
|
|
1139
|
+
lines.push(` ),`);
|
|
965
1140
|
lines.push(` ),`);
|
|
966
1141
|
lines.push(` ),`);
|
|
967
1142
|
lines.push(` ),`);
|
|
@@ -1185,6 +1360,30 @@ class AdminHomeWidgets extends StatelessWidget {
|
|
|
1185
1360
|
);
|
|
1186
1361
|
}
|
|
1187
1362
|
|
|
1363
|
+
/**
|
|
1364
|
+
* Strips the home-widget hook from language_switcher.dart when the widget module
|
|
1365
|
+
* is not selected. The switcher pushes the new locale to the home widget via
|
|
1366
|
+
* myWidgetHomeWidgetProvider, which only exists with the widget module — without
|
|
1367
|
+
* this the generated project fails to compile (undefined provider + dead import).
|
|
1368
|
+
*
|
|
1369
|
+
* @param {string} projectDir
|
|
1370
|
+
*/
|
|
1371
|
+
async function patchLanguageSwitcherNoWidget(projectDir) {
|
|
1372
|
+
const filePath = path.join(
|
|
1373
|
+
projectDir, 'lib', 'features', 'settings', 'ui', 'components', 'language_switcher.dart',
|
|
1374
|
+
);
|
|
1375
|
+
if (!(await fs.pathExists(filePath))) return;
|
|
1376
|
+
let content = await fs.readFile(filePath, 'utf8');
|
|
1377
|
+
// Drop the home-widget service import.
|
|
1378
|
+
content = content.replace(/^import 'package:[^']*home_widget_mywidget_service\.dart';\n/m, '');
|
|
1379
|
+
// Drop the unawaited(...) block that updates the home widget on locale change.
|
|
1380
|
+
content = content.replace(
|
|
1381
|
+
/\n\s*unawaited\(\s*ref[\s\S]*?myWidgetHomeWidgetProvider[\s\S]*?\);/,
|
|
1382
|
+
'',
|
|
1383
|
+
);
|
|
1384
|
+
await fs.outputFile(filePath, content, 'utf8');
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1188
1387
|
/**
|
|
1189
1388
|
* Writes a NoOp feature_request_repository.dart when the feedback module is not selected.
|
|
1190
1389
|
* premium_page_provider.dart imports this provider — without it the project won't compile.
|
|
@@ -1286,7 +1485,12 @@ async function writeNoOpSentryUsages(projectDir) {
|
|
|
1286
1485
|
if (!(await fs.pathExists(filePath))) continue;
|
|
1287
1486
|
let content = await fs.readFile(filePath, 'utf8');
|
|
1288
1487
|
content = content.replace(sentryImport, '');
|
|
1289
|
-
|
|
1488
|
+
// Replace the Sentry call with a comment (not nothing) so the catch block
|
|
1489
|
+
// isn't left empty — an empty catch trips the empty_catches lint.
|
|
1490
|
+
content = content.replace(
|
|
1491
|
+
/^([ \t]*)Sentry\.captureException\([^)]*\);\n/gm,
|
|
1492
|
+
'$1// Error not reported — Sentry is disabled. Run: kasy add sentry.\n',
|
|
1493
|
+
);
|
|
1290
1494
|
await fs.outputFile(filePath, content, 'utf8');
|
|
1291
1495
|
}
|
|
1292
1496
|
}
|
|
@@ -1466,9 +1670,13 @@ sealed class Subscription with _$Subscription {
|
|
|
1466
1670
|
}
|
|
1467
1671
|
|
|
1468
1672
|
// 1. No-op subscription_repository.dart (used by user_repository, onboarding, home_widget_background_task)
|
|
1673
|
+
// The constructor keeps subscriptionApi/inAppSubscriptionApi/prefs params (unused
|
|
1674
|
+
// here) for source-compat with the bundled tests. ignore_for_file stops
|
|
1675
|
+
// `dart fix` from stripping them via avoid_unused_constructor_parameters.
|
|
1469
1676
|
await fs.outputFile(
|
|
1470
1677
|
path.join(projectDir, 'lib', 'features', 'subscription', 'repositories', 'subscription_repository.dart'),
|
|
1471
|
-
|
|
1678
|
+
`// ignore_for_file: avoid_unused_constructor_parameters
|
|
1679
|
+
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
1472
1680
|
import 'package:${pkg}/core/data/models/subscription.dart';
|
|
1473
1681
|
import 'package:${pkg}/core/initializer/onstart_service.dart';
|
|
1474
1682
|
// No-op subscription repository. Run: kasy add revenuecat to activate.
|
|
@@ -1561,7 +1769,8 @@ enum PaywallFactory { basic, basicRow, minimal, withSwitch }
|
|
|
1561
1769
|
// 5. No-op subscription_entity.dart (used by test_utils, fake_subscription_api)
|
|
1562
1770
|
await fs.outputFile(
|
|
1563
1771
|
path.join(projectDir, 'lib', 'features', 'subscription', 'api', 'entities', 'subscription_entity.dart'),
|
|
1564
|
-
`//
|
|
1772
|
+
`// ignore_for_file: constant_identifier_names
|
|
1773
|
+
// No-op subscription entity. Run: kasy add revenuecat to activate.
|
|
1565
1774
|
enum SubscriptionStatus { ACTIVE, PAUSED, EXPIRED, LIFETIME, CANCELLED }
|
|
1566
1775
|
|
|
1567
1776
|
class SubscriptionEntity {
|
|
@@ -1736,6 +1945,7 @@ module.exports = {
|
|
|
1736
1945
|
removeFacebookSigninFromAuthPages,
|
|
1737
1946
|
removeAndroidFacebookMetadata,
|
|
1738
1947
|
writeNoOpAdminHomeWidgets,
|
|
1948
|
+
patchLanguageSwitcherNoWidget,
|
|
1739
1949
|
writeNoOpFeatureRequestRepository,
|
|
1740
1950
|
writeNoOpSubscriptionStubs,
|
|
1741
1951
|
writeNoOpSentryUsages,
|