rothzerg 0.1.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/dist/cli.js +949 -0
- package/package.json +27 -0
- package/readme.md +0 -0
- package/templates/_base-website/App.tsx +59 -0
- package/templates/_base-website/InitialDataContext.tsx +21 -0
- package/templates/_base-website/_gitignore +5 -0
- package/templates/_base-website/client.tsx +26 -0
- package/templates/_base-website/components/DeepLinkLayout.css +41 -0
- package/templates/_base-website/components/DeepLinkLayout.tsx +39 -0
- package/templates/_base-website/components/DownloadSection.css +60 -0
- package/templates/_base-website/components/DownloadSection.tsx +61 -0
- package/templates/_base-website/components/SiteFeaturesSection.tsx +68 -0
- package/templates/_base-website/components/SiteFooter.tsx +29 -0
- package/templates/_base-website/components/SiteHeroSection.tsx +30 -0
- package/templates/_base-website/components/SiteHighlightsSection.tsx +38 -0
- package/templates/_base-website/components/SiteLanguageSwitcher.tsx +9 -0
- package/templates/_base-website/components/SiteNavigation.tsx +24 -0
- package/templates/_base-website/config.ts +78 -0
- package/templates/_base-website/data/blog/authors.json +18 -0
- package/templates/_base-website/data/blog/categories.json +53 -0
- package/templates/_base-website/data/blog/posts.json +29 -0
- package/templates/_base-website/data/blog/tags.json +86 -0
- package/templates/_base-website/i18n/config.ts +60 -0
- package/templates/_base-website/i18n/sections/blog/_index.ts +41 -0
- package/templates/_base-website/i18n/sections/blog/ar.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/de.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/en.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/es.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/fr.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/hi.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/id.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/it.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/ja.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/ko.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/nl.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/pl.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/pt.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/ru.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/sv.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/th.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/tr.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/vi.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/zh.ts +24 -0
- package/templates/_base-website/i18n/sections/common/_index.ts +41 -0
- package/templates/_base-website/i18n/sections/common/ar.ts +29 -0
- package/templates/_base-website/i18n/sections/common/de.ts +29 -0
- package/templates/_base-website/i18n/sections/common/en.ts +29 -0
- package/templates/_base-website/i18n/sections/common/es.ts +29 -0
- package/templates/_base-website/i18n/sections/common/fr.ts +29 -0
- package/templates/_base-website/i18n/sections/common/hi.ts +29 -0
- package/templates/_base-website/i18n/sections/common/id.ts +29 -0
- package/templates/_base-website/i18n/sections/common/it.ts +29 -0
- package/templates/_base-website/i18n/sections/common/ja.ts +29 -0
- package/templates/_base-website/i18n/sections/common/ko.ts +29 -0
- package/templates/_base-website/i18n/sections/common/nl.ts +29 -0
- package/templates/_base-website/i18n/sections/common/pl.ts +29 -0
- package/templates/_base-website/i18n/sections/common/pt.ts +29 -0
- package/templates/_base-website/i18n/sections/common/ru.ts +29 -0
- package/templates/_base-website/i18n/sections/common/sv.ts +29 -0
- package/templates/_base-website/i18n/sections/common/th.ts +29 -0
- package/templates/_base-website/i18n/sections/common/tr.ts +29 -0
- package/templates/_base-website/i18n/sections/common/vi.ts +29 -0
- package/templates/_base-website/i18n/sections/common/zh.ts +29 -0
- package/templates/_base-website/i18n/sections/deepLink/_index.ts +41 -0
- package/templates/_base-website/i18n/sections/deepLink/ar.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/de.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/en.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/es.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/fr.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/hi.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/id.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/it.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/ja.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/ko.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/nl.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/pl.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/pt.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/ru.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/sv.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/th.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/tr.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/vi.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/zh.ts +14 -0
- package/templates/_base-website/i18n/sections/home/_index.ts +41 -0
- package/templates/_base-website/i18n/sections/home/ar.ts +85 -0
- package/templates/_base-website/i18n/sections/home/de.ts +85 -0
- package/templates/_base-website/i18n/sections/home/en.ts +85 -0
- package/templates/_base-website/i18n/sections/home/es.ts +85 -0
- package/templates/_base-website/i18n/sections/home/fr.ts +85 -0
- package/templates/_base-website/i18n/sections/home/hi.ts +85 -0
- package/templates/_base-website/i18n/sections/home/id.ts +85 -0
- package/templates/_base-website/i18n/sections/home/it.ts +85 -0
- package/templates/_base-website/i18n/sections/home/ja.ts +85 -0
- package/templates/_base-website/i18n/sections/home/ko.ts +85 -0
- package/templates/_base-website/i18n/sections/home/nl.ts +85 -0
- package/templates/_base-website/i18n/sections/home/pl.ts +85 -0
- package/templates/_base-website/i18n/sections/home/pt.ts +85 -0
- package/templates/_base-website/i18n/sections/home/ru.ts +85 -0
- package/templates/_base-website/i18n/sections/home/sv.ts +85 -0
- package/templates/_base-website/i18n/sections/home/th.ts +85 -0
- package/templates/_base-website/i18n/sections/home/tr.ts +85 -0
- package/templates/_base-website/i18n/sections/home/vi.ts +85 -0
- package/templates/_base-website/i18n/sections/home/zh.ts +85 -0
- package/templates/_base-website/i18n/sections/support/_index.ts +41 -0
- package/templates/_base-website/i18n/sections/support/ar.ts +37 -0
- package/templates/_base-website/i18n/sections/support/de.ts +37 -0
- package/templates/_base-website/i18n/sections/support/en.ts +37 -0
- package/templates/_base-website/i18n/sections/support/es.ts +37 -0
- package/templates/_base-website/i18n/sections/support/fr.ts +37 -0
- package/templates/_base-website/i18n/sections/support/hi.ts +37 -0
- package/templates/_base-website/i18n/sections/support/id.ts +37 -0
- package/templates/_base-website/i18n/sections/support/it.ts +37 -0
- package/templates/_base-website/i18n/sections/support/ja.ts +37 -0
- package/templates/_base-website/i18n/sections/support/ko.ts +37 -0
- package/templates/_base-website/i18n/sections/support/nl.ts +37 -0
- package/templates/_base-website/i18n/sections/support/pl.ts +37 -0
- package/templates/_base-website/i18n/sections/support/pt.ts +37 -0
- package/templates/_base-website/i18n/sections/support/ru.ts +37 -0
- package/templates/_base-website/i18n/sections/support/sv.ts +37 -0
- package/templates/_base-website/i18n/sections/support/th.ts +37 -0
- package/templates/_base-website/i18n/sections/support/tr.ts +37 -0
- package/templates/_base-website/i18n/sections/support/vi.ts +37 -0
- package/templates/_base-website/i18n/sections/support/zh.ts +37 -0
- package/templates/_base-website/i18n/translations.ts +25 -0
- package/templates/_base-website/index.ts +460 -0
- package/templates/_base-website/pages/404.tsx +35 -0
- package/templates/_base-website/pages/blog/author.tsx +97 -0
- package/templates/_base-website/pages/blog/category.tsx +89 -0
- package/templates/_base-website/pages/blog/index.tsx +81 -0
- package/templates/_base-website/pages/blog/post.tsx +110 -0
- package/templates/_base-website/pages/blog/tag.tsx +86 -0
- package/templates/_base-website/pages/custom-pages/example.tsx +54 -0
- package/templates/_base-website/pages/index.tsx +29 -0
- package/templates/_base-website/pages/privacy.tsx +45 -0
- package/templates/_base-website/pages/support.tsx +154 -0
- package/templates/_base-website/pages/terms.tsx +68 -0
- package/templates/_base-website/public/images/16.png +0 -0
- package/templates/_base-website/public/images/apple-touch-icon.png +0 -0
- package/templates/_base-website/public/images/favicon-32x32.png +0 -0
- package/templates/_base-website/public/images/favicon.png +0 -0
- package/templates/_base-website/public/images/logo_dark.svg +6 -0
- package/templates/_base-website/public/images/logo_light.svg +6 -0
- package/templates/_base-website/public/images/og-image.png +0 -0
- package/templates/_base-website/public/images/screenshots/ar_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/de_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/en_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/es_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/fr_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/hi_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/id_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/it_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/ja_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/ko_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/nl_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/pl_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/pt_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/ru_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/sv_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/th_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/tr_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/vi_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/zh_dashboard.jpg +0 -0
- package/templates/_base-website/rothzerg.template.json +63 -0
- package/templates/_base-website/styles/404.css +32 -0
- package/templates/_base-website/styles/_app.css +131 -0
- package/templates/_base-website/styles/_shared.css +194 -0
- package/templates/_base-website/styles/index.css +1 -0
- package/templates/_base-website/styles/privacy.css +1 -0
- package/templates/_base-website/styles/support.css +148 -0
- package/templates/_base-website/styles/terms.css +22 -0
- package/templates/mobile-app/_gitignore +46 -0
- package/templates/mobile-app/analysis_options.yaml +28 -0
- package/templates/mobile-app/android/app/build.gradle.kts +56 -0
- package/templates/mobile-app/android/app/google-services.json +29 -0
- package/templates/mobile-app/android/app/src/debug/AndroidManifest.xml +7 -0
- package/templates/mobile-app/android/app/src/main/AndroidManifest.xml +54 -0
- package/templates/mobile-app/android/app/src/main/kotlin/com/example/{{projectNameSnake}}/MainActivity.kt +5 -0
- package/templates/mobile-app/android/app/src/main/res/drawable/launch_background.xml +12 -0
- package/templates/mobile-app/android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
- package/templates/mobile-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/templates/mobile-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/templates/mobile-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/templates/mobile-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/templates/mobile-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/templates/mobile-app/android/app/src/main/res/values/styles.xml +18 -0
- package/templates/mobile-app/android/app/src/main/res/values-night/styles.xml +18 -0
- package/templates/mobile-app/android/app/src/profile/AndroidManifest.xml +7 -0
- package/templates/mobile-app/android/build.gradle.kts +24 -0
- package/templates/mobile-app/android/gradle/wrapper/gradle-wrapper.properties +5 -0
- package/templates/mobile-app/android/gradle.properties +2 -0
- package/templates/mobile-app/android/settings.gradle.kts +31 -0
- package/templates/mobile-app/assets/icons/logo.png +0 -0
- package/templates/mobile-app/assets/icons/logo_dark.svg +5 -0
- package/templates/mobile-app/assets/icons/logo_light.svg +5 -0
- package/templates/mobile-app/assets/lottie/tick.json +1 -0
- package/templates/mobile-app/devtools_options.yaml +3 -0
- package/templates/mobile-app/ios/Flutter/AppFrameworkInfo.plist +26 -0
- package/templates/mobile-app/ios/Flutter/Debug.xcconfig +2 -0
- package/templates/mobile-app/ios/Flutter/Release.xcconfig +2 -0
- package/templates/mobile-app/ios/Podfile +46 -0
- package/templates/mobile-app/ios/Podfile.lock +1807 -0
- package/templates/mobile-app/ios/Runner/AppDelegate.swift +16 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +122 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +23 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +5 -0
- package/templates/mobile-app/ios/Runner/Base.lproj/LaunchScreen.storyboard +37 -0
- package/templates/mobile-app/ios/Runner/Base.lproj/Main.storyboard +26 -0
- package/templates/mobile-app/ios/Runner/GoogleService-Info.plist +30 -0
- package/templates/mobile-app/ios/Runner/Info.plist +70 -0
- package/templates/mobile-app/ios/Runner/Runner-Bridging-Header.h +1 -0
- package/templates/mobile-app/ios/Runner.xcodeproj/project.pbxproj +772 -0
- package/templates/mobile-app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/templates/mobile-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/templates/mobile-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
- package/templates/mobile-app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +101 -0
- package/templates/mobile-app/ios/Runner.xcworkspace/contents.xcworkspacedata +10 -0
- package/templates/mobile-app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/templates/mobile-app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
- package/templates/mobile-app/ios/RunnerTests/RunnerTests.swift +12 -0
- package/templates/mobile-app/l10n.yaml +5 -0
- package/templates/mobile-app/lib/core/config/app_config.dart +76 -0
- package/templates/mobile-app/lib/core/constants/app_colors.dart +13 -0
- package/templates/mobile-app/lib/core/constants/app_constants.dart +123 -0
- package/templates/mobile-app/lib/core/constants/countries.dart +199 -0
- package/templates/mobile-app/lib/core/constants/keys.dart +46 -0
- package/templates/mobile-app/lib/core/constants/languages.dart +51 -0
- package/templates/mobile-app/lib/core/services/admin_service.dart +271 -0
- package/templates/mobile-app/lib/core/services/analytics_service.dart +296 -0
- package/templates/mobile-app/lib/core/services/auth_service.dart +185 -0
- package/templates/mobile-app/lib/core/services/device_service.dart +228 -0
- package/templates/mobile-app/lib/core/services/firebase_service.dart +490 -0
- package/templates/mobile-app/lib/core/services/in_app_messaging_service.dart +166 -0
- package/templates/mobile-app/lib/core/services/logging_service.dart +451 -0
- package/templates/mobile-app/lib/core/services/messaging_service.dart +460 -0
- package/templates/mobile-app/lib/core/services/remote_config_service.dart +167 -0
- package/templates/mobile-app/lib/core/services/revenue_cat_service.dart +364 -0
- package/templates/mobile-app/lib/core/services/storage_service.dart +313 -0
- package/templates/mobile-app/lib/core/theme/dark_theme.dart +295 -0
- package/templates/mobile-app/lib/core/theme/light_theme.dart +296 -0
- package/templates/mobile-app/lib/core/utils/formatters.dart +236 -0
- package/templates/mobile-app/lib/core/utils/image_picker_helper.dart +78 -0
- package/templates/mobile-app/lib/core/utils/validators.dart +292 -0
- package/templates/mobile-app/lib/firebase_options.dart +19 -0
- package/templates/mobile-app/lib/l10n/app_en.arb +1371 -0
- package/templates/mobile-app/lib/l10n/app_localizations.dart +2042 -0
- package/templates/mobile-app/lib/l10n/app_localizations_en.dart +1033 -0
- package/templates/mobile-app/lib/main.dart +152 -0
- package/templates/mobile-app/lib/models/device_model.dart +90 -0
- package/templates/mobile-app/lib/models/news_banner.dart +53 -0
- package/templates/mobile-app/lib/models/user_model.dart +120 -0
- package/templates/mobile-app/lib/providers/admin_provider.dart +67 -0
- package/templates/mobile-app/lib/providers/auth_provider.dart +328 -0
- package/templates/mobile-app/lib/providers/remote_config_provider.dart +81 -0
- package/templates/mobile-app/lib/providers/subscription_provider.dart +67 -0
- package/templates/mobile-app/lib/providers/theme_provider.dart +51 -0
- package/templates/mobile-app/lib/screens/admin/admin_panel_screen.dart +436 -0
- package/templates/mobile-app/lib/screens/admin/admin_panel_user_details_screen.dart +942 -0
- package/templates/mobile-app/lib/screens/admin/admin_panel_users_screen.dart +344 -0
- package/templates/mobile-app/lib/screens/auth/auth_home_screen.dart +266 -0
- package/templates/mobile-app/lib/screens/auth/forgot_password_screen.dart +214 -0
- package/templates/mobile-app/lib/screens/auth/login_screen.dart +312 -0
- package/templates/mobile-app/lib/screens/auth/profile_setup_screen.dart +157 -0
- package/templates/mobile-app/lib/screens/auth/register_screen.dart +288 -0
- package/templates/mobile-app/lib/screens/developer/developer_panel_screen.dart +436 -0
- package/templates/mobile-app/lib/screens/developer/remote_config_screen.dart +315 -0
- package/templates/mobile-app/lib/screens/developer/remote_config_test_screen.dart +461 -0
- package/templates/mobile-app/lib/screens/home/home_screen.dart +270 -0
- package/templates/mobile-app/lib/screens/legal/privacy_policy_screen.dart +63 -0
- package/templates/mobile-app/lib/screens/legal/terms_screen.dart +82 -0
- package/templates/mobile-app/lib/screens/main_navigation_screen.dart +85 -0
- package/templates/mobile-app/lib/screens/premium/subscription_screen.dart +516 -0
- package/templates/mobile-app/lib/screens/profile/edit_profile_screen.dart +246 -0
- package/templates/mobile-app/lib/screens/settings/about_app_screen.dart +200 -0
- package/templates/mobile-app/lib/screens/settings/cache_manager_screen.dart +400 -0
- package/templates/mobile-app/lib/screens/settings/change_password_screen.dart +269 -0
- package/templates/mobile-app/lib/screens/settings/delete_account_screen.dart +261 -0
- package/templates/mobile-app/lib/screens/settings/settings_screen.dart +400 -0
- package/templates/mobile-app/lib/screens/splash/splash_screen.dart +125 -0
- package/templates/mobile-app/lib/widgets/app_button.dart +255 -0
- package/templates/mobile-app/lib/widgets/app_drawer.dart +207 -0
- package/templates/mobile-app/lib/widgets/bottom_sheet_selector.dart +79 -0
- package/templates/mobile-app/lib/widgets/button_banner.dart +177 -0
- package/templates/mobile-app/lib/widgets/country_picker.dart +169 -0
- package/templates/mobile-app/lib/widgets/custom_app_bar.dart +30 -0
- package/templates/mobile-app/lib/widgets/email_verification_banner.dart +163 -0
- package/templates/mobile-app/lib/widgets/empty_screen.dart +42 -0
- package/templates/mobile-app/lib/widgets/language_picker.dart +145 -0
- package/templates/mobile-app/lib/widgets/news_banner_widget.dart +264 -0
- package/templates/mobile-app/lib/widgets/remote_config/remote_config_widgets.dart +294 -0
- package/templates/mobile-app/lib/widgets/section_header.dart +23 -0
- package/templates/mobile-app/lib/widgets/success_overlay.dart +117 -0
- package/templates/mobile-app/lib/widgets/top_notification.dart +263 -0
- package/templates/mobile-app/lib/widgets/user_avatar_picker.dart +58 -0
- package/templates/mobile-app/pubspec.yaml +97 -0
- package/templates/mobile-app/rothzerg.template.json +43 -0
- package/templates/web-app/_gitignore +4 -0
- package/templates/web-app/index.html +12 -0
- package/templates/web-app/package.json +23 -0
- package/templates/web-app/rothzerg.template.json +41 -0
- package/templates/web-app/src/App.css +6 -0
- package/templates/web-app/src/App.tsx +12 -0
- package/templates/web-app/src/index.css +14 -0
- package/templates/web-app/src/main.tsx +10 -0
- package/templates/web-app/tsconfig.app.json +20 -0
- package/templates/web-app/tsconfig.json +7 -0
- package/templates/web-app/tsconfig.node.json +18 -0
- package/templates/web-app/vite.config.ts +6 -0
- package/templates/website-blog/overrides/config.ts +76 -0
- package/templates/website-blog/overrides/pages/index.tsx +63 -0
- package/templates/website-blog/rothzerg.template.json +29 -0
- package/templates/website-business/overrides/App.tsx +49 -0
- package/templates/website-business/overrides/components/SiteHeroSection.tsx +33 -0
- package/templates/website-business/overrides/config.ts +76 -0
- package/templates/website-business/overrides/pages/about.tsx +50 -0
- package/templates/website-business/overrides/pages/contact.tsx +61 -0
- package/templates/website-business/overrides/pages/index.tsx +37 -0
- package/templates/website-business/overrides/pages/services.tsx +70 -0
- package/templates/website-business/rothzerg.template.json +18 -0
- package/templates/website-mobile/overrides/App.tsx +62 -0
- package/templates/website-mobile/overrides/components/SiteHeroSection.tsx +18 -0
- package/templates/website-mobile/overrides/config.ts +76 -0
- package/templates/website-mobile/overrides/pages/index.tsx +34 -0
- package/templates/website-mobile/rothzerg.template.json +64 -0
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
import 'package:firebase_messaging/firebase_messaging.dart';
|
|
2
|
+
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
|
3
|
+
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
4
|
+
import 'dart:developer' as developer;
|
|
5
|
+
import 'dart:io';
|
|
6
|
+
import 'device_service.dart';
|
|
7
|
+
import 'in_app_messaging_service.dart';
|
|
8
|
+
|
|
9
|
+
/// Top-level function to handle background messages
|
|
10
|
+
@pragma('vm:entry-point')
|
|
11
|
+
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
|
12
|
+
developer.log(
|
|
13
|
+
'Handling background message: ${message.messageId}',
|
|
14
|
+
name: 'MessagingService',
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class MessagingService {
|
|
19
|
+
static final MessagingService _instance = MessagingService._internal();
|
|
20
|
+
factory MessagingService() => _instance;
|
|
21
|
+
MessagingService._internal();
|
|
22
|
+
|
|
23
|
+
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
|
|
24
|
+
final FlutterLocalNotificationsPlugin _localNotifications =
|
|
25
|
+
FlutterLocalNotificationsPlugin();
|
|
26
|
+
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
|
27
|
+
|
|
28
|
+
bool _isInitialized = false;
|
|
29
|
+
String? _fcmToken;
|
|
30
|
+
String? _userId; // Store userId for later use
|
|
31
|
+
|
|
32
|
+
String? get fcmToken => _fcmToken;
|
|
33
|
+
|
|
34
|
+
/// Initialize Firebase Cloud Messaging
|
|
35
|
+
Future<void> initialize({
|
|
36
|
+
String? userId,
|
|
37
|
+
bool requestPermission = false,
|
|
38
|
+
}) async {
|
|
39
|
+
if (_isInitialized) {
|
|
40
|
+
developer.log('FCM already initialized', name: 'MessagingService');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
developer.log('Initializing FCM...', name: 'MessagingService');
|
|
46
|
+
|
|
47
|
+
// Store userId for later use
|
|
48
|
+
_userId = userId;
|
|
49
|
+
|
|
50
|
+
// Request notification permissions only if explicitly requested
|
|
51
|
+
if (requestPermission) {
|
|
52
|
+
await _requestPermission();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Initialize local notifications
|
|
56
|
+
await _initializeLocalNotifications();
|
|
57
|
+
|
|
58
|
+
// Set up background message handler
|
|
59
|
+
FirebaseMessaging.onBackgroundMessage(
|
|
60
|
+
_firebaseMessagingBackgroundHandler,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// Get and save FCM token
|
|
64
|
+
await _setupTokenHandling(userId);
|
|
65
|
+
|
|
66
|
+
// Handle foreground messages
|
|
67
|
+
FirebaseMessaging.onMessage.listen(_handleForegroundMessage);
|
|
68
|
+
|
|
69
|
+
// Handle notification taps (when app is in background or terminated)
|
|
70
|
+
FirebaseMessaging.onMessageOpenedApp.listen(_handleMessageOpenedApp);
|
|
71
|
+
|
|
72
|
+
// Check if app was opened from a notification
|
|
73
|
+
final initialMessage = await _firebaseMessaging.getInitialMessage();
|
|
74
|
+
if (initialMessage != null) {
|
|
75
|
+
_handleMessageOpenedApp(initialMessage);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
_isInitialized = true;
|
|
79
|
+
developer.log('✅ FCM initialized successfully', name: 'MessagingService');
|
|
80
|
+
} catch (e) {
|
|
81
|
+
developer.log(
|
|
82
|
+
'❌ FCM initialization failed: $e',
|
|
83
|
+
name: 'MessagingService',
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// Request notification permissions with optional delay
|
|
89
|
+
Future<void> requestPermissionWithDelay({
|
|
90
|
+
Duration delay = const Duration(seconds: 5),
|
|
91
|
+
}) async {
|
|
92
|
+
developer.log(
|
|
93
|
+
'Will request notification permission in ${delay.inSeconds} seconds...',
|
|
94
|
+
name: 'MessagingService',
|
|
95
|
+
);
|
|
96
|
+
await Future.delayed(delay);
|
|
97
|
+
await _requestPermission();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/// Request notification permissions
|
|
101
|
+
Future<void> _requestPermission() async {
|
|
102
|
+
try {
|
|
103
|
+
final settings = await _firebaseMessaging.requestPermission(
|
|
104
|
+
alert: true,
|
|
105
|
+
badge: true,
|
|
106
|
+
sound: true,
|
|
107
|
+
provisional: false,
|
|
108
|
+
announcement: false,
|
|
109
|
+
carPlay: false,
|
|
110
|
+
criticalAlert: false,
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
developer.log(
|
|
114
|
+
'Notification permission status: ${settings.authorizationStatus}',
|
|
115
|
+
name: 'MessagingService',
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
|
|
119
|
+
developer.log('✅ User granted permission', name: 'MessagingService');
|
|
120
|
+
// Get and save FCM token after permission is granted
|
|
121
|
+
await _getAndSaveToken();
|
|
122
|
+
} else if (settings.authorizationStatus ==
|
|
123
|
+
AuthorizationStatus.provisional) {
|
|
124
|
+
developer.log(
|
|
125
|
+
'✅ User granted provisional permission',
|
|
126
|
+
name: 'MessagingService',
|
|
127
|
+
);
|
|
128
|
+
// Get and save FCM token after permission is granted
|
|
129
|
+
await _getAndSaveToken();
|
|
130
|
+
} else {
|
|
131
|
+
developer.log(
|
|
132
|
+
'⚠️ User declined or has not accepted permission',
|
|
133
|
+
name: 'MessagingService',
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
} catch (e) {
|
|
137
|
+
developer.log(
|
|
138
|
+
'❌ Permission request failed: $e',
|
|
139
|
+
name: 'MessagingService',
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/// Initialize local notifications for displaying foreground messages
|
|
145
|
+
Future<void> _initializeLocalNotifications() async {
|
|
146
|
+
const androidSettings = AndroidInitializationSettings(
|
|
147
|
+
'@mipmap/ic_launcher',
|
|
148
|
+
);
|
|
149
|
+
const iosSettings = DarwinInitializationSettings(
|
|
150
|
+
requestAlertPermission: true,
|
|
151
|
+
requestBadgePermission: true,
|
|
152
|
+
requestSoundPermission: true,
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const initSettings = InitializationSettings(
|
|
156
|
+
android: androidSettings,
|
|
157
|
+
iOS: iosSettings,
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
await _localNotifications.initialize(
|
|
161
|
+
settings: initSettings,
|
|
162
|
+
onDidReceiveNotificationResponse: _onNotificationTapped,
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
// Create notification channel for Android
|
|
166
|
+
if (Platform.isAndroid) {
|
|
167
|
+
const channel = AndroidNotificationChannel(
|
|
168
|
+
'high_importance_channel', // id
|
|
169
|
+
'High Importance Notifications', // name
|
|
170
|
+
description: 'This channel is used for important notifications.',
|
|
171
|
+
importance: Importance.high,
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
await _localNotifications
|
|
175
|
+
.resolvePlatformSpecificImplementation<
|
|
176
|
+
AndroidFlutterLocalNotificationsPlugin
|
|
177
|
+
>()
|
|
178
|
+
?.createNotificationChannel(channel);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/// Get and save FCM token (called after permission is granted)
|
|
183
|
+
Future<void> _getAndSaveToken() async {
|
|
184
|
+
try {
|
|
185
|
+
developer.log('Getting FCM token...', name: 'MessagingService');
|
|
186
|
+
|
|
187
|
+
// On iOS, wait a bit for token to be ready after permission
|
|
188
|
+
if (Platform.isIOS) {
|
|
189
|
+
await Future.delayed(const Duration(milliseconds: 500));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
final token = await _firebaseMessaging.getToken();
|
|
193
|
+
|
|
194
|
+
if (token != null) {
|
|
195
|
+
_fcmToken = token;
|
|
196
|
+
developer.log(
|
|
197
|
+
'📱 FCM Token obtained: $token',
|
|
198
|
+
name: 'MessagingService',
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// Save to Firestore if userId is available
|
|
202
|
+
if (_userId != null && _userId!.isNotEmpty) {
|
|
203
|
+
await saveFCMToken(_userId!, token);
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
developer.log('⚠️ FCM token is still null', name: 'MessagingService');
|
|
207
|
+
}
|
|
208
|
+
} catch (e) {
|
|
209
|
+
developer.log('❌ Failed to get FCM token: $e', name: 'MessagingService');
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/// Set up FCM token handling
|
|
214
|
+
Future<void> _setupTokenHandling(String? userId) async {
|
|
215
|
+
try {
|
|
216
|
+
// On iOS, we need to wait for APNS token before getting FCM token
|
|
217
|
+
if (Platform.isIOS) {
|
|
218
|
+
// Wait a bit for APNS token to be available
|
|
219
|
+
await Future.delayed(const Duration(seconds: 1));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Get the FCM token
|
|
223
|
+
_fcmToken = await _firebaseMessaging.getToken();
|
|
224
|
+
|
|
225
|
+
if (_fcmToken != null) {
|
|
226
|
+
developer.log('📱 FCM Token: $_fcmToken', name: 'MessagingService');
|
|
227
|
+
|
|
228
|
+
// Save token to Firestore if user is logged in
|
|
229
|
+
if (userId != null && userId.isNotEmpty) {
|
|
230
|
+
await saveFCMToken(userId, _fcmToken!);
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
developer.log('⚠️ FCM token is null', name: 'MessagingService');
|
|
234
|
+
|
|
235
|
+
// On iOS, if token is null, wait for it via token refresh listener
|
|
236
|
+
if (Platform.isIOS) {
|
|
237
|
+
developer.log(
|
|
238
|
+
'⏳ Waiting for APNS token on iOS...',
|
|
239
|
+
name: 'MessagingService',
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Listen for token refresh
|
|
245
|
+
_firebaseMessaging.onTokenRefresh.listen((newToken) {
|
|
246
|
+
developer.log(
|
|
247
|
+
'🔄 FCM Token refreshed: $newToken',
|
|
248
|
+
name: 'MessagingService',
|
|
249
|
+
);
|
|
250
|
+
_fcmToken = newToken;
|
|
251
|
+
|
|
252
|
+
// Use stored userId if available
|
|
253
|
+
final currentUserId = userId ?? _userId;
|
|
254
|
+
if (currentUserId != null && currentUserId.isNotEmpty) {
|
|
255
|
+
saveFCMToken(currentUserId, newToken);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
} catch (e) {
|
|
259
|
+
developer.log('❌ Token setup failed: $e', name: 'MessagingService');
|
|
260
|
+
|
|
261
|
+
// Don't fail the entire initialization if token retrieval fails
|
|
262
|
+
// Token will be available via onTokenRefresh listener
|
|
263
|
+
if (Platform.isIOS) {
|
|
264
|
+
developer.log(
|
|
265
|
+
'💡 Token will be available once APNS token is received',
|
|
266
|
+
name: 'MessagingService',
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/// Save FCM token to Firestore devices collection
|
|
273
|
+
Future<void> saveFCMToken(String userId, String fcmToken) async {
|
|
274
|
+
try {
|
|
275
|
+
// Get device ID from device info
|
|
276
|
+
final deviceId = await _getDeviceId();
|
|
277
|
+
|
|
278
|
+
if (deviceId.isEmpty) {
|
|
279
|
+
developer.log(
|
|
280
|
+
'⚠️ Device ID is empty, skipping FCM token save',
|
|
281
|
+
name: 'MessagingService',
|
|
282
|
+
);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
developer.log(
|
|
287
|
+
'💾 Saving FCM token to Firestore...',
|
|
288
|
+
name: 'MessagingService',
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
// Get FIAM installation ID if available
|
|
292
|
+
final inAppMessagingService = InAppMessagingService();
|
|
293
|
+
final installationId = inAppMessagingService.installationId;
|
|
294
|
+
|
|
295
|
+
final data = {
|
|
296
|
+
'userId': userId,
|
|
297
|
+
'fcmToken': fcmToken,
|
|
298
|
+
'fcmTokenUpdatedAt': FieldValue.serverTimestamp(),
|
|
299
|
+
'lastSeenAt': FieldValue.serverTimestamp(),
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// Add Firebase installation ID if available
|
|
303
|
+
if (installationId != null) {
|
|
304
|
+
data['installationId'] = installationId;
|
|
305
|
+
data['installationIdUpdatedAt'] = FieldValue.serverTimestamp();
|
|
306
|
+
developer.log(
|
|
307
|
+
'💾 Including Firebase installation ID: ${installationId.substring(0, 8)}...',
|
|
308
|
+
name: 'MessagingService',
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
await _firestore
|
|
313
|
+
.collection('devices')
|
|
314
|
+
.doc(deviceId)
|
|
315
|
+
.set(data, SetOptions(merge: true));
|
|
316
|
+
|
|
317
|
+
developer.log('✅ FCM token saved successfully', name: 'MessagingService');
|
|
318
|
+
} catch (e) {
|
|
319
|
+
developer.log('❌ Failed to save FCM token: $e', name: 'MessagingService');
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/// Get device ID (reusing logic from DeviceService)
|
|
324
|
+
Future<String> _getDeviceId() async {
|
|
325
|
+
try {
|
|
326
|
+
final deviceService = DeviceService();
|
|
327
|
+
return await deviceService.getDeviceId();
|
|
328
|
+
} catch (e) {
|
|
329
|
+
developer.log('❌ Failed to get device ID: $e', name: 'MessagingService');
|
|
330
|
+
return '';
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/// Handle foreground messages
|
|
335
|
+
void _handleForegroundMessage(RemoteMessage message) {
|
|
336
|
+
developer.log(
|
|
337
|
+
'Received foreground message: ${message.messageId}',
|
|
338
|
+
name: 'MessagingService',
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
// Show local notification when app is in foreground
|
|
342
|
+
_showLocalNotification(message);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/// Show local notification
|
|
346
|
+
Future<void> _showLocalNotification(RemoteMessage message) async {
|
|
347
|
+
final notification = message.notification;
|
|
348
|
+
final android = message.notification?.android;
|
|
349
|
+
|
|
350
|
+
if (notification != null) {
|
|
351
|
+
await _localNotifications.show(
|
|
352
|
+
id: notification.hashCode,
|
|
353
|
+
title: notification.title,
|
|
354
|
+
body: notification.body,
|
|
355
|
+
notificationDetails: NotificationDetails(
|
|
356
|
+
android: AndroidNotificationDetails(
|
|
357
|
+
'high_importance_channel',
|
|
358
|
+
'High Importance Notifications',
|
|
359
|
+
channelDescription:
|
|
360
|
+
'This channel is used for important notifications.',
|
|
361
|
+
importance: Importance.high,
|
|
362
|
+
priority: Priority.high,
|
|
363
|
+
icon: android?.smallIcon ?? '@mipmap/ic_launcher',
|
|
364
|
+
),
|
|
365
|
+
iOS: const DarwinNotificationDetails(
|
|
366
|
+
presentAlert: true,
|
|
367
|
+
presentBadge: true,
|
|
368
|
+
presentSound: true,
|
|
369
|
+
),
|
|
370
|
+
),
|
|
371
|
+
payload: message.data.toString(),
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/// Handle notification tap
|
|
377
|
+
void _onNotificationTapped(NotificationResponse response) {
|
|
378
|
+
developer.log(
|
|
379
|
+
'Notification tapped: ${response.payload}',
|
|
380
|
+
name: 'MessagingService',
|
|
381
|
+
);
|
|
382
|
+
// TODO: Navigate to specific screen based on payload
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/// Handle message opened app (when app is in background or terminated)
|
|
386
|
+
void _handleMessageOpenedApp(RemoteMessage message) {
|
|
387
|
+
developer.log(
|
|
388
|
+
'Message opened app: ${message.messageId}',
|
|
389
|
+
name: 'MessagingService',
|
|
390
|
+
);
|
|
391
|
+
// TODO: Navigate to specific screen based on message data
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/// Subscribe to a topic
|
|
395
|
+
Future<void> subscribeToTopic(String topic) async {
|
|
396
|
+
try {
|
|
397
|
+
await _firebaseMessaging.subscribeToTopic(topic);
|
|
398
|
+
developer.log('✅ Subscribed to topic: $topic', name: 'MessagingService');
|
|
399
|
+
} catch (e) {
|
|
400
|
+
developer.log(
|
|
401
|
+
'❌ Failed to subscribe to topic: $e',
|
|
402
|
+
name: 'MessagingService',
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/// Unsubscribe from a topic
|
|
408
|
+
Future<void> unsubscribeFromTopic(String topic) async {
|
|
409
|
+
try {
|
|
410
|
+
await _firebaseMessaging.unsubscribeFromTopic(topic);
|
|
411
|
+
developer.log(
|
|
412
|
+
'✅ Unsubscribed from topic: $topic',
|
|
413
|
+
name: 'MessagingService',
|
|
414
|
+
);
|
|
415
|
+
} catch (e) {
|
|
416
|
+
developer.log(
|
|
417
|
+
'❌ Failed to unsubscribe from topic: $e',
|
|
418
|
+
name: 'MessagingService',
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/// Delete FCM token
|
|
424
|
+
Future<void> deleteToken() async {
|
|
425
|
+
try {
|
|
426
|
+
await _firebaseMessaging.deleteToken();
|
|
427
|
+
_fcmToken = null;
|
|
428
|
+
developer.log('✅ FCM token deleted', name: 'MessagingService');
|
|
429
|
+
} catch (e) {
|
|
430
|
+
developer.log(
|
|
431
|
+
'❌ Failed to delete FCM token: $e',
|
|
432
|
+
name: 'MessagingService',
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/// Clean up FCM token from Firestore (call on sign out)
|
|
438
|
+
Future<void> removeFCMTokenFromFirestore(String userId) async {
|
|
439
|
+
try {
|
|
440
|
+
final deviceId = await _getDeviceId();
|
|
441
|
+
|
|
442
|
+
if (deviceId.isEmpty) return;
|
|
443
|
+
|
|
444
|
+
await _firestore.collection('devices').doc(deviceId).set({
|
|
445
|
+
'fcmToken': FieldValue.delete(),
|
|
446
|
+
'fcmTokenUpdatedAt': FieldValue.delete(),
|
|
447
|
+
}, SetOptions(merge: true));
|
|
448
|
+
|
|
449
|
+
developer.log(
|
|
450
|
+
'✅ FCM token removed from Firestore',
|
|
451
|
+
name: 'MessagingService',
|
|
452
|
+
);
|
|
453
|
+
} catch (e) {
|
|
454
|
+
developer.log(
|
|
455
|
+
'❌ Failed to remove FCM token from Firestore: $e',
|
|
456
|
+
name: 'MessagingService',
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import 'package:firebase_remote_config/firebase_remote_config.dart';
|
|
2
|
+
import 'dart:developer' as developer;
|
|
3
|
+
|
|
4
|
+
import '../constants/app_constants.dart';
|
|
5
|
+
|
|
6
|
+
/// Service for managing Firebase Remote Config
|
|
7
|
+
/// Provides feature flags and remote configuration
|
|
8
|
+
class RemoteConfigService {
|
|
9
|
+
static final RemoteConfigService _instance = RemoteConfigService._internal();
|
|
10
|
+
factory RemoteConfigService() => _instance;
|
|
11
|
+
RemoteConfigService._internal();
|
|
12
|
+
|
|
13
|
+
final FirebaseRemoteConfig _remoteConfig = FirebaseRemoteConfig.instance;
|
|
14
|
+
bool _initialized = false;
|
|
15
|
+
|
|
16
|
+
/// Initialize Remote Config with default values
|
|
17
|
+
Future<void> initialize() async {
|
|
18
|
+
if (_initialized) return;
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
// Set config settings
|
|
22
|
+
await _remoteConfig.setConfigSettings(
|
|
23
|
+
RemoteConfigSettings(
|
|
24
|
+
fetchTimeout: AppConstants.remoteConfigFetchTimeout,
|
|
25
|
+
minimumFetchInterval: AppConstants.remoteConfigCacheDuration,
|
|
26
|
+
),
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
// Set default values
|
|
30
|
+
await _remoteConfig.setDefaults(_defaultValues);
|
|
31
|
+
|
|
32
|
+
// Fetch and activate
|
|
33
|
+
await _remoteConfig.fetchAndActivate();
|
|
34
|
+
|
|
35
|
+
_initialized = true;
|
|
36
|
+
developer.log('RemoteConfigService initialized', name: 'RemoteConfig');
|
|
37
|
+
} catch (e) {
|
|
38
|
+
developer.log(
|
|
39
|
+
'Failed to initialize RemoteConfig: $e',
|
|
40
|
+
name: 'RemoteConfig',
|
|
41
|
+
error: e,
|
|
42
|
+
);
|
|
43
|
+
// Don't rethrow - app should work with defaults
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Default values for remote config
|
|
48
|
+
static const Map<String, dynamic> _defaultValues = {
|
|
49
|
+
AppConstants.rcEnableGoogleAuth: false,
|
|
50
|
+
AppConstants.rcEnableAppleAuth: false,
|
|
51
|
+
AppConstants.rcEnableAnonymousAuth: true,
|
|
52
|
+
AppConstants.rcMaintenanceMode: false,
|
|
53
|
+
AppConstants.rcMinimumVersion: '1.0.0',
|
|
54
|
+
AppConstants.rcForceUpdate: false,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// ============= Feature Flags =============
|
|
58
|
+
|
|
59
|
+
bool get enableGoogleAuth {
|
|
60
|
+
return _remoteConfig.getBool(AppConstants.rcEnableGoogleAuth);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
bool get enableAppleAuth {
|
|
64
|
+
return _remoteConfig.getBool(AppConstants.rcEnableAppleAuth);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
bool get enableAnonymousAuth {
|
|
68
|
+
return _remoteConfig.getBool(AppConstants.rcEnableAnonymousAuth);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
bool get maintenanceMode {
|
|
72
|
+
return _remoteConfig.getBool(AppConstants.rcMaintenanceMode);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
String get minimumVersion {
|
|
76
|
+
return _remoteConfig.getString(AppConstants.rcMinimumVersion);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
bool get forceUpdate {
|
|
80
|
+
return _remoteConfig.getBool(AppConstants.rcForceUpdate);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ============= Generic Getters =============
|
|
84
|
+
|
|
85
|
+
bool getBool(String key) {
|
|
86
|
+
return _remoteConfig.getBool(key);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
String getString(String key) {
|
|
90
|
+
return _remoteConfig.getString(key);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
int getInt(String key) {
|
|
94
|
+
return _remoteConfig.getInt(key);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
double getDouble(String key) {
|
|
98
|
+
return _remoteConfig.getDouble(key);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ============= Refresh Config =============
|
|
102
|
+
|
|
103
|
+
Future<bool> fetchConfig() async {
|
|
104
|
+
try {
|
|
105
|
+
final updated = await _remoteConfig.fetchAndActivate();
|
|
106
|
+
developer.log(
|
|
107
|
+
'Config fetched: ${updated ? "updated" : "no changes"}',
|
|
108
|
+
name: 'RemoteConfig',
|
|
109
|
+
);
|
|
110
|
+
return updated;
|
|
111
|
+
} catch (e) {
|
|
112
|
+
developer.log(
|
|
113
|
+
'Failed to fetch config: $e',
|
|
114
|
+
name: 'RemoteConfig',
|
|
115
|
+
error: e,
|
|
116
|
+
);
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/// Force fetch config (ignores cache)
|
|
122
|
+
Future<bool> forceFetchConfig() async {
|
|
123
|
+
try {
|
|
124
|
+
await _remoteConfig.setConfigSettings(
|
|
125
|
+
RemoteConfigSettings(
|
|
126
|
+
fetchTimeout: AppConstants.remoteConfigFetchTimeout,
|
|
127
|
+
minimumFetchInterval: Duration.zero,
|
|
128
|
+
),
|
|
129
|
+
);
|
|
130
|
+
final updated = await _remoteConfig.fetchAndActivate();
|
|
131
|
+
|
|
132
|
+
// Reset to normal fetch interval
|
|
133
|
+
await _remoteConfig.setConfigSettings(
|
|
134
|
+
RemoteConfigSettings(
|
|
135
|
+
fetchTimeout: AppConstants.remoteConfigFetchTimeout,
|
|
136
|
+
minimumFetchInterval: AppConstants.remoteConfigCacheDuration,
|
|
137
|
+
),
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
developer.log('Config force fetched', name: 'RemoteConfig');
|
|
141
|
+
return updated;
|
|
142
|
+
} catch (e) {
|
|
143
|
+
developer.log(
|
|
144
|
+
'Failed to force fetch config: $e',
|
|
145
|
+
name: 'RemoteConfig',
|
|
146
|
+
error: e,
|
|
147
|
+
);
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ============= Utility Methods =============
|
|
153
|
+
|
|
154
|
+
Map<String, dynamic> getAllValues() {
|
|
155
|
+
return _remoteConfig.getAll().map(
|
|
156
|
+
(key, value) => MapEntry(key, value.asString()),
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
DateTime get lastFetchTime {
|
|
161
|
+
return _remoteConfig.lastFetchTime;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
RemoteConfigFetchStatus get lastFetchStatus {
|
|
165
|
+
return _remoteConfig.lastFetchStatus;
|
|
166
|
+
}
|
|
167
|
+
}
|