feat: update compose scene UI appearance
|
@ -240,6 +240,7 @@
|
||||||
},
|
},
|
||||||
"content_input_placeholder": "Type or paste what's on your mind",
|
"content_input_placeholder": "Type or paste what's on your mind",
|
||||||
"compose_action": "Publish",
|
"compose_action": "Publish",
|
||||||
|
"replying_to_user": "replying to %s",
|
||||||
"attachment": {
|
"attachment": {
|
||||||
"photo": "photo",
|
"photo": "photo",
|
||||||
"video": "video",
|
"video": "video",
|
||||||
|
@ -254,7 +255,8 @@
|
||||||
"six_hours": "6 Hours",
|
"six_hours": "6 Hours",
|
||||||
"one_day": "1 Day",
|
"one_day": "1 Day",
|
||||||
"three_days": "3 Days",
|
"three_days": "3 Days",
|
||||||
"seven_days": "7 Days"
|
"seven_days": "7 Days",
|
||||||
|
"option_number": "Option %ld"
|
||||||
},
|
},
|
||||||
"content_warning": {
|
"content_warning": {
|
||||||
"placeholder": "Write an accurate warning here..."
|
"placeholder": "Write an accurate warning here..."
|
||||||
|
@ -336,4 +338,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,7 +7,7 @@
|
||||||
<key>CoreDataStack.xcscheme_^#shared#^_</key>
|
<key>CoreDataStack.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>10</integer>
|
<integer>20</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>Mastodon - RTL.xcscheme_^#shared#^_</key>
|
<key>Mastodon - RTL.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
|
|
@ -50,8 +50,15 @@ extension ComposeStatusSection {
|
||||||
weak composeStatusPollExpiresOptionCollectionViewCellDelegate
|
weak composeStatusPollExpiresOptionCollectionViewCellDelegate
|
||||||
] collectionView, indexPath, item -> UICollectionViewCell? in
|
] collectionView, indexPath, item -> UICollectionViewCell? in
|
||||||
switch item {
|
switch item {
|
||||||
case .replyTo(let repliedToStatusObjectID):
|
case .replyTo(let replyToStatusObjectID):
|
||||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeRepliedToStatusContentCollectionViewCell.self), for: indexPath) as! ComposeRepliedToStatusContentCollectionViewCell
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeRepliedToStatusContentCollectionViewCell.self), for: indexPath) as! ComposeRepliedToStatusContentCollectionViewCell
|
||||||
|
managedObjectContext.perform {
|
||||||
|
guard let replyTo = managedObjectContext.object(with: replyToStatusObjectID) as? Status else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let status = replyTo.reblog ?? replyTo
|
||||||
|
cell.statusView.configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: status.author.avatarImageURL()))
|
||||||
|
}
|
||||||
return cell
|
return cell
|
||||||
case .input(let replyToStatusObjectID, let attribute):
|
case .input(let replyToStatusObjectID, let attribute):
|
||||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeStatusContentCollectionViewCell.self), for: indexPath) as! ComposeStatusContentCollectionViewCell
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeStatusContentCollectionViewCell.self), for: indexPath) as! ComposeStatusContentCollectionViewCell
|
||||||
|
@ -63,9 +70,10 @@ extension ComposeStatusSection {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cell.statusView.headerContainerStackView.isHidden = false
|
cell.statusView.headerContainerStackView.isHidden = false
|
||||||
cell.statusView.headerInfoLabel.text = "[TODO] \(replyTo.author.displayName)"
|
cell.statusView.headerIconLabel.attributedText = StatusView.iconAttributedString(image: StatusView.replyIconImage)
|
||||||
|
cell.statusView.headerInfoLabel.text = L10n.Scene.Compose.replyingToUser(replyTo.author.displayNameWithFallback)
|
||||||
}
|
}
|
||||||
ComposeStatusSection.configure(cell: cell, attribute: attribute)
|
ComposeStatusSection.configureStatusContent(cell: cell, attribute: attribute)
|
||||||
cell.textEditorView.textAttributesDelegate = textEditorViewTextAttributesDelegate
|
cell.textEditorView.textAttributesDelegate = textEditorViewTextAttributesDelegate
|
||||||
cell.composeContent
|
cell.composeContent
|
||||||
.removeDuplicates()
|
.removeDuplicates()
|
||||||
|
@ -196,7 +204,7 @@ extension ComposeStatusSection {
|
||||||
|
|
||||||
extension ComposeStatusSection {
|
extension ComposeStatusSection {
|
||||||
|
|
||||||
static func configure(
|
static func configureStatusContent(
|
||||||
cell: ComposeStatusContentCollectionViewCell,
|
cell: ComposeStatusContentCollectionViewCell,
|
||||||
attribute: ComposeStatusItem.ComposeStatusAttribute
|
attribute: ComposeStatusItem.ComposeStatusAttribute
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -393,7 +393,7 @@ extension StatusSection {
|
||||||
) {
|
) {
|
||||||
if status.reblog != nil {
|
if status.reblog != nil {
|
||||||
cell.statusView.headerContainerStackView.isHidden = false
|
cell.statusView.headerContainerStackView.isHidden = false
|
||||||
cell.statusView.headerIconLabel.attributedText = StatusView.iconAttributedString(image: StatusView.boostIconImage)
|
cell.statusView.headerIconLabel.attributedText = StatusView.iconAttributedString(image: StatusView.reblogIconImage)
|
||||||
cell.statusView.headerInfoLabel.text = {
|
cell.statusView.headerInfoLabel.text = {
|
||||||
let author = status.author
|
let author = status.author
|
||||||
let name = author.displayName.isEmpty ? author.username : author.displayName
|
let name = author.displayName.isEmpty ? author.username : author.displayName
|
||||||
|
|
|
@ -91,26 +91,32 @@ internal enum Asset {
|
||||||
internal enum Connectivity {
|
internal enum Connectivity {
|
||||||
internal static let photoFillSplit = ImageAsset(name: "Connectivity/photo.fill.split")
|
internal static let photoFillSplit = ImageAsset(name: "Connectivity/photo.fill.split")
|
||||||
}
|
}
|
||||||
internal enum Profile {
|
internal enum Scene {
|
||||||
internal enum Banner {
|
internal enum Compose {
|
||||||
internal static let bioEditBackgroundGray = ColorAsset(name: "Profile/Banner/bio.edit.background.gray")
|
internal static let background = ColorAsset(name: "Scene/Compose/background")
|
||||||
internal static let nameEditBackgroundGray = ColorAsset(name: "Profile/Banner/name.edit.background.gray")
|
internal static let toolbarBackground = ColorAsset(name: "Scene/Compose/toolbar.background")
|
||||||
internal static let usernameGray = ColorAsset(name: "Profile/Banner/username.gray")
|
|
||||||
}
|
}
|
||||||
}
|
internal enum Profile {
|
||||||
internal enum Welcome {
|
internal enum Banner {
|
||||||
internal enum Illustration {
|
internal static let bioEditBackgroundGray = ColorAsset(name: "Scene/Profile/Banner/bio.edit.background.gray")
|
||||||
internal static let backgroundCyan = ColorAsset(name: "Welcome/illustration/background.cyan")
|
internal static let nameEditBackgroundGray = ColorAsset(name: "Scene/Profile/Banner/name.edit.background.gray")
|
||||||
internal static let cloudBase = ImageAsset(name: "Welcome/illustration/cloud.base")
|
internal static let usernameGray = ColorAsset(name: "Scene/Profile/Banner/username.gray")
|
||||||
internal static let elephantOnAirplaneWithContrail = ImageAsset(name: "Welcome/illustration/elephant.on.airplane.with.contrail")
|
}
|
||||||
internal static let elephantThreeOnGrass = ImageAsset(name: "Welcome/illustration/elephant.three.on.grass")
|
}
|
||||||
internal static let elephantThreeOnGrassWithTreeThree = ImageAsset(name: "Welcome/illustration/elephant.three.on.grass.with.tree.three")
|
internal enum Welcome {
|
||||||
internal static let elephantThreeOnGrassWithTreeTwo = ImageAsset(name: "Welcome/illustration/elephant.three.on.grass.with.tree.two")
|
internal enum Illustration {
|
||||||
|
internal static let backgroundCyan = ColorAsset(name: "Scene/Welcome/illustration/background.cyan")
|
||||||
|
internal static let cloudBase = ImageAsset(name: "Scene/Welcome/illustration/cloud.base")
|
||||||
|
internal static let elephantOnAirplaneWithContrail = ImageAsset(name: "Scene/Welcome/illustration/elephant.on.airplane.with.contrail")
|
||||||
|
internal static let elephantThreeOnGrass = ImageAsset(name: "Scene/Welcome/illustration/elephant.three.on.grass")
|
||||||
|
internal static let elephantThreeOnGrassWithTreeThree = ImageAsset(name: "Scene/Welcome/illustration/elephant.three.on.grass.with.tree.three")
|
||||||
|
internal static let elephantThreeOnGrassWithTreeTwo = ImageAsset(name: "Scene/Welcome/illustration/elephant.three.on.grass.with.tree.two")
|
||||||
|
}
|
||||||
|
internal static let mastodonLogoBlack = ImageAsset(name: "Scene/Welcome/mastodon.logo.black")
|
||||||
|
internal static let mastodonLogoBlackLarge = ImageAsset(name: "Scene/Welcome/mastodon.logo.black.large")
|
||||||
|
internal static let mastodonLogo = ImageAsset(name: "Scene/Welcome/mastodon.logo")
|
||||||
|
internal static let mastodonLogoLarge = ImageAsset(name: "Scene/Welcome/mastodon.logo.large")
|
||||||
}
|
}
|
||||||
internal static let mastodonLogoBlack = ImageAsset(name: "Welcome/mastodon.logo.black")
|
|
||||||
internal static let mastodonLogoBlackLarge = ImageAsset(name: "Welcome/mastodon.logo.black.large")
|
|
||||||
internal static let mastodonLogo = ImageAsset(name: "Welcome/mastodon.logo")
|
|
||||||
internal static let mastodonLogoLarge = ImageAsset(name: "Welcome/mastodon.logo.large")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// swiftlint:enable identifier_name line_length nesting type_body_length type_name
|
// swiftlint:enable identifier_name line_length nesting type_body_length type_name
|
||||||
|
|
|
@ -224,6 +224,10 @@ internal enum L10n {
|
||||||
internal static let composeAction = L10n.tr("Localizable", "Scene.Compose.ComposeAction")
|
internal static let composeAction = L10n.tr("Localizable", "Scene.Compose.ComposeAction")
|
||||||
/// Type or paste what's on your mind
|
/// Type or paste what's on your mind
|
||||||
internal static let contentInputPlaceholder = L10n.tr("Localizable", "Scene.Compose.ContentInputPlaceholder")
|
internal static let contentInputPlaceholder = L10n.tr("Localizable", "Scene.Compose.ContentInputPlaceholder")
|
||||||
|
/// replying to %@
|
||||||
|
internal static func replyingToUser(_ p1: Any) -> String {
|
||||||
|
return L10n.tr("Localizable", "Scene.Compose.ReplyingToUser", String(describing: p1))
|
||||||
|
}
|
||||||
internal enum Attachment {
|
internal enum Attachment {
|
||||||
/// This %@ is broken and can't be\nuploaded to Mastodon.
|
/// This %@ is broken and can't be\nuploaded to Mastodon.
|
||||||
internal static func attachmentBroken(_ p1: Any) -> String {
|
internal static func attachmentBroken(_ p1: Any) -> String {
|
||||||
|
@ -259,6 +263,10 @@ internal enum L10n {
|
||||||
internal static let oneDay = L10n.tr("Localizable", "Scene.Compose.Poll.OneDay")
|
internal static let oneDay = L10n.tr("Localizable", "Scene.Compose.Poll.OneDay")
|
||||||
/// 1 Hour
|
/// 1 Hour
|
||||||
internal static let oneHour = L10n.tr("Localizable", "Scene.Compose.Poll.OneHour")
|
internal static let oneHour = L10n.tr("Localizable", "Scene.Compose.Poll.OneHour")
|
||||||
|
/// Option %ld
|
||||||
|
internal static func optionNumber(_ p1: Int) -> String {
|
||||||
|
return L10n.tr("Localizable", "Scene.Compose.Poll.OptionNumber", p1)
|
||||||
|
}
|
||||||
/// 7 Days
|
/// 7 Days
|
||||||
internal static let sevenDays = L10n.tr("Localizable", "Scene.Compose.Poll.SevenDays")
|
internal static let sevenDays = L10n.tr("Localizable", "Scene.Compose.Poll.SevenDays")
|
||||||
/// 6 Hours
|
/// 6 Hours
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "1.000",
|
||||||
|
"green" : "1.000",
|
||||||
|
"red" : "1.000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0x1E",
|
||||||
|
"green" : "0x1C",
|
||||||
|
"red" : "0x1C"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "222",
|
||||||
|
"green" : "216",
|
||||||
|
"red" : "214"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "43",
|
||||||
|
"green" : "43",
|
||||||
|
"red" : "43"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"provides-namespace" : true
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"provides-namespace" : true
|
||||||
|
}
|
||||||
|
}
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 130 KiB |
Before Width: | Height: | Size: 238 KiB After Width: | Height: | Size: 238 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 116 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 118 KiB |
|
@ -86,10 +86,12 @@ uploaded to Mastodon.";
|
||||||
"Scene.Compose.Poll.DurationTime" = "Duration: %@";
|
"Scene.Compose.Poll.DurationTime" = "Duration: %@";
|
||||||
"Scene.Compose.Poll.OneDay" = "1 Day";
|
"Scene.Compose.Poll.OneDay" = "1 Day";
|
||||||
"Scene.Compose.Poll.OneHour" = "1 Hour";
|
"Scene.Compose.Poll.OneHour" = "1 Hour";
|
||||||
|
"Scene.Compose.Poll.OptionNumber" = "Option %ld";
|
||||||
"Scene.Compose.Poll.SevenDays" = "7 Days";
|
"Scene.Compose.Poll.SevenDays" = "7 Days";
|
||||||
"Scene.Compose.Poll.SixHours" = "6 Hours";
|
"Scene.Compose.Poll.SixHours" = "6 Hours";
|
||||||
"Scene.Compose.Poll.ThirtyMinutes" = "30 minutes";
|
"Scene.Compose.Poll.ThirtyMinutes" = "30 minutes";
|
||||||
"Scene.Compose.Poll.ThreeDays" = "3 Days";
|
"Scene.Compose.Poll.ThreeDays" = "3 Days";
|
||||||
|
"Scene.Compose.ReplyingToUser" = "replying to %@";
|
||||||
"Scene.Compose.Title.NewPost" = "New Post";
|
"Scene.Compose.Title.NewPost" = "New Post";
|
||||||
"Scene.Compose.Title.NewReply" = "New Reply";
|
"Scene.Compose.Title.NewReply" = "New Reply";
|
||||||
"Scene.Compose.Visibility.Direct" = "Only people I mention";
|
"Scene.Compose.Visibility.Direct" = "Only people I mention";
|
||||||
|
|
|
@ -6,9 +6,22 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Combine
|
||||||
|
|
||||||
final class ComposeRepliedToStatusContentCollectionViewCell: UICollectionViewCell {
|
final class ComposeRepliedToStatusContentCollectionViewCell: UICollectionViewCell {
|
||||||
|
|
||||||
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
let statusView = StatusView()
|
||||||
|
|
||||||
|
override func prepareForReuse() {
|
||||||
|
super.prepareForReuse()
|
||||||
|
|
||||||
|
statusView.isStatusTextSensitive = false
|
||||||
|
statusView.cleanUpContentWarning()
|
||||||
|
disposeBag.removeAll()
|
||||||
|
}
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
_init()
|
_init()
|
||||||
|
@ -24,7 +37,19 @@ final class ComposeRepliedToStatusContentCollectionViewCell: UICollectionViewCel
|
||||||
extension ComposeRepliedToStatusContentCollectionViewCell {
|
extension ComposeRepliedToStatusContentCollectionViewCell {
|
||||||
|
|
||||||
private func _init() {
|
private func _init() {
|
||||||
|
backgroundColor = .clear
|
||||||
|
statusView.contentWarningBlurContentImageView.backgroundColor = Asset.Scene.Compose.background.color
|
||||||
|
|
||||||
|
statusView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
contentView.addSubview(statusView)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
statusView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20),
|
||||||
|
statusView.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
|
||||||
|
contentView.readableContentGuide.trailingAnchor.constraint(equalTo: statusView.trailingAnchor),
|
||||||
|
contentView.bottomAnchor.constraint(equalTo: statusView.bottomAnchor),
|
||||||
|
])
|
||||||
|
|
||||||
|
statusView.actionToolbarContainer.isHidden = true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ final class ComposeViewController: UIViewController, NeedsDependency {
|
||||||
button.setBackgroundImage(.placeholder(color: Asset.Colors.Button.normal.color.withAlphaComponent(0.5)), for: .highlighted)
|
button.setBackgroundImage(.placeholder(color: Asset.Colors.Button.normal.color.withAlphaComponent(0.5)), for: .highlighted)
|
||||||
button.setBackgroundImage(.placeholder(color: Asset.Colors.Button.disabled.color), for: .disabled)
|
button.setBackgroundImage(.placeholder(color: Asset.Colors.Button.disabled.color), for: .disabled)
|
||||||
button.setTitleColor(.white, for: .normal)
|
button.setTitleColor(.white, for: .normal)
|
||||||
button.contentEdgeInsets = UIEdgeInsets(top: 3, left: 16, bottom: 3, right: 16)
|
button.contentEdgeInsets = UIEdgeInsets(top: 5.5, left: 16, bottom: 5.5, right: 16) // set 28pt height
|
||||||
button.adjustsImageWhenHighlighted = false
|
button.adjustsImageWhenHighlighted = false
|
||||||
return button
|
return button
|
||||||
}()
|
}()
|
||||||
|
@ -66,18 +66,18 @@ final class ComposeViewController: UIViewController, NeedsDependency {
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let composeToolbarView: ComposeToolbarView = {
|
let composeToolbarView = ComposeToolbarView()
|
||||||
let composeToolbarView = ComposeToolbarView()
|
|
||||||
let text = UITextView()
|
|
||||||
let inputView = UIInputView(frame: .init(x: 0, y: 0, width: 40, height: 40), inputViewStyle: .keyboard)
|
|
||||||
text.inputAccessoryView = inputView
|
|
||||||
composeToolbarView.backgroundColor = inputView.backgroundColor
|
|
||||||
return composeToolbarView
|
|
||||||
}()
|
|
||||||
var composeToolbarViewBottomLayoutConstraint: NSLayoutConstraint!
|
var composeToolbarViewBottomLayoutConstraint: NSLayoutConstraint!
|
||||||
let composeToolbarBackgroundView: UIView = {
|
let composeToolbarBackgroundView: UIView = {
|
||||||
let backgroundView = UIView()
|
let backgroundView = UIView()
|
||||||
backgroundView.backgroundColor = .secondarySystemBackground
|
// set keyboard background to make the keyboard blurred color fixed
|
||||||
|
backgroundView.backgroundColor = UIColor(dynamicProvider: { traitCollection -> UIColor in
|
||||||
|
// avoid elevated color
|
||||||
|
switch traitCollection.userInterfaceStyle {
|
||||||
|
case .light: return .white
|
||||||
|
default: return .black
|
||||||
|
}
|
||||||
|
})
|
||||||
return backgroundView
|
return backgroundView
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -135,7 +135,7 @@ extension ComposeViewController {
|
||||||
self.title = title
|
self.title = title
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
view.backgroundColor = Asset.Colors.Background.systemBackground.color
|
view.backgroundColor = Asset.Scene.Compose.background.color
|
||||||
navigationItem.leftBarButtonItem = UIBarButtonItem(title: L10n.Common.Controls.Actions.cancel, style: .plain, target: self, action: #selector(ComposeViewController.cancelBarButtonItemPressed(_:)))
|
navigationItem.leftBarButtonItem = UIBarButtonItem(title: L10n.Common.Controls.Actions.cancel, style: .plain, target: self, action: #selector(ComposeViewController.cancelBarButtonItemPressed(_:)))
|
||||||
navigationItem.rightBarButtonItem = publishBarButtonItem
|
navigationItem.rightBarButtonItem = publishBarButtonItem
|
||||||
publishButton.addTarget(self, action: #selector(ComposeViewController.publishBarButtonItemPressed(_:)), for: .touchUpInside)
|
publishButton.addTarget(self, action: #selector(ComposeViewController.publishBarButtonItemPressed(_:)), for: .touchUpInside)
|
||||||
|
@ -266,13 +266,17 @@ extension ComposeViewController {
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
// bind visibility toolbar UI
|
// bind visibility toolbar UI
|
||||||
viewModel.selectedStatusVisibility
|
Publishers.CombineLatest(
|
||||||
.receive(on: DispatchQueue.main)
|
viewModel.selectedStatusVisibility,
|
||||||
.sink { [weak self] type in
|
viewModel.traitCollectionDidChangePublisher
|
||||||
guard let self = self else { return }
|
)
|
||||||
self.composeToolbarView.visibilityButton.setImage(type.image, for: .normal)
|
.receive(on: DispatchQueue.main)
|
||||||
}
|
.sink { [weak self] type, _ in
|
||||||
.store(in: &disposeBag)
|
guard let self = self else { return }
|
||||||
|
let image = type.image(interfaceStyle: self.traitCollection.userInterfaceStyle)
|
||||||
|
self.composeToolbarView.visibilityButton.setImage(image, for: .normal)
|
||||||
|
}
|
||||||
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
viewModel.characterCount
|
viewModel.characterCount
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
|
@ -336,6 +340,12 @@ extension ComposeViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||||
|
super.traitCollectionDidChange(previousTraitCollection)
|
||||||
|
|
||||||
|
viewModel.traitCollectionDidChangePublisher.send()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ComposeViewController {
|
extension ComposeViewController {
|
||||||
|
|
|
@ -29,6 +29,7 @@ final class ComposeViewModel {
|
||||||
let selectedStatusVisibility = CurrentValueSubject<ComposeToolbarView.VisibilitySelectionType, Never>(.public)
|
let selectedStatusVisibility = CurrentValueSubject<ComposeToolbarView.VisibilitySelectionType, Never>(.public)
|
||||||
let activeAuthentication: CurrentValueSubject<MastodonAuthentication?, Never>
|
let activeAuthentication: CurrentValueSubject<MastodonAuthentication?, Never>
|
||||||
let activeAuthenticationBox: CurrentValueSubject<AuthenticationService.MastodonAuthenticationBox?, Never>
|
let activeAuthenticationBox: CurrentValueSubject<AuthenticationService.MastodonAuthenticationBox?, Never>
|
||||||
|
let traitCollectionDidChangePublisher = PassthroughSubject<Void, Never>()
|
||||||
|
|
||||||
// output
|
// output
|
||||||
var diffableDataSource: UICollectionViewDiffableDataSource<ComposeStatusSection, ComposeStatusItem>!
|
var diffableDataSource: UICollectionViewDiffableDataSource<ComposeStatusSection, ComposeStatusItem>!
|
||||||
|
|
|
@ -80,8 +80,12 @@ final class ComposeToolbarView: UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ComposeToolbarView {
|
extension ComposeToolbarView {
|
||||||
|
|
||||||
private func _init() {
|
private func _init() {
|
||||||
backgroundColor = .secondarySystemBackground
|
// magic keyboard color (iOS 14):
|
||||||
|
// light with white background: RGB 214 216 222
|
||||||
|
// dark with black background: RGB 43 43 43
|
||||||
|
backgroundColor = Asset.Scene.Compose.toolbarBackground.color
|
||||||
|
|
||||||
let stackView = UIStackView()
|
let stackView = UIStackView()
|
||||||
stackView.axis = .horizontal
|
stackView.axis = .horizontal
|
||||||
|
@ -125,9 +129,18 @@ extension ComposeToolbarView {
|
||||||
pollButton.addTarget(self, action: #selector(ComposeToolbarView.pollButtonDidPressed(_:)), for: .touchUpInside)
|
pollButton.addTarget(self, action: #selector(ComposeToolbarView.pollButtonDidPressed(_:)), for: .touchUpInside)
|
||||||
emojiButton.addTarget(self, action: #selector(ComposeToolbarView.emojiButtonDidPressed(_:)), for: .touchUpInside)
|
emojiButton.addTarget(self, action: #selector(ComposeToolbarView.emojiButtonDidPressed(_:)), for: .touchUpInside)
|
||||||
contentWarningButton.addTarget(self, action: #selector(ComposeToolbarView.contentWarningButtonDidPressed(_:)), for: .touchUpInside)
|
contentWarningButton.addTarget(self, action: #selector(ComposeToolbarView.contentWarningButtonDidPressed(_:)), for: .touchUpInside)
|
||||||
visibilityButton.menu = createVisibilityContextMenu()
|
visibilityButton.menu = createVisibilityContextMenu(interfaceStyle: traitCollection.userInterfaceStyle)
|
||||||
visibilityButton.showsMenuAsPrimaryAction = true
|
visibilityButton.showsMenuAsPrimaryAction = true
|
||||||
|
|
||||||
|
updateToolbarButtonUserInterfaceStyle()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||||
|
super.traitCollectionDidChange(previousTraitCollection)
|
||||||
|
|
||||||
|
updateToolbarButtonUserInterfaceStyle()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ComposeToolbarView {
|
extension ComposeToolbarView {
|
||||||
|
@ -152,9 +165,15 @@ extension ComposeToolbarView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var image: UIImage {
|
func image(interfaceStyle: UIUserInterfaceStyle) -> UIImage {
|
||||||
switch self {
|
switch self {
|
||||||
case .public: return UIImage(systemName: "person.3", withConfiguration: UIImage.SymbolConfiguration(pointSize: 15, weight: .medium))!
|
case .public:
|
||||||
|
switch interfaceStyle {
|
||||||
|
case .light:
|
||||||
|
return UIImage(systemName: "person.3", withConfiguration: UIImage.SymbolConfiguration(pointSize: 15, weight: .medium))!
|
||||||
|
default:
|
||||||
|
return UIImage(systemName: "person.3.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 15, weight: .medium))!
|
||||||
|
}
|
||||||
case .unlisted: return UIImage(systemName: "eye.slash", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .medium))!
|
case .unlisted: return UIImage(systemName: "eye.slash", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .medium))!
|
||||||
case .private: return UIImage(systemName: "person.crop.circle.badge.plus", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .medium))!
|
case .private: return UIImage(systemName: "person.crop.circle.badge.plus", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .medium))!
|
||||||
case .direct: return UIImage(systemName: "at", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .medium))!
|
case .direct: return UIImage(systemName: "at", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .medium))!
|
||||||
|
@ -182,6 +201,25 @@ extension ComposeToolbarView {
|
||||||
button.layer.cornerCurve = .continuous
|
button.layer.cornerCurve = .continuous
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func updateToolbarButtonUserInterfaceStyle() {
|
||||||
|
switch traitCollection.userInterfaceStyle {
|
||||||
|
case .light:
|
||||||
|
mediaButton.setImage(UIImage(systemName: "photo", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular))!, for: .normal)
|
||||||
|
emojiButton.setImage(UIImage(systemName: "face.smiling", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular))!, for: .normal)
|
||||||
|
contentWarningButton.setImage(UIImage(systemName: "exclamationmark.shield", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular))!, for: .normal)
|
||||||
|
|
||||||
|
case .dark:
|
||||||
|
mediaButton.setImage(UIImage(systemName: "photo.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular))!, for: .normal)
|
||||||
|
emojiButton.setImage(UIImage(systemName: "face.smiling.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular))!, for: .normal)
|
||||||
|
contentWarningButton.setImage(UIImage(systemName: "exclamationmark.shield.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular))!, for: .normal)
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
|
||||||
|
visibilityButton.menu = createVisibilityContextMenu(interfaceStyle: traitCollection.userInterfaceStyle)
|
||||||
|
}
|
||||||
|
|
||||||
private func createMediaContextMenu() -> UIMenu {
|
private func createMediaContextMenu() -> UIMenu {
|
||||||
var children: [UIMenuElement] = []
|
var children: [UIMenuElement] = []
|
||||||
let photoLibraryAction = UIAction(title: L10n.Scene.Compose.MediaSelection.photoLibrary, image: UIImage(systemName: "rectangle.on.rectangle"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] _ in
|
let photoLibraryAction = UIAction(title: L10n.Scene.Compose.MediaSelection.photoLibrary, image: UIImage(systemName: "rectangle.on.rectangle"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] _ in
|
||||||
|
@ -208,9 +246,9 @@ extension ComposeToolbarView {
|
||||||
return UIMenu(title: "", image: nil, identifier: nil, options: .displayInline, children: children)
|
return UIMenu(title: "", image: nil, identifier: nil, options: .displayInline, children: children)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func createVisibilityContextMenu() -> UIMenu {
|
private func createVisibilityContextMenu(interfaceStyle: UIUserInterfaceStyle) -> UIMenu {
|
||||||
let children: [UIMenuElement] = VisibilitySelectionType.allCases.map { type in
|
let children: [UIMenuElement] = VisibilitySelectionType.allCases.map { type in
|
||||||
UIAction(title: type.title, image: type.image, identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] action in
|
UIAction(title: type.title, image: type.image(interfaceStyle: interfaceStyle), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] action in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: visibilitySelectionType: %s", ((#file as NSString).lastPathComponent), #line, #function, type.rawValue)
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: visibilitySelectionType: %s", ((#file as NSString).lastPathComponent), #line, #function, type.rawValue)
|
||||||
self.delegate?.composeToolbarView(self, visibilityButtonDidPressed: self.visibilityButton, visibilitySelectionType: type)
|
self.delegate?.composeToolbarView(self, visibilityButtonDidPressed: self.visibilityButton, visibilitySelectionType: type)
|
||||||
|
|
|
@ -16,14 +16,14 @@ final class WelcomeIllustrationView: UIView {
|
||||||
let leftHillImageView = UIImageView()
|
let leftHillImageView = UIImageView()
|
||||||
let centerHillImageView = UIImageView()
|
let centerHillImageView = UIImageView()
|
||||||
|
|
||||||
private let cloudBaseImage = Asset.Welcome.Illustration.cloudBase.image
|
private let cloudBaseImage = Asset.Scene.Welcome.Illustration.cloudBase.image
|
||||||
private let elephantThreeOnGrassWithTreeTwoImage = Asset.Welcome.Illustration.elephantThreeOnGrassWithTreeTwo.image
|
private let elephantThreeOnGrassWithTreeTwoImage = Asset.Scene.Welcome.Illustration.elephantThreeOnGrassWithTreeTwo.image
|
||||||
private let elephantThreeOnGrassWithTreeThreeImage = Asset.Welcome.Illustration.elephantThreeOnGrassWithTreeThree.image
|
private let elephantThreeOnGrassWithTreeThreeImage = Asset.Scene.Welcome.Illustration.elephantThreeOnGrassWithTreeThree.image
|
||||||
private let elephantThreeOnGrassImage = Asset.Welcome.Illustration.elephantThreeOnGrass.image
|
private let elephantThreeOnGrassImage = Asset.Scene.Welcome.Illustration.elephantThreeOnGrass.image
|
||||||
|
|
||||||
// layout outside
|
// layout outside
|
||||||
let elephantOnAirplaneWithContrailImageView: UIImageView = {
|
let elephantOnAirplaneWithContrailImageView: UIImageView = {
|
||||||
let imageView = UIImageView(image: Asset.Welcome.Illustration.elephantOnAirplaneWithContrail.image)
|
let imageView = UIImageView(image: Asset.Scene.Welcome.Illustration.elephantOnAirplaneWithContrail.image)
|
||||||
imageView.contentMode = .scaleAspectFill
|
imageView.contentMode = .scaleAspectFill
|
||||||
return imageView
|
return imageView
|
||||||
}()
|
}()
|
||||||
|
@ -43,7 +43,7 @@ final class WelcomeIllustrationView: UIView {
|
||||||
extension WelcomeIllustrationView {
|
extension WelcomeIllustrationView {
|
||||||
|
|
||||||
private func _init() {
|
private func _init() {
|
||||||
backgroundColor = Asset.Welcome.Illustration.backgroundCyan.color
|
backgroundColor = Asset.Scene.Welcome.Illustration.backgroundCyan.color
|
||||||
|
|
||||||
let topPaddingView = UIView()
|
let topPaddingView = UIView()
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ final class WelcomeViewController: UIViewController, NeedsDependency {
|
||||||
var welcomeIllustrationViewBottomAnchorLayoutConstraint: NSLayoutConstraint?
|
var welcomeIllustrationViewBottomAnchorLayoutConstraint: NSLayoutConstraint?
|
||||||
|
|
||||||
private(set) lazy var logoImageView: UIImageView = {
|
private(set) lazy var logoImageView: UIImageView = {
|
||||||
let image = view.traitCollection.userInterfaceIdiom == .phone ? Asset.Welcome.mastodonLogo.image : Asset.Welcome.mastodonLogoBlackLarge.image
|
let image = view.traitCollection.userInterfaceIdiom == .phone ? Asset.Scene.Welcome.mastodonLogo.image : Asset.Scene.Welcome.mastodonLogoBlackLarge.image
|
||||||
let imageView = UIImageView(image: image)
|
let imageView = UIImageView(image: image)
|
||||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
return imageView
|
return imageView
|
||||||
|
|
|
@ -100,7 +100,7 @@ final class ProfileHeaderView: UIView {
|
||||||
label.font = UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: .systemFont(ofSize: 15, weight: .regular))
|
label.font = UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: .systemFont(ofSize: 15, weight: .regular))
|
||||||
label.adjustsFontSizeToFitWidth = true
|
label.adjustsFontSizeToFitWidth = true
|
||||||
label.minimumScaleFactor = 0.5
|
label.minimumScaleFactor = 0.5
|
||||||
label.textColor = Asset.Profile.Banner.usernameGray.color
|
label.textColor = Asset.Scene.Profile.Banner.usernameGray.color
|
||||||
label.text = "@alice"
|
label.text = "@alice"
|
||||||
label.applyShadow(color: UIColor.black.withAlphaComponent(0.2), alpha: 0.5, x: 0, y: 2, blur: 2, spread: 0)
|
label.applyShadow(color: UIColor.black.withAlphaComponent(0.2), alpha: 0.5, x: 0, y: 2, blur: 2, spread: 0)
|
||||||
return label
|
return label
|
||||||
|
@ -131,7 +131,7 @@ final class ProfileHeaderView: UIView {
|
||||||
textEditorView.scrollView.isScrollEnabled = false
|
textEditorView.scrollView.isScrollEnabled = false
|
||||||
textEditorView.isScrollEnabled = false
|
textEditorView.isScrollEnabled = false
|
||||||
textEditorView.font = .preferredFont(forTextStyle: .body)
|
textEditorView.font = .preferredFont(forTextStyle: .body)
|
||||||
textEditorView.backgroundColor = Asset.Profile.Banner.bioEditBackgroundGray.color
|
textEditorView.backgroundColor = Asset.Scene.Profile.Banner.bioEditBackgroundGray.color
|
||||||
textEditorView.layer.masksToBounds = true
|
textEditorView.layer.masksToBounds = true
|
||||||
textEditorView.layer.cornerCurve = .continuous
|
textEditorView.layer.cornerCurve = .continuous
|
||||||
textEditorView.layer.cornerRadius = 10
|
textEditorView.layer.cornerRadius = 10
|
||||||
|
@ -356,9 +356,9 @@ extension ProfileHeaderView {
|
||||||
bioTextEditorView.backgroundColor = .clear
|
bioTextEditorView.backgroundColor = .clear
|
||||||
animator.addAnimations {
|
animator.addAnimations {
|
||||||
self.bannerImageViewOverlayView.backgroundColor = ProfileHeaderView.bannerImageViewOverlayViewBackgroundEditingColor
|
self.bannerImageViewOverlayView.backgroundColor = ProfileHeaderView.bannerImageViewOverlayViewBackgroundEditingColor
|
||||||
self.nameTextFieldBackgroundView.backgroundColor = Asset.Profile.Banner.nameEditBackgroundGray.color
|
self.nameTextFieldBackgroundView.backgroundColor = Asset.Scene.Profile.Banner.nameEditBackgroundGray.color
|
||||||
self.editAvatarBackgroundView.alpha = 1
|
self.editAvatarBackgroundView.alpha = 1
|
||||||
self.bioTextEditorView.backgroundColor = Asset.Profile.Banner.bioEditBackgroundGray.color
|
self.bioTextEditorView.backgroundColor = Asset.Scene.Profile.Banner.bioEditBackgroundGray.color
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ final class StatusView: UIView {
|
||||||
static let avatarToLabelSpacing: CGFloat = 5
|
static let avatarToLabelSpacing: CGFloat = 5
|
||||||
static let contentWarningBlurRadius: CGFloat = 12
|
static let contentWarningBlurRadius: CGFloat = 12
|
||||||
|
|
||||||
static let boostIconImage: UIImage = {
|
static let reblogIconImage: UIImage = {
|
||||||
let font = UIFont.systemFont(ofSize: 13, weight: .medium)
|
let font = UIFont.systemFont(ofSize: 13, weight: .medium)
|
||||||
let configuration = UIImage.SymbolConfiguration(font: font)
|
let configuration = UIImage.SymbolConfiguration(font: font)
|
||||||
let image = UIImage(systemName: "arrow.2.squarepath", withConfiguration: configuration)!.withTintColor(Asset.Colors.Label.secondary.color)
|
let image = UIImage(systemName: "arrow.2.squarepath", withConfiguration: configuration)!.withTintColor(Asset.Colors.Label.secondary.color)
|
||||||
|
@ -61,7 +61,7 @@ final class StatusView: UIView {
|
||||||
|
|
||||||
let headerIconLabel: UILabel = {
|
let headerIconLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.attributedText = StatusView.iconAttributedString(image: StatusView.boostIconImage)
|
label.attributedText = StatusView.iconAttributedString(image: StatusView.reblogIconImage)
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|