mirror of
https://github.com/mastodon/mastodon-ios.git
synced 2025-02-03 18:57:46 +01:00
Merge pull request #33 from tootsuite/feature/errorDetail
Feature/error detail
This commit is contained in:
commit
fab3902561
@ -1,5 +1,31 @@
|
|||||||
{
|
{
|
||||||
"common": {
|
"common": {
|
||||||
|
"errors": {
|
||||||
|
"item": {
|
||||||
|
"username": "username",
|
||||||
|
"email": "email",
|
||||||
|
"password": "password",
|
||||||
|
"agreement": "agreement",
|
||||||
|
"locale": "locale",
|
||||||
|
"reason": "reason"
|
||||||
|
},
|
||||||
|
"itemDetail": {
|
||||||
|
"email_invalid": "This is not a valid e-mail address",
|
||||||
|
"username_invalid": "Username must only contain alphanumeric characters and underscores",
|
||||||
|
"password_too_shrot": "password is too short (must be at least 8 characters)",
|
||||||
|
"username_too_long": "username is too long (can't be longer than 30 characters)"
|
||||||
|
},
|
||||||
|
"ERR_BLOCKED": "contains a disallowed e-mail provider",
|
||||||
|
"ERR_UNREACHABLE": "does not seem to exist",
|
||||||
|
"ERR_TAKEN": "is already in use",
|
||||||
|
"ERR_RESERVED": "is a reserved keyword",
|
||||||
|
"ERR_ACCEPTED": "must be accepted",
|
||||||
|
"ERR_BLANK": "is required",
|
||||||
|
"ERR_INVALID": "is invalid",
|
||||||
|
"ERR_TOO_LONG": "is too long",
|
||||||
|
"ERR_TOO_SHORT": "is too short",
|
||||||
|
"ERR_INCLUSION": "is not a supported value"
|
||||||
|
},
|
||||||
"alerts": {
|
"alerts": {
|
||||||
"sign_up_failure": {
|
"sign_up_failure": {
|
||||||
"title": "Sign Up Failure"
|
"title": "Sign Up Failure"
|
||||||
|
@ -55,6 +55,7 @@
|
|||||||
2D61335825C188A000CAE157 /* APIService+Persist+Timeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D61335725C188A000CAE157 /* APIService+Persist+Timeline.swift */; };
|
2D61335825C188A000CAE157 /* APIService+Persist+Timeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D61335725C188A000CAE157 /* APIService+Persist+Timeline.swift */; };
|
||||||
2D61335E25C1894B00CAE157 /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D61335D25C1894B00CAE157 /* APIService.swift */; };
|
2D61335E25C1894B00CAE157 /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D61335D25C1894B00CAE157 /* APIService.swift */; };
|
||||||
2D61336925C18A4F00CAE157 /* AlamofireNetworkActivityIndicator in Frameworks */ = {isa = PBXBuildFile; productRef = 2D61336825C18A4F00CAE157 /* AlamofireNetworkActivityIndicator */; };
|
2D61336925C18A4F00CAE157 /* AlamofireNetworkActivityIndicator in Frameworks */ = {isa = PBXBuildFile; productRef = 2D61336825C18A4F00CAE157 /* AlamofireNetworkActivityIndicator */; };
|
||||||
|
2D650FAB25ECDC9300851B58 /* Mastodon+Entidy+ErrorDetailReason.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D650FAA25ECDC9300851B58 /* Mastodon+Entidy+ErrorDetailReason.swift */; };
|
||||||
2D69CFF425CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D69CFF325CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift */; };
|
2D69CFF425CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D69CFF325CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift */; };
|
||||||
2D69D00A25CAA00300C3A1B2 /* APIService+CoreData+Toot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D69D00925CAA00300C3A1B2 /* APIService+CoreData+Toot.swift */; };
|
2D69D00A25CAA00300C3A1B2 /* APIService+CoreData+Toot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D69D00925CAA00300C3A1B2 /* APIService+CoreData+Toot.swift */; };
|
||||||
2D76316525C14BD100929FB9 /* PublicTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76316425C14BD100929FB9 /* PublicTimelineViewController.swift */; };
|
2D76316525C14BD100929FB9 /* PublicTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76316425C14BD100929FB9 /* PublicTimelineViewController.swift */; };
|
||||||
@ -70,6 +71,7 @@
|
|||||||
2D927F0825C7E9A8004F19B8 /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D927F0725C7E9A8004F19B8 /* Tag.swift */; };
|
2D927F0825C7E9A8004F19B8 /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D927F0725C7E9A8004F19B8 /* Tag.swift */; };
|
||||||
2D927F0E25C7E9C9004F19B8 /* History.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D927F0D25C7E9C9004F19B8 /* History.swift */; };
|
2D927F0E25C7E9C9004F19B8 /* History.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D927F0D25C7E9C9004F19B8 /* History.swift */; };
|
||||||
2D927F1425C7EDD9004F19B8 /* Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D927F1325C7EDD9004F19B8 /* Emoji.swift */; };
|
2D927F1425C7EDD9004F19B8 /* Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D927F1325C7EDD9004F19B8 /* Emoji.swift */; };
|
||||||
|
2D939AB525EDD8A90076FA61 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D939AB425EDD8A90076FA61 /* String.swift */; };
|
||||||
2DA7D04425CA52B200804E11 /* TimelineLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA7D04325CA52B200804E11 /* TimelineLoaderTableViewCell.swift */; };
|
2DA7D04425CA52B200804E11 /* TimelineLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA7D04325CA52B200804E11 /* TimelineLoaderTableViewCell.swift */; };
|
||||||
2DA7D04A25CA52CB00804E11 /* TimelineBottomLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA7D04925CA52CB00804E11 /* TimelineBottomLoaderTableViewCell.swift */; };
|
2DA7D04A25CA52CB00804E11 /* TimelineBottomLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA7D04925CA52CB00804E11 /* TimelineBottomLoaderTableViewCell.swift */; };
|
||||||
2DA7D05725CA693F00804E11 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA7D05625CA693F00804E11 /* Application.swift */; };
|
2DA7D05725CA693F00804E11 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA7D05625CA693F00804E11 /* Application.swift */; };
|
||||||
@ -260,6 +262,7 @@
|
|||||||
2D5A3D6125CFD9CB002347D6 /* HomeTimelineViewController+DebugAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeTimelineViewController+DebugAction.swift"; sourceTree = "<group>"; };
|
2D5A3D6125CFD9CB002347D6 /* HomeTimelineViewController+DebugAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeTimelineViewController+DebugAction.swift"; sourceTree = "<group>"; };
|
||||||
2D61335725C188A000CAE157 /* APIService+Persist+Timeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Persist+Timeline.swift"; sourceTree = "<group>"; };
|
2D61335725C188A000CAE157 /* APIService+Persist+Timeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Persist+Timeline.swift"; sourceTree = "<group>"; };
|
||||||
2D61335D25C1894B00CAE157 /* APIService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIService.swift; sourceTree = "<group>"; };
|
2D61335D25C1894B00CAE157 /* APIService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIService.swift; sourceTree = "<group>"; };
|
||||||
|
2D650FAA25ECDC9300851B58 /* Mastodon+Entidy+ErrorDetailReason.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+Entidy+ErrorDetailReason.swift"; sourceTree = "<group>"; };
|
||||||
2D69CFF325CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadMoreConfigurableTableViewContainer.swift; sourceTree = "<group>"; };
|
2D69CFF325CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadMoreConfigurableTableViewContainer.swift; sourceTree = "<group>"; };
|
||||||
2D69D00925CAA00300C3A1B2 /* APIService+CoreData+Toot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "APIService+CoreData+Toot.swift"; sourceTree = "<group>"; };
|
2D69D00925CAA00300C3A1B2 /* APIService+CoreData+Toot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "APIService+CoreData+Toot.swift"; sourceTree = "<group>"; };
|
||||||
2D76316425C14BD100929FB9 /* PublicTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicTimelineViewController.swift; sourceTree = "<group>"; };
|
2D76316425C14BD100929FB9 /* PublicTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicTimelineViewController.swift; sourceTree = "<group>"; };
|
||||||
@ -275,6 +278,7 @@
|
|||||||
2D927F0725C7E9A8004F19B8 /* Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tag.swift; sourceTree = "<group>"; };
|
2D927F0725C7E9A8004F19B8 /* Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tag.swift; sourceTree = "<group>"; };
|
||||||
2D927F0D25C7E9C9004F19B8 /* History.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = History.swift; sourceTree = "<group>"; };
|
2D927F0D25C7E9C9004F19B8 /* History.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = History.swift; sourceTree = "<group>"; };
|
||||||
2D927F1325C7EDD9004F19B8 /* Emoji.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Emoji.swift; sourceTree = "<group>"; };
|
2D927F1325C7EDD9004F19B8 /* Emoji.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Emoji.swift; sourceTree = "<group>"; };
|
||||||
|
2D939AB425EDD8A90076FA61 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = "<group>"; };
|
||||||
2DA7D04325CA52B200804E11 /* TimelineLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineLoaderTableViewCell.swift; sourceTree = "<group>"; };
|
2DA7D04325CA52B200804E11 /* TimelineLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineLoaderTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
2DA7D04925CA52CB00804E11 /* TimelineBottomLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineBottomLoaderTableViewCell.swift; sourceTree = "<group>"; };
|
2DA7D04925CA52CB00804E11 /* TimelineBottomLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineBottomLoaderTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
2DA7D05025CA545E00804E11 /* LoadMoreConfigurableTableViewContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadMoreConfigurableTableViewContainer.swift; sourceTree = "<group>"; };
|
2DA7D05025CA545E00804E11 /* LoadMoreConfigurableTableViewContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadMoreConfigurableTableViewContainer.swift; sourceTree = "<group>"; };
|
||||||
@ -1007,6 +1011,8 @@
|
|||||||
DB45FAD625CA6C76005A8AC7 /* UIBarButtonItem.swift */,
|
DB45FAD625CA6C76005A8AC7 /* UIBarButtonItem.swift */,
|
||||||
2D32EAB925CB9B0500C9ED86 /* UIView.swift */,
|
2D32EAB925CB9B0500C9ED86 /* UIView.swift */,
|
||||||
0FAA101B25E10E760017CCDE /* UIFont.swift */,
|
0FAA101B25E10E760017CCDE /* UIFont.swift */,
|
||||||
|
2D650FAA25ECDC9300851B58 /* Mastodon+Entidy+ErrorDetailReason.swift */,
|
||||||
|
2D939AB425EDD8A90076FA61 /* String.swift */,
|
||||||
);
|
);
|
||||||
path = Extension;
|
path = Extension;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -1457,6 +1463,7 @@
|
|||||||
DB45FAE325CA7181005A8AC7 /* MastodonUser.swift in Sources */,
|
DB45FAE325CA7181005A8AC7 /* MastodonUser.swift in Sources */,
|
||||||
DB0AC6FC25CD02E600D75117 /* APIService+Instance.swift in Sources */,
|
DB0AC6FC25CD02E600D75117 /* APIService+Instance.swift in Sources */,
|
||||||
2D69D00A25CAA00300C3A1B2 /* APIService+CoreData+Toot.swift in Sources */,
|
2D69D00A25CAA00300C3A1B2 /* APIService+CoreData+Toot.swift in Sources */,
|
||||||
|
2D939AB525EDD8A90076FA61 /* String.swift in Sources */,
|
||||||
0FAA101C25E10E760017CCDE /* UIFont.swift in Sources */,
|
0FAA101C25E10E760017CCDE /* UIFont.swift in Sources */,
|
||||||
2D38F1D525CD465300561493 /* HomeTimelineViewController.swift in Sources */,
|
2D38F1D525CD465300561493 /* HomeTimelineViewController.swift in Sources */,
|
||||||
DB98338825C945ED00AD9700 /* Assets.swift in Sources */,
|
DB98338825C945ED00AD9700 /* Assets.swift in Sources */,
|
||||||
@ -1502,6 +1509,7 @@
|
|||||||
2D38F20825CD491300561493 /* DisposeBagCollectable.swift in Sources */,
|
2D38F20825CD491300561493 /* DisposeBagCollectable.swift in Sources */,
|
||||||
DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */,
|
DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */,
|
||||||
2D76319F25C1521200929FB9 /* StatusSection.swift in Sources */,
|
2D76319F25C1521200929FB9 /* StatusSection.swift in Sources */,
|
||||||
|
2D650FAB25ECDC9300851B58 /* Mastodon+Entidy+ErrorDetailReason.swift in Sources */,
|
||||||
DB118A8C25E4BFB500FAB162 /* HighlightDimmableButton.swift in Sources */,
|
DB118A8C25E4BFB500FAB162 /* HighlightDimmableButton.swift in Sources */,
|
||||||
DB084B5725CBC56C00F898ED /* Toot.swift in Sources */,
|
DB084B5725CBC56C00F898ED /* Toot.swift in Sources */,
|
||||||
DB0140A825C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift in Sources */,
|
DB0140A825C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift in Sources */,
|
||||||
|
99
Mastodon/Extension/Mastodon+Entidy+ErrorDetailReason.swift
Normal file
99
Mastodon/Extension/Mastodon+Entidy+ErrorDetailReason.swift
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
//
|
||||||
|
// Mastodon+Entity+ErrorDetailReason.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by sxiaojian on 2021/3/1.
|
||||||
|
//
|
||||||
|
import MastodonSDK
|
||||||
|
|
||||||
|
extension Mastodon.Entity.ErrorDetailReason {
|
||||||
|
func localizedDescription() -> String {
|
||||||
|
switch self.error {
|
||||||
|
case .ERR_BLOCKED:
|
||||||
|
return L10n.Common.Errors.errBlocked
|
||||||
|
case .ERR_UNREACHABLE:
|
||||||
|
return L10n.Common.Errors.errUnreachable
|
||||||
|
case .ERR_TAKEN:
|
||||||
|
return L10n.Common.Errors.errTaken
|
||||||
|
case .ERR_RESERVED:
|
||||||
|
return L10n.Common.Errors.errReserved
|
||||||
|
case .ERR_ACCEPTED:
|
||||||
|
return L10n.Common.Errors.errAccepted
|
||||||
|
case .ERR_BLANK:
|
||||||
|
return L10n.Common.Errors.errBlank
|
||||||
|
case .ERR_INVALID:
|
||||||
|
return L10n.Common.Errors.errInvalid
|
||||||
|
case .ERR_TOO_LONG:
|
||||||
|
return L10n.Common.Errors.errTooLong
|
||||||
|
case .ERR_TOO_SHORT:
|
||||||
|
return L10n.Common.Errors.errTooShort
|
||||||
|
case .ERR_INCLUSION:
|
||||||
|
return L10n.Common.Errors.errInclusion
|
||||||
|
case ._other:
|
||||||
|
return self.errorDescription ?? ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Mastodon.Entity.ErrorDetail {
|
||||||
|
func localizedDescription() -> String {
|
||||||
|
var messages: [String?] = []
|
||||||
|
|
||||||
|
if let username = self.username, !username.isEmpty {
|
||||||
|
let errors = username.map { errorDetailReason -> String in
|
||||||
|
switch errorDetailReason.error {
|
||||||
|
case .ERR_INVALID:
|
||||||
|
return L10n.Common.Errors.Itemdetail.usernameInvalid
|
||||||
|
case .ERR_TOO_LONG:
|
||||||
|
return L10n.Common.Errors.Itemdetail.usernameTooLong
|
||||||
|
default:
|
||||||
|
return L10n.Common.Errors.Item.username + " " + errorDetailReason.localizedDescription()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
messages.append(contentsOf: errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let email = self.email, !email.isEmpty {
|
||||||
|
let errors = email.map { errorDetailReason -> String in
|
||||||
|
if errorDetailReason.error == .ERR_INVALID {
|
||||||
|
return L10n.Common.Errors.Itemdetail.emailInvalid
|
||||||
|
} else {
|
||||||
|
return L10n.Common.Errors.Item.email + " " + errorDetailReason.localizedDescription()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
messages.append(contentsOf: errors)
|
||||||
|
}
|
||||||
|
if let password = self.password,!password.isEmpty {
|
||||||
|
let errors = password.map { errorDetailReason -> String in
|
||||||
|
if errorDetailReason.error == .ERR_TOO_SHORT {
|
||||||
|
return L10n.Common.Errors.Itemdetail.passwordTooShrot
|
||||||
|
} else {
|
||||||
|
return L10n.Common.Errors.Item.password + " " + errorDetailReason.localizedDescription()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
messages.append(contentsOf: errors)
|
||||||
|
}
|
||||||
|
if let agreement = self.agreement, !agreement.isEmpty {
|
||||||
|
let errors = agreement.map {
|
||||||
|
L10n.Common.Errors.Item.agreement + " " + $0.localizedDescription()
|
||||||
|
}
|
||||||
|
messages.append(contentsOf: errors)
|
||||||
|
}
|
||||||
|
if let locale = self.locale, !locale.isEmpty {
|
||||||
|
let errors = locale.map {
|
||||||
|
L10n.Common.Errors.Item.locale + " " + $0.localizedDescription()
|
||||||
|
}
|
||||||
|
messages.append(contentsOf: errors)
|
||||||
|
}
|
||||||
|
if let reason = self.reason, !reason.isEmpty {
|
||||||
|
let errors = reason.map {
|
||||||
|
L10n.Common.Errors.Item.reason + " " + $0.localizedDescription()
|
||||||
|
}
|
||||||
|
messages.append(contentsOf: errors)
|
||||||
|
}
|
||||||
|
let message = messages
|
||||||
|
.compactMap { $0 }
|
||||||
|
.joined(separator: ", ")
|
||||||
|
return message.capitalizingFirstLetter()
|
||||||
|
}
|
||||||
|
}
|
18
Mastodon/Extension/String.swift
Normal file
18
Mastodon/Extension/String.swift
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// String.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by sxiaojian on 2021/3/2.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension String {
|
||||||
|
func capitalizingFirstLetter() -> String {
|
||||||
|
return prefix(1).capitalized + dropFirst()
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func capitalizeFirstLetter() {
|
||||||
|
self = self.capitalizingFirstLetter()
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import MastodonSDK
|
||||||
// Reference:
|
// Reference:
|
||||||
// https://nshipster.com/swift-foundation-error-protocols/
|
// https://nshipster.com/swift-foundation-error-protocols/
|
||||||
extension UIAlertController {
|
extension UIAlertController {
|
||||||
@ -43,3 +43,43 @@ extension UIAlertController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension UIAlertController {
|
||||||
|
convenience init(
|
||||||
|
for error: Mastodon.API.Error,
|
||||||
|
title: String?,
|
||||||
|
preferredStyle: UIAlertController.Style
|
||||||
|
) {
|
||||||
|
let _title: String
|
||||||
|
let message: String?
|
||||||
|
switch error.mastodonError {
|
||||||
|
case .generic(let mastodonEntityError):
|
||||||
|
|
||||||
|
if let title = title {
|
||||||
|
_title = title
|
||||||
|
} else {
|
||||||
|
_title = error.errorDescription ?? "Error"
|
||||||
|
}
|
||||||
|
var messages: [String?] = []
|
||||||
|
if let details = mastodonEntityError.details {
|
||||||
|
message = details.localizedDescription()
|
||||||
|
} else {
|
||||||
|
messages.append(contentsOf: [
|
||||||
|
error.failureReason,
|
||||||
|
error.recoverySuggestion
|
||||||
|
])
|
||||||
|
message = messages
|
||||||
|
.compactMap { $0 }
|
||||||
|
.joined(separator: " ")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
_title = "Internal Error"
|
||||||
|
message = error.localizedDescription
|
||||||
|
}
|
||||||
|
|
||||||
|
self.init(
|
||||||
|
title: _title,
|
||||||
|
message: message,
|
||||||
|
preferredStyle: preferredStyle
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -82,6 +82,52 @@ internal enum L10n {
|
|||||||
internal static let single = L10n.tr("Localizable", "Common.Countable.Photo.Single")
|
internal static let single = L10n.tr("Localizable", "Common.Countable.Photo.Single")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
internal enum Errors {
|
||||||
|
/// must be accepted
|
||||||
|
internal static let errAccepted = L10n.tr("Localizable", "Common.Errors.ErrAccepted")
|
||||||
|
/// is required
|
||||||
|
internal static let errBlank = L10n.tr("Localizable", "Common.Errors.ErrBlank")
|
||||||
|
/// contains a disallowed e-mail provider
|
||||||
|
internal static let errBlocked = L10n.tr("Localizable", "Common.Errors.ErrBlocked")
|
||||||
|
/// is not a supported value
|
||||||
|
internal static let errInclusion = L10n.tr("Localizable", "Common.Errors.ErrInclusion")
|
||||||
|
/// is invalid
|
||||||
|
internal static let errInvalid = L10n.tr("Localizable", "Common.Errors.ErrInvalid")
|
||||||
|
/// is a reserved keyword
|
||||||
|
internal static let errReserved = L10n.tr("Localizable", "Common.Errors.ErrReserved")
|
||||||
|
/// is already in use
|
||||||
|
internal static let errTaken = L10n.tr("Localizable", "Common.Errors.ErrTaken")
|
||||||
|
/// is too long
|
||||||
|
internal static let errTooLong = L10n.tr("Localizable", "Common.Errors.ErrTooLong")
|
||||||
|
/// is too short
|
||||||
|
internal static let errTooShort = L10n.tr("Localizable", "Common.Errors.ErrTooShort")
|
||||||
|
/// does not seem to exist
|
||||||
|
internal static let errUnreachable = L10n.tr("Localizable", "Common.Errors.ErrUnreachable")
|
||||||
|
internal enum Item {
|
||||||
|
/// agreement
|
||||||
|
internal static let agreement = L10n.tr("Localizable", "Common.Errors.Item.Agreement")
|
||||||
|
/// email
|
||||||
|
internal static let email = L10n.tr("Localizable", "Common.Errors.Item.Email")
|
||||||
|
/// locale
|
||||||
|
internal static let locale = L10n.tr("Localizable", "Common.Errors.Item.Locale")
|
||||||
|
/// password
|
||||||
|
internal static let password = L10n.tr("Localizable", "Common.Errors.Item.Password")
|
||||||
|
/// reason
|
||||||
|
internal static let reason = L10n.tr("Localizable", "Common.Errors.Item.Reason")
|
||||||
|
/// username
|
||||||
|
internal static let username = L10n.tr("Localizable", "Common.Errors.Item.Username")
|
||||||
|
}
|
||||||
|
internal enum Itemdetail {
|
||||||
|
/// This is not a valid e-mail address
|
||||||
|
internal static let emailInvalid = L10n.tr("Localizable", "Common.Errors.Itemdetail.EmailInvalid")
|
||||||
|
/// password is too short (must be at least 8 characters)
|
||||||
|
internal static let passwordTooShrot = L10n.tr("Localizable", "Common.Errors.Itemdetail.PasswordTooShrot")
|
||||||
|
/// Username must only contain alphanumeric characters and underscores
|
||||||
|
internal static let usernameInvalid = L10n.tr("Localizable", "Common.Errors.Itemdetail.UsernameInvalid")
|
||||||
|
/// username is too long ( can't be longer than 30 characters)
|
||||||
|
internal static let usernameTooLong = L10n.tr("Localizable", "Common.Errors.Itemdetail.UsernameTooLong")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal enum Scene {
|
internal enum Scene {
|
||||||
|
@ -23,6 +23,26 @@
|
|||||||
"Common.Controls.Timeline.LoadMore" = "Load More";
|
"Common.Controls.Timeline.LoadMore" = "Load More";
|
||||||
"Common.Countable.Photo.Multiple" = "photos";
|
"Common.Countable.Photo.Multiple" = "photos";
|
||||||
"Common.Countable.Photo.Single" = "photo";
|
"Common.Countable.Photo.Single" = "photo";
|
||||||
|
"Common.Errors.ErrAccepted" = "must be accepted";
|
||||||
|
"Common.Errors.ErrBlank" = "is required";
|
||||||
|
"Common.Errors.ErrBlocked" = "contains a disallowed e-mail provider";
|
||||||
|
"Common.Errors.ErrInclusion" = "is not a supported value";
|
||||||
|
"Common.Errors.ErrInvalid" = "is invalid";
|
||||||
|
"Common.Errors.ErrReserved" = "is a reserved keyword";
|
||||||
|
"Common.Errors.ErrTaken" = "is already in use";
|
||||||
|
"Common.Errors.ErrTooLong" = "is too long";
|
||||||
|
"Common.Errors.ErrTooShort" = "is too short";
|
||||||
|
"Common.Errors.ErrUnreachable" = "does not seem to exist";
|
||||||
|
"Common.Errors.Item.Agreement" = "agreement";
|
||||||
|
"Common.Errors.Item.Email" = "email";
|
||||||
|
"Common.Errors.Item.Locale" = "locale";
|
||||||
|
"Common.Errors.Item.Password" = "password";
|
||||||
|
"Common.Errors.Item.Reason" = "reason";
|
||||||
|
"Common.Errors.Item.Username" = "username";
|
||||||
|
"Common.Errors.Itemdetail.EmailInvalid" = "This is not a valid e-mail address";
|
||||||
|
"Common.Errors.Itemdetail.PasswordTooShrot" = "password is too short (must be at least 8 characters)";
|
||||||
|
"Common.Errors.Itemdetail.UsernameInvalid" = "Username must only contain alphanumeric characters and underscores";
|
||||||
|
"Common.Errors.Itemdetail.UsernameTooLong" = "username is too long ( can't be longer than 30 characters)";
|
||||||
"Scene.ConfirmEmail.Button.DontReceiveEmail" = "I never got an email";
|
"Scene.ConfirmEmail.Button.DontReceiveEmail" = "I never got an email";
|
||||||
"Scene.ConfirmEmail.Button.OpenEmailApp" = "Open Email App";
|
"Scene.ConfirmEmail.Button.OpenEmailApp" = "Open Email App";
|
||||||
"Scene.ConfirmEmail.DontReceiveEmail.Description" = "Check if your email address is correct as well as your junk folder if you haven’t.";
|
"Scene.ConfirmEmail.DontReceiveEmail.Description" = "Check if your email address is correct as well as your junk folder if you haven’t.";
|
||||||
@ -62,4 +82,4 @@ any server.";
|
|||||||
"Scene.ServerRules.Subtitle" = "These rules are set by the admins of %@.";
|
"Scene.ServerRules.Subtitle" = "These rules are set by the admins of %@.";
|
||||||
"Scene.ServerRules.Title" = "Some ground rules.";
|
"Scene.ServerRules.Title" = "Some ground rules.";
|
||||||
"Scene.Welcome.Slogan" = "Social networking
|
"Scene.Welcome.Slogan" = "Social networking
|
||||||
back in your hands.";
|
back in your hands.";
|
@ -267,13 +267,25 @@ extension MastodonPickServerViewController {
|
|||||||
}
|
}
|
||||||
} receiveValue: { [weak self] response in
|
} receiveValue: { [weak self] response in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
let mastodonRegisterViewModel = MastodonRegisterViewModel(
|
if let rules = response.instance.value.rules, !rules.isEmpty {
|
||||||
domain: server.domain,
|
// show server rules before register
|
||||||
authenticateInfo: response.authenticateInfo,
|
let mastodonServerRulesViewModel = MastodonServerRulesViewModel(
|
||||||
instance: response.instance.value,
|
domain: server.domain,
|
||||||
applicationToken: response.applicationToken.value
|
authenticateInfo: response.authenticateInfo,
|
||||||
)
|
rules: rules,
|
||||||
self.coordinator.present(scene: .mastodonRegister(viewModel: mastodonRegisterViewModel), from: nil, transition: .show)
|
instance: response.instance.value,
|
||||||
|
applicationToken: response.applicationToken.value
|
||||||
|
)
|
||||||
|
self.coordinator.present(scene: .mastodonServerRules(viewModel: mastodonServerRulesViewModel), from: self, transition: .show)
|
||||||
|
} else {
|
||||||
|
let mastodonRegisterViewModel = MastodonRegisterViewModel(
|
||||||
|
domain: server.domain,
|
||||||
|
authenticateInfo: response.authenticateInfo,
|
||||||
|
instance: response.instance.value,
|
||||||
|
applicationToken: response.applicationToken.value
|
||||||
|
)
|
||||||
|
self.coordinator.present(scene: .mastodonRegister(viewModel: mastodonRegisterViewModel), from: nil, transition: .show)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
}
|
}
|
||||||
|
@ -105,6 +105,20 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
|
|||||||
|
|
||||||
let usernameIsTakenLabel: UILabel = {
|
let usernameIsTakenLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
|
let color = Asset.Colors.lightDangerRed.color
|
||||||
|
let font = UIFont.preferredFont(forTextStyle: .caption1)
|
||||||
|
let attributeString = NSMutableAttributedString()
|
||||||
|
|
||||||
|
let errorImage = NSTextAttachment()
|
||||||
|
let configuration = UIImage.SymbolConfiguration(font: font)
|
||||||
|
errorImage.image = UIImage(systemName: "xmark.octagon.fill", withConfiguration: configuration)?.withTintColor(color)
|
||||||
|
let errorImageAttachment = NSAttributedString(attachment: errorImage)
|
||||||
|
attributeString.append(errorImageAttachment)
|
||||||
|
|
||||||
|
let errorString = NSAttributedString(string: L10n.Common.Errors.Item.username + " " + L10n.Common.Errors.errTaken, attributes: [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: color])
|
||||||
|
attributeString.append(errorString)
|
||||||
|
label.attributedText = attributeString
|
||||||
|
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -227,11 +241,12 @@ extension MastodonRegisterViewController {
|
|||||||
stackView.addArrangedSubview(largeTitleLabel)
|
stackView.addArrangedSubview(largeTitleLabel)
|
||||||
stackView.addArrangedSubview(photoView)
|
stackView.addArrangedSubview(photoView)
|
||||||
stackView.addArrangedSubview(usernameTextField)
|
stackView.addArrangedSubview(usernameTextField)
|
||||||
|
stackView.addArrangedSubview(usernameIsTakenLabel)
|
||||||
stackView.addArrangedSubview(displayNameTextField)
|
stackView.addArrangedSubview(displayNameTextField)
|
||||||
stackView.addArrangedSubview(emailTextField)
|
stackView.addArrangedSubview(emailTextField)
|
||||||
stackView.addArrangedSubview(passwordTextField)
|
stackView.addArrangedSubview(passwordTextField)
|
||||||
stackView.addArrangedSubview(passwordCheckLabel)
|
stackView.addArrangedSubview(passwordCheckLabel)
|
||||||
if self.viewModel.approvalRequired {
|
if viewModel.approvalRequired {
|
||||||
stackView.addArrangedSubview(inviteTextField)
|
stackView.addArrangedSubview(inviteTextField)
|
||||||
}
|
}
|
||||||
// scrollView
|
// scrollView
|
||||||
@ -375,23 +390,48 @@ extension MastodonRegisterViewController {
|
|||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.setTextFieldValidAppearance(self.passwordTextField, validateState: validateState)
|
self.setTextFieldValidAppearance(self.passwordTextField, validateState: validateState)
|
||||||
self.passwordCheckLabel.attributedText = self.viewModel.attributeStringForPassword(eightCharacters: validateState == .valid)
|
self.passwordCheckLabel.attributedText = self.viewModel.attributeStringForPassword(eightCharacters: validateState == .valid)
|
||||||
|
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
viewModel.isAllValid
|
viewModel.isAllValid
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] isAllValid in
|
.sink { [weak self] isAllValid in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.signUpButton.isEnabled = isAllValid
|
self.signUpButton.isEnabled = isAllValid
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
|
viewModel.isUsernameTaken
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink {[weak self] isUsernameTaken in
|
||||||
|
guard let self = self else { return }
|
||||||
|
if isUsernameTaken {
|
||||||
|
self.usernameIsTakenLabel.isHidden = false
|
||||||
|
stackView.setCustomSpacing(6, after: self.usernameTextField)
|
||||||
|
stackView.setCustomSpacing(16, after: self.usernameIsTakenLabel)
|
||||||
|
} else {
|
||||||
|
self.usernameIsTakenLabel.isHidden = true
|
||||||
|
stackView.setCustomSpacing(40, after: self.usernameTextField)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &disposeBag)
|
||||||
viewModel.error
|
viewModel.error
|
||||||
.compactMap { $0 }
|
.compactMap { $0 }
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] error in
|
.sink { [weak self] error in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
guard let error = error as? Mastodon.API.Error else { return }
|
||||||
|
switch error.mastodonError {
|
||||||
|
case .generic(let mastodonEntityError):
|
||||||
|
if let usernameTakenError = mastodonEntityError.details?.username {
|
||||||
|
let isUsernameAvaliable = usernameTakenError.filter { errorDetailReason -> Bool in
|
||||||
|
errorDetailReason.error == .ERR_TAKEN
|
||||||
|
}.isEmpty
|
||||||
|
self.viewModel.isUsernameTaken.value = !isUsernameAvaliable
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
let alertController = UIAlertController(for: error, title: "Sign Up Failure", preferredStyle: .alert)
|
let alertController = UIAlertController(for: error, title: "Sign Up Failure", preferredStyle: .alert)
|
||||||
let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil)
|
let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil)
|
||||||
alertController.addAction(okAction)
|
alertController.addAction(okAction)
|
||||||
@ -439,7 +479,7 @@ extension MastodonRegisterViewController {
|
|||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
if self.viewModel.approvalRequired {
|
if viewModel.approvalRequired {
|
||||||
|
|
||||||
inviteTextField.delegate = self
|
inviteTextField.delegate = self
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
@ -536,45 +576,29 @@ extension MastodonRegisterViewController {
|
|||||||
locale: "en" // TODO:
|
locale: "en" // TODO:
|
||||||
)
|
)
|
||||||
|
|
||||||
if let rules = viewModel.instance.rules, !rules.isEmpty {
|
// register without show server rules
|
||||||
// show server rules before register
|
context.apiService.accountRegister(
|
||||||
let mastodonServerRulesViewModel = MastodonServerRulesViewModel(
|
domain: viewModel.domain,
|
||||||
context: context,
|
query: query,
|
||||||
domain: viewModel.domain,
|
authorization: viewModel.applicationAuthorization
|
||||||
authenticateInfo: viewModel.authenticateInfo,
|
)
|
||||||
rules: rules,
|
.receive(on: DispatchQueue.main)
|
||||||
registerQuery: query,
|
.sink { [weak self] completion in
|
||||||
applicationAuthorization: viewModel.applicationAuthorization
|
guard let self = self else { return }
|
||||||
)
|
self.viewModel.isRegistering.value = false
|
||||||
|
switch completion {
|
||||||
viewModel.isRegistering.value = false
|
case .failure(let error):
|
||||||
view.endEditing(true)
|
self.viewModel.error.send(error)
|
||||||
coordinator.present(scene: .mastodonServerRules(viewModel: mastodonServerRulesViewModel), from: self, transition: .show)
|
case .finished:
|
||||||
return
|
break
|
||||||
} else {
|
|
||||||
// register without show server rules
|
|
||||||
context.apiService.accountRegister(
|
|
||||||
domain: viewModel.domain,
|
|
||||||
query: query,
|
|
||||||
authorization: viewModel.applicationAuthorization
|
|
||||||
)
|
|
||||||
.receive(on: DispatchQueue.main)
|
|
||||||
.sink { [weak self] completion in
|
|
||||||
guard let self = self else { return }
|
|
||||||
self.viewModel.isRegistering.value = false
|
|
||||||
switch completion {
|
|
||||||
case .failure(let error):
|
|
||||||
self.viewModel.error.send(error)
|
|
||||||
case .finished:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} receiveValue: { [weak self] response in
|
|
||||||
guard let self = self else { return }
|
|
||||||
let userToken = response.value
|
|
||||||
let viewModel = MastodonConfirmEmailViewModel(context: self.context, email: email, authenticateInfo: self.viewModel.authenticateInfo, userToken: userToken)
|
|
||||||
self.coordinator.present(scene: .mastodonConfirmEmail(viewModel: viewModel), from: self, transition: .show)
|
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
} receiveValue: { [weak self] response in
|
||||||
|
guard let self = self else { return }
|
||||||
|
let userToken = response.value
|
||||||
|
let viewModel = MastodonConfirmEmailViewModel(context: self.context, email: email, authenticateInfo: self.viewModel.authenticateInfo, userToken: userToken)
|
||||||
|
self.coordinator.present(scene: .mastodonConfirmEmail(viewModel: viewModel), from: self, transition: .show)
|
||||||
}
|
}
|
||||||
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,8 @@ final class MastodonRegisterViewModel {
|
|||||||
let passwordValidateState = CurrentValueSubject<ValidateState, Never>(.empty)
|
let passwordValidateState = CurrentValueSubject<ValidateState, Never>(.empty)
|
||||||
let inviteValidateState = CurrentValueSubject<ValidateState, Never>(.empty)
|
let inviteValidateState = CurrentValueSubject<ValidateState, Never>(.empty)
|
||||||
|
|
||||||
|
let isUsernameTaken = CurrentValueSubject<Bool, Never>(false)
|
||||||
|
|
||||||
let isRegistering = CurrentValueSubject<Bool, Never>(false)
|
let isRegistering = CurrentValueSubject<Bool, Never>(false)
|
||||||
let isAllValid = CurrentValueSubject<Bool, Never>(false)
|
let isAllValid = CurrentValueSubject<Bool, Never>(false)
|
||||||
let error = CurrentValueSubject<Error?, Never>(nil)
|
let error = CurrentValueSubject<Error?, Never>(nil)
|
||||||
@ -158,7 +160,7 @@ extension MastodonRegisterViewModel {
|
|||||||
let falseColor = UIColor.clear
|
let falseColor = UIColor.clear
|
||||||
let attributeString = NSMutableAttributedString()
|
let attributeString = NSMutableAttributedString()
|
||||||
|
|
||||||
let start = NSAttributedString(string: "Your password needs at least:\n", attributes: [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: color])
|
let start = NSAttributedString(string: "Your password needs at least:", attributes: [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: color])
|
||||||
attributeString.append(start)
|
attributeString.append(start)
|
||||||
|
|
||||||
attributeString.append(checkmarkImage(color: eightCharacters ? color : falseColor))
|
attributeString.append(checkmarkImage(color: eightCharacters ? color : falseColor))
|
||||||
|
@ -148,30 +148,6 @@ extension MastodonServerRulesViewController {
|
|||||||
|
|
||||||
rulesLabel.attributedText = viewModel.rulesAttributedString
|
rulesLabel.attributedText = viewModel.rulesAttributedString
|
||||||
confirmButton.addTarget(self, action: #selector(MastodonServerRulesViewController.confirmButtonPressed(_:)), for: .touchUpInside)
|
confirmButton.addTarget(self, action: #selector(MastodonServerRulesViewController.confirmButtonPressed(_:)), for: .touchUpInside)
|
||||||
|
|
||||||
viewModel.isRegistering
|
|
||||||
.receive(on: DispatchQueue.main)
|
|
||||||
.sink { [weak self] isRegistering in
|
|
||||||
guard let self = self else { return }
|
|
||||||
isRegistering ? self.confirmButton.showLoading() : self.confirmButton.stopLoading()
|
|
||||||
}
|
|
||||||
.store(in: &disposeBag)
|
|
||||||
|
|
||||||
viewModel.error
|
|
||||||
.compactMap { $0 }
|
|
||||||
.receive(on: DispatchQueue.main)
|
|
||||||
.sink { [weak self] error in
|
|
||||||
guard let self = self else { return }
|
|
||||||
let alertController = UIAlertController(for: error, title: "Sign Up Failure", preferredStyle: .alert)
|
|
||||||
let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil)
|
|
||||||
alertController.addAction(okAction)
|
|
||||||
self.coordinator.present(
|
|
||||||
scene: .alertController(alertController: alertController),
|
|
||||||
from: nil,
|
|
||||||
transition: .alertController(animated: true, completion: nil)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.store(in: &disposeBag)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLayoutSubviews() {
|
override func viewDidLayoutSubviews() {
|
||||||
@ -197,31 +173,9 @@ extension MastodonServerRulesViewController {
|
|||||||
extension MastodonServerRulesViewController {
|
extension MastodonServerRulesViewController {
|
||||||
@objc private func confirmButtonPressed(_ sender: UIButton) {
|
@objc private func confirmButtonPressed(_ 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)
|
||||||
|
|
||||||
let email = viewModel.registerQuery.email
|
let viewModel = MastodonRegisterViewModel(domain: self.viewModel.domain, authenticateInfo: self.viewModel.authenticateInfo, instance: self.viewModel.instance, applicationToken: self.viewModel.applicationToken)
|
||||||
|
self.coordinator.present(scene: .mastodonRegister(viewModel: viewModel), from: self, transition: .show)
|
||||||
context.apiService.accountRegister(
|
|
||||||
domain: viewModel.domain,
|
|
||||||
query: viewModel.registerQuery,
|
|
||||||
authorization: viewModel.applicationAuthorization
|
|
||||||
)
|
|
||||||
.receive(on: DispatchQueue.main)
|
|
||||||
.sink { [weak self] completion in
|
|
||||||
guard let self = self else { return }
|
|
||||||
self.viewModel.isRegistering.value = false
|
|
||||||
switch completion {
|
|
||||||
case .failure(let error):
|
|
||||||
self.viewModel.error.send(error)
|
|
||||||
case .finished:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} receiveValue: { [weak self] response in
|
|
||||||
guard let self = self else { return }
|
|
||||||
let userToken = response.value
|
|
||||||
let viewModel = MastodonConfirmEmailViewModel(context: self.context, email: email, authenticateInfo: self.viewModel.authenticateInfo, userToken: userToken)
|
|
||||||
self.coordinator.present(scene: .mastodonConfirmEmail(viewModel: viewModel), from: self, transition: .show)
|
|
||||||
}
|
|
||||||
.store(in: &disposeBag)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,34 +10,27 @@ import Combine
|
|||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
|
||||||
final class MastodonServerRulesViewModel {
|
final class MastodonServerRulesViewModel {
|
||||||
|
|
||||||
// input
|
// input
|
||||||
let context: AppContext
|
|
||||||
let domain: String
|
let domain: String
|
||||||
let authenticateInfo: AuthenticationViewModel.AuthenticateInfo
|
let authenticateInfo: AuthenticationViewModel.AuthenticateInfo
|
||||||
let rules: [Mastodon.Entity.Instance.Rule]
|
let rules: [Mastodon.Entity.Instance.Rule]
|
||||||
let registerQuery: Mastodon.API.Account.RegisterQuery
|
let instance: Mastodon.Entity.Instance
|
||||||
let applicationAuthorization: Mastodon.API.OAuth.Authorization
|
let applicationToken: Mastodon.Entity.Token
|
||||||
|
|
||||||
// output
|
|
||||||
let isRegistering = CurrentValueSubject<Bool, Never>(false)
|
|
||||||
let error = CurrentValueSubject<Error?, Never>(nil)
|
|
||||||
|
|
||||||
|
|
||||||
init(
|
init(
|
||||||
context: AppContext,
|
|
||||||
domain: String,
|
domain: String,
|
||||||
authenticateInfo: AuthenticationViewModel.AuthenticateInfo,
|
authenticateInfo: AuthenticationViewModel.AuthenticateInfo,
|
||||||
rules: [Mastodon.Entity.Instance.Rule],
|
rules: [Mastodon.Entity.Instance.Rule],
|
||||||
registerQuery: Mastodon.API.Account.RegisterQuery,
|
instance: Mastodon.Entity.Instance,
|
||||||
applicationAuthorization: Mastodon.API.OAuth.Authorization
|
applicationToken: Mastodon.Entity.Token
|
||||||
) {
|
) {
|
||||||
self.context = context
|
|
||||||
self.domain = domain
|
self.domain = domain
|
||||||
self.authenticateInfo = authenticateInfo
|
self.authenticateInfo = authenticateInfo
|
||||||
self.rules = rules
|
self.rules = rules
|
||||||
self.registerQuery = registerQuery
|
self.instance = instance
|
||||||
self.applicationAuthorization = applicationAuthorization
|
self.applicationToken = applicationToken
|
||||||
}
|
}
|
||||||
|
|
||||||
var rulesAttributedString: NSAttributedString {
|
var rulesAttributedString: NSAttributedString {
|
||||||
|
@ -19,10 +19,12 @@ extension Mastodon.Entity {
|
|||||||
public struct Error: Codable {
|
public struct Error: Codable {
|
||||||
public let error: String
|
public let error: String
|
||||||
public let errorDescription: String?
|
public let errorDescription: String?
|
||||||
|
public let details: ErrorDetail?
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
enum CodingKeys: String, CodingKey {
|
||||||
case error
|
case error
|
||||||
case errorDescription = "error_description"
|
case errorDescription = "error_description"
|
||||||
|
case details
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,102 @@
|
|||||||
|
//
|
||||||
|
// Mastodon+Entity+ErrorDetail.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by sxiaojian on 2021/3/1.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
extension Mastodon.Entity.Error {
|
||||||
|
/// ERR_BLOCKED When e-mail provider is not allowed
|
||||||
|
/// ERR_UNREACHABLE When e-mail address does not resolve to any IP via DNS (MX, A, AAAA)
|
||||||
|
/// ERR_TAKEN When username or e-mail are already taken
|
||||||
|
/// ERR_RESERVED When a username is reserved, e.g. "webmaster" or "admin"
|
||||||
|
/// ERR_ACCEPTED When agreement has not been accepted
|
||||||
|
/// ERR_BLANK When a required attribute is blank
|
||||||
|
/// ERR_INVALID When an attribute is malformed, e.g. wrong characters or invalid e-mail address
|
||||||
|
/// ERR_TOO_LONG When an attribute is over the character limit
|
||||||
|
/// ERR_INCLUSION When an attribute is not one of the allowed values, e.g. unsupported locale
|
||||||
|
public enum SignUpError: RawRepresentable, Codable {
|
||||||
|
case ERR_BLOCKED
|
||||||
|
case ERR_UNREACHABLE
|
||||||
|
case ERR_TAKEN
|
||||||
|
case ERR_RESERVED
|
||||||
|
case ERR_ACCEPTED
|
||||||
|
case ERR_BLANK
|
||||||
|
case ERR_INVALID
|
||||||
|
case ERR_TOO_LONG
|
||||||
|
case ERR_TOO_SHORT
|
||||||
|
case ERR_INCLUSION
|
||||||
|
case _other(String)
|
||||||
|
|
||||||
|
public init?(rawValue: String) {
|
||||||
|
switch rawValue {
|
||||||
|
case "ERR_BLOCKED": self = .ERR_BLOCKED
|
||||||
|
case "ERR_UNREACHABLE": self = .ERR_UNREACHABLE
|
||||||
|
case "ERR_TAKEN": self = .ERR_TAKEN
|
||||||
|
case "ERR_RESERVED": self = .ERR_RESERVED
|
||||||
|
case "ERR_ACCEPTED": self = .ERR_ACCEPTED
|
||||||
|
case "ERR_BLANK": self = .ERR_BLANK
|
||||||
|
case "ERR_INVALID": self = .ERR_INVALID
|
||||||
|
case "ERR_TOO_LONG": self = .ERR_TOO_LONG
|
||||||
|
case "ERR_TOO_SHORT": self = .ERR_TOO_SHORT
|
||||||
|
case "ERR_INCLUSION": self = .ERR_INCLUSION
|
||||||
|
|
||||||
|
default:
|
||||||
|
self = ._other(rawValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var rawValue: String {
|
||||||
|
switch self {
|
||||||
|
case .ERR_BLOCKED: return "ERR_BLOCKED"
|
||||||
|
case .ERR_UNREACHABLE: return "ERR_UNREACHABLE"
|
||||||
|
case .ERR_TAKEN: return "ERR_TAKEN"
|
||||||
|
case .ERR_RESERVED: return "ERR_RESERVED"
|
||||||
|
case .ERR_ACCEPTED: return "ERR_ACCEPTED"
|
||||||
|
case .ERR_BLANK: return "ERR_BLANK"
|
||||||
|
case .ERR_INVALID: return "ERR_INVALID"
|
||||||
|
case .ERR_TOO_LONG: return "ERR_TOO_LONG"
|
||||||
|
case .ERR_TOO_SHORT: return "ERR_TOO_SHORT"
|
||||||
|
case .ERR_INCLUSION: return "ERR_INCLUSION"
|
||||||
|
|
||||||
|
case ._other(let value): return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extension Mastodon.Entity {
|
||||||
|
public struct ErrorDetail: Codable {
|
||||||
|
public let username: [ErrorDetailReason]?
|
||||||
|
public let email: [ErrorDetailReason]?
|
||||||
|
public let password: [ErrorDetailReason]?
|
||||||
|
public let agreement: [ErrorDetailReason]?
|
||||||
|
public let locale: [ErrorDetailReason]?
|
||||||
|
public let reason: [ErrorDetailReason]?
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case username
|
||||||
|
case email
|
||||||
|
case password
|
||||||
|
case agreement
|
||||||
|
case locale
|
||||||
|
case reason
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct ErrorDetailReason: Codable {
|
||||||
|
public init(error: String, errorDescription: String?) {
|
||||||
|
self.error = Mastodon.Entity.Error.SignUpError(rawValue: error) ?? ._other(error)
|
||||||
|
self.errorDescription = errorDescription
|
||||||
|
}
|
||||||
|
|
||||||
|
public let error: Mastodon.Entity.Error.SignUpError
|
||||||
|
public let errorDescription: String?
|
||||||
|
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case error
|
||||||
|
case errorDescription = "description"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user