From d8cdedde4e5d458055b09ec7b6aae52468e7ba9d Mon Sep 17 00:00:00 2001 From: Doug Date: Wed, 14 Sep 2022 22:39:49 +0100 Subject: [PATCH] #181: Style the session verification banner to match Figma. --- .../Buttons/ElementCapsuleButtonStyle.swift | 88 +++++++++++++++++++ .../en.lproj/Untranslated.strings | 3 + .../Generated/Strings+Untranslated.swift | 4 + .../Screens/HomeScreen/HomeScreenModels.swift | 1 + .../HomeScreen/HomeScreenViewModel.swift | 2 + .../Screens/HomeScreen/View/HomeScreen.swift | 55 ++++++++---- changelog.d/181.change | 1 + 7 files changed, 137 insertions(+), 17 deletions(-) create mode 100644 DesignKit/Sources/Buttons/ElementCapsuleButtonStyle.swift create mode 100644 changelog.d/181.change diff --git a/DesignKit/Sources/Buttons/ElementCapsuleButtonStyle.swift b/DesignKit/Sources/Buttons/ElementCapsuleButtonStyle.swift new file mode 100644 index 0000000000..08c13848cd --- /dev/null +++ b/DesignKit/Sources/Buttons/ElementCapsuleButtonStyle.swift @@ -0,0 +1,88 @@ +// +// Copyright 2022 New Vector Ltd +// +// 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 + +public extension ButtonStyle where Self == ElementCapsuleButtonStyle { + /// A button style that uses a capsule shape with a regular appearance. + static var elementCapsule: ElementCapsuleButtonStyle { + ElementCapsuleButtonStyle(isProminent: false) + } + + /// A button style that uses a capsule shape with a prominent appearance. + static var elementCapsuleProminent: ElementCapsuleButtonStyle { + ElementCapsuleButtonStyle(isProminent: true) + } +} + +public struct ElementCapsuleButtonStyle: ButtonStyle { + let isProminent: Bool + + public func makeBody(configuration: Configuration) -> some View { + configuration.label + .padding(7) + .frame(maxWidth: .infinity) + .font(.element.footnote) + .foregroundColor(fontColor) + .multilineTextAlignment(.center) + .background(background) + .opacity(configuration.isPressed ? 0.6 : 1) + } + + @ViewBuilder + var background: some View { + if isProminent { + Capsule().foregroundColor(Color.element.accent) + } else { + ZStack { + Capsule().foregroundColor(Color.element.systemSecondaryBackground) + Capsule().stroke(Color.element.accent) + } + } + } + + var fontColor: Color { + isProminent ? .element.systemPrimaryBackground : .element.systemPrimaryLabel + } +} + +struct ElementCapsuleButtonStyle_Previews: PreviewProvider { + public static var states: some View { + VStack { + Button("Enabled") { /* preview */ } + .buttonStyle(.elementCapsuleProminent) + + Button("Disabled") { /* preview */ } + .buttonStyle(.elementCapsuleProminent) + .disabled(true) + + Button("Enabled") { /* preview */ } + .buttonStyle(.elementCapsule) + + Button("Disabled") { /* preview */ } + .buttonStyle(.elementCapsule) + .disabled(true) + } + .padding() + } + + public static var previews: some View { + states + .preferredColorScheme(.light) + states + .preferredColorScheme(.dark) + } +} diff --git a/ElementX/Resources/Localizations/en.lproj/Untranslated.strings b/ElementX/Resources/Localizations/en.lproj/Untranslated.strings index 314e0e8a52..2543f7f87e 100644 --- a/ElementX/Resources/Localizations/en.lproj/Untranslated.strings +++ b/ElementX/Resources/Localizations/en.lproj/Untranslated.strings @@ -15,6 +15,9 @@ "room_timeline_replying_to" = "Replying to %@"; +"session_verification_banner_title" = "Help keep your messages secure"; +"session_verification_banner_message" = "Looks like you’re using a new device. Verify its you."; + // MARK: - Authentication "authentication_login_title" = "Welcome back!"; diff --git a/ElementX/Sources/Generated/Strings+Untranslated.swift b/ElementX/Sources/Generated/Strings+Untranslated.swift index fd75f2e025..ddf8cdf5ac 100644 --- a/ElementX/Sources/Generated/Strings+Untranslated.swift +++ b/ElementX/Sources/Generated/Strings+Untranslated.swift @@ -46,6 +46,10 @@ extension ElementL10n { public static let serverSelectionServerUrl = ElementL10n.tr("Untranslated", "server_selection_server_url") /// Choose your server public static let serverSelectionTitle = ElementL10n.tr("Untranslated", "server_selection_title") + /// Looks like you’re using a new device. Verify its you. + public static let sessionVerificationBannerMessage = ElementL10n.tr("Untranslated", "session_verification_banner_message") + /// Help keep your messages secure + public static let sessionVerificationBannerTitle = ElementL10n.tr("Untranslated", "session_verification_banner_title") /// Timeline Style public static let settingsTimelineStyle = ElementL10n.tr("Untranslated", "settings_timeline_style") /// Untranslated diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift index 0e7d715948..60337889d4 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift @@ -35,6 +35,7 @@ enum HomeScreenViewAction { case selectRoom(roomIdentifier: String) case userMenu(action: HomeScreenViewUserMenuAction) case verifySession + case skipSessionVerification } struct HomeScreenViewState: BindableState { diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index e55ef63d8d..3dcbac7d73 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -57,6 +57,8 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol callback?(.userMenu(action: action)) case .verifySession: callback?(.verifySession) + case .skipSessionVerification: + hideSessionVerificationBanner() } } diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift index 11a88c751a..6eb262309e 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift @@ -22,26 +22,13 @@ struct HomeScreen: View { // MARK: Views var body: some View { - VStack(spacing: 0.0) { + Group { if context.viewState.isLoadingRooms { VStack { Text(ElementL10n.loading) ProgressView() } } else { - if context.viewState.showSessionVerificationBanner { - HStack { - Text(ElementL10n.verificationVerifyDevice) - Spacer() - Button(ElementL10n.startVerification) { - context.send(viewAction: .verifySession) - } - } - .padding() - .background(Color.element.quaternaryContent) - .padding(.top, 1) - } - List { Section(ElementL10n.rooms) { ForEach(context.viewState.visibleRooms) { room in @@ -59,13 +46,16 @@ struct HomeScreen: View { } .listStyle(.plain) .searchable(text: $context.searchQuery) + .safeAreaInset(edge: .top) { + if context.viewState.showSessionVerificationBanner { + sessionVerificationBanner + .background(Color.element.systemPrimaryBackground, ignoresSafeAreaEdges: .all) + } + } } - - Spacer() } .transition(.slide) .animation(.elementDefault, value: context.viewState.showSessionVerificationBanner) - .ignoresSafeArea(.all, edges: .bottom) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarLeading) { @@ -119,6 +109,37 @@ struct HomeScreen: View { return .empty } } + + private var sessionVerificationBanner: some View { + VStack(alignment: .leading, spacing: 16) { + VStack(alignment: .leading, spacing: 2) { + Text(ElementL10n.sessionVerificationBannerTitle) + .font(.element.subheadline.bold()) + .foregroundColor(.element.systemPrimaryLabel) + Text(ElementL10n.sessionVerificationBannerMessage) + .font(.element.footnote) + .foregroundColor(.element.systemSecondaryLabel.opacity(0.6)) + } + + HStack(spacing: 16) { + Button(ElementL10n.actionSkip) { + context.send(viewAction: .skipSessionVerification) + } + .frame(maxWidth: .infinity) + .buttonStyle(.elementCapsule) + + Button(ElementL10n.continue) { + context.send(viewAction: .verifySession) + } + .frame(maxWidth: .infinity) + .buttonStyle(.elementCapsuleProminent) + } + } + .padding(16) + .background(Color(uiColor: .quaternarySystemFill)) // Figma has secondary background but with a different color??? + .cornerRadius(14) + .padding(.horizontal, 16) + } private func settings() { context.send(viewAction: .userMenu(action: .settings)) diff --git a/changelog.d/181.change b/changelog.d/181.change new file mode 100644 index 0000000000..011f1a87d8 --- /dev/null +++ b/changelog.d/181.change @@ -0,0 +1 @@ +Style the session verification banner to match Figma.