Merge pull request #1081 from mastodon/ios-171-list-of-default-servers
Make list of default servers configurable (IOS-171)
This commit is contained in:
commit
a843d946b2
|
@ -270,7 +270,7 @@
|
|||
"welcome": {
|
||||
"log_in": "Log In",
|
||||
"learn_more": "Learn more",
|
||||
"join_default_server": "Join mastodon.social",
|
||||
"join_default_server": "Join %@",
|
||||
"pick_server": "Pick another server",
|
||||
"separator": {
|
||||
"or": "or"
|
||||
|
|
|
@ -47,10 +47,6 @@ final class WelcomeViewController: UIViewController, NeedsDependency {
|
|||
|
||||
private(set) lazy var joinDefaultServerButton: UIButton = {
|
||||
var buttonConfiguration = UIButton.Configuration.filled()
|
||||
buttonConfiguration.attributedTitle = AttributedString(
|
||||
L10n.Scene.Welcome.joinDefaultServer,
|
||||
attributes: .init([.font: UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold))])
|
||||
)
|
||||
buttonConfiguration.baseForegroundColor = .white
|
||||
buttonConfiguration.background.backgroundColor = Asset.Colors.Brand.blurple.color
|
||||
buttonConfiguration.background.cornerRadius = 14
|
||||
|
@ -217,6 +213,23 @@ extension WelcomeViewController {
|
|||
.store(in: &disposeBag)
|
||||
|
||||
setupIllustrationLayout()
|
||||
|
||||
joinDefaultServerButton.configuration?.showsActivityIndicator = true
|
||||
joinDefaultServerButton.isEnabled = false
|
||||
joinDefaultServerButton.configuration?.title = nil
|
||||
|
||||
viewModel.downloadDefaultServer { [weak self] in
|
||||
guard let selectedDefaultServer = self?.viewModel.randomDefaultServer else { return }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self?.joinDefaultServerButton.configuration?.showsActivityIndicator = false
|
||||
self?.joinDefaultServerButton.isEnabled = true
|
||||
self?.joinDefaultServerButton.configuration?.attributedTitle = AttributedString(
|
||||
L10n.Scene.Welcome.joinDefaultServer(selectedDefaultServer.domain),
|
||||
attributes: .init([.font: UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold))])
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
|
@ -262,12 +275,12 @@ extension WelcomeViewController {
|
|||
//MARK: - Actions
|
||||
@objc
|
||||
private func joinDefaultServer(_ sender: UIButton) {
|
||||
|
||||
guard let server = viewModel.randomDefaultServer else { return }
|
||||
sender.configuration?.title = nil
|
||||
sender.isEnabled = false
|
||||
sender.configuration?.showsActivityIndicator = true
|
||||
|
||||
let server = Mastodon.Entity.Server.mastodonDotSocial
|
||||
|
||||
authenticationViewModel.isAuthenticating.send(true)
|
||||
|
||||
context.apiService.instance(domain: server.domain)
|
||||
|
@ -320,19 +333,26 @@ extension WelcomeViewController {
|
|||
self.authenticationViewModel.isAuthenticating.send(false)
|
||||
|
||||
switch completion {
|
||||
case .failure(let error):
|
||||
//TODO: show an alert or something
|
||||
break
|
||||
case .finished:
|
||||
break
|
||||
case .failure(let error ):
|
||||
guard let randomServer = self.viewModel.pickRandomDefaultServer() else { return }
|
||||
|
||||
self.viewModel.randomDefaultServer = randomServer
|
||||
|
||||
sender.isEnabled = true
|
||||
sender.configuration?.showsActivityIndicator = false
|
||||
sender.configuration?.attributedTitle = AttributedString(
|
||||
L10n.Scene.Welcome.joinDefaultServer(randomServer.domain),
|
||||
attributes: .init([.font: UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold))])
|
||||
)
|
||||
case .finished:
|
||||
sender.isEnabled = true
|
||||
sender.configuration?.showsActivityIndicator = false
|
||||
sender.configuration?.attributedTitle = AttributedString(
|
||||
L10n.Scene.Welcome.joinDefaultServer(server.domain),
|
||||
attributes: .init([.font: UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold))])
|
||||
)
|
||||
}
|
||||
|
||||
sender.isEnabled = true
|
||||
sender.configuration?.showsActivityIndicator = false
|
||||
sender.configuration?.attributedTitle = AttributedString(
|
||||
L10n.Scene.Welcome.joinDefaultServer,
|
||||
attributes: .init([.font: UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold))])
|
||||
)
|
||||
} receiveValue: { [weak self] response in
|
||||
guard let self = self else { return }
|
||||
if let rules = response.instance.value.rules, !rules.isEmpty {
|
||||
|
|
|
@ -8,11 +8,14 @@
|
|||
import Foundation
|
||||
import Combine
|
||||
import MastodonCore
|
||||
import MastodonSDK
|
||||
|
||||
final class WelcomeViewModel {
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
private(set) var defaultServers: [Mastodon.Entity.DefaultServer]?
|
||||
var randomDefaultServer: Mastodon.Entity.Server?
|
||||
|
||||
// input
|
||||
let context: AppContext
|
||||
|
||||
|
@ -26,5 +29,40 @@ final class WelcomeViewModel {
|
|||
.map { !$0.isEmpty }
|
||||
.assign(to: &$needsShowDismissEntry)
|
||||
}
|
||||
|
||||
|
||||
func downloadDefaultServer(completion: (() -> Void)? = nil) {
|
||||
context.apiService.defaultServers()
|
||||
.timeout(.milliseconds(500) , scheduler: DispatchQueue.main)
|
||||
.sink { [weak self] result in
|
||||
|
||||
switch result {
|
||||
case .finished:
|
||||
if let defaultServers = self?.defaultServers, defaultServers.isEmpty == false {
|
||||
self?.randomDefaultServer = self?.pickRandomDefaultServer()
|
||||
} else {
|
||||
self?.randomDefaultServer = Mastodon.Entity.Server.mastodonDotSocial
|
||||
}
|
||||
case .failure(_):
|
||||
self?.randomDefaultServer = Mastodon.Entity.Server.mastodonDotSocial
|
||||
}
|
||||
|
||||
completion?()
|
||||
} receiveValue: { [weak self] servers in
|
||||
self?.defaultServers = servers.value
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
func pickRandomDefaultServer() -> Mastodon.Entity.Server? {
|
||||
guard let defaultServers else { return nil }
|
||||
|
||||
let weightedServers = defaultServers
|
||||
.compactMap { [Mastodon.Entity.DefaultServer](repeating: $0, count: $0.weight) }
|
||||
.reduce([], +)
|
||||
|
||||
let randomServer = weightedServers.randomElement()
|
||||
.map { Mastodon.Entity.Server(domain: $0.domain, instance: Mastodon.Entity.Instance(domain: $0.domain)) }
|
||||
|
||||
return randomServer
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,4 +33,8 @@ extension APIService {
|
|||
public func languages() -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Language]>, Error> {
|
||||
return Mastodon.API.Onboarding.languages(session: session)
|
||||
}
|
||||
|
||||
public func defaultServers() -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.DefaultServer]>, Error> {
|
||||
return Mastodon.API.Onboarding.defaultServers(session: session)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1510,8 +1510,10 @@ public enum L10n {
|
|||
}
|
||||
}
|
||||
public enum Welcome {
|
||||
/// Join mastodon.social
|
||||
public static let joinDefaultServer = L10n.tr("Localizable", "Scene.Welcome.JoinDefaultServer", fallback: "Join mastodon.social")
|
||||
/// Join %@
|
||||
public static func joinDefaultServer(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "Scene.Welcome.JoinDefaultServer", String(describing: p1), fallback: "Join %@")
|
||||
}
|
||||
/// Learn more
|
||||
public static let learnMore = L10n.tr("Localizable", "Scene.Welcome.LearnMore", fallback: "Learn more")
|
||||
/// Log In
|
||||
|
|
|
@ -530,7 +530,7 @@ uploaded to Mastodon.";
|
|||
"Scene.Welcome.Education.Mastodon.Title" = "Welcome to Mastodon";
|
||||
"Scene.Welcome.Education.Servers.Description" = "Every Mastodon account is hosted on a server — each with its own values, rules, & admins. No matter which one you pick, you can follow and interact with people on any server.";
|
||||
"Scene.Welcome.Education.Servers.Title" = "What are servers?";
|
||||
"Scene.Welcome.JoinDefaultServer" = "Join mastodon.social";
|
||||
"Scene.Welcome.JoinDefaultServer" = "Join %@";
|
||||
"Scene.Welcome.LearnMore" = "Learn more";
|
||||
"Scene.Welcome.LogIn" = "Log In";
|
||||
"Scene.Welcome.PickServer" = "Pick another server";
|
||||
|
@ -556,4 +556,4 @@ uploaded to Mastodon.";
|
|||
"Widget.MultipleFollowers.ConfigurationDescription" = "Show number of followers for multiple accounts.";
|
||||
"Widget.MultipleFollowers.ConfigurationDisplayName" = "Multiple followers";
|
||||
"Widget.MultipleFollowers.MockUser.AccountName" = "another@follower.social";
|
||||
"Widget.MultipleFollowers.MockUser.DisplayName" = "Another follower";
|
||||
"Widget.MultipleFollowers.MockUser.DisplayName" = "Another follower";
|
||||
|
|
|
@ -530,7 +530,7 @@ uploaded to Mastodon.";
|
|||
"Scene.Welcome.Education.Mastodon.Title" = "Welcome to Mastodon";
|
||||
"Scene.Welcome.Education.Servers.Description" = "Every Mastodon account is hosted on a server — each with its own values, rules, & admins. No matter which one you pick, you can follow and interact with people on any server.";
|
||||
"Scene.Welcome.Education.Servers.Title" = "What are servers?";
|
||||
"Scene.Welcome.JoinDefaultServer" = "Join mastodon.social";
|
||||
"Scene.Welcome.JoinDefaultServer" = "Join %@";
|
||||
"Scene.Welcome.LearnMore" = "Learn more";
|
||||
"Scene.Welcome.LogIn" = "Log In";
|
||||
"Scene.Welcome.PickServer" = "Pick another server";
|
||||
|
@ -556,4 +556,4 @@ uploaded to Mastodon.";
|
|||
"Widget.MultipleFollowers.ConfigurationDescription" = "Show number of followers for multiple accounts.";
|
||||
"Widget.MultipleFollowers.ConfigurationDisplayName" = "Multiple followers";
|
||||
"Widget.MultipleFollowers.MockUser.AccountName" = "another@follower.social";
|
||||
"Widget.MultipleFollowers.MockUser.DisplayName" = "Another follower";
|
||||
"Widget.MultipleFollowers.MockUser.DisplayName" = "Another follower";
|
||||
|
|
|
@ -13,6 +13,7 @@ extension Mastodon.API.Onboarding {
|
|||
static let serversEndpointURL = Mastodon.API.joinMastodonEndpointURL.appendingPathComponent("servers")
|
||||
static let categoriesEndpointURL = Mastodon.API.joinMastodonEndpointURL.appendingPathComponent("categories")
|
||||
static let languagesEndpointURL = Mastodon.API.joinMastodonEndpointURL.appendingPathComponent("languages")
|
||||
static let defaultServersEndpointURL = Mastodon.API.joinMastodonEndpointURL.appendingPathComponent("default-servers")
|
||||
|
||||
/// Fetch server list
|
||||
///
|
||||
|
@ -96,6 +97,30 @@ extension Mastodon.API.Onboarding {
|
|||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
/// Fetch default servers
|
||||
///
|
||||
/// Using this endpoint to fetch default servers
|
||||
///
|
||||
/// # Last Update
|
||||
/// 2023/07/02
|
||||
/// # Reference
|
||||
/// undocumented
|
||||
/// - Parameters:
|
||||
/// - session: `URLSession`
|
||||
/// - Returns: `AnyPublisher` contains `Language` nested in the response
|
||||
public static func defaultServers(
|
||||
session: URLSession
|
||||
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.DefaultServer]>, Error> {
|
||||
let request = Mastodon.API.get(url: defaultServersEndpointURL)
|
||||
return session.dataTaskPublisher(for: request)
|
||||
.tryMap { data, response in
|
||||
let value = try Mastodon.API.decode(type: [Mastodon.Entity.DefaultServer].self, from: data, response: response)
|
||||
return Mastodon.Response.Content(value: value, response: response)
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Mastodon.API.Onboarding {
|
||||
|
|
|
@ -134,7 +134,7 @@ extension Mastodon.API {
|
|||
static func get(
|
||||
url: URL,
|
||||
query: GetQuery? = nil,
|
||||
authorization: OAuth.Authorization?
|
||||
authorization: OAuth.Authorization? = nil
|
||||
) -> URLRequest {
|
||||
return buildRequest(url: url, method: .GET, query: query, authorization: authorization)
|
||||
}
|
||||
|
@ -142,7 +142,7 @@ extension Mastodon.API {
|
|||
static func post(
|
||||
url: URL,
|
||||
query: PostQuery?,
|
||||
authorization: OAuth.Authorization?
|
||||
authorization: OAuth.Authorization? = nil
|
||||
) -> URLRequest {
|
||||
return buildRequest(url: url, method: .POST, query: query, authorization: authorization)
|
||||
}
|
||||
|
@ -150,7 +150,7 @@ extension Mastodon.API {
|
|||
static func patch(
|
||||
url: URL,
|
||||
query: PatchQuery?,
|
||||
authorization: OAuth.Authorization?
|
||||
authorization: OAuth.Authorization? = nil
|
||||
) -> URLRequest {
|
||||
return buildRequest(url: url, method: .PATCH, query: query, authorization: authorization)
|
||||
}
|
||||
|
@ -158,7 +158,7 @@ extension Mastodon.API {
|
|||
static func put(
|
||||
url: URL,
|
||||
query: PutQuery? = nil,
|
||||
authorization: OAuth.Authorization?
|
||||
authorization: OAuth.Authorization? = nil
|
||||
) -> URLRequest {
|
||||
return buildRequest(url: url, method: .PUT, query: query, authorization: authorization)
|
||||
}
|
||||
|
@ -166,7 +166,7 @@ extension Mastodon.API {
|
|||
static func delete(
|
||||
url: URL,
|
||||
query: DeleteQuery?,
|
||||
authorization: OAuth.Authorization?
|
||||
authorization: OAuth.Authorization? = nil
|
||||
) -> URLRequest {
|
||||
return buildRequest(url: url, method: .DELETE, query: query, authorization: authorization)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Mastodon.Entity {
|
||||
public struct DefaultServer: Codable {
|
||||
public let domain: String
|
||||
public let weight: Int
|
||||
|
||||
public init(domain: String, weight: Int) {
|
||||
self.domain = domain
|
||||
self.weight = weight
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue