Skip to content

Commit

Permalink
Upgrade Spezi Onboarding (#21)
Browse files Browse the repository at this point in the history
  • Loading branch information
Supereg authored Sep 16, 2023
1 parent 53546b6 commit 3e6c967
Show file tree
Hide file tree
Showing 15 changed files with 223 additions and 183 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ jobs:
swiftlint:
name: SwiftLint
uses: StanfordBDHG/.github/.github/workflows/swiftlint.yml@v2
codeql:
name: CodeQL
uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
with:
codeql: true
fastlanelane: codeql
buildandtest:
name: Build and Test
uses: StanfordBDHG/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
Expand Down
1 change: 1 addition & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ deployment_target: # Availability checks or attributes shouldn’t be using olde
excluded: # paths to ignore during linting. Takes precedence over `included`.
- .build
- .swiftpm
- .codeql
- .derivedData

closure_body_length: # Closure bodies should not span too many lines.
Expand Down
8 changes: 8 additions & 0 deletions NAMS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
653A256228338800005D4D48 /* NAMSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653A256128338800005D4D48 /* NAMSTests.swift */; };
A91459762A4AF4E000A04641 /* SchedulerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91459752A4AF4E000A04641 /* SchedulerTests.swift */; };
A916ADD52AB60227006960DF /* NotificationPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A916ADD42AB60227006960DF /* NotificationPermissions.swift */; };
A916ADD72AB62012006960DF /* ProcessInfo+PreviewSimulator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A916ADD62AB62012006960DF /* ProcessInfo+PreviewSimulator.swift */; };
A916ADD92AB6217D006960DF /* OnboardingFlow+PreviewSimulator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A916ADD82AB6217D006960DF /* OnboardingFlow+PreviewSimulator.swift */; };
A9610AB72AB4847400E46ADE /* SpeziMockWebService in Frameworks */ = {isa = PBXBuildFile; productRef = A9610AB62AB4847400E46ADE /* SpeziMockWebService */; };
A9610AB92AB4868D00E46ADE /* NAMSStandard.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9610AB82AB4868D00E46ADE /* NAMSStandard.swift */; };
A9610ABB2AB49F0900E46ADE /* MockUploadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9610ABA2AB49F0900E46ADE /* MockUploadTests.swift */; };
Expand Down Expand Up @@ -124,6 +126,8 @@
653A258928339462005D4D48 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
A91459752A4AF4E000A04641 /* SchedulerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SchedulerTests.swift; sourceTree = "<group>"; };
A916ADD42AB60227006960DF /* NotificationPermissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissions.swift; sourceTree = "<group>"; };
A916ADD62AB62012006960DF /* ProcessInfo+PreviewSimulator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProcessInfo+PreviewSimulator.swift"; sourceTree = "<group>"; };
A916ADD82AB6217D006960DF /* OnboardingFlow+PreviewSimulator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OnboardingFlow+PreviewSimulator.swift"; sourceTree = "<group>"; };
A9610AB82AB4868D00E46ADE /* NAMSStandard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NAMSStandard.swift; sourceTree = "<group>"; };
A9610ABA2AB49F0900E46ADE /* MockUploadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUploadTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
Expand Down Expand Up @@ -202,6 +206,7 @@
2FE5DC3429EDD7CA004B9AB4 /* Welcome.swift */,
2DC173772F6FD3C05EBFCE52 /* FinishedSetup.swift */,
A916ADD42AB60227006960DF /* NotificationPermissions.swift */,
A916ADD82AB6217D006960DF /* OnboardingFlow+PreviewSimulator.swift */,
);
path = Onboarding;
sourceTree = "<group>";
Expand Down Expand Up @@ -247,6 +252,7 @@
2FE5DC4229EDD7F2004B9AB4 /* Binding+Negate.swift */,
2FE5DC4329EDD7F2004B9AB4 /* Bundle+Image.swift */,
2FE5DC4429EDD7F2004B9AB4 /* CodableArray+RawRepresentable.swift */,
A916ADD62AB62012006960DF /* ProcessInfo+PreviewSimulator.swift */,
);
path = Helper;
sourceTree = "<group>";
Expand Down Expand Up @@ -530,6 +536,8 @@
2FE5DC3A29EDD7CA004B9AB4 /* Welcome.swift in Sources */,
2FE5DC4529EDD7F2004B9AB4 /* Binding+Negate.swift in Sources */,
2FE5DCB229EE6107004B9AB4 /* NAMSLogin.swift in Sources */,
A916ADD72AB62012006960DF /* ProcessInfo+PreviewSimulator.swift in Sources */,
A916ADD92AB6217D006960DF /* OnboardingFlow+PreviewSimulator.swift in Sources */,
2FC975A82978F11A00BA99FE /* Home.swift in Sources */,
2FE5DC4E29EDD7FA004B9AB4 /* ScheduleView.swift in Sources */,
2FE5DC3729EDD7CA004B9AB4 /* OnboardingFlow.swift in Sources */,
Expand Down
15 changes: 15 additions & 0 deletions NAMS/Helper/ProcessInfo+PreviewSimulator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// This source file is part of the Stanford Spezi Template Application project
//
// SPDX-FileCopyrightText: 2023 Stanford University
//
// SPDX-License-Identifier: MIT
//
import Foundation


extension ProcessInfo {
var isPreviewSimulator: Bool {
environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"
}
}
59 changes: 17 additions & 42 deletions NAMS/Onboarding/AccountSetup/AccountSetup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import SwiftUI


struct AccountSetup: View {
@Binding private var onboardingSteps: [OnboardingFlow.Step]
@EnvironmentObject private var account: Account
@EnvironmentObject private var scheduler: NAMSScheduler
@EnvironmentObject private var standard: NAMSStandard
@EnvironmentObject private var onboardingNavigationPath: OnboardingNavigationPath

@State private var signingOutPretrigger = false

var body: some View {
OnboardingView(
Expand All @@ -37,10 +37,12 @@ struct AccountSetup: View {
}
)
.onReceive(account.objectWillChange) {
if account.signedIn {
Task { @MainActor in
await moveToNextOnboardingStep()
if !signingOutPretrigger {
if account.signedIn {
onboardingNavigationPath.nextStep()
}
} else {
signingOutPretrigger = false
}
}
}
Expand Down Expand Up @@ -77,9 +79,7 @@ struct AccountSetup: View {
UserView()
.padding()
Button("Logout", role: .destructive) {
// workaround as of https://github.com/StanfordSpezi/SpeziTemplateApplication/issues/21
account.signedIn = false

signingOutPretrigger = true
try? Auth.auth().signOut()
}
}
Expand All @@ -91,58 +91,33 @@ struct AccountSetup: View {
OnboardingActionsView(
"ACCOUNT_NEXT",
action: {
await moveToNextOnboardingStep()
onboardingNavigationPath.nextStep()
}
)
} else {
OnboardingActionsView(
primaryText: "ACCOUNT_SIGN_UP",
primaryAction: {
onboardingSteps.append(.signUp)
onboardingNavigationPath.append(customView: NAMSSignUp())
},
secondaryText: "ACCOUNT_LOGIN",
secondaryAction: {
onboardingSteps.append(.login)
onboardingNavigationPath.append(customView: NAMSLogin())
}
)
}
}


init(onboardingSteps: Binding<[OnboardingFlow.Step]>) {
self._onboardingSteps = onboardingSteps
}


@MainActor
private func moveToNextOnboardingStep() async {
if await !scheduler.localNotificationAuthorization {
onboardingSteps.append(.notificationPermissions)
} else {
onboardingSteps.append(.finished)
}

await standard.signedIn()

// Unfortunately, SwiftUI currently animates changes in the navigation path that do not change
// the current top view. Therefore we need to do the following async procedure to remove the
// `.login` and `.signUp` steps while disabling the animations before and re-enabling them
// after the elements have been changed.
try? await Task.sleep(for: .seconds(1.0))
UIView.setAnimationsEnabled(false)
onboardingSteps.removeAll(where: { $0 == .login || $0 == .signUp })
try? await Task.sleep(for: .seconds(1.0))
UIView.setAnimationsEnabled(true)
}
}


#if DEBUG
struct AccountSetup_Previews: PreviewProvider {
@State private static var path: [OnboardingFlow.Step] = []

static var previews: some View {
AccountSetup(onboardingSteps: $path)
OnboardingStack(startAtStep: AccountSetup.self) {
for onboardingView in OnboardingFlow.previewSimulatorViews {
onboardingView
}
}
.environmentObject(Account(accountServices: []))
.environmentObject(FirebaseAccountConfiguration(emulatorSettings: (host: "localhost", port: 9099)))
}
Expand Down
2 changes: 1 addition & 1 deletion NAMS/Onboarding/AccountSetup/IconView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import SwiftUI


struct IconView: View {
let size: Double
private let size: Double

var body: some View {
Image(uiImage: Bundle.main.image(withName: "AppIcon", fileExtension: "png"))
Expand Down
6 changes: 3 additions & 3 deletions NAMS/Onboarding/AccountSetup/UserView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import SwiftUI


struct UserView: View {
@EnvironmentObject var account: Account
@EnvironmentObject var firebaseAccountConfiguration: FirebaseAccountConfiguration
@EnvironmentObject private var account: Account
@EnvironmentObject private var firebaseAccountConfiguration: FirebaseAccountConfiguration


var body: some View {
Expand Down Expand Up @@ -57,7 +57,7 @@ struct UserView: View {


#if DEBUG
struct SwiftUIView_Previews: PreviewProvider {
struct UserView_Previews: PreviewProvider {
static var previews: some View {
UserView()
.padding()
Expand Down
5 changes: 2 additions & 3 deletions NAMS/Onboarding/FinishedSetup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import SpeziOnboarding
import SwiftUI

struct FinishedSetup: View {
@AppStorage(StorageKeys.onboardingFlowComplete)
var completedOnboardingFlow = false
@EnvironmentObject private var onboardingNavigationPath: OnboardingNavigationPath

var body: some View {
OnboardingView(
Expand Down Expand Up @@ -42,7 +41,7 @@ struct FinishedSetup: View {
},
actionView: {
OnboardingActionsView("Start") {
completedOnboardingFlow = true
onboardingNavigationPath.nextStep()
}
}
)
Expand Down
30 changes: 15 additions & 15 deletions NAMS/Onboarding/NotificationPermissions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ import SwiftUI


struct NotificationPermissions: View {
@EnvironmentObject var scheduler: NAMSScheduler
@Binding private var onboardingSteps: [OnboardingFlow.Step]
@State var notificationProcessing = false
@EnvironmentObject private var scheduler: NAMSScheduler
@EnvironmentObject private var onboardingNavigationPath: OnboardingNavigationPath

@State private var notificationProcessing = false


var body: some View {
Expand All @@ -41,37 +42,36 @@ struct NotificationPermissions: View {
action: {
do {
notificationProcessing = true
// notifications are not available in the preview simulator.
if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" {
// Notification Authorization are not available in the preview simulator.
if ProcessInfo.processInfo.isPreviewSimulator {
try await _Concurrency.Task.sleep(for: .seconds(5))
} else {
try await scheduler.requestLocalNotificationAuthorization()
}
} catch {
print("Could not request notification permissions.")
}

notificationProcessing = false
onboardingSteps.append(.finished)
onboardingNavigationPath.nextStep()
}
)
}
)
.navigationBarBackButtonHidden(notificationProcessing)
}


init(onboardingSteps: Binding<[OnboardingFlow.Step]>) {
self._onboardingSteps = onboardingSteps
.navigationBarBackButtonHidden(notificationProcessing)
.navigationTitle("") // // Small fix as otherwise "Login" or "Sign up" is still shown in the nav bar
}
}


#if DEBUG
struct NotificationPermissions_Previews: PreviewProvider {
@State private static var path: [OnboardingFlow.Step] = []

static var previews: some View {
NotificationPermissions(onboardingSteps: $path)
OnboardingStack(startAtStep: NotificationPermissions.self) {
for onboardingView in OnboardingFlow.previewSimulatorViews {
onboardingView
}
}
}
}
#endif
16 changes: 16 additions & 0 deletions NAMS/Onboarding/OnboardingFlow+PreviewSimulator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// This source file is part of the Stanford Spezi Template Application project
//
// SPDX-FileCopyrightText: 2023 Stanford University
//
// SPDX-License-Identifier: MIT
//
import SwiftUI


/// Defines onboarding views that are shown in the Xcode preview simulator
extension OnboardingFlow {
static let previewSimulatorViews: [any View] = {
[Welcome(), AccountSetup(), NotificationPermissions(), FinishedSetup()]
}()
}
51 changes: 22 additions & 29 deletions NAMS/Onboarding/OnboardingFlow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,44 +8,37 @@

import SpeziAccount
import SpeziFirebaseAccount
import SpeziOnboarding
import SwiftUI


/// Displays an multi-step onboarding flow for the Neurodevelopment Assessment and Monitoring System (NAMS).
struct OnboardingFlow: View {
enum Step: String, Codable {
case accountSetup
case login
case signUp
case notificationPermissions
case finished
}

@SceneStorage(StorageKeys.onboardingFlowStep)
private var onboardingSteps: [Step] = []
@EnvironmentObject private var scheduler: NAMSScheduler

@AppStorage(StorageKeys.onboardingFlowComplete)
var completedOnboardingFlow = false
private var completedOnboardingFlow = false

@State private var localNotificationAuthorization = false

var body: some View {
NavigationStack(path: $onboardingSteps) {
Welcome(onboardingSteps: $onboardingSteps)
.navigationDestination(for: Step.self) { onboardingStep in
switch onboardingStep {
case .accountSetup:
AccountSetup(onboardingSteps: $onboardingSteps)
case .login:
NAMSLogin()
case .signUp:
NAMSSignUp()
case .notificationPermissions:
NotificationPermissions(onboardingSteps: $onboardingSteps)
case .finished:
FinishedSetup()
}
}
.navigationBarTitleDisplayMode(.inline)
OnboardingStack(onboardingFlowComplete: $completedOnboardingFlow) {
Welcome()

if !FeatureFlags.disableFirebase {
AccountSetup()
}

if !localNotificationAuthorization {
NotificationPermissions()
}

FinishedSetup()
}
.interactiveDismissDisabled(!completedOnboardingFlow)
.task {
localNotificationAuthorization = await scheduler.localNotificationAuthorization
}
.interactiveDismissDisabled(!completedOnboardingFlow)
}
}

Expand Down
Loading

0 comments on commit 3e6c967

Please sign in to comment.