fix: add missing error prompt for sign up scene
This commit is contained in:
parent
033c584eb4
commit
2ae3f21a99
|
@ -145,8 +145,6 @@
|
|||
DB0618012785732C0030EE79 /* ServerRulesTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0618002785732C0030EE79 /* ServerRulesTableViewCell.swift */; };
|
||||
DB0618032785A7100030EE79 /* RegisterSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0618022785A7100030EE79 /* RegisterSection.swift */; };
|
||||
DB0618052785A73D0030EE79 /* RegisterItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0618042785A73D0030EE79 /* RegisterItem.swift */; };
|
||||
DB0618072785A8880030EE79 /* MastodonRegisterViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0618062785A8880030EE79 /* MastodonRegisterViewModel+Diffable.swift */; };
|
||||
DB06180A2785B2AB0030EE79 /* MastodonRegisterAvatarTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0618092785B2AB0030EE79 /* MastodonRegisterAvatarTableViewCell.swift */; };
|
||||
DB084B5725CBC56C00F898ED /* Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB084B5625CBC56C00F898ED /* Status.swift */; };
|
||||
DB0A322E280EE9FD001729D2 /* DiscoveryIntroBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0A322D280EE9FD001729D2 /* DiscoveryIntroBannerView.swift */; };
|
||||
DB0AC6FC25CD02E600D75117 /* APIService+Instance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */; };
|
||||
|
@ -400,10 +398,10 @@
|
|||
DB75BF1E263C1C1B00EDBF1F /* CustomScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB75BF1D263C1C1B00EDBF1F /* CustomScheduler.swift */; };
|
||||
DB789A0B25F9F2950071ACA0 /* ComposeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB789A0A25F9F2950071ACA0 /* ComposeViewController.swift */; };
|
||||
DB789A1225F9F2CC0071ACA0 /* ComposeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB789A1125F9F2CC0071ACA0 /* ComposeViewModel.swift */; };
|
||||
DB7A9F912818EAF10016AF98 /* MastodonRegisterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7A9F902818EAF10016AF98 /* MastodonRegisterView.swift */; };
|
||||
DB7A9F932818F33C0016AF98 /* MastodonServerRulesViewController+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7A9F922818F33C0016AF98 /* MastodonServerRulesViewController+Debug.swift */; };
|
||||
DB7F48452620241000796008 /* ProfileHeaderViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7F48442620241000796008 /* ProfileHeaderViewModel.swift */; };
|
||||
DB8190C62601FF0400020C08 /* AttachmentContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8190C52601FF0400020C08 /* AttachmentContainerView.swift */; };
|
||||
DB8481152788121200BBEABA /* MastodonRegisterTextFieldTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8481142788121200BBEABA /* MastodonRegisterTextFieldTableViewCell.swift */; };
|
||||
DB84811727883C2600BBEABA /* MastodonRegisterPasswordHintTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB84811627883C2600BBEABA /* MastodonRegisterPasswordHintTableViewCell.swift */; };
|
||||
DB852D1926FAEB6B00FC9D81 /* SidebarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB852D1826FAEB6B00FC9D81 /* SidebarViewController.swift */; };
|
||||
DB852D1C26FB021500FC9D81 /* RootSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB852D1B26FB021500FC9D81 /* RootSplitViewController.swift */; };
|
||||
DB852D1F26FB037800FC9D81 /* SidebarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB852D1E26FB037800FC9D81 /* SidebarViewModel.swift */; };
|
||||
|
@ -417,6 +415,7 @@
|
|||
DB8AF54525C13647002E6C99 /* NeedsDependency.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF54325C13647002E6C99 /* NeedsDependency.swift */; };
|
||||
DB8AF55025C13703002E6C99 /* MainTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF54F25C13703002E6C99 /* MainTabBarController.swift */; };
|
||||
DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF55C25C138B7002E6C99 /* UIViewController.swift */; };
|
||||
DB8D8E2F28192EED009FD90F /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = DB8D8E2E28192EED009FD90F /* Introspect */; };
|
||||
DB8F7076279E954700E1225B /* DataSourceFacade+Follow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8F7075279E954700E1225B /* DataSourceFacade+Follow.swift */; };
|
||||
DB8FABC726AEC7B2008E5AF4 /* Intents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB8FAB9E26AEC3A2008E5AF4 /* Intents.framework */; };
|
||||
DB8FABCA26AEC7B2008E5AF4 /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8FABC926AEC7B2008E5AF4 /* IntentHandler.swift */; };
|
||||
|
@ -1146,6 +1145,8 @@
|
|||
DB75BF1D263C1C1B00EDBF1F /* CustomScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomScheduler.swift; sourceTree = "<group>"; };
|
||||
DB789A0A25F9F2950071ACA0 /* ComposeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeViewController.swift; sourceTree = "<group>"; };
|
||||
DB789A1125F9F2CC0071ACA0 /* ComposeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeViewModel.swift; sourceTree = "<group>"; };
|
||||
DB7A9F902818EAF10016AF98 /* MastodonRegisterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonRegisterView.swift; sourceTree = "<group>"; };
|
||||
DB7A9F922818F33C0016AF98 /* MastodonServerRulesViewController+Debug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonServerRulesViewController+Debug.swift"; sourceTree = "<group>"; };
|
||||
DB7F48442620241000796008 /* ProfileHeaderViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileHeaderViewModel.swift; sourceTree = "<group>"; };
|
||||
DB8190C52601FF0400020C08 /* AttachmentContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentContainerView.swift; sourceTree = "<group>"; };
|
||||
DB8481142788121200BBEABA /* MastodonRegisterTextFieldTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonRegisterTextFieldTableViewCell.swift; sourceTree = "<group>"; };
|
||||
|
@ -1379,6 +1380,7 @@
|
|||
5D526FE225BE9AC400460CB9 /* MastodonSDK in Frameworks */,
|
||||
DB6804862637CD4C00430867 /* AppShared.framework in Frameworks */,
|
||||
DBF96326262EC0A6001D8D25 /* AuthenticationServices.framework in Frameworks */,
|
||||
DB8D8E2F28192EED009FD90F /* Introspect in Frameworks */,
|
||||
DBAC6483267D0B21007FE9FD /* DifferenceKit in Frameworks */,
|
||||
DB552D4F26BBD10C00E481F6 /* OrderedCollections in Frameworks */,
|
||||
2D61336925C18A4F00CAE157 /* AlamofireNetworkActivityIndicator in Frameworks */,
|
||||
|
@ -2633,6 +2635,7 @@
|
|||
children = (
|
||||
DB0618082785B2790030EE79 /* Cell */,
|
||||
DB72601B25E36A2100235243 /* MastodonServerRulesViewController.swift */,
|
||||
DB7A9F922818F33C0016AF98 /* MastodonServerRulesViewController+Debug.swift */,
|
||||
DB72602625E36A6F00235243 /* MastodonServerRulesViewModel.swift */,
|
||||
DB0617FE27855D6C0030EE79 /* MastodonServerRulesViewModel+Diffable.swift */,
|
||||
);
|
||||
|
@ -3149,6 +3152,7 @@
|
|||
2D939AE725EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift */,
|
||||
DBE0822325CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift */,
|
||||
DB0618062785A8880030EE79 /* MastodonRegisterViewModel+Diffable.swift */,
|
||||
DB7A9F902818EAF10016AF98 /* MastodonRegisterView.swift */,
|
||||
);
|
||||
path = Register;
|
||||
sourceTree = "<group>";
|
||||
|
@ -3324,6 +3328,7 @@
|
|||
DB552D4E26BBD10C00E481F6 /* OrderedCollections */,
|
||||
DBA5A52E26F07ED800CACBAA /* PanModal */,
|
||||
DB02EA0C280D184B00E751C5 /* CommonOSLog */,
|
||||
DB8D8E2E28192EED009FD90F /* Introspect */,
|
||||
);
|
||||
productName = Mastodon;
|
||||
productReference = DB427DD225BAA00100D1B89D /* Mastodon.app */;
|
||||
|
@ -3540,6 +3545,7 @@
|
|||
DB01E23126A98F0900C3965B /* XCRemoteSwiftPackageReference "MetaTextKit" */,
|
||||
DB552D4D26BBD10C00E481F6 /* XCRemoteSwiftPackageReference "swift-collections" */,
|
||||
DBA5A52D26F07ED800CACBAA /* XCRemoteSwiftPackageReference "PanModal" */,
|
||||
DB8D8E2D28192EED009FD90F /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */,
|
||||
);
|
||||
productRefGroup = DB427DD325BAA00100D1B89D /* Products */;
|
||||
projectDirPath = "";
|
||||
|
@ -3904,7 +3910,6 @@
|
|||
DB63F75A279953F200455B82 /* SearchHistoryUserCollectionViewCell+ViewModel.swift in Sources */,
|
||||
DB023D26279FFB0A005AC798 /* ShareActivityProvider.swift in Sources */,
|
||||
DB71FD5225F8CCAA00512AE1 /* APIService+Status.swift in Sources */,
|
||||
DB8481152788121200BBEABA /* MastodonRegisterTextFieldTableViewCell.swift in Sources */,
|
||||
5D0393962612D266007FE196 /* WebViewModel.swift in Sources */,
|
||||
5B24BBDA262DB14800A9381B /* ReportViewModel.swift in Sources */,
|
||||
2D5A3D3825CF8D9F002347D6 /* ScrollViewContainer.swift in Sources */,
|
||||
|
@ -3956,7 +3961,6 @@
|
|||
DBDFF1952805561700557A48 /* DiscoveryPostsViewModel+Diffable.swift in Sources */,
|
||||
DB03A795272A981400EE37C5 /* ContentSplitViewController.swift in Sources */,
|
||||
DBDFF19C28055BD600557A48 /* DiscoveryViewModel.swift in Sources */,
|
||||
DB06180A2785B2AB0030EE79 /* MastodonRegisterAvatarTableViewCell.swift in Sources */,
|
||||
DBB45B6227B51112002DC5A7 /* SuggestionAccountViewModel+Diffable.swift in Sources */,
|
||||
DBB3BA2A26A81C020004F2D4 /* FLAnimatedImageView.swift in Sources */,
|
||||
DB3E6FF32806D97400B035AE /* DiscoveryNewsViewModel+State.swift in Sources */,
|
||||
|
@ -4024,6 +4028,7 @@
|
|||
DB36679F268ABAF20027D07F /* ComposeStatusAttachmentSection.swift in Sources */,
|
||||
2DA7D04425CA52B200804E11 /* TimelineLoaderTableViewCell.swift in Sources */,
|
||||
DB63F7542799491600455B82 /* DataSourceFacade+SearchHistory.swift in Sources */,
|
||||
DB7A9F912818EAF10016AF98 /* MastodonRegisterView.swift in Sources */,
|
||||
DBF1572F27046F1A00EC00B7 /* SecondaryPlaceholderViewController.swift in Sources */,
|
||||
DB03F7F32689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift in Sources */,
|
||||
2D4AD8A826316D3500613EFC /* SelectedAccountItem.swift in Sources */,
|
||||
|
@ -4118,7 +4123,6 @@
|
|||
5DF1056425F887CB00D6C0D4 /* AVPlayer.swift in Sources */,
|
||||
DB3E6FEF2806D82600B035AE /* DiscoveryNewsViewModel.swift in Sources */,
|
||||
DBBF1DCB2652539E00E5B703 /* AutoCompleteItem.swift in Sources */,
|
||||
DB84811727883C2600BBEABA /* MastodonRegisterPasswordHintTableViewCell.swift in Sources */,
|
||||
2DA6054725F716A2006356F9 /* PlaybackState.swift in Sources */,
|
||||
DB98EB6727B216560082E365 /* ReportResultViewModel+Diffable.swift in Sources */,
|
||||
DBC7A672260C897100E57475 /* StatusContentWarningEditorView.swift in Sources */,
|
||||
|
@ -4274,10 +4278,10 @@
|
|||
DB0FCB6C27950E29006C02E2 /* MastodonMentionContainer.swift in Sources */,
|
||||
DB6D9F502635761F008423CD /* SubscriptionAlerts.swift in Sources */,
|
||||
0F20220726134DA4000C64BF /* HashtagTimelineViewModel+Diffable.swift in Sources */,
|
||||
DB7A9F932818F33C0016AF98 /* MastodonServerRulesViewController+Debug.swift in Sources */,
|
||||
DBE54AC62636C89F004E7C0B /* NotificationPreference.swift in Sources */,
|
||||
2D5A3D2825CF8BC9002347D6 /* HomeTimelineViewModel+Diffable.swift in Sources */,
|
||||
DB98339C25C96DE600AD9700 /* APIService+Account.swift in Sources */,
|
||||
DB0618072785A8880030EE79 /* MastodonRegisterViewModel+Diffable.swift in Sources */,
|
||||
DB6B74FC272FF55800C70B6E /* UserSection.swift in Sources */,
|
||||
2DF75BA725D10E1000694EC8 /* APIService+Favorite.swift in Sources */,
|
||||
DB0FCB862796BDA1006C02E2 /* SearchSection.swift in Sources */,
|
||||
|
@ -5465,6 +5469,14 @@
|
|||
minimumVersion = 4.2.2;
|
||||
};
|
||||
};
|
||||
DB8D8E2D28192EED009FD90F /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/siteline/SwiftUI-Introspect.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 0.1.4;
|
||||
};
|
||||
};
|
||||
DB9A487C2603456B008B817C /* XCRemoteSwiftPackageReference "UITextView-Placeholder" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/MainasuK/UITextView-Placeholder";
|
||||
|
@ -5587,6 +5599,11 @@
|
|||
package = DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */;
|
||||
productName = AlamofireImage;
|
||||
};
|
||||
DB8D8E2E28192EED009FD90F /* Introspect */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = DB8D8E2D28192EED009FD90F /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */;
|
||||
productName = Introspect;
|
||||
};
|
||||
DB9A487D2603456B008B817C /* UITextView+Placeholder */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = DB9A487C2603456B008B817C /* XCRemoteSwiftPackageReference "UITextView-Placeholder" */;
|
||||
|
|
|
@ -109,7 +109,7 @@
|
|||
<key>MastodonIntent.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>34</integer>
|
||||
<integer>24</integer>
|
||||
</dict>
|
||||
<key>MastodonIntents.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
|
@ -124,12 +124,12 @@
|
|||
<key>NotificationService.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>29</integer>
|
||||
<integer>23</integer>
|
||||
</dict>
|
||||
<key>ShareActionExtension.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>33</integer>
|
||||
<integer>22</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SuppressBuildableAutocreation</key>
|
||||
|
|
|
@ -58,6 +58,10 @@ extension HomeTimelineViewController {
|
|||
guard let self = self else { return }
|
||||
self.showWelcomeAction(action)
|
||||
},
|
||||
UIAction(title: "Register", image: UIImage(systemName: "list.bullet.rectangle.portrait.fill"), attributes: []) { [weak self] action in
|
||||
guard let self = self else { return }
|
||||
self.showRegisterAction(action)
|
||||
},
|
||||
UIAction(title: "Confirm Email", image: UIImage(systemName: "envelope"), attributes: []) { [weak self] action in
|
||||
guard let self = self else { return }
|
||||
self.showConfirmEmail(action)
|
||||
|
@ -294,6 +298,33 @@ extension HomeTimelineViewController {
|
|||
@objc private func showWelcomeAction(_ sender: UIAction) {
|
||||
coordinator.present(scene: .welcome, from: self, transition: .modal(animated: true, completion: nil))
|
||||
}
|
||||
|
||||
@objc private func showRegisterAction(_ sender: UIAction) {
|
||||
Task { @MainActor in
|
||||
try await showRegisterController()
|
||||
} // end Task
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func showRegisterController(domain: String = "mstdn.jp") async throws {
|
||||
let viewController = try await MastodonRegisterViewController.create(
|
||||
context: context,
|
||||
coordinator: coordinator,
|
||||
domain: "mstdn.jp"
|
||||
)
|
||||
let navigationController = UINavigationController(rootViewController: viewController)
|
||||
navigationController.modalPresentationStyle = .fullScreen
|
||||
present(navigationController, animated: true) {
|
||||
viewController.navigationItem.leftBarButtonItem = UIBarButtonItem(
|
||||
systemItem: .close,
|
||||
primaryAction: UIAction(handler: { [weak viewController] _ in
|
||||
guard let viewController = viewController else { return }
|
||||
viewController.dismiss(animated: true)
|
||||
}),
|
||||
menu: nil
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func showConfirmEmail(_ sender: UIAction) {
|
||||
let mastodonConfirmEmailViewModel = MastodonConfirmEmailViewModel()
|
||||
|
|
|
@ -341,7 +341,10 @@ extension MastodonPickServerViewController {
|
|||
) else {
|
||||
throw APIService.APIError.explicit(.badResponse)
|
||||
}
|
||||
return MastodonPickServerViewModel.SignUpResponseSecond(instance: response.instance, authenticateInfo: authenticateInfo)
|
||||
return MastodonPickServerViewModel.SignUpResponseSecond(
|
||||
instance: response.instance,
|
||||
authenticateInfo: authenticateInfo
|
||||
)
|
||||
}
|
||||
.compactMap { [weak self] response -> AnyPublisher<MastodonPickServerViewModel.SignUpResponseThird, Error>? in
|
||||
guard let self = self else { return nil }
|
||||
|
@ -353,7 +356,13 @@ extension MastodonPickServerViewController {
|
|||
clientSecret: authenticateInfo.clientSecret,
|
||||
redirectURI: authenticateInfo.redirectURI
|
||||
)
|
||||
.map { MastodonPickServerViewModel.SignUpResponseThird(instance: instance, authenticateInfo: authenticateInfo, applicationToken: $0) }
|
||||
.map {
|
||||
MastodonPickServerViewModel.SignUpResponseThird(
|
||||
instance: instance,
|
||||
authenticateInfo: authenticateInfo,
|
||||
applicationToken: $0
|
||||
)
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
.switchToLatest()
|
||||
|
|
|
@ -0,0 +1,304 @@
|
|||
//
|
||||
// MastodonRegisterView.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK on 2022-4-27.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
import MastodonLocalization
|
||||
import MastodonSDK
|
||||
import MastodonAsset
|
||||
|
||||
struct MastodonRegisterView: View {
|
||||
|
||||
@ObservedObject var viewModel: MastodonRegisterViewModel
|
||||
|
||||
@State var usernameRightViewWidth: CGFloat = 300
|
||||
|
||||
var body: some View {
|
||||
ScrollView(.vertical) {
|
||||
let margin: CGFloat = 16
|
||||
|
||||
// header
|
||||
HStack {
|
||||
Text(L10n.Scene.Register.title(viewModel.domain))
|
||||
.font(Font(MastodonPickServerViewController.largeTitleFont as CTFont))
|
||||
.foregroundColor(Color(Asset.Colors.Label.primary.color))
|
||||
Spacer()
|
||||
}
|
||||
.padding(.horizontal, margin)
|
||||
|
||||
// Avatar selector
|
||||
Menu {
|
||||
// Photo Library
|
||||
Button {
|
||||
viewModel.avatarMediaMenuActionPublisher.send(.photoLibrary)
|
||||
} label: {
|
||||
Label(L10n.Scene.Compose.MediaSelection.photoLibrary, systemImage: "photo")
|
||||
}
|
||||
// Camera
|
||||
if UIImagePickerController.isSourceTypeAvailable(.camera) {
|
||||
Button {
|
||||
viewModel.avatarMediaMenuActionPublisher.send(.camera)
|
||||
} label: {
|
||||
Label(L10n.Scene.Compose.MediaSelection.camera, systemImage: "camera")
|
||||
}
|
||||
}
|
||||
// Browse
|
||||
Button {
|
||||
viewModel.avatarMediaMenuActionPublisher.send(.browse)
|
||||
} label: {
|
||||
Label(L10n.Scene.Compose.MediaSelection.browse, systemImage: "folder")
|
||||
}
|
||||
// Delete
|
||||
if viewModel.avatarImage != nil {
|
||||
Divider()
|
||||
if #available(iOS 15.0, *) {
|
||||
Button(role: .destructive) {
|
||||
viewModel.avatarMediaMenuActionPublisher.send(.delete)
|
||||
} label: {
|
||||
Label(L10n.Scene.Register.Input.Avatar.delete, systemImage: "delete.left")
|
||||
}
|
||||
} else {
|
||||
// Fallback on earlier ve rsions
|
||||
Button {
|
||||
viewModel.avatarMediaMenuActionPublisher.send(.delete)
|
||||
} label: {
|
||||
Label(L10n.Scene.Register.Input.Avatar.delete, systemImage: "delete.left")
|
||||
}
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
let avatarImage = viewModel.avatarImage ?? Asset.Scene.Onboarding.avatarPlaceholder.image
|
||||
Image(uiImage: avatarImage)
|
||||
.resizable()
|
||||
.frame(width: 88, height: 88, alignment: .center)
|
||||
.overlay(ZStack {
|
||||
Color.black.opacity(0.5)
|
||||
.frame(height: 22, alignment: .bottom)
|
||||
Text(L10n.Common.Controls.Actions.edit)
|
||||
.font(.system(size: 13, weight: .semibold))
|
||||
.foregroundColor(.white)
|
||||
}, alignment: .bottom)
|
||||
.cornerRadius(22)
|
||||
}
|
||||
.padding(EdgeInsets(top: 20, leading: 0, bottom: 20, trailing: 0))
|
||||
|
||||
// Display Name & Uesrname
|
||||
VStack(alignment: .leading, spacing: 11) {
|
||||
TextField(L10n.Scene.Register.Input.DisplayName.placeholder.localizedCapitalized, text: $viewModel.name)
|
||||
.textContentType(.name)
|
||||
.disableAutocorrection(true)
|
||||
.modifier(FormTextFieldModifier(validateState: viewModel.displayNameValidateState))
|
||||
HStack {
|
||||
TextField(L10n.Scene.Register.Input.Username.placeholder.localizedCapitalized, text: $viewModel.username)
|
||||
.textContentType(.username)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.keyboardType(.asciiCapable)
|
||||
Text("@\(viewModel.domain)")
|
||||
.lineLimit(1)
|
||||
.truncationMode(.middle)
|
||||
.measureWidth { usernameRightViewWidth = $0 }
|
||||
.frame(width: min(300.0, usernameRightViewWidth), alignment: .trailing)
|
||||
}
|
||||
.modifier(FormTextFieldModifier(validateState: viewModel.usernameValidateState))
|
||||
.environment(\.layoutDirection, .leftToRight) // force LTR
|
||||
if let errorPrompt = viewModel.usernameErrorPrompt {
|
||||
Text(errorPrompt)
|
||||
.modifier(FormFootnoteModifier())
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, margin)
|
||||
.padding(.bottom, 22)
|
||||
|
||||
// Email & Password & Password hint
|
||||
VStack(alignment: .leading, spacing: 11) {
|
||||
TextField(L10n.Scene.Register.Input.Email.placeholder.localizedCapitalized, text: $viewModel.email)
|
||||
.textContentType(.emailAddress)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.keyboardType(.emailAddress)
|
||||
.modifier(FormTextFieldModifier(validateState: viewModel.emailValidateState))
|
||||
if let errorPrompt = viewModel.emailErrorPrompt {
|
||||
Text(errorPrompt)
|
||||
.modifier(FormFootnoteModifier())
|
||||
}
|
||||
SecureField(L10n.Scene.Register.Input.Password.placeholder.localizedCapitalized, text: $viewModel.password)
|
||||
.textContentType(.newPassword)
|
||||
.modifier(FormTextFieldModifier(validateState: viewModel.passwordValidateState))
|
||||
Text(L10n.Scene.Register.Input.Password.hint)
|
||||
.modifier(FormFootnoteModifier(foregroundColor: .secondary))
|
||||
if let errorPrompt = viewModel.passwordErrorPrompt {
|
||||
Text(errorPrompt)
|
||||
.modifier(FormFootnoteModifier())
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, margin)
|
||||
.padding(.bottom, 22)
|
||||
|
||||
// Reason
|
||||
if viewModel.approvalRequired {
|
||||
VStack(alignment: .leading, spacing: 11) {
|
||||
TextField(L10n.Scene.Register.Input.Invite.registrationUserInviteRequest.localizedCapitalized, text: $viewModel.reason)
|
||||
.modifier(FormTextFieldModifier(validateState: viewModel.reasonValidateState))
|
||||
if let errorPrompt = viewModel.reasonErrorPrompt {
|
||||
Text(errorPrompt)
|
||||
.modifier(FormFootnoteModifier())
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, margin)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
.frame(minHeight: viewModel.bottomPaddingHeight)
|
||||
}
|
||||
.background(
|
||||
Color(viewModel.backgroundColor)
|
||||
.onTapGesture {
|
||||
viewModel.endEditing.send()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
struct FormTextFieldModifier: ViewModifier {
|
||||
var validateState: MastodonRegisterViewModel.ValidateState
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
ZStack {
|
||||
let shadowColor: Color = {
|
||||
switch validateState {
|
||||
case .empty: return .black.opacity(0.125)
|
||||
case .invalid: return Color(Asset.Colors.TextField.invalid.color)
|
||||
case .valid: return Color(Asset.Colors.TextField.valid.color)
|
||||
}
|
||||
}()
|
||||
Color(Asset.Scene.Onboarding.textFieldBackground.color)
|
||||
.cornerRadius(10)
|
||||
.shadow(color: shadowColor, radius: 1, x: 0, y: 2)
|
||||
.animation(.easeInOut, value: validateState)
|
||||
content
|
||||
.padding()
|
||||
.background(Color(Asset.Scene.Onboarding.textFieldBackground.color))
|
||||
.cornerRadius(10)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FormFootnoteModifier: ViewModifier {
|
||||
var foregroundColor = Color(Asset.Colors.TextField.invalid.color)
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.font(.footnote)
|
||||
.foregroundColor(foregroundColor)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
struct WidthKey: PreferenceKey {
|
||||
static let defaultValue: CGFloat = 0
|
||||
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
|
||||
value = nextValue()
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
func measureWidth(_ f: @escaping (CGFloat) -> ()) -> some View {
|
||||
overlay(GeometryReader { proxy in
|
||||
Color.clear.preference(key: WidthKey.self, value: proxy.size.width)
|
||||
}
|
||||
.onPreferenceChange(WidthKey.self, perform: f))
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
struct MastodonRegisterView_Previews: PreviewProvider {
|
||||
static var viewMdoel: MastodonRegisterViewModel {
|
||||
let domain = "mstdn.jp"
|
||||
return MastodonRegisterViewModel(
|
||||
context: .shared,
|
||||
domain: domain,
|
||||
authenticateInfo: AuthenticationViewModel.AuthenticateInfo(
|
||||
domain: domain,
|
||||
application: Mastodon.Entity.Application(
|
||||
name: "Preview",
|
||||
website: nil,
|
||||
vapidKey: nil,
|
||||
redirectURI: nil,
|
||||
clientID: "",
|
||||
clientSecret: ""
|
||||
),
|
||||
redirectURI: ""
|
||||
)!,
|
||||
instance: Mastodon.Entity.Instance(domain: "mstdn.jp"),
|
||||
applicationToken: Mastodon.Entity.Token(
|
||||
accessToken: "",
|
||||
tokenType: "",
|
||||
scope: "",
|
||||
createdAt: Date()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
static var viewMdoel2: MastodonRegisterViewModel {
|
||||
let domain = "mstdn.jp"
|
||||
return MastodonRegisterViewModel(
|
||||
context: .shared,
|
||||
domain: domain,
|
||||
authenticateInfo: AuthenticationViewModel.AuthenticateInfo(
|
||||
domain: domain,
|
||||
application: Mastodon.Entity.Application(
|
||||
name: "Preview",
|
||||
website: nil,
|
||||
vapidKey: nil,
|
||||
redirectURI: nil,
|
||||
clientID: "",
|
||||
clientSecret: ""
|
||||
),
|
||||
redirectURI: ""
|
||||
)!,
|
||||
instance: Mastodon.Entity.Instance(domain: "mstdn.jp", approvalRequired: true),
|
||||
applicationToken: Mastodon.Entity.Token(
|
||||
accessToken: "",
|
||||
tokenType: "",
|
||||
scope: "",
|
||||
createdAt: Date()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
static var previews: some View {
|
||||
Group {
|
||||
NavigationView {
|
||||
MastodonRegisterView(viewModel: viewMdoel)
|
||||
.navigationBarTitle(Text(""))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
NavigationView {
|
||||
MastodonRegisterView(viewModel: viewMdoel)
|
||||
.navigationBarTitle(Text(""))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
.preferredColorScheme(.dark)
|
||||
NavigationView {
|
||||
MastodonRegisterView(viewModel: viewMdoel)
|
||||
.navigationBarTitle(Text(""))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
.environment(\.sizeCategory, .accessibilityExtraLarge)
|
||||
NavigationView {
|
||||
MastodonRegisterView(viewModel: viewMdoel2)
|
||||
.navigationBarTitle(Text(""))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -11,6 +11,7 @@ import MastodonSDK
|
|||
import os.log
|
||||
import PhotosUI
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
import MastodonUI
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
|
@ -28,6 +29,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
|
|||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||
|
||||
var viewModel: MastodonRegisterViewModel!
|
||||
private(set) lazy var mastodonRegisterView = MastodonRegisterView(viewModel: viewModel)
|
||||
|
||||
// picker
|
||||
private(set) lazy var imagePicker: PHPickerViewController = {
|
||||
|
@ -52,22 +54,6 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
|
|||
return documentPickerController
|
||||
}()
|
||||
|
||||
let tapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
|
||||
|
||||
let tableView: UITableView = {
|
||||
let tableView = UITableView()
|
||||
tableView.rowHeight = UITableView.automaticDimension
|
||||
tableView.separatorStyle = .none
|
||||
tableView.backgroundColor = .clear
|
||||
tableView.keyboardDismissMode = .onDrag
|
||||
if #available(iOS 15.0, *) {
|
||||
tableView.sectionHeaderTopPadding = .leastNonzeroMagnitude
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
return tableView
|
||||
}()
|
||||
|
||||
let navigationActionView: NavigationActionView = {
|
||||
let navigationActionView = NavigationActionView()
|
||||
navigationActionView.backgroundColor = Asset.Scene.Onboarding.background.color
|
||||
|
@ -88,17 +74,21 @@ extension MastodonRegisterViewController {
|
|||
navigationItem.leftBarButtonItem = UIBarButtonItem()
|
||||
|
||||
setupOnboardingAppearance()
|
||||
viewModel.backgroundColor = view.backgroundColor ?? .clear
|
||||
defer {
|
||||
setupNavigationBarBackgroundView()
|
||||
}
|
||||
|
||||
tableView.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(tableView)
|
||||
let hostingViewController = UIHostingController(rootView: mastodonRegisterView)
|
||||
hostingViewController.view.preservesSuperviewLayoutMargins = true
|
||||
addChild(hostingViewController)
|
||||
hostingViewController.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(hostingViewController.view)
|
||||
NSLayoutConstraint.activate([
|
||||
tableView.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||
hostingViewController.view.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
hostingViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
hostingViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
hostingViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||
])
|
||||
|
||||
navigationActionView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
@ -116,7 +106,7 @@ extension MastodonRegisterViewController {
|
|||
.observe(\.bounds, options: [.initial, .new]) { [weak self] navigationActionView, _ in
|
||||
guard let self = self else { return }
|
||||
let inset = navigationActionView.frame.height
|
||||
self.tableView.contentInset.bottom = inset
|
||||
self.viewModel.bottomPaddingHeight = inset
|
||||
}
|
||||
.store(in: &observations)
|
||||
|
||||
|
@ -130,19 +120,14 @@ extension MastodonRegisterViewController {
|
|||
self.navigationActionView.nextButton.isEnabled = isAllValid
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
viewModel.setupDiffableDataSource(tableView: tableView)
|
||||
|
||||
KeyboardResponderService
|
||||
.configure(
|
||||
scrollView: tableView,
|
||||
layoutNeedsUpdate: viewModel.viewDidAppear.eraseToAnyPublisher()
|
||||
)
|
||||
.store(in: &disposeBag)
|
||||
|
||||
// gesture
|
||||
view.addGestureRecognizer(tapGestureRecognizer)
|
||||
tapGestureRecognizer.addTarget(self, action: #selector(tapGestureRecognizerHandler))
|
||||
viewModel.endEditing
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
self.view.endEditing(true)
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
// // return
|
||||
// if viewModel.approvalRequired {
|
||||
|
@ -150,64 +135,6 @@ extension MastodonRegisterViewController {
|
|||
// } else {
|
||||
// passwordTextField.returnKeyType = .done
|
||||
// }
|
||||
//
|
||||
// viewModel.usernameValidateState
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { [weak self] validateState in
|
||||
// guard let self = self else { return }
|
||||
// self.setTextFieldValidAppearance(self.usernameTextField, validateState: validateState)
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
// viewModel.usernameErrorPrompt
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { [weak self] prompt in
|
||||
// guard let self = self else { return }
|
||||
// self.usernameErrorPromptLabel.attributedText = prompt
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
// viewModel.displayNameValidateState
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { [weak self] validateState in
|
||||
// guard let self = self else { return }
|
||||
// self.setTextFieldValidAppearance(self.displayNameTextField, validateState: validateState)
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
// viewModel.emailValidateState
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { [weak self] validateState in
|
||||
// guard let self = self else { return }
|
||||
// self.setTextFieldValidAppearance(self.emailTextField, validateState: validateState)
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
// viewModel.emailErrorPrompt
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { [weak self] prompt in
|
||||
// guard let self = self else { return }
|
||||
// self.emailErrorPromptLabel.attributedText = prompt
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
// viewModel.passwordValidateState
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { [weak self] validateState in
|
||||
// guard let self = self else { return }
|
||||
// self.setTextFieldValidAppearance(self.passwordTextField, validateState: validateState)
|
||||
// self.passwordCheckLabel.attributedText = MastodonRegisterViewModel.attributeStringForPassword(validateState: validateState)
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
// viewModel.passwordErrorPrompt
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { [weak self] prompt in
|
||||
// guard let self = self else { return }
|
||||
// self.passwordErrorPromptLabel.attributedText = prompt
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
// viewModel.reasonErrorPrompt
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { [weak self] prompt in
|
||||
// guard let self = self else { return }
|
||||
// self.reasonErrorPromptLabel.attributedText = prompt
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
|
||||
viewModel.$error
|
||||
.receive(on: DispatchQueue.main)
|
||||
|
@ -261,10 +188,6 @@ extension MastodonRegisterViewController {
|
|||
|
||||
extension MastodonRegisterViewController {
|
||||
|
||||
@objc private func tapGestureRecognizerHandler(_ sender: UITapGestureRecognizer) {
|
||||
view.endEditing(true)
|
||||
}
|
||||
|
||||
@objc private func backButtonPressed(_ sender: UIButton) {
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
|
||||
navigationController?.popViewController(animated: true)
|
||||
|
|
|
@ -143,7 +143,7 @@ extension MastodonRegisterViewModel {
|
|||
snapshot.appendItems([.header(domain: domain)], toSection: .main)
|
||||
snapshot.appendItems([.avatar, .name, .username, .email, .password, .hint], toSection: .main)
|
||||
if approvalRequired {
|
||||
snapshot.appendItems([.reason], toSection: .main)
|
||||
snapshot.appendItems([.reason], toSection: .main)
|
||||
}
|
||||
diffableDataSource?.applySnapshot(snapshot, animated: false, completion: nil)
|
||||
}
|
||||
|
@ -164,51 +164,6 @@ extension MastodonRegisterViewModel {
|
|||
.store(in: &cell.disposeBag)
|
||||
}
|
||||
|
||||
enum AvatarMediaMenuAction {
|
||||
case photoLibrary
|
||||
case camera
|
||||
case browse
|
||||
case delete
|
||||
}
|
||||
|
||||
private func createAvatarMediaContextMenu() -> UIMenu {
|
||||
var children: [UIMenuElement] = []
|
||||
|
||||
// Photo Library
|
||||
let photoLibraryAction = UIAction(title: L10n.Scene.Compose.MediaSelection.photoLibrary, image: UIImage(systemName: "rectangle.on.rectangle"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
self.avatarMediaMenuActionPublisher.send(.photoLibrary)
|
||||
}
|
||||
children.append(photoLibraryAction)
|
||||
|
||||
// Camera
|
||||
if UIImagePickerController.isSourceTypeAvailable(.camera) {
|
||||
let cameraAction = UIAction(title: L10n.Scene.Compose.MediaSelection.camera, image: UIImage(systemName: "camera"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off, handler: { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
self.avatarMediaMenuActionPublisher.send(.camera)
|
||||
})
|
||||
children.append(cameraAction)
|
||||
}
|
||||
|
||||
// Browse
|
||||
let browseAction = UIAction(title: L10n.Scene.Compose.MediaSelection.browse, image: UIImage(systemName: "ellipsis"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
self.avatarMediaMenuActionPublisher.send(.browse)
|
||||
}
|
||||
children.append(browseAction)
|
||||
|
||||
// Delete
|
||||
if avatarImage != nil {
|
||||
let deleteAction = UIAction(title: L10n.Scene.Register.Input.Avatar.delete, image: UIImage(systemName: "delete.left"), identifier: nil, discoverabilityTitle: nil, attributes: [.destructive], state: .off) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
self.avatarMediaMenuActionPublisher.send(.delete)
|
||||
}
|
||||
children.append(deleteAction)
|
||||
}
|
||||
|
||||
return UIMenu(title: "", image: nil, identifier: nil, options: .displayInline, children: children)
|
||||
}
|
||||
|
||||
private func configureTextFieldCell(
|
||||
cell: MastodonRegisterTextFieldTableViewCell,
|
||||
validateState: Published<ValidateState>.Publisher
|
||||
|
|
|
@ -12,7 +12,7 @@ import UIKit
|
|||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
|
||||
final class MastodonRegisterViewModel {
|
||||
final class MastodonRegisterViewModel: ObservableObject {
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
// input
|
||||
|
@ -23,6 +23,7 @@ final class MastodonRegisterViewModel {
|
|||
let applicationToken: Mastodon.Entity.Token
|
||||
let viewDidAppear = CurrentValueSubject<Void, Never>(Void())
|
||||
|
||||
@Published var backgroundColor: UIColor = Asset.Scene.Onboarding.background.color
|
||||
@Published var avatarImage: UIImage? = nil
|
||||
@Published var name = ""
|
||||
@Published var username = ""
|
||||
|
@ -30,10 +31,12 @@ final class MastodonRegisterViewModel {
|
|||
@Published var password = ""
|
||||
@Published var reason = ""
|
||||
|
||||
let usernameErrorPrompt = CurrentValueSubject<NSAttributedString?, Never>(nil)
|
||||
let emailErrorPrompt = CurrentValueSubject<NSAttributedString?, Never>(nil)
|
||||
let passwordErrorPrompt = CurrentValueSubject<NSAttributedString?, Never>(nil)
|
||||
let reasonErrorPrompt = CurrentValueSubject<NSAttributedString?, Never>(nil)
|
||||
@Published var usernameErrorPrompt: String? = nil
|
||||
@Published var emailErrorPrompt: String? = nil
|
||||
@Published var passwordErrorPrompt: String? = nil
|
||||
@Published var reasonErrorPrompt: String? = nil
|
||||
|
||||
@Published var bottomPaddingHeight: CGFloat = .zero
|
||||
|
||||
// output
|
||||
var diffableDataSource: UITableViewDiffableDataSource<RegisterSection, RegisterItem>?
|
||||
|
@ -51,6 +54,7 @@ final class MastodonRegisterViewModel {
|
|||
@Published var error: Error? = nil
|
||||
|
||||
let avatarMediaMenuActionPublisher = PassthroughSubject<AvatarMediaMenuAction, Never>()
|
||||
let endEditing = PassthroughSubject<Void, Never>()
|
||||
|
||||
init(
|
||||
context: AppContext,
|
||||
|
@ -97,45 +101,46 @@ final class MastodonRegisterViewModel {
|
|||
.assign(to: \.usernameValidateState, on: self)
|
||||
.store(in: &disposeBag)
|
||||
|
||||
// TODO: check username available
|
||||
// username
|
||||
// .filter { !$0.isEmpty }
|
||||
// .debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
|
||||
// .removeDuplicates()
|
||||
// .compactMap { [weak self] text -> AnyPublisher<Result<Mastodon.Response.Content<Mastodon.Entity.Account>, Error>, Never>? in
|
||||
// guard let self = self else { return nil }
|
||||
// let query = Mastodon.API.Account.AccountLookupQuery(acct: text)
|
||||
// return context.apiService.accountLookup(domain: domain, query: query, authorization: self.applicationAuthorization)
|
||||
// .map {
|
||||
// response -> Result<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> in
|
||||
// Result.success(response)
|
||||
// }
|
||||
// .catch { error in
|
||||
// Just(Result.failure(error))
|
||||
// }
|
||||
// .eraseToAnyPublisher()
|
||||
// }
|
||||
// .switchToLatest()
|
||||
// .sink { [weak self] result in
|
||||
// guard let self = self else { return }
|
||||
// switch result {
|
||||
// case .success:
|
||||
// let text = L10n.Scene.Register.Error.Reason.taken(L10n.Scene.Register.Error.Item.username)
|
||||
// self.usernameErrorPrompt.value = MastodonRegisterViewModel.errorPromptAttributedString(for: text)
|
||||
// self.usernameValidateState.value = .invalid
|
||||
// case .failure:
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
//
|
||||
// usernameValidateState
|
||||
// .sink { [weak self] validateState in
|
||||
// if validateState == .valid {
|
||||
// self?.usernameErrorPrompt.value = nil
|
||||
// }
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
// check username available
|
||||
$username
|
||||
.filter { !$0.isEmpty }
|
||||
.debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
|
||||
.removeDuplicates()
|
||||
.compactMap { [weak self] text -> AnyPublisher<Result<Mastodon.Response.Content<Mastodon.Entity.Account>, Error>, Never>? in
|
||||
guard let self = self else { return nil }
|
||||
let query = Mastodon.API.Account.AccountLookupQuery(acct: text)
|
||||
return context.apiService.accountLookup(domain: domain, query: query, authorization: self.applicationAuthorization)
|
||||
.map {
|
||||
response -> Result<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> in
|
||||
Result.success(response)
|
||||
}
|
||||
.catch { error in
|
||||
Just(Result.failure(error))
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
.switchToLatest()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
switch result {
|
||||
case .success:
|
||||
let text = L10n.Scene.Register.Error.Reason.taken(L10n.Scene.Register.Error.Item.username)
|
||||
self.usernameErrorPrompt = text
|
||||
self.usernameValidateState = .invalid
|
||||
case .failure:
|
||||
break
|
||||
}
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
$usernameValidateState
|
||||
.sink { [weak self] validateState in
|
||||
if validateState == .valid {
|
||||
self?.usernameErrorPrompt = nil
|
||||
}
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
$email
|
||||
.map { email in
|
||||
|
@ -163,27 +168,31 @@ final class MastodonRegisterViewModel {
|
|||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
// error
|
||||
// .sink { [weak self] error in
|
||||
// guard let self = self else { return }
|
||||
// let error = error as? Mastodon.API.Error
|
||||
// let mastodonError = error?.mastodonError
|
||||
// if case let .generic(genericMastodonError) = mastodonError,
|
||||
// let details = genericMastodonError.details
|
||||
// {
|
||||
// self.usernameErrorPrompt.value = details.usernameErrorDescriptions.first.flatMap { MastodonRegisterViewModel.errorPromptAttributedString(for: $0) }
|
||||
// self.emailErrorPrompt.value = details.emailErrorDescriptions.first.flatMap { MastodonRegisterViewModel.errorPromptAttributedString(for: $0) }
|
||||
// self.passwordErrorPrompt.value = details.passwordErrorDescriptions.first.flatMap { MastodonRegisterViewModel.errorPromptAttributedString(for: $0) }
|
||||
// self.reasonErrorPrompt.value = details.reasonErrorDescriptions.first.flatMap { MastodonRegisterViewModel.errorPromptAttributedString(for: $0) }
|
||||
// } else {
|
||||
// self.usernameErrorPrompt.value = nil
|
||||
// self.emailErrorPrompt.value = nil
|
||||
// self.passwordErrorPrompt.value = nil
|
||||
// self.reasonErrorPrompt.value = nil
|
||||
// }
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
//
|
||||
$error
|
||||
.sink { [weak self] error in
|
||||
guard let self = self else { return }
|
||||
let error = error as? Mastodon.API.Error
|
||||
let mastodonError = error?.mastodonError
|
||||
if case let .generic(genericMastodonError) = mastodonError,
|
||||
let details = genericMastodonError.details
|
||||
{
|
||||
self.usernameErrorPrompt = details.usernameErrorDescriptions.first
|
||||
details.usernameErrorDescriptions.first.flatMap { _ in self.usernameValidateState = .invalid }
|
||||
self.emailErrorPrompt = details.emailErrorDescriptions.first
|
||||
details.emailErrorDescriptions.first.flatMap { _ in self.emailValidateState = .invalid }
|
||||
self.passwordErrorPrompt = details.passwordErrorDescriptions.first
|
||||
details.passwordErrorDescriptions.first.flatMap { _ in self.passwordValidateState = .invalid }
|
||||
self.reasonErrorPrompt = details.reasonErrorDescriptions.first
|
||||
details.reasonErrorDescriptions.first.flatMap { _ in self.reasonValidateState = .invalid }
|
||||
} else {
|
||||
self.usernameErrorPrompt = nil
|
||||
self.emailErrorPrompt = nil
|
||||
self.passwordErrorPrompt = nil
|
||||
self.reasonErrorPrompt = nil
|
||||
}
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
let publisherOne = Publishers.CombineLatest4(
|
||||
$usernameValidateState,
|
||||
$displayNameValidateState,
|
||||
|
@ -213,7 +222,7 @@ final class MastodonRegisterViewModel {
|
|||
}
|
||||
|
||||
extension MastodonRegisterViewModel {
|
||||
enum ValidateState {
|
||||
enum ValidateState: Hashable {
|
||||
case empty
|
||||
case invalid
|
||||
case valid
|
||||
|
@ -271,3 +280,52 @@ extension MastodonRegisterViewModel {
|
|||
return attributeString
|
||||
}
|
||||
}
|
||||
|
||||
extension MastodonRegisterViewModel {
|
||||
|
||||
enum AvatarMediaMenuAction {
|
||||
case photoLibrary
|
||||
case camera
|
||||
case browse
|
||||
case delete
|
||||
}
|
||||
|
||||
private func createAvatarMediaContextMenu() -> UIMenu {
|
||||
var children: [UIMenuElement] = []
|
||||
|
||||
// Photo Library
|
||||
let photoLibraryAction = UIAction(title: L10n.Scene.Compose.MediaSelection.photoLibrary, image: UIImage(systemName: "rectangle.on.rectangle"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
self.avatarMediaMenuActionPublisher.send(.photoLibrary)
|
||||
}
|
||||
children.append(photoLibraryAction)
|
||||
|
||||
// Camera
|
||||
if UIImagePickerController.isSourceTypeAvailable(.camera) {
|
||||
let cameraAction = UIAction(title: L10n.Scene.Compose.MediaSelection.camera, image: UIImage(systemName: "camera"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off, handler: { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
self.avatarMediaMenuActionPublisher.send(.camera)
|
||||
})
|
||||
children.append(cameraAction)
|
||||
}
|
||||
|
||||
// Browse
|
||||
let browseAction = UIAction(title: L10n.Scene.Compose.MediaSelection.browse, image: UIImage(systemName: "ellipsis"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
self.avatarMediaMenuActionPublisher.send(.browse)
|
||||
}
|
||||
children.append(browseAction)
|
||||
|
||||
// Delete
|
||||
if avatarImage != nil {
|
||||
let deleteAction = UIAction(title: L10n.Scene.Register.Input.Avatar.delete, image: UIImage(systemName: "delete.left"), identifier: nil, discoverabilityTitle: nil, attributes: [.destructive], state: .off) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
self.avatarMediaMenuActionPublisher.send(.delete)
|
||||
}
|
||||
children.append(deleteAction)
|
||||
}
|
||||
|
||||
return UIMenu(title: "", image: nil, identifier: nil, options: .displayInline, children: children)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// MastodonServerRulesViewController+Debug.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK on 2022-4-27.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
#if DEBUG
|
||||
|
||||
extension MastodonRegisterViewController {
|
||||
|
||||
@MainActor
|
||||
static func create(
|
||||
context: AppContext,
|
||||
coordinator: SceneCoordinator,
|
||||
domain: String
|
||||
) async throws -> MastodonRegisterViewController {
|
||||
let viewController = MastodonRegisterViewController()
|
||||
viewController.context = context
|
||||
viewController.coordinator = coordinator
|
||||
|
||||
let instanceResponse = try await context.apiService.instance(domain: domain).singleOutput()
|
||||
let applicationResponse = try await context.apiService.createApplication(domain: domain).singleOutput()
|
||||
let accessTokenResponse = try await context.apiService.applicationAccessToken(
|
||||
domain: domain,
|
||||
clientID: applicationResponse.value.clientID!,
|
||||
clientSecret: applicationResponse.value.clientSecret!,
|
||||
redirectURI: applicationResponse.value.redirectURI!
|
||||
).singleOutput()
|
||||
|
||||
viewController.viewModel = MastodonRegisterViewModel(
|
||||
context: context,
|
||||
domain: domain,
|
||||
authenticateInfo: .init(
|
||||
domain: domain,
|
||||
application: applicationResponse.value
|
||||
)!,
|
||||
instance: instanceResponse.value,
|
||||
applicationToken: accessTokenResponse.value
|
||||
)
|
||||
|
||||
return viewController
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -270,7 +270,15 @@ extension MainTabBarController {
|
|||
updateTabBarDisplay()
|
||||
|
||||
#if DEBUG
|
||||
// selectedIndex = 1
|
||||
// Debug Register viewController
|
||||
// Task { @MainActor in
|
||||
// let _homeTimelineViewController = viewControllers
|
||||
// .compactMap { $0 as? UINavigationController }
|
||||
// .compactMap { $0.topViewController }
|
||||
// .compactMap { $0 as? HomeTimelineViewController }
|
||||
// .first
|
||||
// try await _homeTimelineViewController?.showRegisterController()
|
||||
// } // end Task
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// APIService+HomeTimeline.swift
|
||||
// µ.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021/2/3.
|
||||
|
|
|
@ -38,7 +38,7 @@ extension Mastodon.Entity {
|
|||
// https://github.com/mastodon/mastodon/pull/16485
|
||||
public let configuration: Configuration?
|
||||
|
||||
public init(domain: String) {
|
||||
public init(domain: String, approvalRequired: Bool? = nil) {
|
||||
self.uri = domain
|
||||
self.title = domain
|
||||
self.description = ""
|
||||
|
@ -47,7 +47,7 @@ extension Mastodon.Entity {
|
|||
self.version = nil
|
||||
self.languages = nil
|
||||
self.registrations = nil
|
||||
self.approvalRequired = nil
|
||||
self.approvalRequired = approvalRequired
|
||||
self.invitesEnabled = nil
|
||||
self.urls = nil
|
||||
self.statistics = nil
|
||||
|
|
Loading…
Reference in New Issue