mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			207 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			207 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Swift
		
	
| // Copyright © 2024 Rangeproof Pty Ltd. All rights reserved.
 | |
| 
 | |
| import SwiftUI
 | |
| import SessionUIKit
 | |
| import SessionMessagingKit
 | |
| import SessionUtilitiesKit
 | |
| import SignalUtilitiesKit
 | |
| import SessionSnodeKit
 | |
| 
 | |
| struct NewMessageScreen: View {
 | |
|     @EnvironmentObject var host: HostWrapper
 | |
|     
 | |
|     @State var tabIndex = 0
 | |
|     @State private var accountIdOrONS: String
 | |
|     @State private var errorString: String? = nil
 | |
|     
 | |
|     init(accountId: String = "") {
 | |
|         self.accountIdOrONS = accountId
 | |
|     }
 | |
|     
 | |
|     var body: some View {
 | |
|         ZStack(alignment: .topLeading) {
 | |
|             VStack(
 | |
|                 spacing: 0
 | |
|             ){
 | |
|                 CustomTopTabBar(
 | |
|                     tabIndex: $tabIndex,
 | |
|                     tabTitles: [
 | |
|                         "accountIdEnter".localized(),
 | |
|                         "qrScan".localized()
 | |
|                     ]
 | |
|                 ).frame(maxWidth: .infinity)
 | |
|                     
 | |
|                 if tabIndex == 0 {
 | |
|                     EnterAccountIdScreen(
 | |
|                         accountIdOrONS: $accountIdOrONS,
 | |
|                         error: $errorString, 
 | |
|                         continueAction: continueWithAccountIdOrONS
 | |
|                     )
 | |
|                 }
 | |
|                 else {
 | |
|                     ScanQRCodeScreen(
 | |
|                         $accountIdOrONS,
 | |
|                         error: $errorString,
 | |
|                         continueAction: continueWithAccountIdFromQRCode
 | |
|                     )
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         .backgroundColor(themeColor: .backgroundSecondary)
 | |
|     }
 | |
|     
 | |
|     fileprivate func startNewPrivateChatIfPossible(with sessionId: String, onError: (() -> ())?) {
 | |
|         if !KeyPair.isValidHexEncodedPublicKey(candidate: sessionId) {
 | |
|             errorString = "qrNotAccountId".localized()
 | |
|             
 | |
|             DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
 | |
|                 onError?()
 | |
|             }
 | |
|         }
 | |
|         else {
 | |
|             startNewDM(with: sessionId)
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     func continueWithAccountIdFromQRCode(onSuccess: (() -> ())?, onError: (() -> ())?) {
 | |
|         startNewPrivateChatIfPossible(with: accountIdOrONS, onError: onError)
 | |
|     }
 | |
|     
 | |
|     func continueWithAccountIdOrONS() {
 | |
|         let maybeSessionId: SessionId? = try? SessionId(from: accountIdOrONS)
 | |
|         
 | |
|         if KeyPair.isValidHexEncodedPublicKey(candidate: accountIdOrONS) {
 | |
|             switch maybeSessionId?.prefix {
 | |
|                 case .standard:
 | |
|                     startNewDM(with: accountIdOrONS)
 | |
|                     
 | |
|                 default:
 | |
|                     errorString = "accountIdErrorInvalid".localized()
 | |
|             }
 | |
|             return
 | |
|         }
 | |
|         
 | |
|         // This could be an ONS name
 | |
|         ModalActivityIndicatorViewController
 | |
|             .present(fromViewController: self.host.controller?.navigationController!, canCancel: false) { modalActivityIndicator in
 | |
|             SnodeAPI
 | |
|                 .getSessionID(for: accountIdOrONS)
 | |
|                 .subscribe(on: DispatchQueue.global(qos: .userInitiated))
 | |
|                 .receive(on: DispatchQueue.main)
 | |
|                 .sinkUntilComplete(
 | |
|                     receiveCompletion: { result in
 | |
|                         switch result {
 | |
|                             case .finished: break
 | |
|                             case .failure(let error):
 | |
|                                 modalActivityIndicator.dismiss {
 | |
|                                     let message: String = {
 | |
|                                         switch error {
 | |
|                                             case SnodeAPIError.onsNotFound:
 | |
|                                                 return "onsErrorNotRecognized".localized()
 | |
|                                             default:
 | |
|                                                 return "onsErrorUnableToSearch".localized()
 | |
|                                         }
 | |
|                                     }()
 | |
|                                     
 | |
|                                     errorString = message
 | |
|                                 }
 | |
|                         }
 | |
|                     },
 | |
|                     receiveValue: { sessionId in
 | |
|                         modalActivityIndicator.dismiss {
 | |
|                             self.startNewDM(with: sessionId)
 | |
|                         }
 | |
|                     }
 | |
|                 )
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     private func startNewDM(with sessionId: String) {
 | |
|         SessionApp.presentConversationCreatingIfNeeded(
 | |
|             for: sessionId,
 | |
|             variant: .contact,
 | |
|             action: .compose,
 | |
|             dismissing: self.host.controller,
 | |
|             animated: false
 | |
|         )
 | |
|     }
 | |
| }
 | |
| 
 | |
| struct EnterAccountIdScreen: View {
 | |
|     @Binding var accountIdOrONS: String
 | |
|     @Binding var error: String?
 | |
|     @State var isTextFieldInErrorMode: Bool = false
 | |
|     var continueAction: () -> ()
 | |
|     
 | |
|     var body: some View {
 | |
|         VStack(
 | |
|             alignment: .center,
 | |
|             spacing: Values.mediumSpacing
 | |
|         ) {
 | |
|             SessionTextField(
 | |
|                 $accountIdOrONS,
 | |
|                 placeholder: "accountIdOrOnsEnter".localized(),
 | |
|                 error: $error, 
 | |
|                 accessibility: Accessibility(
 | |
|                     identifier: "Session id input box",
 | |
|                     label: "Session id input box"
 | |
|                 )
 | |
|             ) {
 | |
|                 ZStack {
 | |
|                     Text("\("messageNewDescriptionMobile".localized())\(Image(systemName: "questionmark.circle"))")
 | |
|                         .font(.system(size: Values.verySmallFontSize))
 | |
|                         .foregroundColor(themeColor: .textSecondary)
 | |
|                         .multilineTextAlignment(.center)
 | |
|                 }
 | |
|                 .accessibility(
 | |
|                     Accessibility(
 | |
|                         identifier: "Help desk link",
 | |
|                         label: "Help desk link"
 | |
|                     )
 | |
|                 )
 | |
|                 .padding(.horizontal, Values.smallSpacing)
 | |
|                 .padding(.top, Values.smallSpacing)
 | |
|                 .onTapGesture {
 | |
|                     if let url: URL = URL(string: "https://sessionapp.zendesk.com/hc/en-us/articles/4439132747033-How-do-Session-ID-usernames-work-") {
 | |
|                         UIApplication.shared.open(url)
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             
 | |
|             Spacer()
 | |
|             
 | |
|             if !accountIdOrONS.isEmpty {
 | |
|                 Button {
 | |
|                     continueAction()
 | |
|                 } label: {
 | |
|                     Text("next".localized())
 | |
|                         .bold()
 | |
|                         .font(.system(size: Values.smallFontSize))
 | |
|                         .foregroundColor(themeColor: .sessionButton_text)
 | |
|                         .frame(
 | |
|                             maxWidth: 160,
 | |
|                             maxHeight: Values.largeButtonHeight,
 | |
|                             alignment: .center
 | |
|                         )
 | |
|                         .overlay(
 | |
|                             Capsule()
 | |
|                                 .stroke(themeColor: .sessionButton_border)
 | |
|                         )
 | |
|                 }
 | |
|                 .accessibility(
 | |
|                     Accessibility(
 | |
|                         identifier: "Next",
 | |
|                         label: "Next"
 | |
|                     )
 | |
|                 )
 | |
|                 .padding(.horizontal, Values.massiveSpacing)
 | |
|             }
 | |
|         }
 | |
|         .padding(.all, Values.largeSpacing)
 | |
|     }
 | |
| }
 | |
| 
 | |
| #Preview {
 | |
|     NewMessageScreen()
 | |
| }
 |