Merge branch 'release/0.8.3'

This commit is contained in:
CMK 2021-07-03 03:38:34 +08:00
commit 8fdcf8a6b1
28 changed files with 672 additions and 501 deletions

View File

@ -412,6 +412,7 @@
DB9E0D6F25EE008500CFDD76 /* UIInterpolatingMotionEffect.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9E0D6E25EE008500CFDD76 /* UIInterpolatingMotionEffect.swift */; };
DBA0A10925FB3C2B0079C110 /* RoundedEdgesButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA0A10825FB3C2B0079C110 /* RoundedEdgesButton.swift */; };
DBA0A11325FB3FC10079C110 /* ComposeToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA0A11225FB3FC10079C110 /* ComposeToolbarView.swift */; };
DBA1DB80268F84F80052DB59 /* NotificationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA1DB7F268F84F80052DB59 /* NotificationType.swift */; };
DBA5E7A3263AD0A3004598BB /* PhotoLibraryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA5E7A2263AD0A3004598BB /* PhotoLibraryService.swift */; };
DBA5E7A5263BD28C004598BB /* ContextMenuImagePreviewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA5E7A4263BD28C004598BB /* ContextMenuImagePreviewViewModel.swift */; };
DBA5E7A9263BD3A4004598BB /* ContextMenuImagePreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA5E7A8263BD3A4004598BB /* ContextMenuImagePreviewViewController.swift */; };
@ -1031,6 +1032,7 @@
DB9E0D6E25EE008500CFDD76 /* UIInterpolatingMotionEffect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIInterpolatingMotionEffect.swift; sourceTree = "<group>"; };
DBA0A10825FB3C2B0079C110 /* RoundedEdgesButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedEdgesButton.swift; sourceTree = "<group>"; };
DBA0A11225FB3FC10079C110 /* ComposeToolbarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeToolbarView.swift; sourceTree = "<group>"; };
DBA1DB7F268F84F80052DB59 /* NotificationType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationType.swift; sourceTree = "<group>"; };
DBA5E7A2263AD0A3004598BB /* PhotoLibraryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoLibraryService.swift; sourceTree = "<group>"; };
DBA5E7A4263BD28C004598BB /* ContextMenuImagePreviewViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenuImagePreviewViewModel.swift; sourceTree = "<group>"; };
DBA5E7A8263BD3A4004598BB /* ContextMenuImagePreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenuImagePreviewViewController.swift; sourceTree = "<group>"; };
@ -1776,6 +1778,7 @@
DB6D9F4F2635761F008423CD /* SubscriptionAlerts.swift */,
DBAFB7342645463500371D5F /* Emojis.swift */,
DBA94439265CC0FC00C537E1 /* Fields.swift */,
DBA1DB7F268F84F80052DB59 /* NotificationType.swift */,
);
path = CoreDataStack;
sourceTree = "<group>";
@ -3181,6 +3184,7 @@
DBAC648F267DC84D007FE9FD /* TableNodeDiffableDataSource.swift in Sources */,
2D8434FB25FF46B300EECE90 /* HomeTimelineNavigationBarTitleView.swift in Sources */,
0F1E2D0B2615C39400C38565 /* DoubleTitleLabelNavigationBarTitleView.swift in Sources */,
DBA1DB80268F84F80052DB59 /* NotificationType.swift in Sources */,
DB9A488A26034D40008B817C /* ComposeViewModel+PublishState.swift in Sources */,
DBAC649B267DF8C8007FE9FD /* ActivityIndicatorNode.swift in Sources */,
DB45FAD725CA6C76005A8AC7 /* UIBarButtonItem.swift in Sources */,
@ -3843,7 +3847,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 27;
CURRENT_PROJECT_VERSION = 28;
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = Mastodon/Info.plist;
@ -3851,7 +3855,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.8.2;
MARKETING_VERSION = 0.8.3;
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -3870,7 +3874,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 27;
CURRENT_PROJECT_VERSION = 28;
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = Mastodon/Info.plist;
@ -3878,7 +3882,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.8.2;
MARKETING_VERSION = 0.8.3;
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -4198,7 +4202,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 27;
CURRENT_PROJECT_VERSION = 28;
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = Mastodon/Info.plist;
@ -4206,7 +4210,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.8.2;
MARKETING_VERSION = 0.8.3;
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -4312,7 +4316,7 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 27;
CURRENT_PROJECT_VERSION = 28;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = NotificationService/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@ -4320,7 +4324,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 0.8.2;
MARKETING_VERSION = 0.8.3;
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@ -4431,7 +4435,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 27;
CURRENT_PROJECT_VERSION = 28;
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = Mastodon/Info.plist;
@ -4439,7 +4443,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.8.2;
MARKETING_VERSION = 0.8.3;
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -4545,7 +4549,7 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 27;
CURRENT_PROJECT_VERSION = 28;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = NotificationService/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@ -4553,7 +4557,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 0.8.2;
MARKETING_VERSION = 0.8.3;
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@ -4599,7 +4603,7 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 27;
CURRENT_PROJECT_VERSION = 28;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = NotificationService/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@ -4607,7 +4611,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 0.8.2;
MARKETING_VERSION = 0.8.3;
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@ -4622,7 +4626,7 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 27;
CURRENT_PROJECT_VERSION = 28;
DEVELOPMENT_TEAM = 5Z4GVSS33P;
INFOPLIST_FILE = NotificationService/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@ -4630,7 +4634,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 0.8.2;
MARKETING_VERSION = 0.8.3;
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;

View File

@ -7,12 +7,12 @@
<key>AppShared.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>26</integer>
<integer>20</integer>
</dict>
<key>CoreDataStack.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>20</integer>
<integer>19</integer>
</dict>
<key>Mastodon - ASDK.xcscheme_^#shared#^_</key>
<dict>
@ -37,7 +37,7 @@
<key>NotificationService.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>21</integer>
<integer>18</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>

View File

