---
# System prepended metadata

title: White-Label App Doku

---

# 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/

