Updated Welcome-Screen (IOS-134) (#1005)

This commit is contained in:
Nathan Mattes 2023-04-08 23:10:34 +02:00 committed by GitHub
parent 560003f78b
commit 5e1e22a723
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 887 additions and 455 deletions

View File

@ -80,7 +80,6 @@
"save_photo": "Save Photo",
"copy_photo": "Copy Photo",
"sign_in": "Log in",
"sign_up": "Create Account",
"see_more": "See More",
"preview": "Preview",
"copy": "Copy",
@ -102,7 +101,8 @@
"title": "Translate from %s",
"unknown_language": "Unknown"
},
"edit_post": "Edit"
"edit_post": "Edit",
},
"tabs": {
"home": "Home",
@ -261,34 +261,33 @@
},
"scene": {
"welcome": {
"slogan": "Social networking\nback in your hands.",
"get_started": "Get Started",
"log_in": "Log In",
"learn_more": "Learn more",
"join_default_server": "Join mastodon.social"
"separator": {
"or": "or"
},
"education": {
"what_is_mastodon": {
"title": "What is",
"description": "Imagine you have an email address that ends with @example.com.\n\nYou can still send and receive emails from anyone, even if their email ends in @gmail.com or @icloud.com or @example.com."
},
"mastodon_is_like_that": {
"title": "Mastodon is like that",
"description": "Your handle might be @gothgirl654@example.social, but you can still follow, reblog, and chat with @fallout5ever@example.online."
},
"how_do_i_pick_a_server": {
"title": "How do I pick a server?",
"description": "Different people choose different servers for any number of reasons. art.example is a great place for artists, while glasgow.example might be a good pick for Scots.\n\nYou cant go wrong with any of our recommend servers, so regardless of which one you pick (or if you enter your own in the server search bar), youll never miss a beat anywhere."
},
"mastodon": {
"title": "Welcome to Mastodon",
"description": "Mastodon is a decentralized social network, meaning no single company controls it. Its made up of many independently-run servers, all connected together."
},
"servers": {
"title": "What are 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."
},
"a11y": {
"what_is_mastodon": {
"title": "What is Mastodon?"
}
}
"title": "What is Mastodon?"
}
}
}
},
"login": {
"title": "Welcome back",
"subtitle": "Log you in on the server you created your account on.",
"server_search_field": {
"placeholder": "Enter URL or search for your server"
"placeholder": "Enter URL or search for your server"
}
},
"server_picker": {

View File

@ -135,6 +135,7 @@
9E44C7202967AD17004B2A72 /* MastodonSDKDynamic in Frameworks */ = {isa = PBXBuildFile; productRef = 9E44C71F2967AD17004B2A72 /* MastodonSDKDynamic */; };
9E44C7222967AD17004B2A72 /* MastodonSDKDynamic in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 9E44C71F2967AD17004B2A72 /* MastodonSDKDynamic */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
C24C97032922F30500BAE8CB /* RefreshControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = C24C97022922F30500BAE8CB /* RefreshControl.swift */; };
D807C6C029DE197900A4E17C /* EducationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D807C6BF29DE197900A4E17C /* EducationViewController.swift */; };
D808B94C296ECFDC0031EB1E /* StatusEditHistoryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D808B94B296ECFDC0031EB1E /* StatusEditHistoryViewModel.swift */; };
D808B94E296EFBBA0031EB1E /* StatusEditHistoryTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D808B94D296EFBBA0031EB1E /* StatusEditHistoryTableViewCell.swift */; };
D8099078294BC8A30050219F /* PrivacyTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8099077294BC8A30050219F /* PrivacyTableViewController.swift */; };
@ -144,10 +145,9 @@
D87BFC8B291D5C6B00FEE264 /* MastodonLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87BFC8A291D5C6B00FEE264 /* MastodonLoginView.swift */; };
D87BFC8D291EB81200FEE264 /* MastodonLoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87BFC8C291EB81200FEE264 /* MastodonLoginViewModel.swift */; };
D87BFC8F291EC26A00FEE264 /* MastodonLoginServerTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87BFC8E291EC26A00FEE264 /* MastodonLoginServerTableViewCell.swift */; };
D886FBD329DF710F00272017 /* WelcomeSeparatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D886FBD229DF710F00272017 /* WelcomeSeparatorView.swift */; };
D8916DC029211BE500124085 /* ContentSizedTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8916DBF29211BE500124085 /* ContentSizedTableView.swift */; };
D8A6AB6C291C5136003AB663 /* MastodonLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A6AB6B291C5136003AB663 /* MastodonLoginViewController.swift */; };
D8A6FE5B293244B500666A47 /* WelcomeContentPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A6FE5A293244B500666A47 /* WelcomeContentPage.swift */; };
D8A6FE5F29324BBC00666A47 /* WelcomeContentCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A6FE5E29324BBC00666A47 /* WelcomeContentCollectionViewCell.swift */; };
D8E5C346296DAB84007E76A7 /* DataSourceFacade+Status+History.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E5C345296DAB84007E76A7 /* DataSourceFacade+Status+History.swift */; };
D8E5C349296DB8A3007E76A7 /* StatusEditHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E5C348296DB8A3007E76A7 /* StatusEditHistoryViewController.swift */; };
DB0009A626AEE5DC009B9D2D /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = DB0009A926AEE5DC009B9D2D /* Intents.intentdefinition */; settings = {ATTRIBUTES = (codegen, ); }; };
@ -773,6 +773,7 @@
C3789232A52F43529CA67E95 /* Pods-MastodonIntent.asdk - debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonIntent.asdk - debug.xcconfig"; path = "Target Support Files/Pods-MastodonIntent/Pods-MastodonIntent.asdk - debug.xcconfig"; sourceTree = "<group>"; };
CD92E0F10BDE4FE7C4B999F2 /* Pods_MastodonTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MastodonTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D7D7CF93E262178800077512 /* Pods-Mastodon-AppShared.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-AppShared.debug.xcconfig"; path = "Target Support Files/Pods-Mastodon-AppShared/Pods-Mastodon-AppShared.debug.xcconfig"; sourceTree = "<group>"; };
D807C6BF29DE197900A4E17C /* EducationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EducationViewController.swift; sourceTree = "<group>"; };
D808B94B296ECFDC0031EB1E /* StatusEditHistoryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEditHistoryViewModel.swift; sourceTree = "<group>"; };
D808B94D296EFBBA0031EB1E /* StatusEditHistoryTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEditHistoryTableViewCell.swift; sourceTree = "<group>"; };
D8099077294BC8A30050219F /* PrivacyTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyTableViewController.swift; sourceTree = "<group>"; };
@ -782,10 +783,9 @@
D87BFC8A291D5C6B00FEE264 /* MastodonLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginView.swift; sourceTree = "<group>"; };
D87BFC8C291EB81200FEE264 /* MastodonLoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginViewModel.swift; sourceTree = "<group>"; };
D87BFC8E291EC26A00FEE264 /* MastodonLoginServerTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginServerTableViewCell.swift; sourceTree = "<group>"; };
D886FBD229DF710F00272017 /* WelcomeSeparatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeSeparatorView.swift; sourceTree = "<group>"; };
D8916DBF29211BE500124085 /* ContentSizedTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentSizedTableView.swift; sourceTree = "<group>"; };
D8A6AB6B291C5136003AB663 /* MastodonLoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginViewController.swift; sourceTree = "<group>"; };
D8A6FE5A293244B500666A47 /* WelcomeContentPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeContentPage.swift; sourceTree = "<group>"; };
D8A6FE5E29324BBC00666A47 /* WelcomeContentCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeContentCollectionViewCell.swift; sourceTree = "<group>"; };
D8A6FE6129325F5900666A47 /* Intents.stringsdict */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; path = Intents.stringsdict; sourceTree = "<group>"; };
D8A6FE6229325F5900666A47 /* app.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = app.json; sourceTree = "<group>"; };
D8A6FE6329325F5900666A47 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
@ -1324,6 +1324,7 @@
DBABE3F125ECAC4E00879EE5 /* View */,
0FAA0FDE25E0B57E0017CCDE /* WelcomeViewController.swift */,
DB1D61CE26F1B33600DA8662 /* WelcomeViewModel.swift */,
D807C6BF29DE197900A4E17C /* EducationViewController.swift */,
);
path = Welcome;
sourceTree = "<group>";
@ -1808,15 +1809,6 @@
path = Login;
sourceTree = "<group>";
};
D8A6FE5929323DC200666A47 /* Pages */ = {
isa = PBXGroup;
children = (
D8A6FE5A293244B500666A47 /* WelcomeContentPage.swift */,
D8A6FE5E29324BBC00666A47 /* WelcomeContentCollectionViewCell.swift */,
);
path = Pages;
sourceTree = "<group>";
};
D8A6FE6029325F5900666A47 /* Localization */ = {
isa = PBXGroup;
children = (
@ -2053,6 +2045,7 @@
D8A6FE6029325F5900666A47 /* Localization */,
);
sourceTree = "<group>";
tabWidth = 4;
};
DB427DD325BAA00100D1B89D /* Products */ = {
isa = PBXGroup;
@ -2760,9 +2753,9 @@
DBABE3F125ECAC4E00879EE5 /* View */ = {
isa = PBXGroup;
children = (
D8A6FE5929323DC200666A47 /* Pages */,
DBABE3EB25ECAC4B00879EE5 /* WelcomeIllustrationView.swift */,
DB0617EA277EF3820030EE79 /* GradientBorderView.swift */,
D886FBD229DF710F00272017 /* WelcomeSeparatorView.swift */,
);
path = View;
sourceTree = "<group>";
@ -3259,10 +3252,10 @@
targets = (
DB427DD125BAA00100D1B89D /* Mastodon */,
DB427DE725BAA00100D1B89D /* MastodonTests */,
DB8FABC526AEC7B2008E5AF4 /* MastodonIntent */,
DB427DF225BAA00100D1B89D /* MastodonUITests */,
DBF8AE12263293E400C9C23C /* NotificationService */,
DBC6461126A170AB00B0E31B /* ShareActionExtension */,
DB8FABC526AEC7B2008E5AF4 /* MastodonIntent */,
2A64515C29642A8A00CD8B8A /* OpenInActionExtension */,
2A72811F297EA9D7004138C5 /* WidgetExtension */,
);
@ -3647,7 +3640,6 @@
DB603113279EBEBA00A935FE /* DataSourceFacade+Block.swift in Sources */,
DB63F777279A9A2A00455B82 /* NotificationView+Configuration.swift in Sources */,
DB029E95266A20430062874E /* MastodonAuthenticationController.swift in Sources */,
D8A6FE5F29324BBC00666A47 /* WelcomeContentCollectionViewCell.swift in Sources */,
5B90C461262599800002E742 /* SettingsLinkTableViewCell.swift in Sources */,
DB6180DD263918E30018D199 /* MediaPreviewViewController.swift in Sources */,
DBE3CDEC261C6B2900430CC6 /* FavoriteViewController.swift in Sources */,
@ -3803,6 +3795,7 @@
DB68A04A25E9027700CFDF14 /* AdaptiveStatusBarStyleNavigationController.swift in Sources */,
0FB3D33825E6401400AAD544 /* PickServerCell.swift in Sources */,
6213AF5C28939C8A00BCADB6 /* BookmarkViewModel+State.swift in Sources */,
D807C6C029DE197900A4E17C /* EducationViewController.swift in Sources */,
DB6D9F8426358EEC008423CD /* SettingsItem.swift in Sources */,
2D364F7825E66D8300204FDC /* MastodonResendEmailViewModel.swift in Sources */,
DBEFCD7B282A162400C0ABEA /* ReportReasonView.swift in Sources */,
@ -3935,9 +3928,9 @@
DB0FCB882796BDA9006C02E2 /* SearchItem.swift in Sources */,
DB6180ED26391C6C0018D199 /* TransitioningMath.swift in Sources */,
2D6DE40026141DF600A63F6A /* SearchViewModel.swift in Sources */,
D886FBD329DF710F00272017 /* WelcomeSeparatorView.swift in Sources */,
DB0617FD27855BFE0030EE79 /* ServerRuleItem.swift in Sources */,
5BB04FD5262E7AFF0043BFF6 /* ReportViewController.swift in Sources */,
D8A6FE5B293244B500666A47 /* WelcomeContentPage.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -0,0 +1,85 @@
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
import UIKit
import MastodonLocalization
class EducationViewController: UIViewController {
let mastodonLabel: UILabel
let mastodonExplanation: UILabel
let serversLabel: UILabel
let serversExplanation: UILabel
private let contentStackView: UIStackView
private let contentScrollView: UIScrollView
init() {
mastodonLabel = UILabel()
mastodonLabel.numberOfLines = 0
mastodonLabel.adjustsFontForContentSizeCategory = true
mastodonLabel.text = L10n.Scene.Welcome.Education.Mastodon.title
mastodonLabel.font = UIFontMetrics(forTextStyle: .title2).scaledFont(for: .systemFont(ofSize: 22, weight: .bold))
mastodonLabel.textColor = .label
mastodonExplanation = UILabel()
mastodonExplanation.numberOfLines = 0
mastodonExplanation.adjustsFontForContentSizeCategory = true
mastodonExplanation.text = L10n.Scene.Welcome.Education.Mastodon.description
mastodonExplanation.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular))
mastodonExplanation.textColor = .label
serversLabel = UILabel()
serversLabel.numberOfLines = 0
serversLabel.adjustsFontForContentSizeCategory = true
serversLabel.text = L10n.Scene.Welcome.Education.Servers.title
serversLabel.font = UIFontMetrics(forTextStyle: .title2).scaledFont(for: .systemFont(ofSize: 22, weight: .bold))
serversLabel.textColor = .label
serversExplanation = UILabel()
serversExplanation.numberOfLines = 0
serversExplanation.adjustsFontForContentSizeCategory = true
serversExplanation.text = L10n.Scene.Welcome.Education.Servers.description
serversExplanation.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular))
serversExplanation.textColor = .label
contentStackView = UIStackView(arrangedSubviews: [mastodonLabel, mastodonExplanation, serversLabel, serversExplanation])
contentStackView.translatesAutoresizingMaskIntoConstraints = false
contentStackView.axis = .vertical
contentStackView.alignment = .leading
contentStackView.setCustomSpacing(2, after: mastodonLabel)
contentStackView.setCustomSpacing(24, after: mastodonExplanation)
contentStackView.setCustomSpacing(2, after: serversLabel)
contentScrollView = UIScrollView()
contentScrollView.translatesAutoresizingMaskIntoConstraints = false
contentScrollView.addSubview(contentStackView)
super.init(nibName: nil, bundle: nil)
view.addSubview(contentScrollView)
view.backgroundColor = .systemBackground
setupConstraints()
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
private func setupConstraints() {
let constraints = [
contentStackView.topAnchor.constraint(equalTo: contentScrollView.topAnchor, constant: 32),
contentStackView.leadingAnchor.constraint(equalTo: contentScrollView.leadingAnchor, constant: 16),
contentScrollView.trailingAnchor.constraint(equalTo: contentStackView.trailingAnchor, constant: 16),
contentScrollView.bottomAnchor.constraint(greaterThanOrEqualTo: contentStackView.bottomAnchor, constant: 32),
contentScrollView.topAnchor.constraint(equalTo: view.topAnchor),
contentScrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
view.trailingAnchor.constraint(equalTo: contentScrollView.trailingAnchor),
view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: contentScrollView.bottomAnchor),
contentStackView.widthAnchor.constraint(equalTo: contentScrollView.widthAnchor, constant: -32),
]
NSLayoutConstraint.activate(constraints)
}
}

View File

@ -1,70 +0,0 @@
//
// WelcomeContentPageView.swift
// Mastodon
//
// Created by Nathan Mattes on 26.11.22.
//
import UIKit
class WelcomeContentCollectionViewCell: UICollectionViewCell {
static let identifier = "WelcomeContentCollectionViewCell"
//TODO: Put in ScrollView?
private let contentStackView: UIStackView
private let titleView: UILabel
private let label: UILabel
override init(frame: CGRect) {
titleView = UILabel()
titleView.font = WelcomeViewController.largeTitleFont
titleView.textColor = WelcomeViewController.largeTitleTextColor.resolvedColor(with: UITraitCollection(userInterfaceStyle: .light))
titleView.adjustsFontForContentSizeCategory = true
titleView.numberOfLines = 0
label = UILabel()
label.font = WelcomeViewController.subTitleFont
label.textColor = WelcomeViewController.largeTitleTextColor.resolvedColor(with: UITraitCollection(userInterfaceStyle: .light))
label.adjustsFontForContentSizeCategory = true
label.numberOfLines = 0
contentStackView = UIStackView(arrangedSubviews: [titleView, label, UIView()])
contentStackView.translatesAutoresizingMaskIntoConstraints = false
contentStackView.axis = .vertical
contentStackView.alignment = .leading
contentStackView.spacing = 8
super.init(frame: frame)
addSubview(contentStackView)
setupConstraints()
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
private func setupConstraints() {
let constraints = [
contentStackView.topAnchor.constraint(equalTo: topAnchor),
contentStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
contentStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
trailingAnchor.constraint(equalTo: contentStackView.trailingAnchor, constant: 16),
bottomAnchor.constraint(greaterThanOrEqualTo: contentStackView.bottomAnchor),
]
NSLayoutConstraint.activate(constraints)
}
private func updateAccessibility() {
accessibilityLabel = "\(titleView.accessibilityLabel ?? ""), \(label.accessibilityLabel ?? "")"
isAccessibilityElement = true
}
func update(with page: WelcomeContentPage) {
titleView.attributedText = page.title
titleView.accessibilityLabel = page.accessibilityLabel
label.text = page.content
updateAccessibility()
}
}

View File

@ -1,74 +0,0 @@
//
// WelcomeContentPage.swift
// Mastodon
//
// Created by Nathan Mattes on 26.11.22.
//
import UIKit
import MastodonLocalization
import MastodonAsset
enum WelcomeContentPage: CaseIterable {
case whatIsMastodon
case mastodonIsLikeThat
case howDoIPickAServer
var backgroundColor: UIColor {
switch self {
case .whatIsMastodon:
return .green
case .mastodonIsLikeThat:
return .red
case .howDoIPickAServer:
return .blue
}
}
var title: NSAttributedString {
switch self {
case .whatIsMastodon:
let image = Asset.Scene.Welcome.mastodonLogo.image
let attachment = NSTextAttachment(image: image)
let attributedString = NSMutableAttributedString(string: "\(L10n.Scene.Welcome.Education.WhatIsMastodon.title) ")
attachment.bounds = CGRect(
x: 0,
y: WelcomeViewController.largeTitleFont.descender - 5,
width: image.size.width,
height: image.size.height
)
attributedString.append(NSAttributedString(attachment: attachment))
attributedString.append(NSAttributedString(string: " ?"))
return attributedString
case .mastodonIsLikeThat:
return NSAttributedString(string: L10n.Scene.Welcome.Education.MastodonIsLikeThat.title)
case .howDoIPickAServer:
return NSAttributedString(string: L10n.Scene.Welcome.Education.HowDoIPickAServer.title)
}
}
var accessibilityLabel: String {
switch self {
case .whatIsMastodon:
return L10n.Scene.Welcome.Education.A11Y.WhatIsMastodon.title
case .mastodonIsLikeThat:
return L10n.Scene.Welcome.Education.MastodonIsLikeThat.title
case .howDoIPickAServer:
return L10n.Scene.Welcome.Education.HowDoIPickAServer.title
}
}
var content: String {
switch self {
case .whatIsMastodon:
return L10n.Scene.Welcome.Education.WhatIsMastodon.description
case .mastodonIsLikeThat:
return L10n.Scene.Welcome.Education.MastodonIsLikeThat.description
case .howDoIPickAServer:
return L10n.Scene.Welcome.Education.HowDoIPickAServer.description
}
}
}

View File

@ -13,10 +13,9 @@ import MastodonLocalization
fileprivate extension CGFloat {
static let cloudsStartPosition = -20.0
static let centerHillStartPosition = 20.0
static let airplaneStartPosition = -178.0
static let leftHillStartPosition = 30.0
static let rightHillStartPosition = -200.0
static let rightHillStartPosition = -125.0
static let leftHillSpeed = 6.0
static let centerHillSpeed = 40.0
static let rightHillSpeed = 6.0
@ -25,18 +24,11 @@ fileprivate extension CGFloat {
final class WelcomeIllustrationView: UIView {
private let cloudBaseImage = Asset.Scene.Welcome.Illustration.cloudBase.image
private let cloudBaseExtendImage = Asset.Scene.Welcome.Illustration.cloudBaseExtend.image
private let elephantThreeOnGrassWithTreeTwoImage = Asset.Scene.Welcome.Illustration.elephantThreeOnGrassWithTreeTwo.image
private let elephantThreeOnGrassWithTreeThreeImage = Asset.Scene.Welcome.Illustration.elephantThreeOnGrassWithTreeThree.image
private let elephantThreeOnGrassImage = Asset.Scene.Welcome.Illustration.elephantThreeOnGrass.image
private let elephantThreeOnGrassExtendImage = Asset.Scene.Welcome.Illustration.elephantThreeOnGrassExtend.image
var cloudsLeftAnchor: NSLayoutConstraint?
var elephantOnAirplaneLeftConstraint: NSLayoutConstraint?
var leftHillLeftConstraint: NSLayoutConstraint?
var centerHillLeftConstraint: NSLayoutConstraint?
var rightHillRightConstraint: NSLayoutConstraint?
let elephantOnAirplaneWithContrailImageView: UIImageView = {
let imageView = UIImageView(image: Asset.Scene.Welcome.Illustration.elephantOnAirplaneWithContrail.image)
imageView.translatesAutoresizingMaskIntoConstraints = false
@ -69,7 +61,7 @@ final class WelcomeIllustrationView: UIView {
let imageView = UIImageView(image: Asset.Scene.Welcome.Illustration.cloudBase.image)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
imageView.alpha = 0.3
// imageView.alpha = 0.3
return imageView
}()
@ -97,57 +89,44 @@ extension WelcomeIllustrationView {
cloudBaseImageView.translatesAutoresizingMaskIntoConstraints = false
addSubview(cloudBaseImageView)
let cloudsLeftAnchor = cloudBaseImageView.leftAnchor.constraint(equalTo: leftAnchor, constant: .cloudsStartPosition)
NSLayoutConstraint.activate([
cloudsLeftAnchor,
cloudBaseImageView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 1.4),
cloudBaseImageView.leftAnchor.constraint(equalTo: leftAnchor),
cloudBaseImageView.trailingAnchor.constraint(equalTo: trailingAnchor),
cloudBaseImageView.bottomAnchor.constraint(equalTo: bottomAnchor),
cloudBaseImageView.topAnchor.constraint(equalTo: topAnchor)
])
self.cloudsLeftAnchor = cloudsLeftAnchor
let rightHillRightConstraint = rightAnchor.constraint(equalTo: rightHillImageView.rightAnchor, constant: .rightHillStartPosition)
addSubview(rightHillImageView)
NSLayoutConstraint.activate([
rightHillImageView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.64),
rightHillRightConstraint,
rightAnchor.constraint(equalTo: rightHillImageView.rightAnchor, constant: .rightHillStartPosition),
bottomAnchor.constraint(equalTo: rightHillImageView.bottomAnchor, constant: 149),
])
self.rightHillRightConstraint = rightHillRightConstraint
let leftHillLeftConstraint = leftAnchor.constraint(equalTo: leftHillImageView.leftAnchor, constant: .leftHillStartPosition)
addSubview(leftHillImageView)
NSLayoutConstraint.activate([
leftHillImageView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.72),
leftHillLeftConstraint,
leftAnchor.constraint(equalTo: leftHillImageView.leftAnchor, constant: .leftHillStartPosition),
bottomAnchor.constraint(equalTo: leftHillImageView.bottomAnchor, constant: 187),
])
self.leftHillLeftConstraint = leftHillLeftConstraint
let centerHillLeftConstraint = leftAnchor.constraint(equalTo: centerHillImageView.leftAnchor, constant: .centerHillStartPosition)
addSubview(elephantOnAirplaneWithContrailImageView)
addSubview(centerHillImageView)
NSLayoutConstraint.activate([
centerHillLeftConstraint,
bottomAnchor.constraint(equalTo: centerHillImageView.bottomAnchor, constant: -18),
centerHillImageView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 1.2),
leftAnchor.constraint(equalTo: centerHillImageView.leftAnchor),
bottomAnchor.constraint(equalTo: centerHillImageView.bottomAnchor),
rightAnchor.constraint(equalTo: centerHillImageView.rightAnchor),
])
self.centerHillLeftConstraint = centerHillLeftConstraint
addSubview(elephantOnAirplaneWithContrailImageView)
let elephantOnAirplaneLeftConstraint = elephantOnAirplaneWithContrailImageView.leftAnchor.constraint(equalTo: leftAnchor, constant: .airplaneStartPosition) // add 12pt bleeding
NSLayoutConstraint.activate([
elephantOnAirplaneLeftConstraint,
elephantOnAirplaneWithContrailImageView.leftAnchor.constraint(equalTo: leftAnchor, constant: .airplaneStartPosition),
elephantOnAirplaneWithContrailImageView.bottomAnchor.constraint(equalTo: leftHillImageView.topAnchor),
// make a little bit large
elephantOnAirplaneWithContrailImageView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.84),
])
self.elephantOnAirplaneLeftConstraint = elephantOnAirplaneLeftConstraint
}
func setup() {
@ -175,12 +154,4 @@ extension WelcomeIllustrationView {
UIInterpolatingMotionEffect.motionEffect(minX: -20, maxX: 12, minY: -20, maxY: 12) // maxX should not larger then the bleeding (12pt)
)
}
func update(contentOffset: CGFloat) {
cloudsLeftAnchor?.constant = contentOffset / cloudsDrag + .cloudsStartPosition
elephantOnAirplaneLeftConstraint?.constant = contentOffset / airplaneDrag + .airplaneStartPosition
leftHillLeftConstraint?.constant = contentOffset / .leftHillSpeed + .leftHillStartPosition
centerHillLeftConstraint?.constant = contentOffset / .centerHillSpeed + .centerHillStartPosition
rightHillRightConstraint?.constant = contentOffset / .rightHillSpeed + .rightHillStartPosition
}
}

View File

@ -0,0 +1,60 @@
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
import UIKit
import MastodonLocalization
class WelcomeSeparatorView: UIView {
let leftLine: UIView
let rightLine: UIView
let label: UILabel
override init(frame: CGRect) {
leftLine = UIView()
leftLine.translatesAutoresizingMaskIntoConstraints = false
leftLine.backgroundColor = UIColor.white.withAlphaComponent(0.6)
rightLine = UIView()
rightLine.translatesAutoresizingMaskIntoConstraints = false
rightLine.backgroundColor = UIColor.white.withAlphaComponent(0.6)
label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.adjustsFontForContentSizeCategory = true
label.text = L10n.Scene.Welcome.Separator.or.uppercased()
label.font = UIFontMetrics(forTextStyle: .footnote).scaledFont(for: .systemFont(ofSize: 13, weight: .semibold))
label.textColor = UIColor.white.withAlphaComponent(0.6)
super.init(frame: frame)
addSubview(leftLine)
addSubview(label)
addSubview(rightLine)
setupConstraints()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupConstraints() {
let constraints = [
label.topAnchor.constraint(equalTo: topAnchor),
bottomAnchor.constraint(equalTo: label.bottomAnchor),
label.centerXAnchor.constraint(equalTo: centerXAnchor),
label.centerYAnchor.constraint(equalTo: centerYAnchor),
leftLine.leadingAnchor.constraint(equalTo: leadingAnchor),
label.leadingAnchor.constraint(equalTo: leftLine.trailingAnchor, constant: 8),
leftLine.centerYAnchor.constraint(equalTo: centerYAnchor),
rightLine.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: 8),
trailingAnchor.constraint(equalTo: rightLine.trailingAnchor),
rightLine.centerYAnchor.constraint(equalTo: centerYAnchor),
leftLine.heightAnchor.constraint(equalToConstant: 1),
rightLine.heightAnchor.constraint(equalTo: leftLine.heightAnchor),
]
NSLayoutConstraint.activate(constraints)
}
}

View File

@ -10,6 +10,7 @@ import Combine
import MastodonAsset
import MastodonCore
import MastodonLocalization
import MastodonSDK
final class WelcomeViewController: UIViewController, NeedsDependency {
@ -19,68 +20,111 @@ final class WelcomeViewController: UIViewController, NeedsDependency {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
private(set) lazy var authenticationViewModel = AuthenticationViewModel(
context: context,
coordinator: coordinator,
isAuthenticationExist: false
)
var disposeBag = Set<AnyCancellable>()
var observations = Set<NSKeyValueObservation>()
private(set) lazy var viewModel = WelcomeViewModel(context: context)
let welcomeIllustrationView = WelcomeIllustrationView()
let separatorView = WelcomeSeparatorView(frame: .zero)
private(set) lazy var mastodonLogo: UIImageView = {
let imageView = UIImageView(image: Asset.Scene.Welcome.mastodonLogo.image)
return imageView
}()
//TODO: Extract all those UI-elements in a UIView-subclass
private(set) lazy var dismissBarButtonItem = UIBarButtonItem(barButtonSystemItem: .close, target: self, action: #selector(WelcomeViewController.dismissBarButtonItemDidPressed(_:)))
let buttonContainer = UIStackView()
let educationPages: [WelcomeContentPage] = [.whatIsMastodon, .mastodonIsLikeThat, .howDoIPickAServer]
private(set) lazy var signUpButton: PrimaryActionButton = {
let button = PrimaryActionButton()
button.adjustsBackgroundImageWhenUserInterfaceStyleChanges = false
button.contentEdgeInsets = WelcomeViewController.actionButtonPadding
button.titleLabel?.adjustsFontForContentSizeCategory = true
button.titleLabel?.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold))
button.setTitle(L10n.Common.Controls.Actions.signUp, for: .normal)
let backgroundImageColor: UIColor = .white
let backgroundImageHighlightedColor: UIColor = UIColor(white: 0.8, alpha: 1.0)
button.setBackgroundImage(.placeholder(color: backgroundImageColor), for: .normal)
button.setBackgroundImage(.placeholder(color: backgroundImageHighlightedColor), for: .highlighted)
button.setTitleColor(.black, for: .normal)
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
buttonConfiguration.activityIndicatorColorTransformer = UIConfigurationColorTransformer({ _ in
return UIColor.white
})
buttonConfiguration.contentInsets = .init(top: WelcomeViewController.actionButtonPadding.top,
leading: WelcomeViewController.actionButtonPadding.left,
bottom: WelcomeViewController.actionButtonPadding.bottom,
trailing: WelcomeViewController.actionButtonPadding.right)
let button = UIButton(configuration: buttonConfiguration)
return button
}()
let signUpButtonShadowView = UIView()
private(set) lazy var signUpButton: UIButton = {
var buttonConfiguration = UIButton.Configuration.borderedTinted()
buttonConfiguration.attributedTitle = AttributedString(
L10n.Scene.Welcome.pickServer,
attributes: .init([.font: UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold))])
)
buttonConfiguration.background.cornerRadius = 14
buttonConfiguration.background.strokeColor = UIColor.white.withAlphaComponent(0.6)
buttonConfiguration.background.strokeWidth = 1
buttonConfiguration.baseBackgroundColor = .clear
buttonConfiguration.baseForegroundColor = .white
buttonConfiguration.contentInsets = .init(top: WelcomeViewController.actionButtonPadding.top,
leading: WelcomeViewController.actionButtonPadding.left,
bottom: WelcomeViewController.actionButtonPadding.bottom,
trailing: WelcomeViewController.actionButtonPadding.right)
let button = UIButton(configuration: buttonConfiguration)
return button
}()
private(set) lazy var signInButton: UIButton = {
let button = UIButton()
button.contentEdgeInsets = WelcomeViewController.actionButtonPadding
button.titleLabel?.adjustsFontForContentSizeCategory = true
button.titleLabel?.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold))
button.setTitle(L10n.Scene.Welcome.logIn, for: .normal)
let titleColor: UIColor = UIColor.white.withAlphaComponent(0.9)
button.setTitleColor(titleColor, for: .normal)
button.setTitleColor(titleColor.withAlphaComponent(0.3), for: .highlighted)
var buttonConfiguration = UIButton.Configuration.plain()
buttonConfiguration.baseForegroundColor = .white
buttonConfiguration.attributedTitle = AttributedString(
L10n.Scene.Welcome.logIn,
attributes: .init([.font: UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold))])
)
let button = UIButton(configuration: buttonConfiguration)
return button
}()
private(set) lazy var pageCollectionView: UICollectionView = {
let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = .horizontal
flowLayout.minimumLineSpacing = 0
flowLayout.itemSize = CGSize(width: self.view.frame.width, height: 400)
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.isPagingEnabled = true
collectionView.backgroundColor = nil
collectionView.showsHorizontalScrollIndicator = false
collectionView.bounces = false
collectionView.register(WelcomeContentCollectionViewCell.self, forCellWithReuseIdentifier: WelcomeContentCollectionViewCell.identifier)
private(set) lazy var learnMoreButton: UIButton = {
var buttonConfiguration = UIButton.Configuration.plain()
buttonConfiguration.baseForegroundColor = .white
buttonConfiguration.attributedTitle = AttributedString(
L10n.Scene.Welcome.learnMore,
attributes: .init([.font: UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold))])
)
return collectionView
let button = UIButton(configuration: buttonConfiguration)
return button
}()
private(set) var pageControl: UIPageControl = {
let pageControl = UIPageControl(frame: .zero)
pageControl.backgroundStyle = .prominent
pageControl.translatesAutoresizingMaskIntoConstraints = false
return pageControl
private(set) lazy var bottomButtonStackView: UIStackView = {
let bottomButtonStackView = UIStackView(arrangedSubviews: [learnMoreButton, signInButton])
bottomButtonStackView.axis = .horizontal
bottomButtonStackView.distribution = .fill
bottomButtonStackView.alignment = .center
bottomButtonStackView.spacing = 16
bottomButtonStackView.setContentHuggingPriority(.required, for: .vertical)
return bottomButtonStackView
}()
}
@ -98,15 +142,17 @@ extension WelcomeViewController {
view.overrideUserInterfaceStyle = .light
setupOnboardingAppearance()
view.addSubview(welcomeIllustrationView)
welcomeIllustrationView.translatesAutoresizingMaskIntoConstraints = false
mastodonLogo.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(mastodonLogo)
NSLayoutConstraint.activate([
welcomeIllustrationView.topAnchor.constraint(equalTo: view.topAnchor),
welcomeIllustrationView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
view.trailingAnchor.constraint(equalTo: welcomeIllustrationView.trailingAnchor),
view.bottomAnchor.constraint(equalTo: welcomeIllustrationView.bottomAnchor)
mastodonLogo.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 24),
mastodonLogo.centerXAnchor.constraint(equalTo: view.centerXAnchor),
mastodonLogo.widthAnchor.constraint(equalToConstant: 300),
])
buttonContainer.axis = .vertical
@ -120,48 +166,47 @@ extension WelcomeViewController {
buttonContainer.trailingAnchor.constraint(equalTo: view.readableContentGuide.trailingAnchor),
view.layoutMarginsGuide.bottomAnchor.constraint(equalTo: buttonContainer.bottomAnchor),
])
joinDefaultServerButton.translatesAutoresizingMaskIntoConstraints = false
buttonContainer.addArrangedSubview(joinDefaultServerButton)
NSLayoutConstraint.activate([
joinDefaultServerButton.heightAnchor.constraint(greaterThanOrEqualToConstant: WelcomeViewController.actionButtonHeight)
])
signUpButton.translatesAutoresizingMaskIntoConstraints = false
buttonContainer.addArrangedSubview(signUpButton)
NSLayoutConstraint.activate([
signUpButton.heightAnchor.constraint(greaterThanOrEqualToConstant: WelcomeViewController.actionButtonHeight).priority(.required - 1),
signUpButton.heightAnchor.constraint(greaterThanOrEqualToConstant: WelcomeViewController.actionButtonHeight)
])
buttonContainer.addArrangedSubview(separatorView)
signInButton.translatesAutoresizingMaskIntoConstraints = false
buttonContainer.addArrangedSubview(signInButton)
NSLayoutConstraint.activate([
signInButton.heightAnchor.constraint(greaterThanOrEqualToConstant: WelcomeViewController.actionButtonHeight).priority(.required - 1),
])
signUpButtonShadowView.translatesAutoresizingMaskIntoConstraints = false
buttonContainer.addSubview(signUpButtonShadowView)
buttonContainer.sendSubviewToBack(signUpButtonShadowView)
signUpButtonShadowView.pinTo(to: signUpButton)
signUpButton.addTarget(self, action: #selector(signUpButtonDidClicked(_:)), for: .touchUpInside)
signInButton.addTarget(self, action: #selector(signInButtonDidClicked(_:)), for: .touchUpInside)
pageCollectionView.delegate = self
pageCollectionView.dataSource = self
view.addSubview(pageCollectionView)
pageControl.numberOfPages = self.educationPages.count
pageControl.addTarget(self, action: #selector(WelcomeViewController.pageControlDidChange(_:)), for: .valueChanged)
view.addSubview(pageControl)
let scrollView = pageCollectionView as UIScrollView
scrollView.delegate = self
NSLayoutConstraint.activate([
pageCollectionView.topAnchor.constraint(equalTo: view.topAnchor),
pageCollectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
view.trailingAnchor.constraint(equalTo: pageCollectionView.trailingAnchor),
pageControl.topAnchor.constraint(equalTo: pageCollectionView.bottomAnchor, constant: 16),
pageControl.leadingAnchor.constraint(equalTo: view.leadingAnchor),
view.trailingAnchor.constraint(equalTo: pageControl.trailingAnchor),
buttonContainer.topAnchor.constraint(equalTo: pageControl.bottomAnchor, constant: 16),
signInButton.heightAnchor.constraint(greaterThanOrEqualToConstant: WelcomeViewController.actionButtonHeight)
])
learnMoreButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
learnMoreButton.heightAnchor.constraint(greaterThanOrEqualToConstant: WelcomeViewController.actionButtonHeight),
bottomButtonStackView.heightAnchor.constraint(equalTo: learnMoreButton.heightAnchor),
])
buttonContainer.addArrangedSubview(bottomButtonStackView)
NSLayoutConstraint.activate([
welcomeIllustrationView.topAnchor.constraint(equalTo: view.topAnchor),
welcomeIllustrationView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
view.trailingAnchor.constraint(equalTo: welcomeIllustrationView.trailingAnchor),
separatorView.centerYAnchor.constraint(equalTo: welcomeIllustrationView.bottomAnchor)
])
joinDefaultServerButton.addTarget(self, action: #selector(joinDefaultServer(_:)), for: .touchUpInside)
signUpButton.addTarget(self, action: #selector(signUp(_:)), for: .touchUpInside)
signInButton.addTarget(self, action: #selector(signIn(_:)), for: .touchUpInside)
learnMoreButton.addTarget(self, action: #selector(learnMore(_:)), for: .touchUpInside)
view.backgroundColor = Asset.Scene.Welcome.Illustration.backgroundGreen.color
viewModel.$needsShowDismissEntry
.receive(on: DispatchQueue.main)
@ -172,12 +217,7 @@ extension WelcomeViewController {
.store(in: &disposeBag)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
setupButtonShadowView()
}
override func viewSafeAreaInsetsDidChange() {
super.viewSafeAreaInsetsDidChange()
@ -194,14 +234,6 @@ extension WelcomeViewController {
view.layoutIfNeeded()
setupIllustrationLayout()
setupButtonShadowView()
let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = .horizontal
flowLayout.minimumLineSpacing = 0
flowLayout.itemSize = CGSize(width: self.view.frame.width, height: 400)
pageCollectionView.setCollectionViewLayout(flowLayout, animated: true)
}
private var computedTopAnchorInset: CGFloat {
@ -210,28 +242,14 @@ extension WelcomeViewController {
}
extension WelcomeViewController {
private func setupButtonShadowView() {
signUpButtonShadowView.layer.setupShadow(
color: .black,
alpha: 0.25,
x: 0,
y: 1,
blur: 2,
spread: 0,
roundedRect: signUpButtonShadowView.bounds,
byRoundingCorners: .allCorners,
cornerRadii: CGSize(width: 10, height: 10)
)
}
private func updateButtonContainerLayoutMargins(traitCollection: UITraitCollection) {
switch traitCollection.userInterfaceIdiom {
case .phone:
buttonContainer.layoutMargins = UIEdgeInsets(
top: 0,
left: WelcomeViewController.actionButtonMargin,
bottom: WelcomeViewController.viewBottomPaddingHeight,
bottom: 0,
right: WelcomeViewController.actionButtonMargin
)
default:
@ -239,7 +257,7 @@ extension WelcomeViewController {
buttonContainer.layoutMargins = UIEdgeInsets(
top: 0,
left: margin,
bottom: WelcomeViewController.viewBottomPaddingHeightExtend,
bottom: 0,
right: margin
)
}
@ -254,27 +272,132 @@ extension WelcomeViewController {
//MARK: - Actions
@objc
private func signUpButtonDidClicked(_ sender: UIButton) {
private func joinDefaultServer(_ sender: UIButton) {
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)
.compactMap { [weak self] response -> AnyPublisher<MastodonPickServerViewModel.SignUpResponseFirst, Error>? in
guard let self = self else { return nil }
guard response.value.registrations != false else {
return Fail(error: AuthenticationViewModel.AuthenticationError.registrationClosed).eraseToAnyPublisher()
}
return self.context.apiService.createApplication(domain: server.domain)
.map { MastodonPickServerViewModel.SignUpResponseFirst(instance: response, application: $0) }
.eraseToAnyPublisher()
}
.switchToLatest()
.tryMap { response -> MastodonPickServerViewModel.SignUpResponseSecond in
let application = response.application.value
guard let authenticateInfo = AuthenticationViewModel.AuthenticateInfo(
domain: server.domain,
application: application
) else {
throw APIService.APIError.explicit(.badResponse)
}
return MastodonPickServerViewModel.SignUpResponseSecond(
instance: response.instance,
authenticateInfo: authenticateInfo
)
}
.compactMap { [weak self] response -> AnyPublisher<MastodonPickServerViewModel.SignUpResponseThird, Error>? in
guard let self = self else { return nil }
let instance = response.instance
let authenticateInfo = response.authenticateInfo
return self.context.apiService.applicationAccessToken(
domain: server.domain,
clientID: authenticateInfo.clientID,
clientSecret: authenticateInfo.clientSecret,
redirectURI: authenticateInfo.redirectURI
)
.map {
MastodonPickServerViewModel.SignUpResponseThird(
instance: instance,
authenticateInfo: authenticateInfo,
applicationToken: $0
)
}
.eraseToAnyPublisher()
}
.switchToLatest()
.receive(on: DispatchQueue.main)
.sink { [weak self] completion in
guard let self = self else { return }
self.authenticationViewModel.isAuthenticating.send(false)
switch completion {
case .failure(let error):
//TODO: show an alert or something
break
case .finished:
break
}
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 {
// show server rules before register
let mastodonServerRulesViewModel = MastodonServerRulesViewModel(
domain: server.domain,
authenticateInfo: response.authenticateInfo,
rules: rules,
instance: response.instance.value,
applicationToken: response.applicationToken.value
)
_ = self.coordinator.present(scene: .mastodonServerRules(viewModel: mastodonServerRulesViewModel), from: self, transition: .show)
} else {
let mastodonRegisterViewModel = MastodonRegisterViewModel(
context: self.context,
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)
}
@objc
private func signUp(_ sender: UIButton) {
_ = coordinator.present(scene: .mastodonPickServer(viewMode: MastodonPickServerViewModel(context: context)), from: self, transition: .show)
}
@objc
private func signInButtonDidClicked(_ sender: UIButton) {
private func signIn(_ sender: UIButton) {
_ = coordinator.present(scene: .mastodonLogin, from: self, transition: .show)
}
@objc
private func learnMore(_ sender: UIButton) {
let educationViewController = EducationViewController()
educationViewController.modalPresentationStyle = .pageSheet
if let sheetPresentationController = educationViewController.sheetPresentationController {
sheetPresentationController.detents = [.medium()]
sheetPresentationController.prefersGrabberVisible = true
}
present(educationViewController, animated: true)
}
@objc
private func dismissBarButtonItemDidPressed(_ sender: UIButton) {
dismiss(animated: true, completion: nil)
}
@objc
private func pageControlDidChange(_ sender: UIPageControl) {
let item = sender.currentPage
let indexPath = IndexPath(item: item, section: 0)
pageCollectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: false)
}
}
// MARK: - OnboardingViewControllerAppearance
@ -316,37 +439,5 @@ extension WelcomeViewController: UIAdaptivePresentationControllerDelegate {
}
}
//MARK: - UIScrollViewDelegate
extension WelcomeViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let contentOffset = scrollView.contentOffset.x
welcomeIllustrationView.update(contentOffset: contentOffset)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
pageControl.currentPage = Int(scrollView.contentOffset.x) / Int(scrollView.frame.width)
}
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
pageControl.currentPage = Int(scrollView.contentOffset.x) / Int(scrollView.frame.width)
}
}
//MARK: - UICollectionViewDelegate
extension WelcomeViewController: UICollectionViewDelegate { }
//MARK: - UICollectionViewDataSource
extension WelcomeViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
educationPages.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: WelcomeContentCollectionViewCell.identifier, for: indexPath) as? WelcomeContentCollectionViewCell else { fatalError("WTF? Wrong cell?") }
let page = educationPages[indexPath.item]
cell.update(with: page)
return cell
}
}

View File

@ -5,9 +5,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.506",
"green" : "0.675",
"red" : "0.345"
"blue" : "0.416",
"green" : "0.557",
"red" : "0.278"
}
},
"idiom" : "universal"

View File

@ -1,17 +1,17 @@
{
"images" : [
{
"filename" : "untitled10007Group61.png",
"filename" : "Untitled-1_0007_Group-6 1.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "untitled10007Group61@2x.png",
"filename" : "Untitled-1_0007_Group-6 1@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "untitled10007Group61@3x.png",
"filename" : "Untitled-1_0007_Group-6 1@3x.png",
"idiom" : "universal",
"scale" : "3x"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -1,17 +1,17 @@
{
"images" : [
{
"filename" : "untitled10003Group11.png",
"filename" : "Untitled-1_0003_Group-1 1.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "untitled10003Group11@2x.png",
"filename" : "Untitled-1_0003_Group-1 1@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "untitled10003Group11@3x.png",
"filename" : "Untitled-1_0003_Group-1 1@3x.png",
"idiom" : "universal",
"scale" : "3x"
}

View File

@ -1,19 +1,8 @@
{
"images" : [
{
"filename" : "wordmark-white-text.01e9f493 1.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "wordmark-white-text.01e9f493 1@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "wordmark-white-text.01e9f493 1@3x.png",
"idiom" : "universal",
"scale" : "3x"
"filename" : "mastodon_logo.pdf",
"idiom" : "universal"
}
],
"info" : {

View File

@ -0,0 +1,384 @@
%PDF-1.7
1 0 obj
<< /Length 2 0 R
/Range [ 0.000000 1.000000 0.000000 1.000000 0.000000 1.000000 ]
/Domain [ 0.000000 1.000000 ]
/FunctionType 4
>>
stream
{ 0.388235 exch 0.392157 exch 1.000000 exch dup 0.000000 gt { exch pop exch pop exch pop dup 0.000000 sub -0.050980 mul 0.388235 add exch dup 0.000000 sub -0.164706 mul 0.392157 add exch dup 0.000000 sub -0.200000 mul 1.000000 add exch } if dup 1.000000 gt { exch pop exch pop exch pop 0.337255 exch 0.227451 exch 0.800000 exch } if pop }
endstream
endobj
2 0 obj
339
endobj
3 0 obj
<< /Type /XObject
/Length 4 0 R
/Group << /Type /Group
/S /Transparency
>>
/Subtype /Form
/Resources << /Pattern << /P1 << /Matrix [ 0.000000 -75.233063 75.233063 0.000000 -75.233063 76.115967 ]
/Shading << /Coords [ 0.000000 0.000000 1.000000 0.000000 ]
/ColorSpace /DeviceRGB
/Function 1 0 R
/Domain [ 0.000000 1.000000 ]
/ShadingType 2
/Extend [ true true ]
>>
/PatternType 2
/Type /Pattern
>> >> >>
/BBox [ 0.000000 0.000000 299.000000 77.000000 ]
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.200195 cm
/Pattern cs
/P1 scn
69.682762 59.260414 m
68.604645 67.339211 61.618645 73.716652 53.348095 74.943527 c
51.948719 75.151863 46.660526 75.915771 34.409454 75.915771 c
34.317654 75.915771 l
22.055214 75.915771 19.428312 75.151863 18.028839 74.943527 c
9.976187 73.739799 2.634763 68.022095 0.845287 59.839058 c
-0.003568 55.811195 -0.095337 51.343483 0.065258 47.246231 c
0.294677 41.366470 0.340562 35.509907 0.868228 29.653252 c
1.235300 25.764275 1.866209 21.909996 2.772418 18.113708 c
4.470126 11.099670 11.329807 5.266113 18.051863 2.893471 c
25.244102 0.416542 32.987148 -0.000023 40.397285 1.701393 c
41.211750 1.898071 42.014755 2.117950 42.817757 2.372620 c
44.618729 2.951355 46.729305 3.599487 48.289360 4.733765 c
48.312382 4.745361 48.323845 4.768463 48.335308 4.791656 c
48.346771 4.814758 48.358234 4.837959 48.358234 4.872749 c
48.358234 10.544128 l
48.358234 10.544128 48.358234 10.590431 48.335308 10.613525 c
48.335308 10.636627 48.312382 10.659821 48.289360 10.671417 c
48.266434 10.682922 48.243507 10.694611 48.220676 10.706116 c
48.197556 10.706116 48.174725 10.706116 48.151798 10.706116 c
43.402664 9.560242 38.527630 8.981506 33.652401 8.993103 c
25.244101 8.993103 22.984310 13.021061 22.341986 14.687683 c
21.825758 16.134426 21.493132 17.650753 21.355478 19.178490 c
21.355478 19.201687 21.355476 19.224789 21.366940 19.247982 c
21.366940 19.271084 21.389868 19.294277 21.412889 19.305878 c
21.435720 19.317379 21.458647 19.328976 21.481573 19.340572 c
21.561913 19.340572 l
26.230612 18.206295 31.025501 17.627560 35.831856 17.627560 c
36.990505 17.627560 38.137497 17.627563 39.296146 17.662258 c
44.125427 17.801239 49.218647 18.044308 53.979053 18.981712 c
54.093685 19.004910 54.219971 19.028008 54.323139 19.051205 c
61.825268 20.509544 68.960197 25.069847 69.682762 36.620991 c
69.705879 37.072342 69.774658 41.389572 69.774658 41.852524 c
69.774658 43.461441 70.290794 53.230080 69.694321 59.237125 c
69.682762 59.260414 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 14.151680 50.687012 cm
1.000000 1.000000 1.000000 scn
0.000000 4.259313 m
0.000000 6.620456 1.869849 8.518555 4.186955 8.518555 c
6.504158 8.518555 8.373816 6.608859 8.373816 4.259313 c
8.373816 1.909767 6.504158 -0.000021 4.186955 -0.000021 c
1.869849 -0.000021 0.000000 1.909767 0.000000 4.259313 c
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 32.677444 31.369507 cm
1.000000 1.000000 1.000000 scn
43.761944 19.907759 m
43.761944 0.324093 l
36.133713 0.324093 l
36.133713 19.329117 l
36.133713 23.333782 34.481960 25.359358 31.166786 25.359358 c
27.507523 25.359358 25.660692 22.940228 25.660692 18.183340 c
25.660692 7.777977 l
18.089882 7.777977 l
18.089882 18.183340 l
18.089882 22.963518 16.265982 25.359358 12.583696 25.359358 c
9.280177 25.359358 7.616762 23.333782 7.616762 19.329117 c
7.616762 0.335594 l
0.000000 0.335594 l
0.000000 19.907759 l
0.000000 23.901016 0.997971 27.083967 3.005379 29.445015 c
5.081661 31.806253 7.800268 32.998425 11.161296 32.998425 c
15.061478 32.998425 18.021006 31.470591 19.971098 28.415022 c
21.875336 25.174082 l
23.779476 28.415022 l
25.729471 31.458992 28.677633 32.998425 32.589184 32.998425 c
35.950211 32.998425 38.668812 31.794561 40.745094 29.445015 c
42.752502 27.083967 43.750477 23.924116 43.750477 19.907759 c
43.761944 19.907759 l
h
70.007538 10.173912 m
71.590424 11.875328 72.336494 13.981800 72.336494 16.551319 c
72.336494 19.120838 71.578964 21.250410 70.007538 22.870832 c
68.493439 24.572248 66.566658 25.382553 64.237709 25.382553 c
61.909042 25.382553 59.993439 24.572248 58.467869 22.870832 c
56.953671 21.250410 56.196613 19.120838 56.196613 16.551319 c
56.196613 13.981800 56.953671 11.852131 58.467869 10.173912 c
59.981976 8.553490 61.909042 7.731586 64.237709 7.731586 c
66.566658 7.731586 68.481972 8.541893 70.007538 10.173912 c
h
72.336494 32.211311 m
79.849709 32.211311 l
79.849709 0.891232 l
72.336494 0.891232 l
72.336494 4.583427 l
70.064857 1.516258 66.922012 0.000027 62.838326 0.000027 c
58.754635 0.000027 55.611507 1.562557 52.824028 4.757107 c
50.082493 7.951561 48.694580 11.898426 48.694580 16.528124 c
48.694580 21.157915 50.093956 25.046890 52.824028 28.241344 c
55.622974 31.435799 58.949608 33.056221 62.838326 33.056221 c
66.727142 33.056221 70.064857 31.551582 72.336494 28.495920 c
72.336494 32.188118 l
72.336494 32.211311 l
h
105.131912 17.141556 m
107.346237 15.440237 108.447670 13.067402 108.390358 10.069630 c
108.390358 6.875175 107.288925 4.363552 105.017288 2.627438 c
102.746605 0.925930 100.004028 0.057922 96.677773 0.057922 c
90.678665 0.057922 86.606346 2.569450 84.449348 7.500206 c
90.966202 11.446980 l
91.836449 8.761768 93.752731 7.372826 96.677773 7.372826 c
99.362083 7.372826 100.692780 8.240929 100.692780 10.058128 c
100.692780 11.377586 98.948448 12.569756 95.392929 13.495655 c
94.050774 13.866112 92.938835 14.248070 92.078140 14.560537 c
90.849655 15.058186 89.806503 15.625322 88.934341 16.319748 c
86.778290 18.021162 85.677818 20.266710 85.677818 23.148697 c
85.677818 26.215769 86.720978 28.657999 88.819710 30.417307 c
90.977669 32.234413 93.602745 33.102612 96.746544 33.102612 c
101.758850 33.102612 105.418510 30.915051 107.816238 26.470440 c
101.414963 22.720444 l
100.486435 24.850111 98.902588 25.914900 96.746544 25.914900 c
94.474907 25.914900 93.373489 25.046890 93.373489 23.345476 c
93.373489 22.025925 95.117821 20.833754 98.673340 19.907759 c
101.414970 19.282822 103.570999 18.345324 105.131912 17.141556 c
h
129.014664 24.456558 m
122.429970 24.456558 l
122.429970 11.423878 l
122.429970 9.861351 123.015564 8.912251 124.127495 8.483906 c
124.942345 8.171436 126.571075 8.113640 129.026123 8.229328 c
129.026123 0.902828 l
123.967003 0.277798 120.296860 0.787136 118.140816 2.465355 c
115.983810 4.085777 114.939697 7.106651 114.939697 11.412281 c
114.939697 24.456558 l
109.880577 24.456558 l
109.880577 32.222908 l
114.939697 32.222908 l
114.939697 38.542469 l
122.452904 40.984653 l
122.452904 32.211311 l
129.037598 32.211311 l
129.037598 24.444960 l
129.026123 24.444960 l
129.014664 24.456558 l
h
152.966187 10.358997 m
154.480286 11.979420 155.236862 14.051291 155.236862 16.562822 c
155.236862 19.074543 154.480286 21.123121 152.966187 22.766739 c
151.439667 24.387066 149.581665 25.208876 147.310974 25.208876 c
145.039337 25.208876 143.181335 24.398664 141.654816 22.766739 c
140.198975 21.065325 139.441452 19.016554 139.441452 16.562822 c
139.441452 14.109184 140.198975 12.060410 141.654816 10.358997 c
143.169876 8.738670 145.039337 7.916862 147.310974 7.916862 c
149.581665 7.916862 151.439667 8.726978 152.966187 10.358997 c
h
136.368347 4.791801 m
133.396500 7.986256 131.939697 11.863636 131.939697 16.562822 c
131.939697 21.262102 133.396500 25.081491 136.368347 28.276041 c
139.338287 31.470591 143.009384 33.091015 147.310974 33.091015 c
151.611618 33.091015 155.295135 31.470591 158.254562 28.276041 c
161.213989 25.081491 162.739563 21.134720 162.739563 16.562822 c
162.739563 11.991016 161.213989 7.986256 158.254562 4.791801 c
155.282715 1.597252 151.669876 0.034821 147.310974 0.034821 c
142.952072 0.034821 139.326813 1.597252 136.368347 4.791801 c
h
187.860336 10.185417 m
189.374451 11.886829 190.131989 13.993303 190.131989 16.562822 c
190.131989 19.132339 189.374451 21.262102 187.860336 22.882429 c
186.347183 24.583939 184.420410 25.394054 182.090500 25.394054 c
179.762512 25.394054 177.834778 24.583939 176.264313 22.882429 c
174.749252 21.262102 173.992661 19.132339 173.992661 16.562822 c
173.992661 13.993303 174.749252 11.863636 176.264313 10.185417 c
177.846237 8.564995 179.819824 7.743279 182.090500 7.743279 c
184.363098 7.743279 186.335724 8.553490 187.860336 10.185417 c
h
190.131989 44.757874 m
197.645187 44.757874 l
197.645187 0.902828 l
190.131989 0.902828 l
190.131989 4.595028 l
187.918625 1.527859 184.774811 0.011623 180.691986 0.011623 c
176.608200 0.011623 173.419510 1.574059 170.608139 4.768703 c
167.866516 7.963158 166.479446 11.909931 166.479446 16.539722 c
166.479446 21.169418 167.877975 25.058393 170.608139 28.252848 c
173.395615 31.447491 176.779190 33.067818 180.691986 33.067818 c
184.602859 33.067818 187.918625 31.563181 190.131989 28.507517 c
190.131989 44.746300 l
190.131989 44.757874 l
h
224.040298 10.393791 m
225.554413 12.014214 226.311920 14.085894 226.311920 16.597614 c
226.311920 19.109144 225.554413 21.157915 224.040298 22.801437 c
222.526199 24.421764 220.668182 25.243574 218.385086 25.243574 c
216.102936 25.243574 214.256409 24.433363 212.729889 22.801437 c
211.273102 21.099928 210.515564 19.051348 210.515564 16.597614 c
210.515564 14.143785 211.273102 12.095207 212.729889 10.393791 c
214.244949 8.773273 216.114395 7.951561 218.385086 7.951561 c
220.656723 7.951561 222.514725 8.761772 224.040298 10.393791 c
h
207.441498 4.826504 m
204.483017 8.021053 203.013824 11.898430 203.013824 16.597614 c
203.013824 21.296705 204.471558 25.116285 207.441498 28.310740 c
210.412384 31.505289 214.083496 33.125710 218.385086 33.125710 c
222.686661 33.125710 226.369232 31.505289 229.328659 28.310740 c
232.298599 25.116285 233.813675 21.169418 233.813675 16.597614 c
233.813675 12.025715 232.298599 8.021053 229.328659 4.826504 c
226.357773 1.632050 222.743988 0.069424 218.385086 0.069424 c
214.026184 0.069424 210.400925 1.632050 207.441498 4.826504 c
h
266.322540 20.174026 m
266.322540 0.937622 l
258.809326 0.937622 l
258.809326 19.167038 l
258.809326 21.238909 258.292511 22.801437 257.226440 23.981915 c
256.239655 25.046890 254.840164 25.602430 253.038528 25.602430 c
248.795212 25.602430 246.638229 23.032913 246.638229 17.836079 c
246.638229 0.925926 l
239.124985 0.925926 l
239.124985 32.222908 l
246.638229 32.222908 l
246.638229 28.704294 l
248.438904 31.644268 251.306610 33.091015 255.310150 33.091015 c
258.510315 33.091015 261.137299 31.968239 263.179688 29.653391 c
265.279358 27.338543 266.322540 24.201887 266.322540 20.139330 c
f
n
Q
endstream
endobj
4 0 obj
9975
endobj
5 0 obj
<< /Type /XObject
/Length 6 0 R
/Group << /Type /Group
/S /Transparency
>>
/Subtype /Form
/Resources << >>
/BBox [ 0.000000 0.000000 299.000000 77.000000 ]
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
0.000000 0.000000 0.000000 scn
0.000000 77.000000 m
299.000000 77.000000 l
299.000000 0.000000 l
0.000000 0.000000 l
0.000000 77.000000 l
h
f
n
Q
endstream
endobj
6 0 obj
234
endobj
7 0 obj
<< /XObject << /X1 3 0 R >>
/ExtGState << /E1 << /SMask << /Type /Mask
/G 5 0 R
/S /Alpha
>>
/Type /ExtGState
>> >>
>>
endobj
8 0 obj
<< /Length 9 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
/E1 gs
/X1 Do
Q
endstream
endobj
9 0 obj
46
endobj
10 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 299.000000 77.000000 ]
/Resources 7 0 R
/Contents 8 0 R
/Parent 11 0 R
>>
endobj
11 0 obj
<< /Kids [ 10 0 R ]
/Count 1
/Type /Pages
>>
endobj
12 0 obj
<< /Pages 11 0 R
/Type /Catalog
>>
endobj
xref
0 13
0000000000 65535 f
0000000010 00000 n
0000000533 00000 n
0000000555 00000 n
0000011521 00000 n
0000011544 00000 n
0000012027 00000 n
0000012049 00000 n
0000012347 00000 n
0000012449 00000 n
0000012470 00000 n
0000012646 00000 n
0000012722 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 12 0 R
/Size 13
>>
startxref
12783
%%EOF

View File

@ -205,6 +205,7 @@ public enum Asset {
public enum Welcome {
public enum Illustration {
public static let backgroundCyan = ColorAsset(name: "Scene/Welcome/illustration/background.cyan")
public static let backgroundGreen = ColorAsset(name: "Scene/Welcome/illustration/background.green")
public static let cloudBaseExtend = ImageAsset(name: "Scene/Welcome/illustration/cloud.base.extend")
public static let cloudBase = ImageAsset(name: "Scene/Welcome/illustration/cloud.base")
public static let elephantOnAirplaneWithContrail = ImageAsset(name: "Scene/Welcome/illustration/elephant.on.airplane.with.contrail")
@ -214,7 +215,6 @@ public enum Asset {
public static let elephantThreeOnGrassWithTreeTwo = ImageAsset(name: "Scene/Welcome/illustration/elephant.three.on.grass.with.tree.two")
}
public static let mastodonLogo = ImageAsset(name: "Scene/Welcome/mastodon.logo")
public static let signInButtonBackground = ColorAsset(name: "Scene/Welcome/sign.in.button.background")
}
}
public enum Settings {

View File

@ -41,7 +41,7 @@ extension APIService {
let managedObjectContext = self.backgroundManagedObjectContext
try await managedObjectContext.performChanges {
guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else {
assertionFailure()
assertionFailure()
return
}

View File

@ -136,6 +136,8 @@ public enum L10n {
public static let editPost = L10n.tr("Localizable", "Common.Controls.Actions.EditPost", fallback: "Edit")
/// Find people to follow
public static let findPeople = L10n.tr("Localizable", "Common.Controls.Actions.FindPeople", fallback: "Find people to follow")
/// Join mastodon.social
public static let joinDefaultServer = L10n.tr("Localizable", "Common.Controls.Actions.JoinDefaultServer", fallback: "Join mastodon.social")
/// Manually search instead
public static let manuallySearch = L10n.tr("Localizable", "Common.Controls.Actions.ManuallySearch", fallback: "Manually search instead")
/// Next
@ -178,8 +180,6 @@ public enum L10n {
}
/// Log in
public static let signIn = L10n.tr("Localizable", "Common.Controls.Actions.SignIn", fallback: "Log in")
/// Create Account
public static let signUp = L10n.tr("Localizable", "Common.Controls.Actions.SignUp", fallback: "Create Account")
/// Skip
public static let skip = L10n.tr("Localizable", "Common.Controls.Actions.Skip", fallback: "Skip")
/// Take Photo
@ -1494,8 +1494,14 @@ public enum L10n {
public enum Welcome {
/// Get Started
public static let getStarted = L10n.tr("Localizable", "Scene.Welcome.GetStarted", fallback: "Get Started")
/// Join mastodon.social
public static let joinDefaultServer = L10n.tr("Localizable", "Scene.Welcome.JoinDefault_server", fallback: "Join mastodon.social")
/// Learn more
public static let learnMore = L10n.tr("Localizable", "Scene.Welcome.LearnMore", fallback: "Learn more")
/// Log In
public static let logIn = L10n.tr("Localizable", "Scene.Welcome.LogIn", fallback: "Log In")
/// Pick my own server
public static let pickServer = L10n.tr("Localizable", "Scene.Welcome.PickServer", fallback: "Pick my own server")
/// Social networking
/// back in your hands.
public static let slogan = L10n.tr("Localizable", "Scene.Welcome.Slogan", fallback: "Social networking\nback in your hands.")
@ -1506,29 +1512,23 @@ public enum L10n {
public static let title = L10n.tr("Localizable", "Scene.Welcome.Education.A11Y.WhatIsMastodon.Title", fallback: "What is Mastodon?")
}
}
public enum HowDoIPickAServer {
/// Different people choose different servers for any number of reasons. art.example is a great place for artists, while glasgow.example might be a good pick for Scots.
///
/// You cant go wrong with any of our recommend servers, so regardless of which one you pick (or if you enter your own in the server search bar), youll never miss a beat anywhere.
public static let description = L10n.tr("Localizable", "Scene.Welcome.Education.HowDoIPickAServer.Description", fallback: "Different people choose different servers for any number of reasons. art.example is a great place for artists, while glasgow.example might be a good pick for Scots.\n\nYou cant go wrong with any of our recommend servers, so regardless of which one you pick (or if you enter your own in the server search bar), youll never miss a beat anywhere.")
/// How do I pick a server?
public static let title = L10n.tr("Localizable", "Scene.Welcome.Education.HowDoIPickAServer.Title", fallback: "How do I pick a server?")
public enum Mastodon {
/// Mastodon is a decentralized social network, meaning no single company controls it. Its made up of many independently-run servers, all connected together.
public static let description = L10n.tr("Localizable", "Scene.Welcome.Education.Mastodon.Description", fallback: "Mastodon is a decentralized social network, meaning no single company controls it. Its made up of many independently-run servers, all connected together.")
/// Welcome to Mastodon
public static let title = L10n.tr("Localizable", "Scene.Welcome.Education.Mastodon.Title", fallback: "Welcome to Mastodon")
}
public enum MastodonIsLikeThat {
/// Your handle might be @gothgirl654@example.social, but you can still follow, reblog, and chat with @fallout5ever@example.online.
public static let description = L10n.tr("Localizable", "Scene.Welcome.Education.MastodonIsLikeThat.Description", fallback: "Your handle might be @gothgirl654@example.social, but you can still follow, reblog, and chat with @fallout5ever@example.online.")
/// Mastodon is like that
public static let title = L10n.tr("Localizable", "Scene.Welcome.Education.MastodonIsLikeThat.Title", fallback: "Mastodon is like that")
}
public enum WhatIsMastodon {
/// Imagine you have an email address that ends with @example.com.
///
/// You can still send and receive emails from anyone, even if their email ends in @gmail.com or @icloud.com or @example.com.
public static let description = L10n.tr("Localizable", "Scene.Welcome.Education.WhatIsMastodon.Description", fallback: "Imagine you have an email address that ends with @example.com.\n\nYou can still send and receive emails from anyone, even if their email ends in @gmail.com or @icloud.com or @example.com.")
/// What is
public static let title = L10n.tr("Localizable", "Scene.Welcome.Education.WhatIsMastodon.Title", fallback: "What is")
public enum Servers {
/// 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.
public static let description = L10n.tr("Localizable", "Scene.Welcome.Education.Servers.Description", fallback: "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.")
/// What are servers?
public static let title = L10n.tr("Localizable", "Scene.Welcome.Education.Servers.Title", fallback: "What are servers?")
}
}
public enum Separator {
/// Or
public static let or = L10n.tr("Localizable", "Scene.Welcome.Separator.Or", fallback: "Or")
}
}
}
public enum Widget {

View File

@ -61,13 +61,13 @@ Please check your internet connection.";
"Common.Controls.Actions.SharePost" = "Share Post";
"Common.Controls.Actions.ShareUser" = "Share %@";
"Common.Controls.Actions.SignIn" = "Log in";
"Common.Controls.Actions.SignUp" = "Create Account";
"Common.Controls.Actions.Skip" = "Skip";
"Common.Controls.Actions.TakePhoto" = "Take Photo";
"Common.Controls.Actions.TranslatePost.Title" = "Translate from %@";
"Common.Controls.Actions.TranslatePost.UnknownLanguage" = "Unknown";
"Common.Controls.Actions.TryAgain" = "Try Again";
"Common.Controls.Actions.UnblockDomain" = "Unblock %@";
"Common.Controls.Actions.JoinDefaultServer" = "Join mastodon.social";
"Common.Controls.Friendship.Block" = "Block";
"Common.Controls.Friendship.BlockDomain" = "Block %@";
"Common.Controls.Friendship.BlockUser" = "Block %@";
@ -520,21 +520,22 @@ uploaded to Mastodon.";
"Scene.SuggestionAccount.Title" = "Find People to Follow";
"Scene.Thread.BackTitle" = "Post";
"Scene.Thread.Title" = "Post from %@";
"Scene.Welcome.Education.A11Y.WhatIsMastodon.Title" = "What is Mastodon?";
"Scene.Welcome.Education.HowDoIPickAServer.Description" = "Different people choose different servers for any number of reasons. art.example is a great place for artists, while glasgow.example might be a good pick for Scots.
"Scene.Welcome.Education.Mastodon.Title" = "Welcome to Mastodon";
"Scene.Welcome.Education.Mastodon.Description" = "Mastodon is a decentralized social network, meaning no single company controls it. Its made up of many independently-run servers, all connected together.";
"Scene.Welcome.Education.Servers.Title" = "What are servers?";
"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.";
You cant go wrong with any of our recommend servers, so regardless of which one you pick (or if you enter your own in the server search bar), youll never miss a beat anywhere.";
"Scene.Welcome.Education.HowDoIPickAServer.Title" = "How do I pick a server?";
"Scene.Welcome.Education.MastodonIsLikeThat.Description" = "Your handle might be @gothgirl654@example.social, but you can still follow, reblog, and chat with @fallout5ever@example.online.";
"Scene.Welcome.Education.MastodonIsLikeThat.Title" = "Mastodon is like that";
"Scene.Welcome.Education.WhatIsMastodon.Description" = "Imagine you have an email address that ends with @example.com.
You can still send and receive emails from anyone, even if their email ends in @gmail.com or @icloud.com or @example.com.";
"Scene.Welcome.Education.WhatIsMastodon.Title" = "What is";
"Scene.Welcome.GetStarted" = "Get Started";
"Scene.Welcome.LogIn" = "Log In";
"Scene.Welcome.Slogan" = "Social networking
back in your hands.";
"Scene.Welcome.LogIn" = "Log In";
"Scene.Welcome.LearnMore" = "Learn more";
"Scene.Welcome.JoinDefault_server" = "Join mastodon.social";
"Scene.Welcome.PickServer" = "Pick my own server";
"Scene.Welcome.Separator.Or" = "Or";
"Widget.Common.UnsupportedWidgetFamily" = "Sorry but this Widget family is unsupported.";
"Widget.Common.UserNotLoggedIn" = "Please open Mastodon to log in to an Account.";
"Widget.FollowersCount.ConfigurationDescription" = "Show number of followers.";

View File

@ -61,13 +61,15 @@ Please check your internet connection.";
"Common.Controls.Actions.SharePost" = "Share Post";
"Common.Controls.Actions.ShareUser" = "Share %@";
"Common.Controls.Actions.SignIn" = "Log in";
"Common.Controls.Actions.SignUp" = "Create account";
"Common.Controls.Actions.PickServer" = "Pick my own server";
"Common.Controls.Actions.LearnMore" = "Learn More";
"Common.Controls.Actions.Skip" = "Skip";
"Common.Controls.Actions.TakePhoto" = "Take Photo";
"Common.Controls.Actions.TranslatePost.Title" = "Translate from %@";
"Common.Controls.Actions.TranslatePost.UnknownLanguage" = "Unknown";
"Common.Controls.Actions.TryAgain" = "Try Again";
"Common.Controls.Actions.UnblockDomain" = "Unblock %@";
"Common.Controls.Actions.JoinDefaultServer" = "Join mastodon.social";
"Common.Controls.Friendship.Block" = "Block";
"Common.Controls.Friendship.BlockDomain" = "Block %@";
"Common.Controls.Friendship.BlockUser" = "Block %@";
@ -520,24 +522,22 @@ uploaded to Mastodon.";
"Scene.SuggestionAccount.Title" = "Find People to Follow";
"Scene.Thread.BackTitle" = "Post";
"Scene.Thread.Title" = "Post from %@";
"Scene.Welcome.Education.A11Y.WhatIsMastodon.Title" = "What is Mastodon?";
"Scene.Welcome.Education.HowDoIPickAServer.Description" = "Different people choose different servers for any number of reasons. art.example is a great place for artists, while glasgow.example might be a good pick for Scots.
You cant go wrong with any of our recommend servers, so regardless of which one you pick (or if you enter your own in the server search bar), youll never miss a beat anywhere.";
"Scene.Welcome.Education.HowDoIPickAServer.Title" = "How do I pick a server?";
"Scene.Welcome.Education.MastodonIsLikeThat.Description" = "Your handle might be @gothgirl654@example.social, but you can still follow, reblog, and chat with @fallout5ever@example.online.";
"Scene.Welcome.Education.MastodonIsLikeThat.Title" = "Mastodon is like that";
"Scene.Welcome.Education.WhatIsMastodon.Description" = "Imagine you have an email address that ends with @example.com.
"Scene.Welcome.Education.Mastodon.Title" = "Welcome to Mastodon";
"Scene.Welcome.Education.Mastodon.Description" = "Mastodon is a decentralized social network, meaning no single company controls it. Its made up of many independently-run servers, all connected together.";
"Scene.Welcome.Education.Servers.Title" = "What are servers?";
"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.";
You can still send and receive emails from anyone, even if their email ends in @gmail.com or @icloud.com or @example.com.";
"Scene.Welcome.Education.WhatIsMastodon.Title" = "What is";
"Scene.Welcome.GetStarted" = "Get Started";
"Scene.Welcome.LogIn" = "Log In";
"Scene.Welcome.Slogan" = "Social networking
back in your hands.";
"Scene.Wizard.AccessibilityHint" = "Double tap to dismiss this wizard";
"Scene.Wizard.MultipleAccountSwitchIntroDescription" = "Switch between multiple accounts by holding the profile button.";
"Scene.Wizard.NewInMastodon" = "New in Mastodon";
"Scene.Welcome.LogIn" = "Log In";
"Scene.Welcome.LearnMore" = "Learn more";
"Scene.Welcome.JoinDefault_server" = "Join mastodon.social";
"Scene.Welcome.PickServer" = "Pick my own server";
"Scene.Welcome.Separator.Or" = "Or";
"Widget.Common.UnsupportedWidgetFamily" = "Sorry but this Widget family is unsupported.";
"Widget.Common.UserNotLoggedIn" = "Please open Mastodon to log in to an Account.";
"Widget.FollowersCount.ConfigurationDescription" = "Show number of followers.";

View File

@ -22,7 +22,7 @@ extension Mastodon.Entity {
public let approvalRequired: Bool
public let language: String
public let category: String
enum CodingKeys: String, CodingKey {
case domain
case version
@ -56,6 +56,9 @@ extension Mastodon.Entity {
public static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.domain.caseInsensitiveCompare(rhs.domain) == .orderedSame
}
public static var mastodonDotSocial: Server {
return Server(domain: "mastodon.social", instance: Instance(domain: "mastodon.social"))
}
}
}