Skip to content

Commit

Permalink
Add temporary waitlist views.
Browse files Browse the repository at this point in the history
  • Loading branch information
samsymons committed Oct 31, 2023
1 parent 58f3cf5 commit cc1a50b
Show file tree
Hide file tree
Showing 3 changed files with 489 additions and 0 deletions.
8 changes: 8 additions & 0 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,8 @@
4B52648B25F9613B00CB4C24 /* trackerData.json in Resources */ = {isa = PBXBuildFile; fileRef = 4B52648A25F9613B00CB4C24 /* trackerData.json */; };
4B53648A26718D0E001AA041 /* EmailWaitlist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B53648926718D0E001AA041 /* EmailWaitlist.swift */; };
4B5C46232AF1B4D4002A4432 /* VPNWaitlist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5C46222AF1B4D4002A4432 /* VPNWaitlist.swift */; };
4B5C46252AF1BEAA002A4432 /* VPNWaitlistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5C46242AF1BEAA002A4432 /* VPNWaitlistView.swift */; };
4B5C46272AF1C075002A4432 /* VPNWaitlistViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5C46262AF1C075002A4432 /* VPNWaitlistViewController.swift */; };
4B60AC97252EC07B00E8D219 /* fullscreenvideo.js in Resources */ = {isa = PBXBuildFile; fileRef = 4B60AC96252EC07B00E8D219 /* fullscreenvideo.js */; };
4B60ACA1252EC0B100E8D219 /* FullScreenVideoUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B60ACA0252EC0B100E8D219 /* FullScreenVideoUserScript.swift */; };
4B62C4BA25B930DD008912C6 /* AppConfigurationFetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B62C4B925B930DD008912C6 /* AppConfigurationFetchTests.swift */; };
Expand Down Expand Up @@ -1282,6 +1284,8 @@
4B52648A25F9613B00CB4C24 /* trackerData.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = trackerData.json; sourceTree = "<group>"; };
4B53648926718D0E001AA041 /* EmailWaitlist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailWaitlist.swift; sourceTree = "<group>"; };
4B5C46222AF1B4D4002A4432 /* VPNWaitlist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNWaitlist.swift; sourceTree = "<group>"; };
4B5C46242AF1BEAA002A4432 /* VPNWaitlistView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNWaitlistView.swift; sourceTree = "<group>"; };
4B5C46262AF1C075002A4432 /* VPNWaitlistViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNWaitlistViewController.swift; sourceTree = "<group>"; };
4B60AC96252EC07B00E8D219 /* fullscreenvideo.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = fullscreenvideo.js; sourceTree = "<group>"; };
4B60ACA0252EC0B100E8D219 /* FullScreenVideoUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenVideoUserScript.swift; sourceTree = "<group>"; };
4B62C4B925B930DD008912C6 /* AppConfigurationFetchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfigurationFetchTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3357,6 +3361,8 @@
isa = PBXGroup;
children = (
4B5C46222AF1B4D4002A4432 /* VPNWaitlist.swift */,
4B5C46242AF1BEAA002A4432 /* VPNWaitlistView.swift */,
4B5C46262AF1C075002A4432 /* VPNWaitlistViewController.swift */,
);
name = VPN;
sourceTree = "<group>";
Expand Down Expand Up @@ -6345,6 +6351,7 @@
312E5746283BB04A00C18FA0 /* AutofillEmptySearchView.swift in Sources */,
F1A5683A1E70F98E0081082E /* AutocompleteRequest.swift in Sources */,
8565A34B1FC8D96B00239327 /* LaunchTabNotification.swift in Sources */,
4B5C46252AF1BEAA002A4432 /* VPNWaitlistView.swift in Sources */,
0290472829E861BE0008FE3C /* AppTPTrackerDetailViewModel.swift in Sources */,
311BD1AD2836BB3900AEF6C1 /* AutofillItemsEmptyView.swift in Sources */,
C1F341C52A6924000032057B /* EmailAddressPromptView.swift in Sources */,
Expand Down Expand Up @@ -6386,6 +6393,7 @@
85DFEDEF24C7EA3B00973FE7 /* SmallOmniBarState.swift in Sources */,
1E908BF129827C480008C8F3 /* AutoconsentUserScript.swift in Sources */,
4B0295192537BC6700E00CEF /* ConfigurationDebugViewController.swift in Sources */,
4B5C46272AF1C075002A4432 /* VPNWaitlistViewController.swift in Sources */,
1E7A71192934EC6100B7EA19 /* OmniBarNotificationContainerView.swift in Sources */,
984D035C24AE15CD0066CFB8 /* TabSwitcherSettings.swift in Sources */,
98B31292218CCB8C00E54DE1 /* AppDependencyProvider.swift in Sources */,
Expand Down
346 changes: 346 additions & 0 deletions DuckDuckGo/VPNWaitlistView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,346 @@
//
// VPNWaitlistView.swift
// DuckDuckGo
//
// Copyright © 2023 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import SwiftUI
import Core
import Waitlist
import DesignResourcesKit