@ -20,7 +20,6 @@ enum NotificationSection: Equatable, Hashable {
extension NotificationSection {
static func tableViewDiffableDataSource(
for tableView: UITableView,
timestampUpdatePublisher: AnyPublisher<Date, Never>,
managedObjectContext: NSManagedObjectContext,
delegate: NotificationTableViewCellDelegate,
dependency: NeedsDependency
@ -34,73 +33,46 @@ extension NotificationSection {
guard let notification = try? managedObjectContext.existingObject(with: objectID) as? MastodonNotification else {
return UITableViewCell()
}
guard let type = Mastodon.Entity.Notification.NotificationType(rawValue: notification.typeRaw) else {
// filter out invalid type using predicate
assertionFailure()
return UITableViewCell()
}
let createAt = notification.createAt
let timeText = createAt.timeAgoSinceNow
let actionText = type.actionText
let actionImageName = type.actionImageName
let color = type.color
if let status = notification.status {
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: NotificationStatusTableViewCell.self), for: indexPath) as! NotificationStatusTableViewCell
cell.delegate = delegate
let activeMastodonAuthenticationBox = dependency.context.authenticationService.activeMastodonAuthenticationBox.value
let requestUserID = activeMastodonAuthenticationBox?.userID ?? ""
let frame = CGRect(x: 0, y: 0, width: tableView.readableContentGuide.layoutFrame.width - NotificationStatusTableViewCell.statusPadding.left - NotificationStatusTableViewCell.statusPadding.right, height: tableView.readableContentGuide.layoutFrame.height)
StatusSection.configure(
cell: cell,
tableView: tableView,
dependency: dependency,
readableLayoutFrame: frame,
status: status,
requestUserID: requestUserID,
statusItemAttribute: attribute
)
cell.actionImageBackground.backgroundColor = color
cell.nameLabel.configure(content: notification.account.displayNameWithFallback, emojiDict: notification.account.emojiDict)
cell.actionLabel.text = actionText + " · " + timeText
timestampUpdatePublisher
.sink { [weak cell] _ in
guard let cell = cell else { return }
let timeText = createAt.timeAgoSinceNow
cell.actionLabel.text = actionText + " · " + timeText
}
.store(in: &cell.disposeBag)
if let url = notification.account.avatarImageURL() {
// configure author
cell.avatarImageViewTask = Nuke.loadImage(
with: url,
with: notification.account.avatarImageURL(),
options: ImageLoadingOptions(
placeholder: UIImage.placeholder(color: .systemFill),
transition: .fadeIn(duration: 0.2)
),
into: cell.avatarImageView
)
}
cell.avatarImageView.gesture().sink { [weak cell] _ in
cell?.delegate?.userAvatarDidPressed(notification: notification)
}
.store(in: &cell.disposeBag)
if let actionImage = UIImage(systemName: actionImageName, withConfiguration: UIImage.SymbolConfiguration(pointSize: 12, weight: .semibold))?.withRenderingMode(.alwaysTemplate) {
cell.actionImageView.image = actionImage
}
return cell
cell.actionImageView.image = UIImage(
systemName: notification.notificationType.actionImageName,
withConfiguration: UIImage.SymbolConfiguration(
pointSize: 12, weight: .semibold
)
)?.withRenderingMode(.alwaysTemplate)
cell.actionImageBackground.backgroundColor = notification.notificationType.color
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: NotificationTableViewCell.self), for: indexPath) as! NotificationTableViewCell
cell.delegate = delegate
timestampUpdatePublisher
// configure author name, notification description, timestamp
cell.nameLabel.configure(content: notification.account.displayNameWithFallback, emojiDict: notification.account.emojiDict)
let createAt = notification.createAt
let actionText = notification.notificationType.actionText
cell.actionLabel.text = actionText + " · " + createAt.timeAgoSinceNow
AppContext.shared.timestampUpdatePublisher
.receive(on: DispatchQueue.main)
.sink { [weak cell] _ in
guard let cell = cell else { return }
let timeText = createAt.timeAgoSinceNow
cell.actionLabel.text = actionText + " · " + timeText
cell.actionLabel.text = actionText + " · " + createAt.timeAgoSinceNow
}
.store(in: &cell.disposeBag)
// configure follow request (if exist)
if case .followRequest = notification.notificationType {
cell.acceptButton.publisher(for: .touchUpInside)
.sink { [weak cell] _ in
guard let cell = cell else { return }
@ -113,29 +85,43 @@ extension NotificationSection {
cell.delegate?.notificationTableViewCell(cell, notification: notification, rejectButtonDidPressed: cell.rejectButton)
}
.store(in: &cell.disposeBag)
cell.actionImageBackground.backgroundColor = color
cell.actionLabel.text = actionText + " · " + timeText
cell.nameLabel.configure(content: notification.account.displayNameWithFallback, emojiDict: notification.account.emojiDict)
if let url = notification.account.avatarImageURL() {
cell.avatarImageViewTask = Nuke.loadImage(
with: url,
options: ImageLoadingOptions(
placeholder: UIImage.placeholder(color: .systemFill),
transition: .fadeIn(duration: 0.2)
),
into: cell.avatarImageView
cell.buttonStackView.isHidden = false
} else {
cell.buttonStackView.isHidden = true
}
// configure status (if exist)
if let status = notification.status {
let frame = CGRect(
x: 0,
y: 0,
width: tableView.readableContentGuide.layoutFrame.width - NotificationStatusTableViewCell.statusPadding.left - NotificationStatusTableViewCell.statusPadding.right,
height: tableView.readableContentGuide.layoutFrame.height
)
StatusSection.configure(
cell: cell,
tableView: tableView,
dependency: dependency,
readableLayoutFrame: frame,
status: status,
requestUserID: notification.userID,
statusItemAttribute: attribute
)
cell.statusContainerView.isHidden = false
cell.containerStackView.alignment = .top
cell.containerStackViewBottomLayoutConstraint.constant = 0
} else {
if case .followRequest = notification.notificationType {
cell.containerStackView.alignment = .top
} else {
cell.containerStackView.alignment = .center
}
cell.avatarImageView.gesture().sink { [weak cell] _ in
cell?.delegate?.userAvatarDidPressed(notification: notification)
cell.statusContainerView.isHidden = true
cell.containerStackViewBottomLayoutConstraint.constant = 5 // 5pt margin when no status view
}
.store(in: &cell.disposeBag)
if let actionImage = UIImage(systemName: actionImageName, withConfiguration: UIImage.SymbolConfiguration(pointSize: 12, weight: .semibold))?.withRenderingMode(.alwaysTemplate) {
cell.actionImageView.image = actionImage
}
cell.buttonStackView.isHidden = (type != .followRequest)
return cell
}
case .bottomLoader:
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self)) as! TimelineBottomLoaderTableViewCell
cell.startAnimating()

View File

@ -512,13 +512,8 @@ extension StatusSection {
let name = author.displayName.isEmpty ? author.username : author.displayName
return L10n.Common.Controls.Status.userReblogged(name)
}()
MastodonStatusContent.parseResult(content: headerText, emojiDict: status.author.emojiDict)
.receive(on: DispatchQueue.main)
.sink { [weak cell] parseResult in
guard let cell = cell else { return }
cell.statusView.headerInfoLabel.configure(contentParseResult: parseResult)
}
.store(in: &cell.disposeBag)
// sync set display name to avoid layout issue
cell.statusView.headerInfoLabel.configure(content: headerText, emojiDict: status.author.emojiDict)
cell.statusView.headerInfoLabel.accessibilityLabel = headerText
cell.statusView.headerInfoLabel.isAccessibilityElement = true
} else if status.inReplyToID != nil {

View File

@ -0,0 +1,16 @@
//
// NotificationType.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-7-3.
//
import Foundation
import CoreDataStack
import MastodonSDK
extension MastodonNotification {
var notificationType: Mastodon.Entity.Notification.NotificationType {
return Mastodon.Entity.Notification.NotificationType(rawValue: typeRaw) ?? ._other(typeRaw)
}
}

View File

@ -40,10 +40,10 @@ internal enum Asset {
internal static let disabled = ColorAsset(name: "Colors/Background/Poll/disabled")
}
internal static let alertYellow = ColorAsset(name: "Colors/Background/alert.yellow")
internal static let bar = ColorAsset(name: "Colors/Background/bar")
internal static let dangerBorder = ColorAsset(name: "Colors/Background/danger.border")
internal static let danger = ColorAsset(name: "Colors/Background/danger")
internal static let mediaTypeIndicotor = ColorAsset(name: "Colors/Background/media.type.indicotor")
internal static let navigationBar = ColorAsset(name: "Colors/Background/navigationBar")
internal static let onboardingBackground = ColorAsset(name: "Colors/Background/onboarding.background")
internal static let secondaryGroupedSystemBackground = ColorAsset(name: "Colors/Background/secondary.grouped.system.background")
internal static let secondarySystemBackground = ColorAsset(name: "Colors/Background/secondary.system.background")
@ -54,8 +54,10 @@ internal enum Asset {
internal static let tertiarySystemGroupedBackground = ColorAsset(name: "Colors/Background/tertiary.system.grouped.background")
}
internal enum Border {
internal static let notification = ColorAsset(name: "Colors/Border/notification")
internal static let composePoll = ColorAsset(name: "Colors/Border/compose.poll")
internal static let notificationStatus = ColorAsset(name: "Colors/Border/notification.status")
internal static let searchCard = ColorAsset(name: "Colors/Border/searchCard")
internal static let status = ColorAsset(name: "Colors/Border/status")
}
internal enum Button {
internal static let actionToolbar = ColorAsset(name: "Colors/Button/action.toolbar")
@ -84,6 +86,9 @@ internal enum Asset {
internal enum Slider {
internal static let track = ColorAsset(name: "Colors/Slider/track")
}
internal enum TabBar {
internal static let itemInactive = ColorAsset(name: "Colors/TabBar/item.inactive")
}
internal enum TextField {
internal static let background = ColorAsset(name: "Colors/TextField/background")
internal static let invalid = ColorAsset(name: "Colors/TextField/invalid")

View File

@ -22,10 +22,10 @@
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.940",
"blue" : "29",
"green" : "29",
"red" : "29"
"alpha" : "1.000",
"blue" : "0.263",
"green" : "0.208",
"red" : "0.192"
}
},
"idiom" : "universal"

View File

@ -23,9 +23,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x43",
"green" : "0x35",
"red" : "0x31"
"blue" : "67",
"green" : "53",
"red" : "49"
}
},
"idiom" : "universal"

View File

@ -23,9 +23,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.216",
"green" : "0.173",
"red" : "0.157"
"blue" : "55",
"green" : "44",
"red" : "40"
}
},
"idiom" : "universal"

