Skip to content

Commit 31eb0ae

Browse files
committed
ux: improve onboarding view
1 parent 7a9b8ea commit 31eb0ae

File tree

3 files changed

+179
-118
lines changed

3 files changed

+179
-118
lines changed

Localizable.xcstrings

Lines changed: 48 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4812,42 +4812,42 @@
48124812
}
48134813
}
48144814
},
4815-
"Before we start, we need to go over a few things:" : {
4815+
"Before we start, we need to go over a few things." : {
48164816
"localizations" : {
48174817
"de" : {
48184818
"stringUnit" : {
48194819
"state" : "translated",
4820-
"value" : "Bevor wir starten, müssen wir einige Dinge durchgehen:"
4820+
"value" : "Bevor wir beginnen, müssen wir ein paar Dinge durchgehen."
48214821
}
48224822
},
48234823
"es" : {
48244824
"stringUnit" : {
48254825
"state" : "translated",
4826-
"value" : "Antes de empezar, necesitamos repasar algunas cosas:"
4826+
"value" : "Antes de comenzar, necesitamos repasar algunas cosas."
48274827
}
48284828
},
48294829
"it" : {
48304830
"stringUnit" : {
48314831
"state" : "translated",
4832-
"value" : "Prima di iniziare, dobbiamo rivedere alcune cose:"
4832+
"value" : "Prima di iniziare, dobbiamo rivedere alcune cose."
48334833
}
48344834
},
48354835
"nl" : {
48364836
"stringUnit" : {
48374837
"state" : "translated",
4838-
"value" : "Voordat we beginnen moeten we even een paar dingen doornemen:"
4838+
"value" : "Voordat we beginnen, moeten we een paar punten doornemen."
48394839
}
48404840
},
48414841
"uk" : {
48424842
"stringUnit" : {
48434843
"state" : "translated",
4844-
"value" : "Перш ніж почати, нам потрібно обговорити кілька речей:"
4844+
"value" : "Перш ніж розпочати, нам потрібно обговорити кілька речей."
48454845
}
48464846
},
48474847
"zh-Hans" : {
48484848
"stringUnit" : {
48494849
"state" : "translated",
4850-
"value" : "在开始之前,我们需要先了解一些事情"
4850+
"value" : "在我们开始之前,我们需要先了解一些事情"
48514851
}
48524852
}
48534853
}
@@ -13799,7 +13799,7 @@
1379913799
"nl" : {
1380013800
"stringUnit" : {
1380113801
"state" : "translated",
13802-
"value" : "Ik begrijp het!"
13802+
"value" : "Ik begrijp het, tijd om te beginnen!"
1380313803
}
1380413804
},
1380513805
"uk" : {
@@ -13816,6 +13816,46 @@
1381613816
}
1381713817
}
1381813818
},
13819+
"I understand!" : {
13820+
"localizations" : {
13821+
"de" : {
13822+
"stringUnit" : {
13823+
"state" : "translated",
13824+
"value" : "Ich verstehe!"
13825+
}
13826+
},
13827+
"es" : {
13828+
"stringUnit" : {
13829+
"state" : "translated",
13830+
"value" : "¡Entiendo!"
13831+
}
13832+
},
13833+
"it" : {
13834+
"stringUnit" : {
13835+
"state" : "translated",
13836+
"value" : "Capisco!"
13837+
}
13838+
},
13839+
"nl" : {
13840+
"stringUnit" : {
13841+
"state" : "translated",
13842+
"value" : "Ik begrijp het!"
13843+
}
13844+
},
13845+
"uk" : {
13846+
"stringUnit" : {
13847+
"state" : "translated",
13848+
"value" : "Зрозуміло!"
13849+
}
13850+
},
13851+
"zh-Hans" : {
13852+
"stringUnit" : {
13853+
"state" : "translated",
13854+
"value" : "我明白!"
13855+
}
13856+
}
13857+
}
13858+
},
1381913859
"If a device is not enabled, synchronization with this device is paused." : {
1382013860
"localizations" : {
1382113861
"de" : {
@@ -30408,47 +30448,6 @@
3040830448
}
3040930449
}
3041030450
},
30411-
"Warn when not connected for a while" : {
30412-
"extractionState" : "stale",
30413-
"localizations" : {
30414-
"de" : {
30415-
"stringUnit" : {
30416-
"state" : "translated",
30417-
"value" : "Warnen, wenn für eine Weile keine Verbindung besteht"
30418-
}
30419-
},
30420-
"es" : {
30421-
"stringUnit" : {
30422-
"state" : "translated",
30423-
"value" : "Advertir cuando no haya conexión durante un tiempo"
30424-
}
30425-
},
30426-
"it" : {
30427-
"stringUnit" : {
30428-
"state" : "translated",
30429-
"value" : "Avvisa quando non connesso da un po'"
30430-
}
30431-
},
30432-
"nl" : {
30433-
"stringUnit" : {
30434-
"state" : "translated",
30435-
"value" : "Waarschuw wanneer er een tijdje geen verbinding is"
30436-
}
30437-
},
30438-
"uk" : {
30439-
"stringUnit" : {
30440-
"state" : "translated",
30441-
"value" : "Попереджати, якщо немає з'єднання протягом деякого часу"
30442-
}
30443-
},
30444-
"zh-Hans" : {
30445-
"stringUnit" : {
30446-
"state" : "translated",
30447-
"value" : "长时间未连接时提醒"
30448-
}
30449-
}
30450-
}
30451-
},
3045230451
"Watch for changes" : {
3045330452
"localizations" : {
3045430453
"de" : {

Sushitrain/ContentView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ private struct ContentView: View {
255255
Log.info(
256256
"Current onboarding version is \(Self.currentOnboardingVersion), user last saw \(self.onboardingVersionShown)"
257257
)
258-
if onboardingVersionShown < Self.currentOnboardingVersion {
258+
if onboardingVersionShown < Self.currentOnboardingVersion || true {
259259
self.showOnboarding = true
260260
onboardingVersionShown = Self.currentOnboardingVersion
261261
}

Sushitrain/OnboardingView.swift

Lines changed: 130 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
// Copyright (C) 2024 Tommy van der Vorst
1+
// Copyright (C) 2024-2025 Tommy van der Vorst
22
//
33
// This Source Code Form is subject to the terms of the Mozilla Public
44
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
55
// You can obtain one at https://mozilla.org/MPL/2.0/.
66
import Foundation
77
import SwiftUI
88

9-
struct FeatureView: View {
9+
private struct FeatureView: View {
1010
var image: String
1111
var title: String
1212
var description: String
@@ -18,89 +18,151 @@ struct FeatureView: View {
1818
VStack(alignment: .leading, spacing: 5) {
1919
Text(self.title).bold()
2020
Text(self.description)
21-
Spacer()
2221
}
2322
}
2423
}
2524
}
2625

27-
struct OnboardingView: View {
28-
@Environment(\.dismiss) private var dismiss
26+
private struct ExplainView: View {
27+
var image: String
28+
var title: String
29+
var description: String
2930

3031
var body: some View {
31-
ScrollView(.vertical) {
32-
VStack(alignment: .leading, spacing: 20) {
33-
self.title
34-
35-
HStack(alignment: .center) {
36-
Text("Synchronize your files securely with your other devices.")
37-
.bold()
38-
.multilineTextAlignment(.leading)
39-
.fixedSize(horizontal: false, vertical: true)
40-
}.frame(
41-
minWidth: 0,
42-
minHeight: 0,
43-
maxHeight: .infinity,
44-
alignment: .topLeading
45-
)
32+
VStack(alignment: .center, spacing: 15) {
33+
Image(systemName: image).foregroundColor(.accentColor)
34+
.font(.system(size: 96.0, weight: .light))
35+
Text(self.title).bold()
36+
Text(self.description)
37+
}
38+
}
39+
}
4640

47-
Text("Before we start, we need to go over a few things:").multilineTextAlignment(
48-
.leading)
49-
50-
FeatureView(
51-
image: "bolt.horizontal.circle",
52-
title: String(localized: "Synchronization is not back-up"),
53-
description: String(
54-
localized:
55-
"When you synchronize files, all changes, including deleting files, also happen on your other devices. Do not use Synctrain for back-up purposes, and always keep a back-up of your data."
56-
))
57-
58-
FeatureView(
59-
image: "hand.raised.circle",
60-
title: String(localized: "Your devices, your data, your responsibility"),
61-
description: String(
62-
localized:
63-
"You decide with which devices you share which files. This also means the app makers cannot help you access or recover any lost files."
64-
)
65-
)
41+
private enum OnboardingPage: Int {
42+
case start = 0
43+
case explainNoBackup = 1
44+
case explainResponsibility = 2
45+
case explainSyncthing = 3
46+
case finished = 4
47+
}
6648

67-
FeatureView(
68-
image: "gear.circle",
69-
title: String(localized: "Powered by Syncthing"),
70-
description: String(
71-
localized:
72-
"This app is powered by Syncthing. This app is however not associated with or endorsed by Syncthing nor its developers. Please do not expect support from the Syncthing developers. Instead, contact the maker of this app if you have questions."
73-
)
49+
private struct OnboardingExplainView: View {
50+
let page: OnboardingPage
51+
52+
var body: some View {
53+
switch page {
54+
case .start:
55+
HStack {
56+
Spacer()
57+
Image("Logo")
58+
Spacer()
59+
}
60+
61+
HStack(alignment: .center) {
62+
Text("Synchronize your files securely with your other devices.")
63+
.dynamicTypeSize(.medium)
64+
.bold()
65+
.multilineTextAlignment(.center)
66+
.fixedSize(horizontal: false, vertical: true)
67+
}
68+
69+
Text("Before we start, we need to go over a few things.")
70+
.multilineTextAlignment(.leading)
71+
72+
case .explainNoBackup:
73+
ExplainView(
74+
image: "bolt.horizontal.circle",
75+
title: String(localized: "Synchronization is not back-up"),
76+
description: String(
77+
localized:
78+
"When you synchronize files, all changes, including deleting files, also happen on your other devices. Do not use Synctrain for back-up purposes, and always keep a back-up of your data."
79+
))
80+
81+
case .explainResponsibility:
82+
ExplainView(
83+
image: "hand.raised.circle",
84+
title: String(localized: "Your devices, your data, your responsibility"),
85+
description: String(
86+
localized:
87+
"You decide with which devices you share which files. This also means the app makers cannot help you access or recover any lost files."
7488
)
89+
)
7590

76-
self.footer.padding(.bottom).padding(10)
91+
case .explainSyncthing:
92+
ExplainView(
93+
image: "gear.circle",
94+
title: String(localized: "Powered by Syncthing"),
95+
description: String(
96+
localized:
97+
"This app is powered by Syncthing. This app is however not associated with or endorsed by Syncthing nor its developers. Please do not expect support from the Syncthing developers. Instead, contact the maker of this app if you have questions."
98+
)
99+
)
77100

78-
}.padding(.all).padding(20)
101+
default:
102+
EmptyView()
79103
}
80104
}
105+
}
81106

82-
var title: some View {
83-
Text(
84-
"Welcome to Synctrain!"
85-
)
86-
.font(.largeTitle.bold())
87-
.multilineTextAlignment(.center)
88-
}
107+
struct OnboardingView: View {
108+
@Environment(\.dismiss) private var dismiss
109+
@State private var page = OnboardingPage.start
89110

90-
var footer: some View {
91-
Color.blue
92-
.frame(
93-
minHeight: 48, maxHeight: .infinity
94-
)
95-
.cornerRadius(9.0)
96-
.overlay(alignment: .center) {
97-
Text("I understand, let's get started!").bold().foregroundColor(.white)
98-
}.onTapGesture {
99-
self.dismiss()
111+
var body: some View {
112+
#if os(iOS)
113+
GeometryReader { proxy in
114+
ScrollView(.vertical) {
115+
self.contents()
116+
.frame(minHeight: proxy.size.height)
117+
}
100118
}
119+
#else
120+
self.contents().padding()
121+
#endif
101122
}
102-
}
123+
124+
@ViewBuilder private func contents() -> some View {
125+
VStack(spacing: 20) {
126+
Text(
127+
"Welcome to Synctrain!"
128+
)
129+
.font(.largeTitle.bold())
130+
.multilineTextAlignment(.center)
131+
.padding(.top, 20)
132+
133+
Spacer()
134+
switch page {
135+
case .start, .explainNoBackup, .explainResponsibility, .explainSyncthing:
136+
OnboardingExplainView(page: page).padding(20)
137+
138+
case .finished:
139+
EmptyView()
140+
}
141+
Spacer()
103142

104-
#Preview {
105-
OnboardingView()
143+
Color.blue
144+
.frame(
145+
minHeight: 48, maxHeight: 48
146+
)
147+
.cornerRadius(9.0)
148+
.padding(10)
149+
.overlay(alignment: .center) {
150+
Text(
151+
(page.rawValue == OnboardingPage.finished.rawValue - 1) ? "I understand, let's get started!" : "I understand!"
152+
)
153+
.bold()
154+
.foregroundColor(.white)
155+
156+
}.onTapGesture {
157+
if page.rawValue < (OnboardingPage.finished.rawValue - 1) {
158+
withAnimation {
159+
page = OnboardingPage(rawValue: page.rawValue + 1)!
160+
}
161+
}
162+
else {
163+
self.dismiss()
164+
}
165+
}
166+
}
167+
}
106168
}

0 commit comments

Comments
 (0)