struct VPNWaitlistView: View {

@EnvironmentObject var viewModel: WaitlistViewModel

var body: some View {
switch viewModel.viewState {
case .notJoinedQueue:
VPNWaitlistSignUpView(requestInFlight: false) { action in
Task { await viewModel.perform(action: action) }
}
case .joiningQueue:
VPNWaitlistSignUpView(requestInFlight: true) { action in
Task { await viewModel.perform(action: action) }
}
case .joinedQueue(let state):
VPNWaitlistJoinedWaitlistView(notificationState: state) { action in
Task { await viewModel.perform(action: action) }
}
case .invited(let inviteCode):
VPNWaitlistInvitedView(inviteCode: inviteCode) { action in
Task { await viewModel.perform(action: action) }
}
case .waitlistRemoved:
Text("Not supported")
}
}
}

struct VPNWaitlistSignUpView: View {

let requestInFlight: Bool

let action: WaitlistViewActionHandler

var body: some View {
GeometryReader { proxy in
ScrollView {
VStack(alignment: .center, spacing: 8) {
HeaderView(imageName: "WindowsWaitlistJoinWaitlist", title: UserText.windowsWaitlistTryDuckDuckGoForWindows)

Text(UserText.windowsWaitlistSummary)
.daxBodyRegular()
.foregroundColor(.waitlistTextSecondary)
.multilineTextAlignment(.center)
.lineSpacing(6)

Button(UserText.waitlistJoin, action: { action(.joinQueue) })
.buttonStyle(RoundedButtonStyle(enabled: !requestInFlight))
.padding(.top, 24)

if requestInFlight {
HStack {
Text(UserText.waitlistJoining)
.daxSubheadRegular()
.foregroundColor(.waitlistTextSecondary)

ActivityIndicator(style: .medium)
}
.padding(.top, 14)
}

Spacer(minLength: 24)

Button(
action: {
action(.custom(.openMacBrowserWaitlist))
}, label: {
Text(UserText.windowsWaitlistMac)
.daxHeadline()
.foregroundColor(.waitlistBlue)
.multilineTextAlignment(.center)
.lineSpacing(5)
}
)
.padding(.bottom, 12)
.fixedSize(horizontal: false, vertical: true)

Text(UserText.waitlistPrivacyDisclaimer)
.daxFootnoteRegular()
.foregroundColor(.waitlistTextSecondary)
.multilineTextAlignment(.center)
.lineSpacing(5)
.padding(.bottom, 12)
.fixedSize(horizontal: false, vertical: true)
}
.padding([.leading, .trailing], 24)
.frame(minHeight: proxy.size.height)
}
}
}

}

// MARK: - Joined Waitlist Views

struct VPNWaitlistJoinedWaitlistView: View {

let notificationState: WaitlistViewModel.NotificationPermissionState

let action: (WaitlistViewModel.ViewAction) -> Void

var body: some View {
VStack(spacing: 16) {
HeaderView(imageName: "WaitlistJoined", title: UserText.waitlistOnTheList)

switch notificationState {
case .notificationAllowed:
Text(UserText.windowsWaitlistJoinedWithNotifications)
.daxBodyRegular()
.foregroundColor(.waitlistTextSecondary)
.lineSpacing(6)

default:
Text(UserText.windowsWaitlistJoinedWithoutNotifications)
.daxBodyRegular()
.foregroundColor(.waitlistTextSecondary)
.lineSpacing(6)

if notificationState == .notificationsDisabled {
AllowNotificationsView(action: action)
.padding(.top, 4)
} else {
Button(UserText.waitlistNotifyMe) {
action(.requestNotificationPermission)
}
.buttonStyle(RoundedButtonStyle(enabled: true))
.padding(.top, 32)
}
}

Spacer()
}
.padding([.leading, .trailing], 24)
.multilineTextAlignment(.center)
}

}

private struct AllowNotificationsView: View {

let action: (WaitlistViewModel.ViewAction) -> Void

var body: some View {

VStack(spacing: 20) {

Text(UserText.waitlistNotificationDisabled)
.daxBodyRegular()
.foregroundColor(.waitlistTextSecondary)
.fixMultilineScrollableText()
.lineSpacing(5)

Button(UserText.waitlistAllowNotifications) {
action(.openNotificationSettings)
}
.buttonStyle(RoundedButtonStyle(enabled: true))

}
.padding(24)
.background(Color.waitlistNotificationBackground)
.cornerRadius(8)
.shadow(color: .black.opacity(0.05), radius: 12, x: 0, y: 4)

}

}

