mirror of https://github.com/oxen-io/session-ios
ui: refactor QR code screen with SwiftUI and update QR code style
parent
5eca31aaa2
commit
9faf835617
@ -1,13 +0,0 @@
|
|||||||
// Copyright © 2024 Rangeproof Pty Ltd. All rights reserved.
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct QRCodeScreen: View {
|
|
||||||
var body: some View {
|
|
||||||
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#Preview {
|
|
||||||
QRCodeScreen()
|
|
||||||
}
|
|
@ -0,0 +1,102 @@
|
|||||||
|
// Copyright © 2024 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import SessionUIKit
|
||||||
|
import SignalUtilitiesKit
|
||||||
|
import SessionUtilitiesKit
|
||||||
|
import AVFoundation
|
||||||
|
|
||||||
|
struct QRCodeScreen: View {
|
||||||
|
@EnvironmentObject var host: HostWrapper
|
||||||
|
|
||||||
|
@State var tabIndex = 0
|
||||||
|
@State private var accountId: String = ""
|
||||||
|
@State private var errorString: String? = nil
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack(alignment: .topLeading) {
|
||||||
|
if #available(iOS 14.0, *) {
|
||||||
|
ThemeManager.currentTheme.colorSwiftUI(for: .backgroundPrimary).ignoresSafeArea()
|
||||||
|
} else {
|
||||||
|
ThemeManager.currentTheme.colorSwiftUI(for: .backgroundPrimary)
|
||||||
|
}
|
||||||
|
VStack(
|
||||||
|
spacing: 0
|
||||||
|
){
|
||||||
|
CustomTopTabBar(
|
||||||
|
tabIndex: $tabIndex,
|
||||||
|
tabTitles: [
|
||||||
|
"settings_view_qr_code_tab_title".localized(),
|
||||||
|
"settings_scan_qr_code_tab_title".localized()
|
||||||
|
]
|
||||||
|
).frame(maxWidth: .infinity)
|
||||||
|
|
||||||
|
if tabIndex == 0 {
|
||||||
|
MyQRCodeScreen()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ScanQRCodeScreen(
|
||||||
|
$accountId,
|
||||||
|
error: $errorString,
|
||||||
|
continueAction: continueWithAccountId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func startNewPrivateChatIfPossible(with hexEncodedPublicKey: String, onError: (() -> ())?) {
|
||||||
|
if !KeyPair.isValidHexEncodedPublicKey(candidate: hexEncodedPublicKey) {
|
||||||
|
let modal: ConfirmationModal = ConfirmationModal(
|
||||||
|
info: ConfirmationModal.Info(
|
||||||
|
title: "invalid_session_id".localized(),
|
||||||
|
body: .text("INVALID_SESSION_ID_MESSAGE".localized()),
|
||||||
|
cancelTitle: "BUTTON_OK".localized(),
|
||||||
|
cancelStyle: .alert_text,
|
||||||
|
afterClosed: onError
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.host.controller?.present(modal, animated: true)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SessionApp.presentConversationCreatingIfNeeded(
|
||||||
|
for: hexEncodedPublicKey,
|
||||||
|
variant: .contact,
|
||||||
|
dismissing: self.host.controller,
|
||||||
|
animated: false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func continueWithAccountId(onError: (() -> ())?) {
|
||||||
|
let hexEncodedPublicKey = accountId
|
||||||
|
startNewPrivateChatIfPossible(with: hexEncodedPublicKey, onError: onError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MyQRCodeScreen: View {
|
||||||
|
var body: some View{
|
||||||
|
VStack(
|
||||||
|
spacing: Values.mediumSpacing
|
||||||
|
) {
|
||||||
|
QRCodeView(
|
||||||
|
string: getUserHexEncodedPublicKey(),
|
||||||
|
hasBackground: false,
|
||||||
|
logo: "SessionWhite40",
|
||||||
|
themeStyle: ThemeManager.currentTheme.interfaceStyle
|
||||||
|
)
|
||||||
|
.aspectRatio(1, contentMode: .fit)
|
||||||
|
|
||||||
|
Text("settings_view_my_qr_code_explanation".localized())
|
||||||
|
.font(.system(size: Values.verySmallFontSize))
|
||||||
|
.foregroundColor(themeColor: .textSecondary)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, Values.mediumSpacing)
|
||||||
|
.padding(.all, Values.veryLargeSpacing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
QRCodeScreen()
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
// Copyright © 2024 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import SessionUIKit
|
||||||
|
|
||||||
|
struct ScanQRCodeScreen: View {
|
||||||
|
@Binding var result: String
|
||||||
|
@Binding var error: String?
|
||||||
|
@State var hasCameraAccess: Bool = (AVCaptureDevice.authorizationStatus(for: .video) == .authorized)
|
||||||
|
|
||||||
|
var continueAction: (((() -> ())?) -> Void)?
|
||||||
|
|
||||||
|
init(
|
||||||
|
_ result: Binding<String>,
|
||||||
|
error: Binding<String?>,
|
||||||
|
continueAction: (((() -> ())?) -> Void)?
|
||||||
|
) {
|
||||||
|
self._result = result
|
||||||
|
self._error = error
|
||||||
|
self.continueAction = continueAction
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View{
|
||||||
|
ZStack{
|
||||||
|
if hasCameraAccess {
|
||||||
|
VStack {
|
||||||
|
QRCodeScanningVC_SwiftUI { result, onError in
|
||||||
|
self.result = result
|
||||||
|
continueAction?(onError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(
|
||||||
|
maxWidth: .infinity,
|
||||||
|
maxHeight: .infinity
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
VStack(
|
||||||
|
alignment: .center,
|
||||||
|
spacing: Values.mediumSpacing
|
||||||
|
) {
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Text("vc_scan_qr_code_camera_access_explanation".localized())
|
||||||
|
.font(.system(size: Values.smallFontSize))
|
||||||
|
.foregroundColor(themeColor: .textPrimary)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
|
||||||
|
Button {
|
||||||
|
requestCameraAccess()
|
||||||
|
} label: {
|
||||||
|
Text("continue_2".localized())
|
||||||
|
.bold()
|
||||||
|
.font(.system(size: Values.mediumFontSize))
|
||||||
|
.foregroundColor(themeColor: .primary)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(.horizontal, Values.massiveSpacing)
|
||||||
|
.padding(.bottom, Values.massiveSpacing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toastView(message: $error)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func requestCameraAccess() {
|
||||||
|
Permissions.requestCameraPermissionIfNeeded {
|
||||||
|
hasCameraAccess.toggle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
// Copyright © 2024 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import SessionUIKit
|
||||||
|
|
||||||
|
struct TabBarButton: View {
|
||||||
|
@Binding var isSelected: Bool
|
||||||
|
|
||||||
|
let text: String
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack(
|
||||||
|
alignment: .bottom
|
||||||
|
) {
|
||||||
|
Text(text)
|
||||||
|
.bold()
|
||||||
|
.font(.system(size: Values.mediumFontSize))
|
||||||
|
.foregroundColor(themeColor: .textPrimary)
|
||||||
|
.padding(.bottom, 5)
|
||||||
|
.frame(
|
||||||
|
maxWidth: .infinity,
|
||||||
|
maxHeight: .infinity
|
||||||
|
)
|
||||||
|
if isSelected {
|
||||||
|
Rectangle()
|
||||||
|
.foregroundColor(themeColor: .primary)
|
||||||
|
.frame(
|
||||||
|
maxWidth: .infinity,
|
||||||
|
maxHeight: 5
|
||||||
|
)
|
||||||
|
.padding(.horizontal, Values.verySmallSpacing)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CustomTopTabBar: View {
|
||||||
|
@Binding var tabIndex: Int
|
||||||
|
let tabTitles: [String]
|
||||||
|
|
||||||
|
private static let height = isIPhone5OrSmaller ? CGFloat(32) : CGFloat(48)
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
ForEach(0..<tabTitles.count, id: \.self) { index in
|
||||||
|
TabBarButton(
|
||||||
|
isSelected: .constant(tabIndex == index),
|
||||||
|
text: tabTitles[index]
|
||||||
|
)
|
||||||
|
.onTapGesture { onButtonTapped(index: index) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(
|
||||||
|
maxWidth: .infinity,
|
||||||
|
maxHeight: Self.height
|
||||||
|
)
|
||||||
|
.border(width: 1, edges: [.bottom], color: .borderSeparator)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func onButtonTapped(index: Int) {
|
||||||
|
withAnimation(.easeInOut(duration: 0.2)) {
|
||||||
|
tabIndex = index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue