forked from zelo72/mastodon-ios
fix: AutoLayout issue. Update keyboard listener. Expose server error message
This commit is contained in:
parent
0e2aa4570d
commit
6285cb95fa
|
@ -83,6 +83,7 @@
|
||||||
DB427DE225BAA00100D1B89D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DB427DE025BAA00100D1B89D /* LaunchScreen.storyboard */; };
|
DB427DE225BAA00100D1B89D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DB427DE025BAA00100D1B89D /* LaunchScreen.storyboard */; };
|
||||||
DB427DED25BAA00100D1B89D /* MastodonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DEC25BAA00100D1B89D /* MastodonTests.swift */; };
|
DB427DED25BAA00100D1B89D /* MastodonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DEC25BAA00100D1B89D /* MastodonTests.swift */; };
|
||||||
DB427DF825BAA00100D1B89D /* MastodonUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DF725BAA00100D1B89D /* MastodonUITests.swift */; };
|
DB427DF825BAA00100D1B89D /* MastodonUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DF725BAA00100D1B89D /* MastodonUITests.swift */; };
|
||||||
|
DB4563BD25E11A24004DA0B9 /* KeyboardResponderService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4563BC25E11A24004DA0B9 /* KeyboardResponderService.swift */; };
|
||||||
DB45FAB625CA5485005A8AC7 /* UIAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FAB525CA5485005A8AC7 /* UIAlertController.swift */; };
|
DB45FAB625CA5485005A8AC7 /* UIAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FAB525CA5485005A8AC7 /* UIAlertController.swift */; };
|
||||||
DB45FAD725CA6C76005A8AC7 /* UIBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FAD625CA6C76005A8AC7 /* UIBarButtonItem.swift */; };
|
DB45FAD725CA6C76005A8AC7 /* UIBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FAD625CA6C76005A8AC7 /* UIBarButtonItem.swift */; };
|
||||||
DB45FADD25CA6F6B005A8AC7 /* APIService+CoreData+MastodonUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FADC25CA6F6B005A8AC7 /* APIService+CoreData+MastodonUser.swift */; };
|
DB45FADD25CA6F6B005A8AC7 /* APIService+CoreData+MastodonUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FADC25CA6F6B005A8AC7 /* APIService+CoreData+MastodonUser.swift */; };
|
||||||
|
@ -266,6 +267,7 @@
|
||||||
DB427DF325BAA00100D1B89D /* MastodonUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MastodonUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
DB427DF325BAA00100D1B89D /* MastodonUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MastodonUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
DB427DF725BAA00100D1B89D /* MastodonUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonUITests.swift; sourceTree = "<group>"; };
|
DB427DF725BAA00100D1B89D /* MastodonUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonUITests.swift; sourceTree = "<group>"; };
|
||||||
DB427DF925BAA00100D1B89D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
DB427DF925BAA00100D1B89D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
DB4563BC25E11A24004DA0B9 /* KeyboardResponderService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardResponderService.swift; sourceTree = "<group>"; };
|
||||||
DB45FAB525CA5485005A8AC7 /* UIAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIAlertController.swift; sourceTree = "<group>"; };
|
DB45FAB525CA5485005A8AC7 /* UIAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIAlertController.swift; sourceTree = "<group>"; };
|
||||||
DB45FAD625CA6C76005A8AC7 /* UIBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIBarButtonItem.swift; sourceTree = "<group>"; };
|
DB45FAD625CA6C76005A8AC7 /* UIBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIBarButtonItem.swift; sourceTree = "<group>"; };
|
||||||
DB45FADC25CA6F6B005A8AC7 /* APIService+CoreData+MastodonUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+CoreData+MastodonUser.swift"; sourceTree = "<group>"; };
|
DB45FADC25CA6F6B005A8AC7 /* APIService+CoreData+MastodonUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+CoreData+MastodonUser.swift"; sourceTree = "<group>"; };
|
||||||
|
@ -444,6 +446,7 @@
|
||||||
children = (
|
children = (
|
||||||
DB45FB0425CA87B4005A8AC7 /* APIService */,
|
DB45FB0425CA87B4005A8AC7 /* APIService */,
|
||||||
DB45FB0E25CA87D0005A8AC7 /* AuthenticationService.swift */,
|
DB45FB0E25CA87D0005A8AC7 /* AuthenticationService.swift */,
|
||||||
|
DB4563BC25E11A24004DA0B9 /* KeyboardResponderService.swift */,
|
||||||
);
|
);
|
||||||
path = Service;
|
path = Service;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1247,6 +1250,7 @@
|
||||||
2D46976425C2A71500CF4AA9 /* UIIamge.swift in Sources */,
|
2D46976425C2A71500CF4AA9 /* UIIamge.swift in Sources */,
|
||||||
DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */,
|
DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */,
|
||||||
2D3F9E0425DFA133004262D9 /* UITapGestureRecognizer.swift in Sources */,
|
2D3F9E0425DFA133004262D9 /* UITapGestureRecognizer.swift in Sources */,
|
||||||
|
DB4563BD25E11A24004DA0B9 /* KeyboardResponderService.swift in Sources */,
|
||||||
DB5086BE25CC0D9900C2C187 /* SplashPreference.swift in Sources */,
|
DB5086BE25CC0D9900C2C187 /* SplashPreference.swift in Sources */,
|
||||||
2DF75BA125D0E29D00694EC8 /* StatusProvider+TimelinePostTableViewCellDelegate.swift in Sources */,
|
2DF75BA125D0E29D00694EC8 /* StatusProvider+TimelinePostTableViewCellDelegate.swift in Sources */,
|
||||||
2D42FF7E25C82218004A627A /* ActionToolBarContainer.swift in Sources */,
|
2D42FF7E25C82218004A627A /* ActionToolBarContainer.swift in Sources */,
|
||||||
|
|
|
@ -273,6 +273,7 @@ extension AuthenticationViewController {
|
||||||
}
|
}
|
||||||
guard viewModel.isIdle.value else { return }
|
guard viewModel.isIdle.value else { return }
|
||||||
viewModel.isRegistering.value = true
|
viewModel.isRegistering.value = true
|
||||||
|
|
||||||
context.apiService.instance(domain: domain)
|
context.apiService.instance(domain: domain)
|
||||||
.compactMap { [weak self] response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Application>, Error>? in
|
.compactMap { [weak self] response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Application>, Error>? in
|
||||||
guard let self = self else { return nil }
|
guard let self = self else { return nil }
|
||||||
|
@ -289,9 +290,11 @@ extension AuthenticationViewController {
|
||||||
}
|
}
|
||||||
return authenticateInfo
|
return authenticateInfo
|
||||||
}
|
}
|
||||||
.compactMap { [weak self] authenticateInfo -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Token>, Error>? in
|
.compactMap { [weak self] authenticateInfo -> AnyPublisher<(Mastodon.Response.Content<Mastodon.Entity.Token>, AuthenticationViewModel.AuthenticateInfo), Error>? in
|
||||||
guard let self = self else { return nil }
|
guard let self = self else { return nil }
|
||||||
return self.context.apiService.applicationAccessToken(domain: domain, clientID: authenticateInfo.clientID, clientSecret: authenticateInfo.clientSecret)
|
return self.context.apiService.applicationAccessToken(domain: domain, clientID: authenticateInfo.clientID, clientSecret: authenticateInfo.clientSecret)
|
||||||
|
.map { ($0, authenticateInfo) }
|
||||||
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
.switchToLatest()
|
.switchToLatest()
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
|
@ -305,9 +308,13 @@ extension AuthenticationViewController {
|
||||||
case .finished:
|
case .finished:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} receiveValue: { [weak self] response in
|
} receiveValue: { [weak self] response, authenticateInfo in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
let mastodonRegisterViewModel = MastodonRegisterViewModel(domain: domain, applicationToken: response.value)
|
let mastodonRegisterViewModel = MastodonRegisterViewModel(
|
||||||
|
domain: domain,
|
||||||
|
authenticateInfo: authenticateInfo,
|
||||||
|
applicationToken: response.value
|
||||||
|
)
|
||||||
self.coordinator.present(scene: .mastodonRegister(viewModel: mastodonRegisterViewModel), from: self, transition: .show)
|
self.coordinator.present(scene: .mastodonRegister(viewModel: mastodonRegisterViewModel), from: self, transition: .show)
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
|
@ -20,15 +20,13 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency {
|
||||||
var viewModel: MastodonRegisterViewModel!
|
var viewModel: MastodonRegisterViewModel!
|
||||||
|
|
||||||
let tapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
|
let tapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
|
||||||
|
|
||||||
let stackViewTopDistance: CGFloat = 16
|
let stackViewTopDistance: CGFloat = 16
|
||||||
|
|
||||||
var keyboardFrame: CGRect!
|
|
||||||
|
|
||||||
var scrollview: UIScrollView = {
|
var scrollview: UIScrollView = {
|
||||||
let scrollview = UIScrollView()
|
let scrollview = UIScrollView()
|
||||||
scrollview.showsVerticalScrollIndicator = false
|
scrollview.showsVerticalScrollIndicator = false
|
||||||
scrollview.translatesAutoresizingMaskIntoConstraints = false
|
scrollview.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
scrollview.keyboardDismissMode = .interactive
|
||||||
return scrollview
|
return scrollview
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -226,21 +224,21 @@ extension MastodonRegisterViewController {
|
||||||
view.addSubview(scrollview)
|
view.addSubview(scrollview)
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
scrollview.frameLayoutGuide.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
|
scrollview.frameLayoutGuide.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
|
||||||
scrollview.frameLayoutGuide.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
|
scrollview.frameLayoutGuide.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor),
|
||||||
view.trailingAnchor.constraint(equalTo: scrollview.frameLayoutGuide.trailingAnchor, constant: 20),
|
view.readableContentGuide.trailingAnchor.constraint(equalTo: scrollview.frameLayoutGuide.trailingAnchor),
|
||||||
scrollview.frameLayoutGuide.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor),
|
scrollview.frameLayoutGuide.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor),
|
||||||
|
scrollview.frameLayoutGuide.widthAnchor.constraint(equalTo: scrollview.contentLayoutGuide.widthAnchor),
|
||||||
])
|
])
|
||||||
|
|
||||||
// stackview
|
// stackview
|
||||||
scrollview.addSubview(stackView)
|
scrollview.addSubview(stackView)
|
||||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
let bottomEdgeLayoutConstraint: NSLayoutConstraint = scrollview.contentLayoutGuide.bottomAnchor.constraint(equalTo: stackView.bottomAnchor)
|
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
stackView.topAnchor.constraint(equalTo: scrollview.contentLayoutGuide.topAnchor, constant: stackViewTopDistance),
|
stackView.topAnchor.constraint(equalTo: scrollview.contentLayoutGuide.topAnchor, constant: stackViewTopDistance),
|
||||||
stackView.leadingAnchor.constraint(equalTo: scrollview.contentLayoutGuide.leadingAnchor),
|
stackView.leadingAnchor.constraint(equalTo: scrollview.contentLayoutGuide.leadingAnchor),
|
||||||
stackView.trailingAnchor.constraint(equalTo: scrollview.contentLayoutGuide.trailingAnchor),
|
stackView.trailingAnchor.constraint(equalTo: scrollview.contentLayoutGuide.trailingAnchor),
|
||||||
stackView.widthAnchor.constraint(equalTo: scrollview.frameLayoutGuide.widthAnchor),
|
stackView.widthAnchor.constraint(equalTo: scrollview.frameLayoutGuide.widthAnchor),
|
||||||
bottomEdgeLayoutConstraint,
|
scrollview.contentLayoutGuide.bottomAnchor.constraint(equalTo: stackView.bottomAnchor),
|
||||||
])
|
])
|
||||||
|
|
||||||
// photoview
|
// photoview
|
||||||
|
@ -268,7 +266,7 @@ extension MastodonRegisterViewController {
|
||||||
plusIcon.trailingAnchor.constraint(equalTo: photoButton.trailingAnchor),
|
plusIcon.trailingAnchor.constraint(equalTo: photoButton.trailingAnchor),
|
||||||
plusIcon.bottomAnchor.constraint(equalTo: photoButton.bottomAnchor),
|
plusIcon.bottomAnchor.constraint(equalTo: photoButton.bottomAnchor),
|
||||||
])
|
])
|
||||||
|
|
||||||
// textfield
|
// textfield
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
usernameTextField.heightAnchor.constraint(equalToConstant: 50).priority(.defaultHigh),
|
usernameTextField.heightAnchor.constraint(equalToConstant: 50).priority(.defaultHigh),
|
||||||
|
@ -280,34 +278,49 @@ extension MastodonRegisterViewController {
|
||||||
// password
|
// password
|
||||||
stackView.setCustomSpacing(6, after: passwordTextField)
|
stackView.setCustomSpacing(6, after: passwordTextField)
|
||||||
stackView.setCustomSpacing(32, after: passwordCheckLabel)
|
stackView.setCustomSpacing(32, after: passwordCheckLabel)
|
||||||
|
|
||||||
// button
|
// button
|
||||||
signUpButton.translatesAutoresizingMaskIntoConstraints = false
|
signUpButton.translatesAutoresizingMaskIntoConstraints = false
|
||||||
stackView.addArrangedSubview(signUpButton)
|
stackView.addArrangedSubview(signUpButton)
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
signUpButton.heightAnchor.constraint(equalToConstant: 44).priority(.defaultHigh),
|
signUpButton.heightAnchor.constraint(equalToConstant: 44).priority(.defaultHigh),
|
||||||
])
|
])
|
||||||
|
|
||||||
signUpActivityIndicatorView.translatesAutoresizingMaskIntoConstraints = false
|
signUpActivityIndicatorView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
scrollview.addSubview(signUpActivityIndicatorView)
|
scrollview.addSubview(signUpActivityIndicatorView)
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
signUpActivityIndicatorView.centerXAnchor.constraint(equalTo: signUpButton.centerXAnchor),
|
signUpActivityIndicatorView.centerXAnchor.constraint(equalTo: signUpButton.centerXAnchor),
|
||||||
signUpActivityIndicatorView.centerYAnchor.constraint(equalTo: signUpButton.centerYAnchor),
|
signUpActivityIndicatorView.centerYAnchor.constraint(equalTo: signUpButton.centerYAnchor),
|
||||||
])
|
])
|
||||||
|
|
||||||
NotificationCenter.default.publisher(for: UIResponder.keyboardWillChangeFrameNotification, object: nil)
|
Publishers.CombineLatest3(
|
||||||
.sink { [weak self] notification in
|
KeyboardResponderService.shared.isShow.eraseToAnyPublisher(),
|
||||||
guard let endFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else {
|
KeyboardResponderService.shared.state.eraseToAnyPublisher(),
|
||||||
return
|
KeyboardResponderService.shared.endFrame.eraseToAnyPublisher()
|
||||||
}
|
)
|
||||||
self?.keyboardFrame = endFrame
|
.sink(receiveValue: { [weak self] isShow, state, endFrame in
|
||||||
UIView.animate(withDuration: 0.3) {
|
guard let self = self else { return }
|
||||||
bottomEdgeLayoutConstraint.constant = UIScreen.main.bounds.height - endFrame.origin.y + 26
|
|
||||||
self?.view.layoutIfNeeded()
|
guard isShow, state == .dock else {
|
||||||
}
|
self.scrollview.contentInset.bottom = 0.0
|
||||||
|
self.scrollview.verticalScrollIndicatorInsets.bottom = 0.0
|
||||||
|
return
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
|
||||||
|
// isShow AND dock state
|
||||||
|
let contentFrame = self.view.convert(self.scrollview.frame, to: nil)
|
||||||
|
let padding = contentFrame.maxY - endFrame.minY
|
||||||
|
guard padding > 0 else {
|
||||||
|
self.scrollview.contentInset.bottom = 0.0
|
||||||
|
self.scrollview.verticalScrollIndicatorInsets.bottom = 0.0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.scrollview.contentInset.bottom = padding + 16
|
||||||
|
self.scrollview.verticalScrollIndicatorInsets.bottom = padding + 16
|
||||||
|
})
|
||||||
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
viewModel.isRegistering
|
viewModel.isRegistering
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] isRegistering in
|
.sink { [weak self] isRegistering in
|
||||||
|
@ -317,7 +330,7 @@ extension MastodonRegisterViewController {
|
||||||
self.signUpButton.isEnabled = !isRegistering
|
self.signUpButton.isEnabled = !isRegistering
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
viewModel.isUsernameValid
|
viewModel.isUsernameValid
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] isValid in
|
.sink { [weak self] isValid in
|
||||||
|
@ -359,7 +372,7 @@ extension MastodonRegisterViewController {
|
||||||
self.signUpButton.isEnabled = isUsernameValid ?? false && isDisplaynameValid ?? false && isEmailValid ?? false && isPasswordValid ?? false
|
self.signUpButton.isEnabled = isUsernameValid ?? false && isDisplaynameValid ?? false && isEmailValid ?? false && isPasswordValid ?? false
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
viewModel.error
|
viewModel.error
|
||||||
.compactMap { $0 }
|
.compactMap { $0 }
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
|
@ -375,6 +388,7 @@ extension MastodonRegisterViewController {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
NotificationCenter.default
|
NotificationCenter.default
|
||||||
.publisher(for: UITextField.textDidChangeNotification, object: passwordTextField)
|
.publisher(for: UITextField.textDidChangeNotification, object: passwordTextField)
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
|
@ -385,20 +399,26 @@ extension MastodonRegisterViewController {
|
||||||
self.passwordCheckLabel.attributedText = self.viewModel.attributeStringForPassword(eightCharacters: validations.0, oneNumber: validations.1, oneSpecialCharacter: validations.2)
|
self.passwordCheckLabel.attributedText = self.viewModel.attributeStringForPassword(eightCharacters: validations.0, oneNumber: validations.1, oneSpecialCharacter: validations.2)
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
signUpButton.addTarget(self, action: #selector(MastodonRegisterViewController.signUpButtonPressed(_:)), for: .touchUpInside)
|
signUpButton.addTarget(self, action: #selector(MastodonRegisterViewController.signUpButtonPressed(_:)), for: .touchUpInside)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension MastodonRegisterViewController: UITextFieldDelegate {
|
extension MastodonRegisterViewController: UITextFieldDelegate {
|
||||||
func textFieldDidBeginEditing(_ textField: UITextField) {
|
func textFieldDidBeginEditing(_ textField: UITextField) {
|
||||||
var bottomOffsetY: CGFloat = textField.frame.origin.y + textField.frame.height - scrollview.frame.height + keyboardFrame.size.height + stackViewTopDistance
|
// align to password label when overlap
|
||||||
if textField == passwordTextField {
|
if textField === passwordTextField,
|
||||||
bottomOffsetY += passwordCheckLabel.frame.height
|
KeyboardResponderService.shared.isShow.value,
|
||||||
}
|
KeyboardResponderService.shared.state.value == .dock {
|
||||||
|
let endFrame = KeyboardResponderService.shared.endFrame.value
|
||||||
if bottomOffsetY > 0 {
|
let contentFrame = self.scrollview.convert(self.passwordCheckLabel.frame, to: nil)
|
||||||
scrollview.setContentOffset(CGPoint(x: 0, y: bottomOffsetY), animated: true)
|
let padding = contentFrame.maxY - endFrame.minY
|
||||||
|
if padding > 0 {
|
||||||
|
let contentOffsetY = scrollview.contentOffset.y
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.scrollview.setContentOffset(CGPoint(x: 0, y: contentOffsetY + padding + 16.0), animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -454,7 +474,10 @@ extension MastodonRegisterViewController {
|
||||||
|
|
||||||
@objc private func signUpButtonPressed(_ sender: UIButton) {
|
@objc private func signUpButtonPressed(_ sender: UIButton) {
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", (#file as NSString).lastPathComponent, #line, #function)
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", (#file as NSString).lastPathComponent, #line, #function)
|
||||||
if !validateAllTextField() {
|
guard validateAllTextField(),
|
||||||
|
let username = viewModel.username.value,
|
||||||
|
let email = viewModel.email.value,
|
||||||
|
let password = viewModel.password.value else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -463,9 +486,9 @@ extension MastodonRegisterViewController {
|
||||||
|
|
||||||
let query = Mastodon.API.Account.RegisterQuery(
|
let query = Mastodon.API.Account.RegisterQuery(
|
||||||
reason: nil,
|
reason: nil,
|
||||||
username: viewModel.username.value!,
|
username: username,
|
||||||
email: viewModel.email.value!,
|
email: email,
|
||||||
password: viewModel.password.value!,
|
password: password,
|
||||||
agreement: true, // TODO:
|
agreement: true, // TODO:
|
||||||
locale: "en" // TODO:
|
locale: "en" // TODO:
|
||||||
)
|
)
|
||||||
|
|
|
@ -15,7 +15,9 @@ final class MastodonRegisterViewModel {
|
||||||
|
|
||||||
// input
|
// input
|
||||||
let domain: String
|
let domain: String
|
||||||
|
let authenticateInfo: AuthenticationViewModel.AuthenticateInfo
|
||||||
let applicationToken: Mastodon.Entity.Token
|
let applicationToken: Mastodon.Entity.Token
|
||||||
|
|
||||||
let isRegistering = CurrentValueSubject<Bool, Never>(false)
|
let isRegistering = CurrentValueSubject<Bool, Never>(false)
|
||||||
let username = CurrentValueSubject<String?, Never>(nil)
|
let username = CurrentValueSubject<String?, Never>(nil)
|
||||||
let displayname = CurrentValueSubject<String?, Never>(nil)
|
let displayname = CurrentValueSubject<String?, Never>(nil)
|
||||||
|
@ -32,8 +34,13 @@ final class MastodonRegisterViewModel {
|
||||||
|
|
||||||
let error = CurrentValueSubject<Error?, Never>(nil)
|
let error = CurrentValueSubject<Error?, Never>(nil)
|
||||||
|
|
||||||
init(domain: String, applicationToken: Mastodon.Entity.Token) {
|
init(
|
||||||
|
domain: String,
|
||||||
|
authenticateInfo: AuthenticationViewModel.AuthenticateInfo,
|
||||||
|
applicationToken: Mastodon.Entity.Token
|
||||||
|
) {
|
||||||
self.domain = domain
|
self.domain = domain
|
||||||
|
self.authenticateInfo = authenticateInfo
|
||||||
self.applicationToken = applicationToken
|
self.applicationToken = applicationToken
|
||||||
self.applicationAuthorization = Mastodon.API.OAuth.Authorization(accessToken: applicationToken.accessToken)
|
self.applicationAuthorization = Mastodon.API.OAuth.Authorization(accessToken: applicationToken.accessToken)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
//
|
||||||
|
// KeyboardResponderService.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by Cirno MainasuK on 2021-2-20.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
final class KeyboardResponderService {
|
||||||
|
|
||||||
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
// MARK: - Singleton
|
||||||
|
public static let shared = KeyboardResponderService()
|
||||||
|
|
||||||
|
// output
|
||||||
|
let isShow = CurrentValueSubject<Bool, Never>(false)
|
||||||
|
let state = CurrentValueSubject<KeyboardState, Never>(.none)
|
||||||
|
let endFrame = CurrentValueSubject<CGRect, Never>(.zero)
|
||||||
|
|
||||||
|
private init() {
|
||||||
|
NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification, object: nil)
|
||||||
|
.sink { notification in
|
||||||
|
self.isShow.value = true
|
||||||
|
self.updateInternalStatus(notification: notification)
|
||||||
|
}
|
||||||
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
|
NotificationCenter.default.publisher(for: UIResponder.keyboardDidHideNotification, object: nil)
|
||||||
|
.sink { notification in
|
||||||
|
self.isShow.value = false
|
||||||
|
self.updateInternalStatus(notification: notification)
|
||||||
|
}
|
||||||
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
|
NotificationCenter.default.publisher(for: UIResponder.keyboardDidChangeFrameNotification, object: nil)
|
||||||
|
.sink { notification in
|
||||||
|
self.updateInternalStatus(notification: notification)
|
||||||
|
}
|
||||||
|
.store(in: &disposeBag)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension KeyboardResponderService {
|
||||||
|
|
||||||
|
private func updateInternalStatus(notification: Notification) {
|
||||||
|
guard let isLocal = notification.userInfo?[UIWindow.keyboardIsLocalUserInfoKey] as? Bool,
|
||||||
|
let endFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.endFrame.value = endFrame
|
||||||
|
|
||||||
|
guard isLocal else {
|
||||||
|
self.state.value = .notLocal
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if floating
|
||||||
|
guard endFrame.width == UIScreen.main.bounds.width else {
|
||||||
|
self.state.value = .floating
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if undock | split
|
||||||
|
let dockMinY = UIScreen.main.bounds.height - endFrame.height
|
||||||
|
if endFrame.minY < dockMinY {
|
||||||
|
self.state.value = .notDock
|
||||||
|
} else {
|
||||||
|
self.state.value = .dock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension KeyboardResponderService {
|
||||||
|
enum KeyboardState {
|
||||||
|
case none
|
||||||
|
case notLocal
|
||||||
|
case notDock // undock | split
|
||||||
|
case floating // iPhone size floating
|
||||||
|
case dock
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,3 +34,27 @@ extension Mastodon.API {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Mastodon.API.Error: LocalizedError {
|
||||||
|
|
||||||
|
public var errorDescription: String? {
|
||||||
|
guard let mastodonError = mastodonError else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
switch mastodonError {
|
||||||
|
case .generic(let error):
|
||||||
|
return error.error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var failureReason: String? {
|
||||||
|
guard let mastodonError = mastodonError else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
switch mastodonError {
|
||||||
|
case .generic(let error):
|
||||||
|
return error.errorDescription
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue