feat: add interaction for follow request notification

This commit is contained in:
CMK 2022-06-28 19:00:39 +08:00
parent 44b06f3a6b
commit bcfdaf2ca7
19 changed files with 504 additions and 106 deletions

View File

@ -114,7 +114,7 @@
<key>MastodonIntent.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>34</integer>
<integer>24</integer>
</dict>
<key>MastodonIntents.xcscheme_^#shared#^_</key>
<dict>
@ -129,12 +129,12 @@
<key>NotificationService.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>32</integer>
<integer>23</integer>
</dict>
<key>ShareActionExtension.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>33</integer>
<integer>22</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>

View File

@ -14,7 +14,7 @@ extension DataSourceFacade {
user: ManagedObjectRecord<MastodonUser>,
authenticationBox: MastodonAuthenticationBox
) async throws {
let selectionFeedbackGenerator = UISelectionFeedbackGenerator()
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
await selectionFeedbackGenerator.selectionChanged()
_ = try await dependency.context.apiService.toggleBlock(

View File

@ -15,7 +15,7 @@ extension DataSourceFacade {
status: ManagedObjectRecord<Status>,
authenticationBox: MastodonAuthenticationBox
) async throws {
let selectionFeedbackGenerator = UISelectionFeedbackGenerator()
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
await selectionFeedbackGenerator.selectionChanged()
_ = try await provider.context.apiService.favorite(

View File

@ -7,6 +7,8 @@
import UIKit
import CoreDataStack
import class CoreDataStack.Notification
import MastodonSDK
extension DataSourceFacade {
static func responseToUserFollowAction(
@ -14,7 +16,7 @@ extension DataSourceFacade {
user: ManagedObjectRecord<MastodonUser>,
authenticationBox: MastodonAuthenticationBox
) async throws {
let selectionFeedbackGenerator = UISelectionFeedbackGenerator()
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
await selectionFeedbackGenerator.selectionChanged()
_ = try await dependency.context.apiService.toggleFollow(
@ -23,3 +25,32 @@ extension DataSourceFacade {
)
} // end func
}
extension DataSourceFacade {
static func responseToUserFollowRequestAction(
dependency: NeedsDependency,
notification: ManagedObjectRecord<Notification>,
query: Mastodon.API.Account.FollowReqeustQuery,
authenticationBox: MastodonAuthenticationBox
) async throws {
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
await selectionFeedbackGenerator.selectionChanged()
let managedObjectContext = dependency.context.managedObjectContext
let _userID: MastodonUser.ID? = try await managedObjectContext.perform {
guard let notification = notification.object(in: managedObjectContext) else { return nil }
return notification.account.id
}
guard let userID = _userID else {
assertionFailure()
throw APIService.APIError.implicit(.badRequest)
}
_ = try await dependency.context.apiService.followRequest(
userID: userID,
query: query,
authenticationBox: authenticationBox
)
} // end func
}

View File

@ -14,7 +14,7 @@ extension DataSourceFacade {
user: ManagedObjectRecord<MastodonUser>,
authenticationBox: MastodonAuthenticationBox
) async throws {
let selectionFeedbackGenerator = UISelectionFeedbackGenerator()
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
await selectionFeedbackGenerator.selectionChanged()
_ = try await dependency.context.apiService.toggleMute(

View File

@ -15,7 +15,7 @@ extension DataSourceFacade {
status: ManagedObjectRecord<Status>,
authenticationBox: MastodonAuthenticationBox
) async throws {
let selectionFeedbackGenerator = UISelectionFeedbackGenerator()
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
await selectionFeedbackGenerator.selectionChanged()
_ = try await provider.context.apiService.reblog(

View File

@ -87,6 +87,68 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
}
}
extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
func tableViewCell(
_ cell: UITableViewCell,
notificationView: NotificationView,
acceptFollowRequestButtonDidPressed button: UIButton
) {
Task {
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
guard let item = await item(from: source) else {
assertionFailure()
return
}
guard case let .notification(notification) = item else {
assertionFailure("only works for status data provider")
return
}
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else {
return
}
try await DataSourceFacade.responseToUserFollowRequestAction(
dependency: self,
notification: notification,
query: .accept,
authenticationBox: authenticationBox
)
} // end Task
}
func tableViewCell(
_ cell: UITableViewCell,
notificationView: NotificationView,
rejectFollowRequestButtonDidPressed button: UIButton
) {
Task {
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
guard let item = await item(from: source) else {
assertionFailure()
return
}
guard case let .notification(notification) = item else {
assertionFailure("only works for status data provider")
return
}
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else {
return
}
try await DataSourceFacade.responseToUserFollowRequestAction(
dependency: self,
notification: notification,
query: .reject,
authenticationBox: authenticationBox
)
} // end Task
}
}
// MARK: - Status Content
extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
func tableViewCell(

View File

@ -25,6 +25,8 @@ protocol NotificationTableViewCellDelegate: AnyObject, AutoGenerateProtocolDeleg
// sourcery:inline:NotificationTableViewCellDelegate.AutoGenerateProtocolDelegate
func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, authorAvatarButtonDidPressed button: AvatarButton)
func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, menuButton button: UIButton, didSelectAction action: MastodonMenu.Action)
func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, acceptFollowRequestButtonDidPressed button: UIButton)
func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, rejectFollowRequestButtonDidPressed button: UIButton)
func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta)
func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, statusView: StatusView, spoilerOverlayViewDidPressed overlayView: SpoilerOverlayView)
func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, statusView: StatusView, mediaGridContainerView: MediaGridContainerView, mediaView: MediaView, didSelectMediaViewAt index: Int)
@ -49,6 +51,14 @@ extension NotificationViewDelegate where Self: NotificationViewContainerTableVie
delegate?.tableViewCell(self, notificationView: notificationView, menuButton: button, didSelectAction: action)
}
func notificationView(_ notificationView: NotificationView, acceptFollowRequestButtonDidPressed button: UIButton) {
delegate?.tableViewCell(self, notificationView: notificationView, acceptFollowRequestButtonDidPressed: button)
}
func notificationView(_ notificationView: NotificationView, rejectFollowRequestButtonDidPressed button: UIButton) {
delegate?.tableViewCell(self, notificationView: notificationView, rejectFollowRequestButtonDidPressed: button)
}
func notificationView(_ notificationView: NotificationView, statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta) {
delegate?.tableViewCell(self, notificationView: notificationView, statusView: statusView, metaText: metaText, didSelectMeta: meta)
}

