# White-Label App Doku Describes steps needed to add a new white label app. ## Steps Needed in Repository 1. Create a new flutter app in the root directory of the repository - `flutter create mozarteum_app` 2. Remove not needed directories - `./linux`, `./windows`, `./web`, `./test`, `./macos` 3. Copy `assets/images` from `hublz_app` - delete event fallback images - exchange logos, markers, onboarding images 5. Copy `assets/config.json` from `hublz_app` 6. Copy `l10n.yaml` from `hublz_app` 7. Add a `CHANGELOG.md` file 8. Copy `l10n` directory from `hublz_app/lib` and adjust all translations in each language file that you want to adapt - the folder structure in `./mozarteum_app` should look like ``` .dart_tool .idea android ios lib l10n intl_de.arb intl_en.arb main.dart .gitignore .metadata analysis_options.yaml mozarteum_app.iml pubspeck.lock pubspec.yaml l10n.yaml CHANGELOG.md README.md ``` 6. Adjust `pubspec.yaml` to ``` name: mozarteum_app description: Mozarteum Whitelabel App. publish_to: 'none' version: 1.0.0+1 environment: sdk: '>=2.18.2 <3.0.0' dependencies: flutter: sdk: flutter hublz: path: ../hublz_app cupertino_icons: ^1.0.2 flutter_native_splash: ^2.0.4 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^2.0.0 flutter_launcher_icons: "^0.13.1" flutter_launcher_icons: image_path_android: assets/images/Mozarteum_Logo.png image_path_ios: assets/images/Mozarteum_Logo.png android: true ios: true remove_alpha_ios: true flutter_native_splash: color: "#ff66cc" image: assets/images/Mozarteum_Logo.png flutter: uses-material-design: true generate: true assets: - assets/config.json - assets/images/ - ../hublz_app/assets/images/ - ../hublz_app/assets/audio/ - ../hublz_app/assets/icons/ ``` 7. Adjust `config.json` 8. Adjust the `lib/main.dart` to ``` // @dart=2.9 import 'package:hublz/main.dart' as entry; void main() => entry.main(); ``` 10. Adjust `.gitlab-ci.yaml` and add a new translation check job for the new app ``` # mozarteum_app check-translations_mozarteum: stage: quality variables: APP_DIRECTORY: ./mozarteum_app <<: *translation_check ``` ### iOS - for registering the App in Firebase take at look at the [Firebase](firebase) section below - in `ios/Podfile` - uncomment Line 2 and set to `platform :ios, '12.1'` - add following pods (Line 30) ``` target 'Runner' do pod 'FirebaseAuth' pod 'FirebaseFirestore' pod 'FirebaseCrashlytics' # Recommended: Add the Firebase pod for Google Analytics pod 'FirebaseAnalytics' use_frameworks! use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) target.build_configurations.each do |config| config.build_settings['ENABLE_BITCODE'] = 'NO' config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0' config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ '$(inherited)', # dart: PermissionGroup.notification 'PERMISSION_NOTIFICATIONS=1', ] end end end ``` - also change in Xcode: (Project) Runner -Deployment Target and Target-Runner - Minimum Deployment to 12.1 ? - in Xcode (Targets) Runner `Signing & Capabilities` tab add following capabilities - `Associated Domains`: - insert domains that are used for deeplinks e.g. - `applinks:content.hublz.art` - `applinks:event.hublz.art` - `applinks:spot.hublz.art` - `applinks:package.hublz.art` - `applinks:group.hublz.art` - `Background Modes`: (needed for push notifications) - make sure `Background fetch` and `Remote notifications` are enabled - `MDM Managed Associated Domains` - `Push Notifications` (needed for push notifications) - `Sign in with Apple` - Copy following keys to `ios/Runner/Info.plist` ``` <key>CFBundleLocalizations</key> <array> <string>de</string> <string>en</string> </array> <key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleTypeRole</key> <string>Editor</string> <key>CFBundleURLName</key> <string>Bundle ID</string> <key>CFBundleURLSchemes</key> <array> <!-- Google Sign-in Section : copied from GoogleService-Info.plist key REVERSED_CLIENT_ID --> <string>com.googleusercontent.apps.912886982895-o42uqbdfi1d6vlql4rcghg3ha6f40vne</string> </array> </dict> <dict> <key>CFBundleIdentifier</key> <string></string> <key>CFBundleTypeRole</key> <string>Editor</string> </dict> <dict/> </array> <key>FacebookAppID</key> <string>212179623833113</string> <key>FirebaseDynamicLinksCustomDomains</key> <array> <string>https://content.hublz.art</string> <string>https://package.hublz.art</string> <string>https://spot.hublz.art</string> <string>https://event.hublz.art</string> <string>https://group.hublz.art</string> </array> <key>ITSAppUsesNonExemptEncryption</key> <false/> <key>LSApplicationQueriesSchemes</key> <array> <string>instagram-stories</string> <string>facebook-stories</string> <string>facebook</string> <string>instagram</string> <string>twitter</string> <string>whatsapp</string> <string>tg</string> </array> <key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> <key>NSAllowsArbitraryLoadsInWebContent</key> <true/> </dict> <key>NSCalendarsUsageDescription</key> <string>Calendar permission is required to be able to add cultural events to your calendar.</string> <key>NSCameraUsageDescription</key> <string>Camera permission is required for barcode scanning.</string> <key>NSContactsUsageDescription</key> <string>Contacts permission is required for location autocomplete when adding an event to your calendar.</string> <key>NSLocationAlwaysAndWhenInUseUsageDescription</key> <string>Location permission is required to play outdoor artventures and filter content by distance.</string> <key>NSLocationAlwaysUsageDescription</key> <string>Location permission is required to play outdoor artventures and filter content by distance.</string> <key>NSLocationWhenInUseUsageDescription</key> <string>Location permission is required to play outdoor artventures and filter content by distance.</string> <key>NSMicrophoneUsageDescription</key> <string>Microphone permission is required to play an autio quiz.</string> <key>UIBackgroundModes</key> <array> <string>fetch</string> <string>remote-notification</string> </array> ``` - Replace content in `ios/AppDelegate.swift` with ``` import UIKit import Flutter import Firebase import GoogleMaps @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { FirebaseApp.configure() GMSServices.provideAPIKey("AIzaSyBryjVQjLGE1DJ5VzllWhLbbimFQeuMACE") // This is required to make any communication available in the action isolate. FlutterLocalNotificationsPlugin.setPluginRegistrantCallback { (registry) in GeneratedPluginRegistrant.register(with: registry) } if #available(iOS 10.0, *) { UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate } GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } ``` - Crashlytics Setup: https://firebase.google.com/docs/crashlytics/get-deobfuscated-reports?hl=de&authuser=0&platform=flutter#apple ### Android - for registering the App in Firebase take at look at the [Firebase](firebase) section below - in `android/build.gradle` - in dependencies add ``` classpath 'com.google.gms:google-services:4.3.14' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2' ``` - in `android/app/build.gradle` - add after Line 26 `apply plugin: 'com.google.gms.google-services'` - change `compileSkdVersion` = 33 - add signingConfigs ``` signingConfigs { release { keyAlias keystoreProperties['keyAlias'] keyPassword keystoreProperties['keyPassword'] storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null storePassword keystoreProperties['storePassword'] } } ``` - change application id - in `android { defaultConfig { applicationId "here"}}` - and in all Manifest files (debug, main, profile) - MainAktivity.kt - change minSdkVersion = 23, targetSdkVersion = 31 in defaultConfig - add `multiDexEnabled true` in defaultConfig (Line 56) - add dependencies ``` implementation "androidx.browser:browser:1.3.0" implementation 'androidx.multidex:multidex:2.0.1' implementation platform('com.google.firebase:firebase-bom:31.0.2') implementation 'com.google.firebase:firebase-analytics-ktx' implementation 'com.google.firebase:firebase-crashlytics-ktx' ``` - in `android/app/src/main/AndroidManifest.xml` - change the package name in the `<manifest ... package="here"` tag - add tools to manifest tag as `<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="art.hublz.mozarteum_app">` - add permission requests starting after mainfest start tag in Line 4 ``` <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_CALENDAR" /> <uses-permission android:name="android.permission.READ_CALENDAR" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.USE_EXACT_ALARM" /> ``` - add visibility configuration for add_2_calender after permissions ``` <!-- visibility configuration needed from API 30 for add_2_calendar package --> <queries> <intent> <action android:name="android.intent.action.INSERT" /> <data android:mimeType="vnd.android.cursor.item/event" /> </intent> </queries> ``` - inside the ` <activity android:name=".MainActivity"` add a new intent-filter for deeplinks ``` <intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:host="content.hublz.art" android:scheme="https"/> <data android:host="spot.hublz.art" android:scheme="https"/> <data android:host="package.hublz.art" android:scheme="https"/> <data android:host="event.hublz.art" android:scheme="https"/> <data android:host="group.hublz.art" android:scheme="https"/> </intent-filter> ``` - inside the ` <activity android:name=".MainActivity"` add a new receivers for flutter_local_notifications ``` <!-- Flutter Local Notifications Scheduled Notifications --> <receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" /> <receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED"/> <action android:name="android.intent.action.MY_PACKAGE_REPLACED"/> <action android:name="android.intent.action.QUICKBOOT_POWERON" /> <action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/> </intent-filter> </receiver> <receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ActionBroadcastReceiver" /> ``` - after the `</activity>` tag add a new meta-data for google maps api key ``` <meta-data android:name="com.google.android.geo.API_KEY" android:value="AIzaSyBryjVQjLGE1DJ5VzllWhLbbimFQeuMACE"/> ``` - after the meta tags add a new provider tag for social sharing ``` <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.com.shekarmudaliyar.social_share" android:exported="false" android:grantUriPermissions="true" tools:replace="android:authorities"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" /> </provider> ``` - copy `android/app/src/main/res/xml` from `hublz_app` to the whitelabel app ### Launch Icon - we use this package for generating the launcher icon: https://pub.dev/packages/flutter_launcher_icons - in `pubspec.yaml` - Add `flutter_launcher_icons: "^0.11.0"` to the `dev_dependencies` section - Add new section ``` flutter_icons: image_path_android: assets/images/Mozarteum_Logo.png image_path_ios: assets/images/Mozarteum_Logo.png android: true ios: true remove_alpha_ios: true ``` - change image paths - run `flutter clean && flutter pub get` - run `flutter pub run flutter_launcher_icons ` ### Native Splash - we use this package for generating the native splash https://pub.dev/packages/flutter_native_splash - in `pubspec.yaml` - add `flutter_native_splash: ^2.2.17` to the `dependencies` section - add new section ``` flutter_native_splash: color: "#ff66cc" image: assets/images/Mozarteum_Logo.png ``` - change image and icon to primary color - run `flutter pub get` - run `flutter pub run flutter_native_splash:create ` --- ## Firebase Steps need to do in Firebase Console https://console.firebase.google.com/project/_/overview?pli=1. 1. Create new project. See [Google cloud / Firebase setup for white labels](/QdPBMoWZTP6DqUnSnA7bSQ) 2. Add an **ios** app. - Apple-Bundle-Id: can be found in Xcode `ios/Runner.xcworkspace` in General tab - Download `GoogleService-Info.plist` and add it to the Xcode project by right-click in the `Runner` from the left-hand side and click `Add files to Runner` (make sure checkbox`Copy items if needed` is selected) 3. Add the `dSYM File` from AppStore for crashlytics support - Add a new Build Phase with [firebase Crashlytics] see hublz App 4. Add an **android** app. - enter the Android package name from the app e.g. `cult.spot` - Download google-service.json - Add the `google-service.json` to `android/app/` directory - make sure the SHA Hash from the signing key is in the firebase projekt - get the HASH `keytool -v -list -keystore key.jks` 5. Add an **ios** app. - enter the Ios package name from the app e.g. `cult.spot` - Download GoogleService-Info.plist - Add the `GoogleService-Info.plist` to `ios/Runner` directory 6. What needs to be done for Push Notification on iOS - go to https://developer.apple.com/account/resources/authkeys/list - create a "Apple Push Notification service (APNs)" key - download the key (.p8) - Upload in it Firebase > Project Settings > Cloud Messaging > APNs-Authentifizierungsschlüssel ### Authentication - Enable Sign-In Methods - Google - add SHA1 to Apps in Firebase Projekt Settings - download google-service.json again - change CFBundleURLSchemes in `Info.plist` to REVERSED_CLIENT_ID from `GoogleService-Info.plist` ``` <key>CFBundleURLSchemes</key> <array> <string>com.googleusercontent.apps.962674250814-3ghmmfonnps0qqam862pmt10l6m1daup</string> </array> ``` - Telefon - add Test Number"+43 7444 555666" with Code "012345" - Anonym - Email/Password - Apple ## Push Notifictions Using `flutter_local_notifications` and Firebase Messaging https://firebase.flutter.dev/docs/messaging/overview/ ### Android - request permissions and add Gradle dependencies/setup as described here https://pub.dev/packages/flutter_local_notifications#-android-setup ### iOS - add to the AppDelegate.swift ``` if #available(iOS 10.0, *) { UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate } ``` - add Push Notification & Background Modes capability in "Signing & Capabilities" - for Background Modes make sure "Background Fetch" & "Remote notifications" are enabled ## Deeplinks ### Firebase - enable Firebase Hosting - in "Firebase Dynamic Links" tab add Url Präfixe e.g. https://content.mzrt.hublz.art and add DNS entry to server ### Android in the AndroidManifest.yaml add/change the <intent-filter> in the <activity> tag ``` <intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:host="mozarteum.hublz.art" android:scheme="https"/> <data android:host="content.mozarteum.hublz.art" android:scheme="https"/> <data android:host="spot.mozarteum.hublz.art" android:scheme="https"/> <data android:host="package.mozarteum.hublz.art" android:scheme="https"/> <data android:host="event.mozarteum.hublz.art" android:scheme="https"/> <data android:host="group.mozarteum.hublz.art" android:scheme="https"/> </intent-filter> ``` ### iOS - open in Xcode and in "Signing & Capabilities" add domains as needed e.g. `applinks:content.skgt24.hublz.art` - in `ios/Runner/Info.plist` for key `FirebaseDynamicLinksCustomDomains` also adjust domains as done in Xcode ``` <key>FirebaseDynamicLinksCustomDomains</key> <array> <string>https://mozarteum.hublz.art</string> <string>https://content.mozarteum.hublz.art</string> <string>https://package.mozarteum.hublz.art</string> <string>https://spot.mozarteum.hublz.art</string> <string>https://event.mozarteum.hublz.art</string> <string>https://group.mozarteum.hublz.art</string> </array> ``` ## Code Magic Steps need to do in CodeMagic. 1. Duplicate workflow on hublz_app and rename. 2. **Build triggers**: - check trigger on tag creation - adjust watched tag patterns 3. **Environment Variables**: Replace environments variables with new ones (should be secure) - IOS_FIREBASE_SECRET: Copy Google.Info.plist - ANDROID_FIREBASE_SECRET: Copy content of google-services.json 4. **Pre-Build**: Change PROJECT_ROOT in Pre-Build script 5. **Build**: Set build dir to subdirectory of the app e.g. `mozarteum_app`. 7. **Distribution** - **Android Code Signing**: - Create keystore for signing `keytool -genkey -v -keystore keystore_name.jks -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 -alias alias_name` - Upload Keystore file (cultspot.key.jks) - Update Keystore password, key alias, and key password - https://docs.codemagic.io/flutter-code-signing/android-code-signing/ - **Google Play Publishing**: - Create API Key needed! - Upload created API Key in "Credentials (.json)*" - https://docs.codemagic.io/flutter-publishing/publishing-to-google-play/ - **iOS Code Signing**: - https://docs.codemagic.io/flutter-code-signing/ios-code-signing/ - Select "Manual" - Create a Distribution Certificate and export it from your keychain in .p12 format (https://mycodetips.com/development/exporting-importing-certificates-profiles-1482.html) - Create a Provisioning Profile that includes your Distribution Certificate and download it - Upload both in CodeMagic - **iOS App Store Publishing**: - Create API Key needed! - Log in to App Store Connect and navigate to Users and Access > Keys. - Click on the + sign to generate a new API key. - Enter 'codemagic' as the name for the key and select an access level. The key needs to have App Manager permissions. - Click "Generate". - Download API Key - Take note of the Issuer ID and the Key ID. - in CodeMagic go to Teams > Personal Account > Integrations - Click on "Manage Keys" on Developer Portal Integration - Click on "Add another key" and upload downloaded API key and enter Issuer ID and Key ID. - In the workflow in "Distribution > App Store Connect" select the new create Api Key - https://docs.codemagic.io/flutter-publishing/publishing-to-app-store/