View File

@ -23,9 +23,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.263",
"green" : "0.208",
"red" : "0.192"
"blue" : "67",
"green" : "53",
"red" : "49"
}
},
"idiom" : "universal"

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.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" : "0.431",
"green" : "0.341",
"red" : "0.310"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -5,9 +5,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xE8",
"green" : "0xE1",
"red" : "0xD9"
"blue" : "232",
"green" : "225",
"red" : "217"
}
},
"idiom" : "universal"
@ -23,9 +23,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "60",
"green" : "58",
"red" : "58"
"blue" : "110",
"green" : "87",
"red" : "79"
}
},
"idiom" : "universal"

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.003",
"blue" : "213",
"green" : "213",
"red" : "213"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -5,9 +5,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.549",
"green" : "0.510",
"red" : "0.431"
"blue" : "140",
"green" : "130",
"red" : "110"
}
},
"idiom" : "universal"
@ -23,9 +23,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.392",
"green" : "0.365",
"red" : "0.310"
"blue" : "100",
"green" : "93",
"red" : "79"
}
},
"idiom" : "universal"

View File

@ -0,0 +1,9 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"provides-namespace" : true
}
}

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "140",
"green" : "130",
"red" : "110"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "200",
"green" : "174",
"red" : "155"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -100,7 +100,7 @@ extension MainTabBarController {
delegate = self
view.backgroundColor = .systemBackground
view.backgroundColor = Asset.Colors.Background.systemBackground.color
let tabs = Tab.allCases
let viewControllers: [UIViewController] = tabs.map { tab in
@ -113,13 +113,6 @@ extension MainTabBarController {
setViewControllers(viewControllers, animated: false)
selectedIndex = 0
// TODO: custom accent color
let tabBarAppearance = UITabBarAppearance()
tabBarAppearance.configureWithDefaultBackground()
tabBarAppearance.selectionIndicatorTintColor = Asset.Colors.brandBlue.color
tabBar.standardAppearance = tabBarAppearance
context.apiService.error
.receive(on: DispatchQueue.main)
.sink { [weak self] error in

View File

@ -32,11 +32,8 @@ final class NotificationViewController: UIViewController, NeedsDependency {
let tableView: UITableView = {
let tableView = ControlContainableTableView()
tableView.separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
tableView.register(NotificationTableViewCell.self, forCellReuseIdentifier: String(describing: NotificationTableViewCell.self))
tableView.register(NotificationStatusTableViewCell.self, forCellReuseIdentifier: String(describing: NotificationStatusTableViewCell.self))
tableView.register(TimelineBottomLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self))
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = UITableView.automaticDimension
tableView.separatorStyle = .none
tableView.tableFooterView = UIView()
@ -192,33 +189,33 @@ extension NotificationViewController {
extension NotificationViewController: StatusTableViewControllerAspect { }
// MARK: - TableViewCellHeightCacheableContainer
//extension NotificationViewController: TableViewCellHeightCacheableContainer {
// var cellFrameCache: NSCache<NSNumber, NSValue> {
// viewModel.cellFrameCache
// }
//
// func cacheTableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
// guard let diffableDataSource = viewModel.diffableDataSource else { return }
// guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
// let key = item.hashValue
// let frame = cell.frame
// viewModel.cellFrameCache.setObject(NSValue(cgRect: frame), forKey: NSNumber(value: key))
// }
//
// func handleTableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
// guard let diffableDataSource = viewModel.diffableDataSource else { return UITableView.automaticDimension }
// guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return UITableView.automaticDimension }
// guard let frame = viewModel.cellFrameCache.object(forKey: NSNumber(value: item.hashValue))?.cgRectValue else {
// if case .bottomLoader = item {
// return TimelineLoaderTableViewCell.cellHeight
// } else {
// return UITableView.automaticDimension
// }
// }
//
// return ceil(frame.height)
// }
//}
extension NotificationViewController: TableViewCellHeightCacheableContainer {
var cellFrameCache: NSCache<NSNumber, NSValue> {
viewModel.cellFrameCache
}
func cacheTableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard let diffableDataSource = viewModel.diffableDataSource else { return }
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
let key = item.hashValue
let frame = cell.frame
viewModel.cellFrameCache.setObject(NSValue(cgRect: frame), forKey: NSNumber(value: key))
}
func handleTableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
guard let diffableDataSource = viewModel.diffableDataSource else { return UITableView.automaticDimension }
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return UITableView.automaticDimension }
guard let frame = viewModel.cellFrameCache.object(forKey: NSNumber(value: item.hashValue))?.cgRectValue else {
if case .bottomLoader = item {
return TimelineLoaderTableViewCell.cellHeight
} else {
return UITableView.automaticDimension
}
}
return ceil(frame.height)
}
}
// MARK: - UITableViewDelegate
@ -274,11 +271,11 @@ extension NotificationViewController: ContentOffsetAdjustableTimelineViewControl
// MARK: - NotificationTableViewCellDelegate
extension NotificationViewController: NotificationTableViewCellDelegate {
func notificationTableViewCell(_ cell: NotificationTableViewCell, notification: MastodonNotification, acceptButtonDidPressed button: UIButton) {
func notificationTableViewCell(_ cell: NotificationStatusTableViewCell, notification: MastodonNotification, acceptButtonDidPressed button: UIButton) {
viewModel.acceptFollowRequest(notification: notification)
}
func notificationTableViewCell(_ cell: NotificationTableViewCell, notification: MastodonNotification, rejectButtonDidPressed button: UIButton) {
func notificationTableViewCell(_ cell: NotificationStatusTableViewCell, notification: MastodonNotification, rejectButtonDidPressed button: UIButton) {
viewModel.rejectFollowRequest(notification: notification)
}

View File

@ -17,14 +17,8 @@ extension NotificationViewModel {
delegate: NotificationTableViewCellDelegate,
dependency: NeedsDependency
) {
let timestampUpdatePublisher = Timer.publish(every: 1.0, on: .main, in: .common)
.autoconnect()
.share()
.eraseToAnyPublisher()
diffableDataSource = NotificationSection.tableViewDiffableDataSource(
for: tableView,
timestampUpdatePublisher: timestampUpdatePublisher,
managedObjectContext: context.managedObjectContext,
delegate: delegate,
dependency: dependency

View File

@ -15,12 +15,17 @@ import FLAnimatedImage
import Nuke
final class NotificationStatusTableViewCell: UITableViewCell, StatusCell {
static let actionImageBorderWidth: CGFloat = 2
static let statusPadding = UIEdgeInsets(top: 50, left: 73, bottom: 24, right: 24)
var disposeBag = Set<AnyCancellable>()
var pollCountdownSubscription: AnyCancellable?
var delegate: NotificationTableViewCellDelegate?
var containerStackViewBottomLayoutConstraint: NSLayoutConstraint!
let containerStackView = UIStackView()
var avatarImageViewTask: ImageTask?
let avatarImageView: UIImageView = {
let imageView = FLAnimatedImageView()
@ -52,6 +57,8 @@ final class NotificationStatusTableViewCell: UITableViewCell, StatusCell {
return view
}()
let contentStackView = UIStackView()
let actionLabel: UILabel = {
let label = UILabel()
label.textColor = Asset.Colors.Label.secondary.color
@ -68,17 +75,33 @@ final class NotificationStatusTableViewCell: UITableViewCell, StatusCell {
return label
}()
let statusBorder: UIView = {
let view = UIView()
view.backgroundColor = .clear
view.layer.cornerRadius = 6
view.layer.borderWidth = 2
view.layer.cornerCurve = .continuous
view.layer.borderColor = Asset.Colors.Border.notification.color.cgColor
view.clipsToBounds = true
return view
let buttonStackView = UIStackView()
let acceptButton: UIButton = {
let button = UIButton(type: .custom)
let actionImage = UIImage(systemName: "checkmark.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 28, weight: .semibold))?.withRenderingMode(.alwaysTemplate)
button.setImage(actionImage, for: .normal)
button.tintColor = Asset.Colors.Label.secondary.color
return button
}()
let rejectButton: UIButton = {
let button = UIButton(type: .custom)
let actionImage = UIImage(systemName: "xmark.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 28, weight: .semibold))?.withRenderingMode(.alwaysTemplate)
button.setImage(actionImage, for: .normal)
button.tintColor = Asset.Colors.Label.secondary.color
return button
}()
let statusContainerView: UIView = {
let view = UIView()
view.layer.masksToBounds = true
view.layer.cornerRadius = 6
view.layer.cornerCurve = .continuous
view.layer.borderWidth = 2
view.layer.borderColor = Asset.Colors.Border.notificationStatus.color.cgColor
return view
}()
let statusView = StatusView()
let separatorLine = UIView.separatorLine
@ -120,91 +143,102 @@ extension NotificationStatusTableViewCell {
view.backgroundColor = Asset.Colors.Background.Cell.highlight.color
return view
}()
let containerStackView = UIStackView()
containerStackView.axis = .horizontal
containerStackView.alignment = .top
containerStackView.spacing = 4
containerStackView.distribution = .fill
containerStackView.spacing = 14 + 2 // 2pt for status container outline border
containerStackView.layoutMargins = UIEdgeInsets(top: 14, left: 0, bottom: 12, right: 0)
containerStackView.isLayoutMarginsRelativeArrangement = true
containerStackView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(containerStackView)
containerStackViewBottomLayoutConstraint = contentView.bottomAnchor.constraint(equalTo: containerStackView.bottomAnchor)
NSLayoutConstraint.activate([
containerStackView.topAnchor.constraint(equalTo: contentView.topAnchor),
containerStackView.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
contentView.readableContentGuide.trailingAnchor.constraint(equalTo: containerStackView.trailingAnchor),
contentView.bottomAnchor.constraint(equalTo: containerStackView.bottomAnchor),
containerStackViewBottomLayoutConstraint.priority(.required - 1),
])
containerStackView.addArrangedSubview(avatarContainer)
avatarContainer.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
avatarContainer.heightAnchor.constraint(equalToConstant: 47).priority(.required - 1),
avatarContainer.widthAnchor.constraint(equalToConstant: 47).priority(.required - 1)
])
avatarContainer.addSubview(avatarImageView)
avatarImageView.translatesAutoresizingMaskIntoConstraints = false
avatarContainer.addSubview(avatarImageView)
NSLayoutConstraint.activate([
avatarImageView.topAnchor.constraint(equalTo: avatarContainer.topAnchor),
avatarImageView.leadingAnchor.constraint(equalTo: avatarContainer.leadingAnchor),
avatarImageView.trailingAnchor.constraint(equalTo: avatarContainer.trailingAnchor),
avatarImageView.bottomAnchor.constraint(equalTo: avatarContainer.bottomAnchor),
avatarImageView.heightAnchor.constraint(equalToConstant: 35).priority(.required - 1),
avatarImageView.widthAnchor.constraint(equalToConstant: 35).priority(.required - 1),
avatarImageView.topAnchor.constraint(equalTo: avatarContainer.topAnchor),
avatarImageView.leadingAnchor.constraint(equalTo: avatarContainer.leadingAnchor)
])
avatarContainer.addSubview(actionImageBackground)
actionImageBackground.translatesAutoresizingMaskIntoConstraints = false
avatarContainer.addSubview(actionImageBackground)
NSLayoutConstraint.activate([
actionImageBackground.heightAnchor.constraint(equalToConstant: 24 + NotificationTableViewCell.actionImageBorderWidth).priority(.required - 1),
actionImageBackground.widthAnchor.constraint(equalToConstant: 24 + NotificationTableViewCell.actionImageBorderWidth).priority(.required - 1),
actionImageBackground.bottomAnchor.constraint(equalTo: avatarContainer.bottomAnchor),
actionImageBackground.trailingAnchor.constraint(equalTo: avatarContainer.trailingAnchor)
actionImageBackground.heightAnchor.constraint(equalToConstant: 24 + NotificationStatusTableViewCell.actionImageBorderWidth).priority(.required - 1),
actionImageBackground.widthAnchor.constraint(equalToConstant: 24 + NotificationStatusTableViewCell.actionImageBorderWidth).priority(.required - 1),
actionImageBackground.centerYAnchor.constraint(equalTo: avatarImageView.bottomAnchor),
actionImageBackground.centerXAnchor.constraint(equalTo: avatarContainer.trailingAnchor),
])
avatarContainer.addSubview(actionImageView)
actionImageView.translatesAutoresizingMaskIntoConstraints = false
actionImageBackground.addSubview(actionImageView)
NSLayoutConstraint.activate([
actionImageView.centerXAnchor.constraint(equalTo: actionImageBackground.centerXAnchor),
actionImageView.centerYAnchor.constraint(equalTo: actionImageBackground.centerYAnchor)
actionImageView.centerYAnchor.constraint(equalTo: actionImageBackground.centerYAnchor),
])
containerStackView.addArrangedSubview(contentStackView)
contentStackView.axis = .vertical
contentStackView.spacing = 6
// header
let actionStackView = UIStackView()
contentStackView.addArrangedSubview(actionStackView)
actionStackView.axis = .horizontal
actionStackView.distribution = .fill
actionStackView.spacing = 4
actionStackView.translatesAutoresizingMaskIntoConstraints = false
nameLabel.translatesAutoresizingMaskIntoConstraints = false
actionStackView.addArrangedSubview(nameLabel)
actionLabel.translatesAutoresizingMaskIntoConstraints = false
actionStackView.addArrangedSubview(actionLabel)
nameLabel.setContentHuggingPriority(.required - 1, for: .horizontal)
nameLabel.setContentHuggingPriority(.required - 1, for: .vertical)
nameLabel.setContentCompressionResistancePriority(.required - 1, for: .horizontal)
nameLabel.setContentCompressionResistancePriority(.required - 1, for: .vertical)
nameLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
actionLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)
let statusStackView = UIStackView()
statusStackView.axis = .vertical
statusStackView.distribution = .fill
statusStackView.spacing = 4
statusStackView.translatesAutoresizingMaskIntoConstraints = false
statusView.translatesAutoresizingMaskIntoConstraints = false
statusStackView.addArrangedSubview(actionStackView)
// follow request
contentStackView.addArrangedSubview(buttonStackView)
buttonStackView.addArrangedSubview(acceptButton)
buttonStackView.addArrangedSubview(rejectButton)
buttonStackView.axis = .horizontal
buttonStackView.distribution = .fillEqually
statusBorder.translatesAutoresizingMaskIntoConstraints = false
// status
contentStackView.addArrangedSubview(statusContainerView)
statusContainerView.layoutMargins = UIEdgeInsets(top: 12, left: 12, bottom: 12, right: 12)
statusView.translatesAutoresizingMaskIntoConstraints = false
statusBorder.addSubview(statusView)
statusContainerView.addSubview(statusView)
NSLayoutConstraint.activate([
statusView.topAnchor.constraint(equalTo: statusBorder.topAnchor, constant: 12),
statusView.leadingAnchor.constraint(equalTo: statusBorder.leadingAnchor, constant: 12),
statusBorder.bottomAnchor.constraint(equalTo: statusView.bottomAnchor, constant: 12),
statusBorder.trailingAnchor.constraint(equalTo: statusView.trailingAnchor, constant: 12),
statusView.topAnchor.constraint(equalTo: statusContainerView.layoutMarginsGuide.topAnchor),
statusView.leadingAnchor.constraint(equalTo: statusContainerView.layoutMarginsGuide.leadingAnchor),
statusView.trailingAnchor.constraint(equalTo: statusContainerView.layoutMarginsGuide.trailingAnchor),
statusView.bottomAnchor.constraint(equalTo: statusContainerView.layoutMarginsGuide.bottomAnchor),
])
statusContainerView.backgroundColor = UIColor(dynamicProvider: { collection in
switch collection.userInterfaceStyle {
case .dark:
return Asset.Colors.Background.tertiarySystemGroupedBackground.color
default:
return .clear
}
})
// remove item don't display
statusView.actionToolbarContainer.removeFromStackView()
// it affect stackView's height, need remove
statusView.headerContainerView.removeFromStackView()
statusView.delegate = self
statusStackView.addArrangedSubview(statusBorder)
containerStackView.addArrangedSubview(statusStackView)
// adaptive separator
separatorLine.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(separatorLine)
separatorLineToEdgeLeadingLayoutConstraint = separatorLine.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
@ -215,21 +249,18 @@ extension NotificationStatusTableViewCell {
separatorLine.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
separatorLine.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: contentView)),
])
resetSeparatorLineLayout()
// remove item don't display
statusView.actionToolbarContainer.removeFromStackView()
// it affect stackView's height,need remove
statusView.avatarView.removeFromStackView()
statusView.usernameLabel.removeFromStackView()
statusView.delegate = self
resetSeparatorLineLayout()
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
resetSeparatorLineLayout()
statusBorder.layer.borderColor = Asset.Colors.Border.notification.color.cgColor
actionImageBackground.layer.borderColor = Asset.Colors.Background.systemBackground.color.cgColor
statusContainerView.layer.borderColor = Asset.Colors.Border.notificationStatus.color.cgColor
}
}

View File

@ -28,238 +28,238 @@ protocol NotificationTableViewCellDelegate: AnyObject {
func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, playerContainerView: PlayerContainerView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView)
func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta)
func notificationTableViewCell(_ cell: NotificationTableViewCell, notification: MastodonNotification, acceptButtonDidPressed button: UIButton)
func notificationTableViewCell(_ cell: NotificationTableViewCell, notification: MastodonNotification, rejectButtonDidPressed button: UIButton)
func notificationTableViewCell(_ cell: NotificationStatusTableViewCell, notification: MastodonNotification, acceptButtonDidPressed button: UIButton)
func notificationTableViewCell(_ cell: NotificationStatusTableViewCell, notification: MastodonNotification, rejectButtonDidPressed button: UIButton)
}
final class NotificationTableViewCell: UITableViewCell {
static let actionImageBorderWidth: CGFloat = 2
var disposeBag = Set<AnyCancellable>()
var delegate: NotificationTableViewCellDelegate?
var avatarImageViewTask: ImageTask?
let avatarImageView: UIImageView = {
let imageView = FLAnimatedImageView()
imageView.layer.cornerRadius = 4
imageView.layer.cornerCurve = .continuous
imageView.clipsToBounds = true
return imageView
}()
let actionImageView: UIImageView = {
let imageView = UIImageView()
imageView.tintColor = Asset.Colors.Background.systemBackground.color
return imageView
}()
let actionImageBackground: UIView = {
let view = UIView()
view.layer.cornerRadius = (24 + NotificationTableViewCell.actionImageBorderWidth) / 2
view.layer.cornerCurve = .continuous
view.clipsToBounds = true
view.layer.borderWidth = NotificationTableViewCell.actionImageBorderWidth
view.layer.borderColor = Asset.Colors.Background.systemBackground.color.cgColor
view.tintColor = Asset.Colors.Background.systemBackground.color
return view
}()
let avatarContainer: UIView = {
let view = UIView()
return view
}()
let actionLabel: UILabel = {
let label = UILabel()
label.textColor = Asset.Colors.Label.secondary.color
label.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .regular), maximumPointSize: 20)
label.lineBreakMode = .byTruncatingTail
return label
}()
let nameLabel: ActiveLabel = {
let label = ActiveLabel()
label.textColor = Asset.Colors.brandBlue.color
label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .semibold), maximumPointSize: 20)
label.lineBreakMode = .byTruncatingTail
return label
}()
let acceptButton: UIButton = {
let button = UIButton(type: .custom)
let actionImage = UIImage(systemName: "checkmark.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 28, weight: .semibold))?.withRenderingMode(.alwaysTemplate)
button.setImage(actionImage, for: .normal)
button.tintColor = Asset.Colors.Label.secondary.color
return button
}()
let rejectButton: UIButton = {
let button = UIButton(type: .custom)
let actionImage = UIImage(systemName: "xmark.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 28, weight: .semibold))?.withRenderingMode(.alwaysTemplate)
button.setImage(actionImage, for: .normal)
button.tintColor = Asset.Colors.Label.secondary.color
return button
}()
let buttonStackView = UIStackView()
let separatorLine = UIView.separatorLine
var separatorLineToEdgeLeadingLayoutConstraint: NSLayoutConstraint!
var separatorLineToEdgeTrailingLayoutConstraint: NSLayoutConstraint!
var separatorLineToMarginLeadingLayoutConstraint: NSLayoutConstraint!
var separatorLineToMarginTrailingLayoutConstraint: NSLayoutConstraint!
override func prepareForReuse() {
super.prepareForReuse()
avatarImageViewTask?.cancel()
avatarImageViewTask = nil
disposeBag.removeAll()
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configure()
}
}
extension NotificationTableViewCell {
func configure() {
backgroundColor = Asset.Colors.Background.systemBackground.color
selectedBackgroundView = {
let view = UIView()
view.backgroundColor = Asset.Colors.Background.Cell.highlight.color
return view
}()
let containerStackView = UIStackView()
containerStackView.axis = .vertical
containerStackView.alignment = .fill
containerStackView.layoutMargins = UIEdgeInsets(top: 14, left: 0, bottom: 12, right: 0)
containerStackView.isLayoutMarginsRelativeArrangement = true
containerStackView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(containerStackView)
NSLayoutConstraint.activate([
containerStackView.topAnchor.constraint(equalTo: contentView.topAnchor),
containerStackView.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
contentView.readableContentGuide.trailingAnchor.constraint(equalTo: containerStackView.trailingAnchor),
contentView.bottomAnchor.constraint(equalTo: containerStackView.bottomAnchor),
])
let horizontalStackView = UIStackView()
horizontalStackView.translatesAutoresizingMaskIntoConstraints = false
horizontalStackView.axis = .horizontal
horizontalStackView.spacing = 6
horizontalStackView.addArrangedSubview(avatarContainer)
avatarContainer.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
avatarContainer.heightAnchor.constraint(equalToConstant: 47).priority(.required - 1),
avatarContainer.widthAnchor.constraint(equalToConstant: 47).priority(.required - 1)
])
avatarContainer.addSubview(avatarImageView)
avatarImageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
avatarImageView.heightAnchor.constraint(equalToConstant: 35).priority(.required - 1),
avatarImageView.widthAnchor.constraint(equalToConstant: 35).priority(.required - 1),
avatarImageView.topAnchor.constraint(equalTo: avatarContainer.topAnchor),
avatarImageView.leadingAnchor.constraint(equalTo: avatarContainer.leadingAnchor)
])
avatarContainer.addSubview(actionImageBackground)
actionImageBackground.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
actionImageBackground.heightAnchor.constraint(equalToConstant: 24 + NotificationTableViewCell.actionImageBorderWidth).priority(.required - 1),
actionImageBackground.widthAnchor.constraint(equalToConstant: 24 + NotificationTableViewCell.actionImageBorderWidth).priority(.required - 1),
actionImageBackground.bottomAnchor.constraint(equalTo: avatarContainer.bottomAnchor),
actionImageBackground.trailingAnchor.constraint(equalTo: avatarContainer.trailingAnchor)
])
avatarContainer.addSubview(actionImageView)
actionImageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
actionImageView.centerXAnchor.constraint(equalTo: actionImageBackground.centerXAnchor),
actionImageView.centerYAnchor.constraint(equalTo: actionImageBackground.centerYAnchor)
])
nameLabel.translatesAutoresizingMaskIntoConstraints = false
horizontalStackView.addArrangedSubview(nameLabel)
actionLabel.translatesAutoresizingMaskIntoConstraints = false
horizontalStackView.addArrangedSubview(actionLabel)
nameLabel.setContentCompressionResistancePriority(.required - 1, for: .horizontal)
nameLabel.setContentHuggingPriority(.required - 1, for: .horizontal)
actionLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)
containerStackView.addArrangedSubview(horizontalStackView)
buttonStackView.translatesAutoresizingMaskIntoConstraints = false
buttonStackView.axis = .horizontal
buttonStackView.distribution = .fillEqually
acceptButton.translatesAutoresizingMaskIntoConstraints = false
rejectButton.translatesAutoresizingMaskIntoConstraints = false
buttonStackView.addArrangedSubview(acceptButton)
buttonStackView.addArrangedSubview(rejectButton)
containerStackView.addArrangedSubview(buttonStackView)
separatorLine.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(separatorLine)
separatorLineToEdgeLeadingLayoutConstraint = separatorLine.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
separatorLineToEdgeTrailingLayoutConstraint = separatorLine.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
separatorLineToMarginLeadingLayoutConstraint = separatorLine.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor)
separatorLineToMarginTrailingLayoutConstraint = separatorLine.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor)
NSLayoutConstraint.activate([
separatorLine.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
separatorLine.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: contentView)),
])
resetSeparatorLineLayout()
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
actionImageBackground.layer.borderColor = Asset.Colors.Background.systemBackground.color.cgColor
resetSeparatorLineLayout()
}
}
extension NotificationTableViewCell {
private func resetSeparatorLineLayout() {
separatorLineToEdgeLeadingLayoutConstraint.isActive = false
separatorLineToEdgeTrailingLayoutConstraint.isActive = false
separatorLineToMarginLeadingLayoutConstraint.isActive = false
separatorLineToMarginTrailingLayoutConstraint.isActive = false
if traitCollection.userInterfaceIdiom == .phone {
// to edge
NSLayoutConstraint.activate([
separatorLineToEdgeLeadingLayoutConstraint,
separatorLineToEdgeTrailingLayoutConstraint,
])
} else {
if traitCollection.horizontalSizeClass == .compact {
// to edge
NSLayoutConstraint.activate([
separatorLineToEdgeLeadingLayoutConstraint,
separatorLineToEdgeTrailingLayoutConstraint,
])
} else {
// to margin
NSLayoutConstraint.activate([
separatorLineToMarginLeadingLayoutConstraint,
separatorLineToMarginTrailingLayoutConstraint,
])
}
}
}
}
//final class NotificationTableViewCell: UITableViewCell {
// static let actionImageBorderWidth: CGFloat = 2
//
// var disposeBag = Set<AnyCancellable>()
//
// var delegate: NotificationTableViewCellDelegate?
//
// var avatarImageViewTask: ImageTask?
// let avatarImageView: UIImageView = {
// let imageView = FLAnimatedImageView()
// imageView.layer.cornerRadius = 4
// imageView.layer.cornerCurve = .continuous
// imageView.clipsToBounds = true
// return imageView
// }()
//
// let actionImageView: UIImageView = {
// let imageView = UIImageView()
// imageView.tintColor = Asset.Colors.Background.systemBackground.color
// return imageView
// }()
//
// let actionImageBackground: UIView = {
// let view = UIView()
// view.layer.cornerRadius = (24 + NotificationTableViewCell.actionImageBorderWidth) / 2
// view.layer.cornerCurve = .continuous
// view.clipsToBounds = true
// view.layer.borderWidth = NotificationTableViewCell.actionImageBorderWidth
// view.layer.borderColor = Asset.Colors.Background.systemBackground.color.cgColor
// view.tintColor = Asset.Colors.Background.systemBackground.color
// return view
// }()
//
// let avatarContainer: UIView = {
// let view = UIView()
// return view
// }()
//
// let actionLabel: UILabel = {
// let label = UILabel()
// label.textColor = Asset.Colors.Label.secondary.color
// label.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .regular), maximumPointSize: 20)
// label.lineBreakMode = .byTruncatingTail
// return label
// }()
//
// let nameLabel: ActiveLabel = {
// let label = ActiveLabel()
// label.textColor = Asset.Colors.brandBlue.color
// label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .semibold), maximumPointSize: 20)
// label.lineBreakMode = .byTruncatingTail
// return label
// }()
//
// let acceptButton: UIButton = {
// let button = UIButton(type: .custom)
// let actionImage = UIImage(systemName: "checkmark.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 28, weight: .semibold))?.withRenderingMode(.alwaysTemplate)
// button.setImage(actionImage, for: .normal)
// button.tintColor = Asset.Colors.Label.secondary.color
// return button
// }()
//
// let rejectButton: UIButton = {
// let button = UIButton(type: .custom)
// let actionImage = UIImage(systemName: "xmark.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 28, weight: .semibold))?.withRenderingMode(.alwaysTemplate)
// button.setImage(actionImage, for: .normal)
// button.tintColor = Asset.Colors.Label.secondary.color
// return button
// }()
//
// let buttonStackView = UIStackView()
//
// let separatorLine = UIView.separatorLine
//
// var separatorLineToEdgeLeadingLayoutConstraint: NSLayoutConstraint!
// var separatorLineToEdgeTrailingLayoutConstraint: NSLayoutConstraint!
//
// var separatorLineToMarginLeadingLayoutConstraint: NSLayoutConstraint!
// var separatorLineToMarginTrailingLayoutConstraint: NSLayoutConstraint!
//
// override func prepareForReuse() {
// super.prepareForReuse()
// avatarImageViewTask?.cancel()
// avatarImageViewTask = nil
// disposeBag.removeAll()
// }
//
// override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
// super.init(style: style, reuseIdentifier: reuseIdentifier)
// configure()
// }
//
// required init?(coder: NSCoder) {
// super.init(coder: coder)
// configure()
// }
//}
//
//extension NotificationTableViewCell {
// func configure() {
// backgroundColor = Asset.Colors.Background.systemBackground.color
// selectedBackgroundView = {
// let view = UIView()
// view.backgroundColor = Asset.Colors.Background.Cell.highlight.color
// return view
// }()
//
// let containerStackView = UIStackView()
// containerStackView.axis = .vertical
// containerStackView.alignment = .fill
// containerStackView.layoutMargins = UIEdgeInsets(top: 14, left: 0, bottom: 12, right: 0)
// containerStackView.isLayoutMarginsRelativeArrangement = true
// containerStackView.translatesAutoresizingMaskIntoConstraints = false
// contentView.addSubview(containerStackView)
// NSLayoutConstraint.activate([
// containerStackView.topAnchor.constraint(equalTo: contentView.topAnchor),
// containerStackView.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
// contentView.readableContentGuide.trailingAnchor.constraint(equalTo: containerStackView.trailingAnchor),
// contentView.bottomAnchor.constraint(equalTo: containerStackView.bottomAnchor),
// ])
//
// let horizontalStackView = UIStackView()
// horizontalStackView.translatesAutoresizingMaskIntoConstraints = false
// horizontalStackView.axis = .horizontal
// horizontalStackView.spacing = 6
//
// horizontalStackView.addArrangedSubview(avatarContainer)
// avatarContainer.translatesAutoresizingMaskIntoConstraints = false
// NSLayoutConstraint.activate([
// avatarContainer.heightAnchor.constraint(equalToConstant: 47).priority(.required - 1),
// avatarContainer.widthAnchor.constraint(equalToConstant: 47).priority(.required - 1)
// ])
//
// avatarContainer.addSubview(avatarImageView)
// avatarImageView.translatesAutoresizingMaskIntoConstraints = false
// NSLayoutConstraint.activate([
// avatarImageView.heightAnchor.constraint(equalToConstant: 35).priority(.required - 1),
// avatarImageView.widthAnchor.constraint(equalToConstant: 35).priority(.required - 1),
// avatarImageView.topAnchor.constraint(equalTo: avatarContainer.topAnchor),
// avatarImageView.leadingAnchor.constraint(equalTo: avatarContainer.leadingAnchor)
// ])
//
// avatarContainer.addSubview(actionImageBackground)
// actionImageBackground.translatesAutoresizingMaskIntoConstraints = false
// NSLayoutConstraint.activate([
// actionImageBackground.heightAnchor.constraint(equalToConstant: 24 + NotificationTableViewCell.actionImageBorderWidth).priority(.required - 1),
// actionImageBackground.widthAnchor.constraint(equalToConstant: 24 + NotificationTableViewCell.actionImageBorderWidth).priority(.required - 1),
// actionImageBackground.bottomAnchor.constraint(equalTo: avatarContainer.bottomAnchor),
// actionImageBackground.trailingAnchor.constraint(equalTo: avatarContainer.trailingAnchor)
// ])
//
// avatarContainer.addSubview(actionImageView)
// actionImageView.translatesAutoresizingMaskIntoConstraints = false
// NSLayoutConstraint.activate([
// actionImageView.centerXAnchor.constraint(equalTo: actionImageBackground.centerXAnchor),
// actionImageView.centerYAnchor.constraint(equalTo: actionImageBackground.centerYAnchor)
// ])
//
// nameLabel.translatesAutoresizingMaskIntoConstraints = false
// horizontalStackView.addArrangedSubview(nameLabel)
// actionLabel.translatesAutoresizingMaskIntoConstraints = false
// horizontalStackView.addArrangedSubview(actionLabel)
// nameLabel.setContentCompressionResistancePriority(.required - 1, for: .horizontal)
// nameLabel.setContentHuggingPriority(.required - 1, for: .horizontal)
// actionLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)
//
// containerStackView.addArrangedSubview(horizontalStackView)
//
// buttonStackView.translatesAutoresizingMaskIntoConstraints = false
// buttonStackView.axis = .horizontal
// buttonStackView.distribution = .fillEqually
// acceptButton.translatesAutoresizingMaskIntoConstraints = false
// rejectButton.translatesAutoresizingMaskIntoConstraints = false
// buttonStackView.addArrangedSubview(acceptButton)
// buttonStackView.addArrangedSubview(rejectButton)
// containerStackView.addArrangedSubview(buttonStackView)
//
// separatorLine.translatesAutoresizingMaskIntoConstraints = false
// contentView.addSubview(separatorLine)
// separatorLineToEdgeLeadingLayoutConstraint = separatorLine.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
// separatorLineToEdgeTrailingLayoutConstraint = separatorLine.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
// separatorLineToMarginLeadingLayoutConstraint = separatorLine.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor)
// separatorLineToMarginTrailingLayoutConstraint = separatorLine.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor)
// NSLayoutConstraint.activate([
// separatorLine.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
// separatorLine.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: contentView)),
// ])
// resetSeparatorLineLayout()
// }
//
// override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
// super.traitCollectionDidChange(previousTraitCollection)
//
// actionImageBackground.layer.borderColor = Asset.Colors.Background.systemBackground.color.cgColor
// resetSeparatorLineLayout()
// }
//}
//
//extension NotificationTableViewCell {
//
// private func resetSeparatorLineLayout() {
// separatorLineToEdgeLeadingLayoutConstraint.isActive = false
// separatorLineToEdgeTrailingLayoutConstraint.isActive = false
// separatorLineToMarginLeadingLayoutConstraint.isActive = false
// separatorLineToMarginTrailingLayoutConstraint.isActive = false
//
// if traitCollection.userInterfaceIdiom == .phone {
// // to edge
// NSLayoutConstraint.activate([
// separatorLineToEdgeLeadingLayoutConstraint,
// separatorLineToEdgeTrailingLayoutConstraint,
// ])
// } else {
// if traitCollection.horizontalSizeClass == .compact {
// // to edge
// NSLayoutConstraint.activate([
// separatorLineToEdgeLeadingLayoutConstraint,
// separatorLineToEdgeTrailingLayoutConstraint,
// ])
// } else {
// // to margin
// NSLayoutConstraint.activate([
// separatorLineToMarginLeadingLayoutConstraint,
// separatorLineToMarginTrailingLayoutConstraint,
// ])
// }
// }
// }
//
//}

