Merge pull request #730 from omnivore-app/feature/update-welcome-screen

Update welcome screen
This commit is contained in:
Satindar Dhillon 2022-06-02 14:31:53 -07:00 committed by GitHub
commit 4771d11c51
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 207 additions and 317 deletions

View file

@ -135,7 +135,7 @@ struct ProfileView: View {
}
}
private extension BasicWebAppView {
extension BasicWebAppView {
static func privacyPolicyWebView(baseURL: URL) -> BasicWebAppView {
omnivoreWebView(path: "/app/privacy", baseURL: baseURL)
}

View file

@ -84,69 +84,10 @@ final class RegistrationViewModel: ObservableObject {
}
}
struct RegistrationView: View {
@EnvironmentObject var authenticator: Authenticator
@EnvironmentObject var dataService: DataService
@Environment(\.horizontalSizeClass) var horizontalSizeClass
@StateObject private var viewModel = RegistrationViewModel()
var authenticationView: some View {
VStack(spacing: 0) {
VStack(spacing: 28) {
if horizontalSizeClass == .regular {
Spacer()
}
VStack(alignment: .center, spacing: 16) {
Text(LocalText.registrationViewHeadline)
.font(.appTitle)
.multilineTextAlignment(.center)
.padding(.bottom, horizontalSizeClass == .compact ? 0 : 50)
.padding(.top, horizontalSizeClass == .compact ? 30 : 0)
AppleSignInButton {
viewModel.handleAppleSignInCompletion(result: $0, authenticator: authenticator)
}
if AppKeys.sharedInstance?.iosClientGoogleId != nil {
GoogleAuthButton {
viewModel.handleGoogleAuth(authenticator: authenticator)
}
}
}
if let loginError = viewModel.loginError {
LoginErrorMessageView(loginError: loginError)
}
Spacer()
}
.frame(maxWidth: 316)
.padding(.horizontal, 16)
}
}
var body: some View {
if let registrationState = viewModel.registrationState {
if case let RegistrationViewModel.RegistrationState.createProfile(userProfile) = registrationState {
CreateProfileView(userProfile: userProfile)
} else if case let RegistrationViewModel.RegistrationState.newAppleSignUp(userProfile) = registrationState {
NewAppleSignupView(
userProfile: userProfile,
showProfileEditView: { viewModel.registrationState = .createProfile(userProfile: userProfile) }
)
} else {
authenticationView
}
} else {
authenticationView
}
}
}
private func presentingViewController() -> PlatformViewController? {
#if os(iOS)
return UIApplication.shared.windows
let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene
return scene?.windows
.filter(\.isKeyWindow)
.first?
.rootViewController

View file

@ -7,11 +7,19 @@ import Views
struct WelcomeView: View {
@EnvironmentObject var dataService: DataService
@EnvironmentObject var authenticator: Authenticator
@Environment(\.horizontalSizeClass) var horizontalSizeClass
@StateObject private var viewModel = RegistrationViewModel()
@State private var showRegistrationView = false
@State private var isKeyboardOnScreen = false
@State private var showDebugModal = false
@State private var showTermsLinks = false
@State private var showTermsModal = false
@State private var showPrivacyModal = false
@State private var showAboutPage = false
@State private var selectedEnvironment = AppEnvironment.initialAppEnvironment
@State private var containerSize: CGSize = .zero
func handleHiddenGestureAction() {
if !Bundle.main.isAppStoreBuild {
@ -19,68 +27,183 @@ struct WelcomeView: View {
}
}
@ViewBuilder func userInteractiveView(width: CGFloat) -> some View {
var headlineText: some View {
Group {
if showRegistrationView {
RegistrationView()
if horizontalSizeClass == .compact {
Text("Everything you read. Safe, organized, and easy to share.")
} else {
GetStartedView(showRegistrationView: $showRegistrationView)
Text("Everything you read. Safe,\norganized, and easy to share.")
}
}
.frame(width: width)
.zIndex(2)
.font(.appLargeTitle)
}
@ViewBuilder func primaryContent() -> some View {
if horizontalSizeClass == .compact {
GeometryReader { geometry in
ZStack(alignment: .leading) {
Color.systemBackground
.edgesIgnoringSafeArea(.all)
var headlineView: some View {
VStack(alignment: .leading, spacing: 8) {
headlineText
if geometry.size.width < geometry.size.height, !isKeyboardOnScreen {
VStack {
Color.appDeepBackground.frame(height: 100)
Spacer()
}
.edgesIgnoringSafeArea(.all)
}
VStack {
if geometry.size.width < geometry.size.height, !isKeyboardOnScreen {
RegistrationHeroImageView(tapGestureHandler: handleHiddenGestureAction)
}
userInteractiveView(width: geometry.size.width)
Spacer()
Button(
action: { showAboutPage = true },
label: {
HStack(spacing: 4) {
Text("Learn more")
Image(systemName: "arrow.right")
}
.font(.appTitleThree)
}
)
.foregroundColor(.appGrayTextContrast)
}
}
var footerView: some View {
Group {
Text("By signing up, you agree to Omnivores\n")
+ Text("Terms of Service").underline()
+ Text(" and ")
+ Text("Privacy Policy").underline()
}
.font(.appSubheadline)
.confirmationDialog("", isPresented: $showTermsLinks, titleVisibility: .hidden) {
Button("View Terms of Service") {
showTermsModal = true
}
} else {
GeometryReader { geometry in
ZStack(alignment: .leading) {
SplitColorBackground(width: geometry.size.width)
VStack {
TitleLogoView(handleHiddenGestureAction: handleHiddenGestureAction)
Spacer()
}
.padding()
Button("View Privacy Policy") {
showPrivacyModal = true
}
}
.sheet(isPresented: $showPrivacyModal) {
VStack {
HStack {
Spacer()
Button(
action: {
showPrivacyModal = false
},
label: {
Image(systemName: "xmark.circle").foregroundColor(.appGrayTextContrast)
}
)
}
.padding()
BasicWebAppView.privacyPolicyWebView(baseURL: dataService.appEnvironment.webAppBaseURL)
}
}
.sheet(isPresented: $showTermsModal) {
VStack {
HStack {
Spacer()
Button(
action: {
showTermsModal = false
},
label: {
Image(systemName: "xmark.circle").foregroundColor(.appGrayTextContrast)
}
)
}
.padding()
BasicWebAppView.termsConditionsWebView(baseURL: dataService.appEnvironment.webAppBaseURL)
}
}
.sheet(isPresented: $showAboutPage) {
if let url = URL(string: "https://omnivore.app/about") {
SafariView(url: url)
}
}
.onTapGesture {
showTermsLinks = true
}
}
HStack(spacing: 0) {
userInteractiveView(width: geometry.size.width * 0.5)
ReadingIllustrationXXLView(width: geometry.size.width * 0.5)
var logoView: some View {
Image.omnivoreTitleLogo
.gesture(
TapGesture(count: 2)
.onEnded {
if !Bundle.main.isAppStoreBuild {
showDebugModal = true
}
}
)
}
var authProviderButtonStack: some View {
let useHorizontalLayout = containerSize.width > 400
let buttonGroup = Group {
AppleSignInButton {
viewModel.handleAppleSignInCompletion(result: $0, authenticator: authenticator)
}
if AppKeys.sharedInstance?.iosClientGoogleId != nil {
GoogleAuthButton {
viewModel.handleGoogleAuth(authenticator: authenticator)
}
}
}
return
VStack(alignment: .center, spacing: 16) {
if useHorizontalLayout {
HStack { buttonGroup }
} else {
buttonGroup
}
if let loginError = viewModel.loginError {
HStack {
LoginErrorMessageView(loginError: loginError)
Spacer()
}
}
}
}
public var body: some View {
primaryContent()
.sheet(isPresented: $showDebugModal) {
DebugMenuView(selectedEnvironment: $selectedEnvironment)
ZStack(alignment: .leading) {
Color.appBackground
.edgesIgnoringSafeArea(.all)
.modifier(SizeModifier())
.onPreferenceChange(SizePreferenceKey.self) {
self.containerSize = $0
}
if let registrationState = viewModel.registrationState {
if case let RegistrationViewModel.RegistrationState.createProfile(userProfile) = registrationState {
CreateProfileView(userProfile: userProfile)
} else if case let RegistrationViewModel.RegistrationState.newAppleSignUp(userProfile) = registrationState {
NewAppleSignupView(
userProfile: userProfile,
showProfileEditView: { viewModel.registrationState = .createProfile(userProfile: userProfile) }
)
} else {
EmptyView() // will never be called
}
} else {
VStack(alignment: .leading, spacing: containerSize.height < 500 ? 12 : 50) {
logoView
.padding(.bottom, 20)
headlineView
if containerSize.width > 400 {
authProviderButtonStack
} else {
HStack {
Spacer()
authProviderButtonStack
Spacer()
}
}
footerView
Spacer()
}
.padding()
.sheet(isPresented: $showDebugModal) {
DebugMenuView(selectedEnvironment: $selectedEnvironment)
}
}
.onReceive(Publishers.keyboardHeight) { isKeyboardOnScreen = $0 > 1 }
.onAppear { selectedEnvironment = dataService.appEnvironment }
}
.preferredColorScheme(.light)
.task { selectedEnvironment = dataService.appEnvironment }
}
}

View file

@ -18,8 +18,9 @@ public struct AppleSignInButton: View {
},
onCompletion: onCompletion
)
.frame(height: 44)
.frame(height: 54)
.frame(maxWidth: 300)
.cornerRadius(8)
.signInWithAppleButtonStyle(colorScheme == .dark ? .white : .black)
.signInWithAppleButtonStyle(.white)
}
}

View file

@ -12,13 +12,14 @@ public struct GoogleAuthButton: View {
HStack(spacing: 8) {
Image.googleIcon
.resizable()
.frame(width: 12, height: 12)
.frame(width: 16, height: 16)
Text(LocalText.googleAuthButton)
.font(isMacApp ? .appCaption : .appBody)
.font(isMacApp ? .appCaption : .appTitleThree)
.foregroundColor(.black)
.fontWeight(Font.Weight.medium)
}
.frame(maxWidth: .infinity)
.frame(height: isMacApp ? 30 : 44)
.frame(maxWidth: 300)
.frame(height: isMacApp ? 30 : 54)
}
.buttonStyle(GoogleButtonStyle())
}

View file

@ -5,9 +5,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x9F",
"green" : "0xEA",
"red" : "0xFF"
"blue" : "0xA8",
"green" : "0xEB",
"red" : "0xFB"
}
},
"idiom" : "universal"
@ -23,9 +23,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x9F",
"green" : "0xEA",
"red" : "0xFF"
"blue" : "0xA8",
"green" : "0xEB",
"red" : "0xFB"
}
},
"idiom" : "universal"

View file

@ -3,10 +3,7 @@ import SwiftUI
public extension Image {
static var smallOmnivoreLogo: Image { Image("_smallOmnivoreLogo", bundle: .module) }
static var omnivoreTitleLogo: Image { Image("_omnivoreTitleLogo", bundle: .module) }
static var readingIllustration: Image { Image("_readingIllustration", bundle: .module) }
static var readingIllustrationXXL: Image { Image("_readingIllustrationXXL", bundle: .module) }
static var googleIcon: Image { Image("_googleIcon", bundle: .module) }
static var feedItemPlaceholder: Image { Image("_feedItemPlaceholder", bundle: .module) }
static var sunHorizon: Image { Image("_sun-horizon", bundle: .module) }
static var mountains: Image { Image("_mountains", bundle: .module) }
static var moon: Image { Image("_moon", bundle: .module) }

View file

@ -1,12 +0,0 @@
{
"images" : [
{
"filename" : "imagePlaceholder.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -1,23 +0,0 @@
{
"images" : [
{
"filename" : "Mask Group.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Mask Group@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Mask Group@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 614 KiB

View file

@ -1,12 +0,0 @@
{
"images" : [
{
"filename" : "XXLIllustration.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

View file

@ -1,25 +0,0 @@
import SwiftUI
public struct RegistrationHeroImageView: View {
let tapGestureHandler: () -> Void
public init(tapGestureHandler: @escaping () -> Void) {
self.tapGestureHandler = tapGestureHandler
}
public var body: some View {
ZStack(alignment: .topLeading) {
Image.readingIllustration
.resizable()
.aspectRatio(contentMode: .fit)
Image.omnivoreTitleLogo
.padding()
.gesture(
TapGesture(count: 2)
.onEnded {
tapGestureHandler()
}
)
}
}
}

View file

@ -1,41 +0,0 @@
import Models
import SwiftUI
struct ToggleAuthFlowButton: View {
let authFlow: AuthFlow
let action: () -> Void
var buttonTitle: String {
switch authFlow {
case .signIn:
return "Dont have an account? "
case .signUp:
return "Already have an account? "
}
}
var buttonTitleSuffix: String {
switch authFlow {
case .signIn:
return "Sign Up"
case .signUp:
return "Log In"
}
}
var body: some View {
Button(
action: action,
label: {
Text(buttonTitle)
.font(.appFootnote)
.foregroundColor(.appGrayText)
+ Text(buttonTitleSuffix)
.underline()
.font(.appFootnote)
.foregroundColor(.red)
}
)
.buttonStyle(PlainButtonStyle())
}
}

View file

@ -0,0 +1,23 @@
import SwiftUI
public struct SizePreferenceKey: PreferenceKey {
public static var defaultValue: CGSize = .zero
public static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue()
}
}
public struct SizeModifier: ViewModifier {
public init() {}
private var sizeView: some View {
GeometryReader { geometry in
Color.clear.preference(key: SizePreferenceKey.self, value: geometry.size)
}
}
public func body(content: Content) -> some View {
content.background(sizeView)
}
}

View file

@ -1,83 +0,0 @@
import SwiftUI
public struct ReadingIllustrationXXLView: View {
let width: CGFloat
public init(width: CGFloat) {
self.width = width
}
public var body: some View {
Image.readingIllustrationXXL
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: width)
.clipped()
.edgesIgnoringSafeArea([.vertical, .trailing])
}
}
public struct TitleLogoView: View {
let handleHiddenGestureAction: () -> Void
public init(handleHiddenGestureAction: @escaping () -> Void) {
self.handleHiddenGestureAction = handleHiddenGestureAction
}
public var body: some View {
Image.omnivoreTitleLogo
.renderingMode(.template)
.foregroundColor(.appGrayTextContrast)
.frame(height: 40)
.gesture(
TapGesture(count: 2)
.onEnded {
handleHiddenGestureAction()
}
)
}
}
public struct GetStartedView: View {
@Environment(\.horizontalSizeClass) var horizontalSizeClass
@Binding var showRegistrationView: Bool
public init(showRegistrationView: Binding<Bool>) {
self._showRegistrationView = showRegistrationView
}
public var body: some View {
HStack {
VStack(alignment: .leading, spacing: 32) {
Text("A better social\nreading experience\nstarts with Omnivore.")
.font(.appTitle)
.multilineTextAlignment(.leading)
BorderedButton(color: .appGrayTextContrast, text: "Get Started") {
showRegistrationView = true
}
.frame(width: 220)
}
.padding(.leading, horizontalSizeClass == .compact ? 16 : 80)
.padding(.top, horizontalSizeClass == .compact ? 16 : 0)
Spacer()
}
}
}
public struct SplitColorBackground: View {
let width: CGFloat
public init(width: CGFloat) {
self.width = width
}
public var body: some View {
HStack(spacing: 0) {
Color.systemBackground.frame(width: width * 0.5)
Color.appBackground.frame(width: width * 0.5)
}
.edgesIgnoringSafeArea(.all)
}
}