From 0fc40edb7102001258e8c7579f7be81f8f90030f Mon Sep 17 00:00:00 2001 From: Ryan ZHAO <> Date: Mon, 29 Jan 2024 13:43:30 +1100 Subject: [PATCH] update the text input to support multiple lines --- .../New Conversation/NewMessageScreen.swift | 31 +++-- .../Translations/en.lproj/Localizable.strings | 3 + Session/Onboarding/DisplayNameScreen.swift | 2 +- Session/Onboarding/LoadAccountScreen.swift | 2 +- .../Components/SessionTextField.swift | 114 +++++++++++++----- .../Utilities/SwiftUI+Utilities.swift | 10 ++ 6 files changed, 119 insertions(+), 43 deletions(-) diff --git a/Session/Home/New Conversation/NewMessageScreen.swift b/Session/Home/New Conversation/NewMessageScreen.swift index 3be567b7f..a79591f45 100644 --- a/Session/Home/New Conversation/NewMessageScreen.swift +++ b/Session/Home/New Conversation/NewMessageScreen.swift @@ -30,14 +30,15 @@ struct NewMessageScreen: View { if tabIndex == 0 { EnterAccountIdScreen( accountIdOrONS: $accountIdOrONS, - error: $errorString + error: $errorString, + continueAction: continueWithAccountIdOrONS ) } else { ScanQRCodeScreen( $accountIdOrONS, error: $errorString, - continueAction: continueWithAccountId + continueAction: continueWithAccountIdFromQRCode ) } } @@ -54,10 +55,14 @@ struct NewMessageScreen: View { } } - func continueWithAccountId(onError: (() -> ())?) { + func continueWithAccountIdFromQRCode(onError: (() -> ())?) { startNewPrivateChatIfPossible(with: accountIdOrONS, onError: onError) } + func continueWithAccountIdOrONS() { + startNewDMIfPossible(with: accountIdOrONS, onError: nil) + } + fileprivate func startNewDMIfPossible(with onsNameOrPublicKey: String, onError: (() -> ())?) { let maybeSessionId: SessionId? = SessionId(from: onsNameOrPublicKey) @@ -67,10 +72,10 @@ struct NewMessageScreen: View { startNewDM(with: onsNameOrPublicKey) case .blinded15, .blinded25: - errorString = "DM_ERROR_DIRECT_BLINDED_ID".localized() + errorString = "new_message_screen_error_msg_invalid_account_id".localized() default: - errorString = "DM_ERROR_INVALID".localized() + break } return } @@ -93,6 +98,8 @@ struct NewMessageScreen: View { switch error { case .decryptionFailed, .hashingFailed, .validationFailed: messageOrNil = error.errorDescription + case .generic: + messageOrNil = "new_message_screen_error_msg_network_issue".localized() default: break } } @@ -102,8 +109,8 @@ struct NewMessageScreen: View { } return (maybeSessionId?.prefix == .blinded15 || maybeSessionId?.prefix == .blinded25 ? - "DM_ERROR_DIRECT_BLINDED_ID".localized() : - "DM_ERROR_INVALID".localized() + "new_message_screen_error_msg_invalid_account_id".localized() : + "new_message_screen_error_msg_unrecognized_ons".localized() ) }() @@ -133,6 +140,8 @@ struct NewMessageScreen: View { struct EnterAccountIdScreen: View { @Binding var accountIdOrONS: String @Binding var error: String? + @State var isTextFieldInErrorMode: Bool = false + var continueAction: () -> () var body: some View { VStack( @@ -143,9 +152,7 @@ struct EnterAccountIdScreen: View { $accountIdOrONS, placeholder: "new_message_screen_enter_account_id_hint".localized(), error: $error - ) - - if error?.isEmpty != true { + ) { ZStack { if #available(iOS 14.0, *) { Text("\("new_message_screen_enter_account_id_explanation".localized())\(Image(systemName: "questionmark.circle"))") @@ -160,7 +167,7 @@ struct EnterAccountIdScreen: View { } } .padding(.horizontal, Values.smallSpacing) - .padding(.top, -50) + .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) @@ -172,7 +179,7 @@ struct EnterAccountIdScreen: View { if !accountIdOrONS.isEmpty { Button { - + continueAction() } label: { Text("next".localized()) .bold() diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index 105722746..2b79a7ec5 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -877,3 +877,6 @@ The point that a message will disappear in a disappearing message update message "new_message_screen_enter_account_id_tab_title" = "Enter Account ID"; "new_message_screen_enter_account_id_hint" = "Enter Account ID or ONS"; "new_message_screen_enter_account_id_explanation" = "Start a new conversation by entering your friend's Account ID, ONS or scanning their QR code."; +"new_message_screen_error_msg_invalid_account_id" = "This Account ID is invalid. Please check and try again."; +"new_message_screen_error_msg_unrecognized_ons" = "We couldn’t recognize this ONS. Please check and try again."; +"new_message_screen_error_msg_network_issue" = "We were unable to search for this ONS. Please try again later."; diff --git a/Session/Onboarding/DisplayNameScreen.swift b/Session/Onboarding/DisplayNameScreen.swift index df950caf8..6dfcee012 100644 --- a/Session/Onboarding/DisplayNameScreen.swift +++ b/Session/Onboarding/DisplayNameScreen.swift @@ -53,7 +53,7 @@ struct DisplayNameScreen: View { $displayName, placeholder: "onboarding_display_name_hint".localized(), error: $error - ) + ) {} Spacer(minLength: 0) .frame(maxHeight: Values.massiveSpacing) diff --git a/Session/Onboarding/LoadAccountScreen.swift b/Session/Onboarding/LoadAccountScreen.swift index 9664e7733..c9bf84f4d 100644 --- a/Session/Onboarding/LoadAccountScreen.swift +++ b/Session/Onboarding/LoadAccountScreen.swift @@ -163,7 +163,7 @@ struct EnterRecoveryPasswordScreen: View{ $recoveryPassword, placeholder: "onboarding_recovery_password_hint".localized(), error: $error - ) + ) {} Spacer(minLength: 0) .frame(maxHeight: Values.massiveSpacing) diff --git a/SessionUIKit/Components/SessionTextField.swift b/SessionUIKit/Components/SessionTextField.swift index 9fd96129d..e285fb257 100644 --- a/SessionUIKit/Components/SessionTextField.swift +++ b/SessionUIKit/Components/SessionTextField.swift @@ -2,12 +2,14 @@ import SwiftUI import Combine +import UIKit -public struct SessionTextField: View { +public struct SessionTextField: View where ExplanationView: View { @Binding var text: String @Binding var error: String? @State var previousError: String = "" + let explanationView: () -> ExplanationView let placeholder: String var textThemeColor: ThemeValue { (error?.isEmpty == false) ? .danger : .textPrimary @@ -18,13 +20,15 @@ public struct SessionTextField: View { return false } - static let height: CGFloat = isIPhone5OrSmaller ? CGFloat(48) : CGFloat(80) - static let cornerRadius: CGFloat = 13 + let height: CGFloat = isIPhone5OrSmaller ? CGFloat(48) : CGFloat(80) + let cornerRadius: CGFloat = 13 - public init(_ text: Binding, placeholder: String, error: Binding) { + public init(_ text: Binding, placeholder: String, error: Binding, @ViewBuilder explanationView: @escaping () -> ExplanationView) { self._text = text self.placeholder = placeholder self._error = error + self.explanationView = explanationView + UITextView.appearance().backgroundColor = .clear } public var body: some View { @@ -32,49 +36,95 @@ public struct SessionTextField: View { alignment: .center, spacing: Values.smallSpacing ) { - ZStack(alignment: .topLeading) { + ZStack(alignment: .leading) { if text.isEmpty { Text(placeholder) .font(.system(size: Values.smallFontSize)) .foregroundColor(themeColor: isErrorMode ? .danger : .textSecondary) } - SwiftUI.TextField( - "", - text: $text.onChange{ value in - if error?.isEmpty == false && text != value { - previousError = error! - error = nil - } + if #available(iOS 16.0, *) { + SwiftUI.TextField( + "", + text: $text.onChange{ value in + if error?.isEmpty == false && text != value { + previousError = error! + error = nil + } + }, + axis: .vertical + ) + .font(.system(size: Values.smallFontSize)) + .foregroundColor(themeColor: textThemeColor) + } else if #available(iOS 14.0, *) { + ZStack { + TextEditor( + text: $text.onChange{ value in + if error?.isEmpty == false && text != value { + previousError = error! + error = nil + } + } + ) + .font(.system(size: Values.smallFontSize)) + .foregroundColor(themeColor: textThemeColor) + .transparentScrolling() + .frame(maxHeight: self.height) + .padding(.all, -4) + + // FIXME: This is a workaround for dynamic height of the TextEditor. + Text(text.isEmpty ? placeholder : text) + .font(.system(size: Values.smallFontSize)) + .opacity(0) + .padding(.all, 4) + .frame( + maxWidth: .infinity, + maxHeight: self.height + ) } - ) - .font(.system(size: Values.smallFontSize)) - .foregroundColor(themeColor: textThemeColor) + .fixedSize(horizontal: false, vertical: true) + } else { + SwiftUI.TextField( + "", + text: $text.onChange{ value in + if error?.isEmpty == false && text != value { + previousError = error! + error = nil + } + } + ) + .font(.system(size: Values.smallFontSize)) + .foregroundColor(themeColor: textThemeColor) + } } .padding(.horizontal, Values.largeSpacing) .frame(maxWidth: .infinity) - .frame(height: Self.height) + .frame(height: self.height) .overlay( RoundedRectangle( cornerSize: CGSize( - width: Self.cornerRadius, - height: Self.cornerRadius + width: self.cornerRadius, + height: self.cornerRadius ) ) .stroke(themeColor: isErrorMode ? .danger : .borderSeparator) ) - ZStack { - Text(error ?? previousError) - .bold() - .font(.system(size: Values.smallFontSize)) - .foregroundColor(themeColor: .danger) - .multilineTextAlignment(.center) + if isErrorMode { + ZStack { + Text(error ?? previousError) + .bold() + .font(.system(size: Values.smallFontSize)) + .foregroundColor(themeColor: .danger) + .multilineTextAlignment(.center) + } + .frame( + height: 50, + alignment: .top + ) + } else { + explanationView() } - .frame( - height: 50, - alignment: .top - ) } } } @@ -82,7 +132,13 @@ public struct SessionTextField: View { struct SessionTextField_Previews: PreviewProvider { @State static var text: String = "test" @State static var error: String? = "test error" + @State static var emptyText: String = "" + @State static var emptyError: String? = nil static var previews: some View { - SessionTextField($text, placeholder: "Placeholder", error: $error) + VStack { + SessionTextField($text, placeholder: "Placeholder", error: $error) {} + SessionTextField($emptyText, placeholder: "Placeholder", error: $emptyError) {} + } + } } diff --git a/SessionUIKit/Utilities/SwiftUI+Utilities.swift b/SessionUIKit/Utilities/SwiftUI+Utilities.swift index c550d1e40..de424ccf2 100644 --- a/SessionUIKit/Utilities/SwiftUI+Utilities.swift +++ b/SessionUIKit/Utilities/SwiftUI+Utilities.swift @@ -102,6 +102,16 @@ extension View { public func toastView(message: Binding) -> some View { self.modifier(ToastModifier(message: message)) } + + public func transparentScrolling() -> some View { + if #available(iOS 16.0, *) { + return scrollContentBackground(.hidden) + } else { + return onAppear { + UITextView.appearance().backgroundColor = .clear + } + } + } } extension Binding {