View File

@ -36,23 +36,26 @@ extension NotificationView {
return
}
if let status = notification.status {
switch type {
case .follow, .followRequest:
setAuthorContainerBottomPaddingViewDisplay()
case .mention, .status:
switch type {
case .follow:
setAuthorContainerBottomPaddingViewDisplay()
case .followRequest:
setFollowRequestAdaptiveMarginContainerViewDisplay()
case .mention, .status:
if let status = notification.status {
statusView.configure(status: status)
setStatusViewDisplay()
case .reblog, .favourite, .poll:
}
case .reblog, .favourite, .poll:
if let status = notification.status {
quoteStatusView.configure(status: status)
setQuoteStatusViewDisplay()
case ._other:
setAuthorContainerBottomPaddingViewDisplay()
assertionFailure()
}
} else {
case ._other:
setAuthorContainerBottomPaddingViewDisplay()
assertionFailure()
}
}
}

View File

@ -15,91 +15,42 @@ import CommonOSLog
import MastodonSDK
extension APIService {
// func acceptFollowRequest(
// mastodonUserID: MastodonUser.ID,
// mastodonAuthenticationBox: MastodonAuthenticationBox
// ) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> {
// let domain = mastodonAuthenticationBox.domain
// let authorization = mastodonAuthenticationBox.userAuthorization
// let requestMastodonUserID = mastodonAuthenticationBox.userID
//
// return Mastodon.API.Account.acceptFollowRequest(
// session: session,
// domain: domain,
// userID: mastodonUserID,
// authorization: authorization)
// .flatMap { response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> in
// let managedObjectContext = self.backgroundManagedObjectContext
// return managedObjectContext.performChanges {
// let requestMastodonUserRequest = MastodonUser.sortedFetchRequest
// requestMastodonUserRequest.predicate = MastodonUser.predicate(domain: domain, id: requestMastodonUserID)
// requestMastodonUserRequest.fetchLimit = 1
// guard let requestMastodonUser = managedObjectContext.safeFetch(requestMastodonUserRequest).first else { return }
//
// let lookUpMastodonUserRequest = MastodonUser.sortedFetchRequest
// lookUpMastodonUserRequest.predicate = MastodonUser.predicate(domain: domain, id: mastodonUserID)
// lookUpMastodonUserRequest.fetchLimit = 1
// let lookUpMastodonuser = managedObjectContext.safeFetch(lookUpMastodonUserRequest).first
//
// if let lookUpMastodonuser = lookUpMastodonuser {
// let entity = response.value
// APIService.CoreData.update(user: lookUpMastodonuser, entity: entity, requestMastodonUser: requestMastodonUser, domain: domain, networkDate: response.networkDate)
// }
// }
// .tryMap { result -> Mastodon.Response.Content<Mastodon.Entity.Relationship> in
// switch result {
// case .success:
// return response
// case .failure(let error):
// throw error
// }
// }
// .eraseToAnyPublisher()
// }
// .eraseToAnyPublisher()
// }
// func rejectFollowRequest(
// mastodonUserID: MastodonUser.ID,
// mastodonAuthenticationBox: MastodonAuthenticationBox
// ) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> {
// let domain = mastodonAuthenticationBox.domain
// let authorization = mastodonAuthenticationBox.userAuthorization
// let requestMastodonUserID = mastodonAuthenticationBox.userID
//
// return Mastodon.API.Account.rejectFollowRequest(
// session: session,
// domain: domain,
// userID: mastodonUserID,
// authorization: authorization)
// .flatMap { response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> in
// let managedObjectContext = self.backgroundManagedObjectContext
// return managedObjectContext.performChanges {
// let requestMastodonUserRequest = MastodonUser.sortedFetchRequest
// requestMastodonUserRequest.predicate = MastodonUser.predicate(domain: domain, id: requestMastodonUserID)
// requestMastodonUserRequest.fetchLimit = 1
// guard let requestMastodonUser = managedObjectContext.safeFetch(requestMastodonUserRequest).first else { return }
//
// let lookUpMastodonUserRequest = MastodonUser.sortedFetchRequest
// lookUpMastodonUserRequest.predicate = MastodonUser.predicate(domain: domain, id: mastodonUserID)
// lookUpMastodonUserRequest.fetchLimit = 1
// let lookUpMastodonuser = managedObjectContext.safeFetch(lookUpMastodonUserRequest).first
//
// if let lookUpMastodonuser = lookUpMastodonuser {
// let entity = response.value
// APIService.CoreData.update(user: lookUpMastodonuser, entity: entity, requestMastodonUser: requestMastodonUser, domain: domain, networkDate: response.networkDate)
// }
// }
// .tryMap { result -> Mastodon.Response.Content<Mastodon.Entity.Relationship> in
// switch result {
// case .success:
// return response
// case .failure(let error):
// throw error
// }
// }
// .eraseToAnyPublisher()
// }
// .eraseToAnyPublisher()
// }
func followRequest(
userID: Mastodon.Entity.Account.ID,
query: Mastodon.API.Account.FollowReqeustQuery,
authenticationBox: MastodonAuthenticationBox
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Relationship> {
let response = try await Mastodon.API.Account.followRequest(
session: session,
domain: authenticationBox.domain,
userID: userID,
query: query,
authorization: authenticationBox.userAuthorization
).singleOutput()
let managedObjectContext = self.backgroundManagedObjectContext
try await managedObjectContext.performChanges {
let request = MastodonUser.sortedFetchRequest
request.predicate = MastodonUser.predicate(
domain: authenticationBox.domain,
id: authenticationBox.userID
)
request.fetchLimit = 1
guard let user = managedObjectContext.safeFetch(request).first else { return }
guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return }
Persistence.MastodonUser.update(
mastodonUser: user,
context: Persistence.MastodonUser.RelationshipContext(
entity: response.value,
me: me,
networkDate: response.networkDate
)
)
}
return response
}
}