// MARK: - Invite Available Views

private struct ShareButtonFramePreferenceKey: PreferenceKey {
static var defaultValue: CGRect = .zero
static func reduce(value: inout CGRect, nextValue: () -> CGRect) {}
}

struct VPNWaitlistInvitedView: View {

let inviteCode: String
let action: (WaitlistViewModel.ViewAction) -> Void

@State private var shareButtonFrame: CGRect = .zero

var body: some View {
GeometryReader { proxy in
ScrollView {
VStack(alignment: .center, spacing: 0) {
HeaderView(imageName: "WaitlistInvited", title: UserText.waitlistYoureInvited)

Text(UserText.windowsWaitlistInviteScreenSubtitle)
.daxBodyRegular()
.foregroundColor(.waitlistTextSecondary)
.padding(.top, 16)
.lineSpacing(6)
.fixedSize(horizontal: false, vertical: true)

Text(UserText.waitlistInviteScreenStepTitle(step: 1))
.daxHeadline()
.foregroundColor(.waitlistTextSecondary)
.padding(.top, 28)
.padding(.bottom, 8)

Text(UserText.windowsWaitlistInviteScreenStep1Description)
.daxBodyRegular()
.foregroundColor(.waitlistTextSecondary)
.lineSpacing(6)

Text(URL.windows.absoluteString.dropping(prefix: "https://"))
.daxHeadline()
.foregroundColor(.waitlistBlue)
.menuController(UserText.waitlistCopy) {
action(.copyDownloadURLToPasteboard)
}
.scaledToFit()

Text(UserText.waitlistInviteScreenStepTitle(step: 2))
.daxHeadline()
.foregroundColor(.waitlistTextSecondary)
.padding(.top, 22)
.padding(.bottom, 8)

Text(UserText.windowsWaitlistInviteScreenStep2Description)
.daxBodyRegular()
.foregroundColor(.waitlistTextSecondary)
.lineSpacing(6)

InviteCodeView(title: UserText.waitlistInviteCode, inviteCode: inviteCode)
.menuController(UserText.waitlistCopy) {
action(.copyInviteCodeToPasteboard)
}
.fixedSize()
.padding(.top, 28)

Spacer(minLength: 24)

shareButton
.padding(.bottom, 26)

}
.frame(maxWidth: .infinity, minHeight: proxy.size.height)
.padding([.leading, .trailing], 18)
.multilineTextAlignment(.center)
}
}
}

var shareButton: some View {

Button(action: {
action(.openShareSheet(shareButtonFrame))
}, label: {
Image("Share")
.foregroundColor(.waitlistTextSecondary)
})
.frame(width: 44, height: 44)
.background(
GeometryReader { proxy in
Color.clear
.preference(key: ShareButtonFramePreferenceKey.self, value: proxy.frame(in: .global))
}
)
.onPreferenceChange(ShareButtonFramePreferenceKey.self) { newFrame in
if UIDevice.current.userInterfaceIdiom == .pad {
self.shareButtonFrame = newFrame
}
}

}

}

// MARK: - Previews

private struct VPNWaitlistView_Previews: PreviewProvider {

static var previews: some View {
Group {
PreviewView("Sign Up") {
WindowsBrowserWaitlistSignUpView(requestInFlight: false) { _ in }
}

PreviewView("Sign Up (API Request In Progress)") {
WindowsBrowserWaitlistSignUpView(requestInFlight: true) { _ in }
}

PreviewView("Joined Waitlist (Notifications Allowed)") {
WindowsBrowserWaitlistJoinedWaitlistView(notificationState: .notificationAllowed) { _ in }
}

PreviewView("Joined Waitlist (Notifications Not Allowed)") {
WindowsBrowserWaitlistJoinedWaitlistView(notificationState: .notificationsDisabled) { _ in }
}

PreviewView("Invite Screen With Code") {
WindowsBrowserWaitlistInvitedView(inviteCode: "T3STC0DE") { _ in }
}

if #available(iOS 15.0, *) {
WindowsBrowserWaitlistInvitedView(inviteCode: "T3STC0DE") { _ in }
.previewInterfaceOrientation(.landscapeLeft)
}
}
}

private struct PreviewView<Content: View>: View {
let title: String
var content: () -> Content

init(_ title: String, @ViewBuilder content: @escaping () -> Content) {
self.title = title
self.content = content
}

var body: some View {
NavigationView {
content()
.navigationTitle("DuckDuckGo Desktop App")
.navigationBarTitleDisplayMode(.inline)
.overlay(Divider(), alignment: .top)
}
.previewDisplayName(title)
}
}
}
Loading

0 comments on commit cc1a50b

Please sign in to comment.