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