View File

@ -21,7 +21,7 @@ public enum MastodonNotificationType: RawRepresentable {
public init?(rawValue: String) {
switch rawValue {
case "follow": self = .follow
case "followRequest": self = .followRequest
case "follow_request": self = .followRequest
case "mention": self = .mention
case "reblog": self = .reblog
case "favourite": self = .favourite
@ -34,7 +34,7 @@ public enum MastodonNotificationType: RawRepresentable {
public var rawValue: String {
switch self {
case .follow: return "follow"
case .followRequest: return "followRequest"
case .followRequest: return "follow_request"
case .mention: return "mention"
case .reblog: return "reblog"
case .favourite: return "favourite"

View File

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

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "checkmark.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -0,0 +1,76 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 3.250000 5.105469 cm
0.129412 0.129412 0.129412 scn
1.280330 5.924861 m
0.987437 6.217754 0.512563 6.217754 0.219670 5.924861 c
-0.073223 5.631968 -0.073223 5.157095 0.219670 4.864202 c
4.719670 0.364201 l
5.012563 0.071307 5.487436 0.071307 5.780330 0.364201 c
16.780331 11.364201 l
17.073223 11.657094 17.073223 12.131968 16.780331 12.424861 c
16.487438 12.717754 16.012562 12.717754 15.719669 12.424861 c
5.250000 1.955191 l
1.280330 5.924861 l
h
f
n
Q
endstream
endobj
3 0 obj
523
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 24.000000 24.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000000613 00000 n
0000000635 00000 n
0000000808 00000 n
0000000882 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
941
%%EOF

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "xmark.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -0,0 +1,89 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 4.250000 4.105469 cm
0.129412 0.129412 0.129412 scn
0.147052 15.340743 m
0.219670 15.424861 l
0.485936 15.691128 0.902600 15.715334 1.196212 15.497479 c
1.280330 15.424861 l
7.750000 8.955531 l
14.219669 15.424861 l
14.512563 15.717754 14.987437 15.717754 15.280331 15.424861 c
15.573224 15.131968 15.573224 14.657094 15.280331 14.364201 c
8.811000 7.894531 l
15.280331 1.424862 l
15.546597 1.158595 15.570804 0.741932 15.352949 0.448320 c
15.280331 0.364201 l
15.014064 0.097934 14.597401 0.073728 14.303789 0.291582 c
14.219669 0.364201 l
7.750000 6.833531 l
1.280330 0.364201 l
0.987437 0.071307 0.512563 0.071307 0.219670 0.364201 c
-0.073223 0.657094 -0.073223 1.131968 0.219670 1.424862 c
6.689000 7.894531 l
0.219670 14.364201 l
-0.046597 14.630467 -0.070803 15.047132 0.147052 15.340743 c
0.219670 15.424861 l
0.147052 15.340743 l
h
f
n
Q
endstream
endobj
3 0 obj
914
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 24.000000 24.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000001004 00000 n
0000001026 00000 n
0000001199 00000 n
0000001273 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
1332
%%EOF

View File

@ -94,6 +94,10 @@ public enum Asset {
public enum Connectivity {
public static let photoFillSplit = ImageAsset(name: "Connectivity/photo.fill.split")
}
public enum Editing {
public static let checkmark = ImageAsset(name: "Editing/checkmark")
public static let xmark = ImageAsset(name: "Editing/xmark")
}
public enum Human {
public static let eyeCircleFill = ImageAsset(name: "Human/eye.circle.fill")
public static let eyeSlashCircleFill = ImageAsset(name: "Human/eye.slash.circle.fill")

View File

@ -89,3 +89,37 @@ extension Mastodon.API.Account {
.eraseToAnyPublisher()
}
}
extension Mastodon.API.Account {
public enum FollowReqeustQuery {
case accept
case reject
}
public static func followRequest(
session: URLSession,
domain: String,
userID: Mastodon.Entity.Account.ID,
query: FollowReqeustQuery,
authorization: Mastodon.API.OAuth.Authorization
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> {
switch query {
case .accept:
return acceptFollowRequest(
session: session,
domain: domain,
userID: userID,
authorization: authorization
)
case .reject:
return rejectFollowRequest(
session: session,
domain: domain,
userID: userID,
authorization: authorization
)
} // end switch
}
}

View File

@ -17,6 +17,9 @@ public protocol NotificationViewDelegate: AnyObject {
func notificationView(_ notificationView: NotificationView, authorAvatarButtonDidPressed button: AvatarButton)
func notificationView(_ notificationView: NotificationView, menuButton button: UIButton, didSelectAction action: MastodonMenu.Action)
func notificationView(_ notificationView: NotificationView, acceptFollowRequestButtonDidPressed button: UIButton)
func notificationView(_ notificationView: NotificationView, rejectFollowRequestButtonDidPressed button: UIButton)
func notificationView(_ notificationView: NotificationView, statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta)
func notificationView(_ notificationView: NotificationView, statusView: StatusView, spoilerOverlayViewDidPressed overlayView: SpoilerOverlayView)
func notificationView(_ notificationView: NotificationView, statusView: StatusView, mediaGridContainerView: MediaGridContainerView, mediaView: MediaView, didSelectMediaViewAt index: Int)
@ -101,6 +104,44 @@ public final class NotificationView: UIView {
// notification type indicator imageView
public let notificationTypeIndicatorLabel = MetaLabel(style: .notificationTitle)
// follow request
let followRequestAdaptiveMarginContainerView = AdaptiveMarginContainerView()
let followRequestContainerView = UIView()
let acceptFollowRequestButtonShadowBackgroundContainer = ShadowBackgroundContainer()
private(set) lazy var acceptFollowRequestButton: UIButton = {
let button = UIButton()
button.setImage(Asset.Editing.checkmark.image.withRenderingMode(.alwaysTemplate), for: .normal)
button.imageView?.contentMode = .scaleAspectFit
button.setBackgroundImage(.placeholder(color: .systemGreen), for: .normal)
button.tintColor = .white
button.layer.masksToBounds = true
button.layer.cornerCurve = .continuous
button.layer.cornerRadius = 4
acceptFollowRequestButtonShadowBackgroundContainer.cornerRadius = 4
acceptFollowRequestButtonShadowBackgroundContainer.shadowAlpha = 0.1
button.addTarget(self, action: #selector(NotificationView.acceptFollowRequestButtonDidPressed(_:)), for: .touchUpInside)
return button
}()
let rejectFollowRequestButtonShadowBackgroundContainer = ShadowBackgroundContainer()
private(set) lazy var rejectFollowRequestButton: UIButton = {
let button = UIButton()
button.setImage(Asset.Editing.xmark.image.withRenderingMode(.alwaysTemplate), for: .normal)
button.imageView?.contentMode = .scaleAspectFit
button.imageEdgeInsets = UIEdgeInsets(top: 2, left: 2, bottom: 2, right: 2) // tweak xmark size
button.setBackgroundImage(.placeholder(color: .systemRed), for: .normal)
button.tintColor = .white
button.layer.masksToBounds = true
button.layer.cornerCurve = .continuous
button.layer.cornerRadius = 4
rejectFollowRequestButtonShadowBackgroundContainer.cornerRadius = 4
rejectFollowRequestButtonShadowBackgroundContainer.shadowAlpha = 0.1
button.addTarget(self, action: #selector(NotificationView.rejectFollowRequestButtonDidPressed(_:)), for: .touchUpInside)
return button
}()
// status
public let statusView = StatusView()
public let quoteStatusViewContainerView = UIView()
@ -115,6 +156,8 @@ public final class NotificationView: UIView {
authorContainerViewBottomPaddingView.isHidden = true
followRequestAdaptiveMarginContainerView.isHidden = true
statusView.isHidden = true
statusView.prepareForReuse()
@ -222,6 +265,46 @@ extension NotificationView {
])
authorContainerViewBottomPaddingView.isHidden = true
// follow reqeust
followRequestAdaptiveMarginContainerView.contentView = followRequestContainerView
followRequestAdaptiveMarginContainerView.margin = StatusView.containerLayoutMargin
containerStackView.addArrangedSubview(followRequestAdaptiveMarginContainerView)
acceptFollowRequestButton.translatesAutoresizingMaskIntoConstraints = false
acceptFollowRequestButtonShadowBackgroundContainer.addSubview(acceptFollowRequestButton)
NSLayoutConstraint.activate([
acceptFollowRequestButton.topAnchor.constraint(equalTo: acceptFollowRequestButtonShadowBackgroundContainer.topAnchor),
acceptFollowRequestButton.leadingAnchor.constraint(equalTo: acceptFollowRequestButtonShadowBackgroundContainer.leadingAnchor),
acceptFollowRequestButton.trailingAnchor.constraint(equalTo: acceptFollowRequestButtonShadowBackgroundContainer.trailingAnchor),
acceptFollowRequestButton.bottomAnchor.constraint(equalTo: acceptFollowRequestButtonShadowBackgroundContainer.bottomAnchor),
])
rejectFollowRequestButton.translatesAutoresizingMaskIntoConstraints = false
rejectFollowRequestButtonShadowBackgroundContainer.addSubview(rejectFollowRequestButton)
NSLayoutConstraint.activate([
rejectFollowRequestButton.topAnchor.constraint(equalTo: rejectFollowRequestButtonShadowBackgroundContainer.topAnchor),
rejectFollowRequestButton.leadingAnchor.constraint(equalTo: rejectFollowRequestButtonShadowBackgroundContainer.leadingAnchor),
rejectFollowRequestButton.trailingAnchor.constraint(equalTo: rejectFollowRequestButtonShadowBackgroundContainer.trailingAnchor),
rejectFollowRequestButton.bottomAnchor.constraint(equalTo: rejectFollowRequestButtonShadowBackgroundContainer.bottomAnchor),
])
let followReqeustContainerBottomMargin: CGFloat = 8
acceptFollowRequestButtonShadowBackgroundContainer.translatesAutoresizingMaskIntoConstraints = false
followRequestContainerView.addSubview(acceptFollowRequestButtonShadowBackgroundContainer)
rejectFollowRequestButtonShadowBackgroundContainer.translatesAutoresizingMaskIntoConstraints = false
followRequestContainerView.addSubview(rejectFollowRequestButtonShadowBackgroundContainer)
NSLayoutConstraint.activate([
acceptFollowRequestButtonShadowBackgroundContainer.topAnchor.constraint(equalTo: followRequestContainerView.topAnchor),
acceptFollowRequestButtonShadowBackgroundContainer.leadingAnchor.constraint(equalTo: followRequestContainerView.leadingAnchor),
followRequestContainerView.bottomAnchor.constraint(equalTo: acceptFollowRequestButtonShadowBackgroundContainer.bottomAnchor, constant: followReqeustContainerBottomMargin),
rejectFollowRequestButtonShadowBackgroundContainer.topAnchor.constraint(equalTo: followRequestContainerView.topAnchor),
rejectFollowRequestButtonShadowBackgroundContainer.leadingAnchor.constraint(equalTo: acceptFollowRequestButtonShadowBackgroundContainer.trailingAnchor, constant: 8),
followRequestContainerView.trailingAnchor.constraint(equalTo: rejectFollowRequestButtonShadowBackgroundContainer.trailingAnchor),
followRequestContainerView.bottomAnchor.constraint(equalTo: rejectFollowRequestButtonShadowBackgroundContainer.bottomAnchor, constant: followReqeustContainerBottomMargin),
acceptFollowRequestButtonShadowBackgroundContainer.widthAnchor.constraint(equalTo: rejectFollowRequestButtonShadowBackgroundContainer.widthAnchor),
])
followRequestAdaptiveMarginContainerView.isHidden = true
// statusView
containerStackView.addArrangedSubview(statusView)
statusView.setup(style: .notification)
@ -271,10 +354,22 @@ extension NotificationView {
}
extension NotificationView {
@objc private func avatarButtonDidPressed(_ sender: UIButton) {
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
delegate?.notificationView(self, authorAvatarButtonDidPressed: avatarButton)
}
@objc private func acceptFollowRequestButtonDidPressed(_ sender: UIButton) {
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
delegate?.notificationView(self, acceptFollowRequestButtonDidPressed: sender)
}
@objc private func rejectFollowRequestButtonDidPressed(_ sender: UIButton) {
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
delegate?.notificationView(self, rejectFollowRequestButtonDidPressed: sender)
}
}
extension NotificationView {
@ -282,6 +377,10 @@ extension NotificationView {
public func setAuthorContainerBottomPaddingViewDisplay() {
authorContainerViewBottomPaddingView.isHidden = false
}
public func setFollowRequestAdaptiveMarginContainerViewDisplay() {
followRequestAdaptiveMarginContainerView.isHidden = false
}
public func setStatusViewDisplay() {
statusView.isHidden = false