View File

@ -39,7 +39,7 @@ final class SearchViewController: UIViewController, NeedsDependency {
let statusBar: UIView = {
let view = UIView()
view.backgroundColor = Asset.Colors.Background.navigationBar.color
view.backgroundColor = Asset.Colors.Background.bar.color
return view
}()
@ -52,7 +52,7 @@ final class SearchViewController: UIViewController, NeedsDependency {
// searchBar.setImage(micImage, for: .bookmark, state: .normal)
// searchBar.showsBookmarkButton = true
searchBar.scopeButtonTitles = [L10n.Scene.Search.Searching.Segment.all, L10n.Scene.Search.Searching.Segment.people, L10n.Scene.Search.Searching.Segment.hashtags]
searchBar.barTintColor = Asset.Colors.Background.navigationBar.color
searchBar.barTintColor = Asset.Colors.Background.bar.color
return searchBar
}()

View File

@ -95,6 +95,7 @@ class SettingsViewController: UIViewController, NeedsDependency {
tableView.delegate = self
tableView.rowHeight = UITableView.automaticDimension
tableView.backgroundColor = .clear
tableView.separatorColor = Asset.Colors.Background.Cell.separator.color
tableView.register(SettingsAppearanceTableViewCell.self, forCellReuseIdentifier: String(describing: SettingsAppearanceTableViewCell.self))
tableView.register(SettingsToggleTableViewCell.self, forCellReuseIdentifier: String(describing: SettingsToggleTableViewCell.self))

View File

@ -15,7 +15,7 @@ protocol PlayerContainerViewDelegate: AnyObject {
}
final class PlayerContainerView: UIView {
static let cornerRadius: CGFloat = 8
static let cornerRadius: CGFloat = ContentWarningOverlayView.cornerRadius
private let container = UIView()
private let touchBlockingView = TouchBlockingView()

View File

@ -77,7 +77,6 @@ extension ContentWarningOverlayView {
private func _init() {
backgroundColor = .clear
isUserInteractionEnabled = true
layer.masksToBounds = false
// visual effect style
// add blur visual effect view in the setup method
@ -115,10 +114,10 @@ extension ContentWarningOverlayView {
contentOverlayView.translatesAutoresizingMaskIntoConstraints = false
addSubview(contentOverlayView)
NSLayoutConstraint.activate([
topAnchor.constraint(equalTo: contentOverlayView.topAnchor, constant: 2),
leadingAnchor.constraint(equalTo: contentOverlayView.leadingAnchor, constant: 2),
contentOverlayView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 2),
contentOverlayView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 2),
topAnchor.constraint(equalTo: contentOverlayView.topAnchor),
leadingAnchor.constraint(equalTo: contentOverlayView.leadingAnchor),
contentOverlayView.trailingAnchor.constraint(equalTo: trailingAnchor),
contentOverlayView.bottomAnchor.constraint(equalTo: bottomAnchor),
])
let blurContentWarningLabelContainer = UIStackView()

View File

@ -97,8 +97,8 @@ final class StatusView: UIView {
}()
let avatarImageView: UIImageView = {
let imageView = FLAnimatedImageView()
imageView.layer.shouldRasterize = true
imageView.layer.rasterizationScale = UIScreen.main.scale
// imageView.layer.shouldRasterize = true
// imageView.layer.rasterizationScale = UIScreen.main.scale
return imageView
}()
let avatarStackedContainerButton: AvatarStackContainerButton = AvatarStackContainerButton()
@ -343,6 +343,7 @@ extension StatusView {
let titleContainerStackView = UIStackView()
authorMetaContainerStackView.addArrangedSubview(titleContainerStackView)
titleContainerStackView.axis = .horizontal
titleContainerStackView.alignment = .center
titleContainerStackView.spacing = 4
nameLabel.translatesAutoresizingMaskIntoConstraints = false
titleContainerStackView.addArrangedSubview(nameLabel)
@ -379,7 +380,7 @@ extension StatusView {
authorContainerStackView.topAnchor.constraint(equalTo: authorContainerView.topAnchor),
authorContainerStackView.leadingAnchor.constraint(equalTo: authorContainerView.leadingAnchor),
authorContainerStackView.trailingAnchor.constraint(equalTo: authorContainerView.trailingAnchor),
authorContainerView.bottomAnchor.constraint(equalTo: authorContainerStackView.bottomAnchor, constant: StatusView.containerStackViewSpacing).priority(.defaultHigh),
authorContainerView.bottomAnchor.constraint(equalTo: authorContainerStackView.bottomAnchor, constant: StatusView.containerStackViewSpacing).priority(.required - 1),
])
containerStackView.addArrangedSubview(authorContainerView)

View File

@ -46,11 +46,12 @@ final class AvatarStackContainerButton: UIControl {
extension AvatarStackContainerButton {
private func _init() {
topLeadingAvatarStackedImageView.layer.shouldRasterize = true
topLeadingAvatarStackedImageView.layer.rasterizationScale = UIScreen.main.scale
bottomTrailingAvatarStackedImageView.layer.shouldRasterize = true
bottomTrailingAvatarStackedImageView.layer.rasterizationScale = UIScreen.main.scale
// GIF get worse when enable rasterize
// topLeadingAvatarStackedImageView.layer.shouldRasterize = true
// topLeadingAvatarStackedImageView.layer.rasterizationScale = UIScreen.main.scale
//
// bottomTrailingAvatarStackedImageView.layer.shouldRasterize = true
// bottomTrailingAvatarStackedImageView.layer.rasterizationScale = UIScreen.main.scale
topLeadingAvatarStackedImageView.translatesAutoresizingMaskIntoConstraints = false
addSubview(topLeadingAvatarStackedImageView)

View File

@ -34,6 +34,31 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
// set tint color
window.tintColor = Asset.Colors.brandBlue.color
// set navigation bar appearance
let appearance = UINavigationBarAppearance()
appearance.configureWithDefaultBackground()
appearance.backgroundColor = Asset.Colors.Background.bar.color
UINavigationBar.appearance().standardAppearance = appearance
UINavigationBar.appearance().compactAppearance = appearance
UINavigationBar.appearance().scrollEdgeAppearance = appearance
// set tab bar appearance
let tabBarAppearance = UITabBarAppearance()
tabBarAppearance.configureWithDefaultBackground()
let tabBarItemAppearance = UITabBarItemAppearance()
tabBarItemAppearance.selected.iconColor = Asset.Colors.brandBlue.color
tabBarItemAppearance.focused.iconColor = Asset.Colors.TabBar.itemInactive.color
tabBarItemAppearance.normal.iconColor = Asset.Colors.TabBar.itemInactive.color
tabBarItemAppearance.disabled.iconColor = Asset.Colors.TabBar.itemInactive.color
tabBarAppearance.stackedLayoutAppearance = tabBarItemAppearance
tabBarAppearance.inlineLayoutAppearance = tabBarItemAppearance
tabBarAppearance.compactInlineLayoutAppearance = tabBarItemAppearance
tabBarAppearance.backgroundColor = Asset.Colors.Background.bar.color
tabBarAppearance.selectionIndicatorTintColor = Asset.Colors.brandBlue.color
UITabBar.appearance().standardAppearance = tabBarAppearance
let appContext = AppContext.shared
let sceneCoordinator = SceneCoordinator(scene: scene, sceneDelegate: self, appContext: appContext)
self.coordinator = sceneCoordinator