Merge branch 'develop' into fix/profile-and-fix-issue

This commit is contained in:
CMK 2021-05-07 11:07:06 +08:00
commit 9a255bbd84
31 changed files with 935 additions and 191 deletions

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17709" systemVersion="20D91" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier=""> <model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="18154" systemVersion="20D75" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="Application" representedClassName=".Application" syncable="YES"> <entity name="Application" representedClassName=".Application" syncable="YES">
<attribute name="identifier" optional="YES" attributeType="UUID" usesScalarValueType="NO"/> <attribute name="identifier" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
<attribute name="name" attributeType="String"/> <attribute name="name" attributeType="String"/>
@ -24,6 +24,19 @@
<attribute name="url" optional="YES" attributeType="String"/> <attribute name="url" optional="YES" attributeType="String"/>
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="mediaAttachments" inverseEntity="Status"/> <relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="mediaAttachments" inverseEntity="Status"/>
</entity> </entity>
<entity name="DomainBlock" representedClassName=".DomainBlock" syncable="YES">
<attribute name="blockedDomain" attributeType="String"/>
<attribute name="createAt" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="domain" attributeType="String"/>
<attribute name="userID" attributeType="String"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="userID"/>
<constraint value="domain"/>
<constraint value="blockedDomain"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="Emoji" representedClassName=".Emoji" syncable="YES"> <entity name="Emoji" representedClassName=".Emoji" syncable="YES">
<attribute name="category" optional="YES" attributeType="String"/> <attribute name="category" optional="YES" attributeType="String"/>
<attribute name="createAt" attributeType="Date" defaultDateTimeInterval="631123200" usesScalarValueType="NO"/> <attribute name="createAt" attributeType="Date" defaultDateTimeInterval="631123200" usesScalarValueType="NO"/>
@ -253,6 +266,7 @@
<elements> <elements>
<element name="Application" positionX="0" positionY="0" width="128" height="104"/> <element name="Application" positionX="0" positionY="0" width="128" height="104"/>
<element name="Attachment" positionX="0" positionY="0" width="128" height="254"/> <element name="Attachment" positionX="0" positionY="0" width="128" height="254"/>
<element name="DomainBlock" positionX="45" positionY="162" width="128" height="89"/>
<element name="Emoji" positionX="0" positionY="0" width="128" height="149"/> <element name="Emoji" positionX="0" positionY="0" width="128" height="149"/>
<element name="History" positionX="0" positionY="0" width="128" height="119"/> <element name="History" positionX="0" positionY="0" width="128" height="119"/>
<element name="HomeTimelineIndex" positionX="0" positionY="0" width="128" height="134"/> <element name="HomeTimelineIndex" positionX="0" positionY="0" width="128" height="134"/>

View File

@ -0,0 +1,73 @@
//
// DomainBlock.swift
// CoreDataStack
//
// Created by sxiaojian on 2021/4/29.
//
import CoreData
import Foundation
public final class DomainBlock: NSManagedObject {
@NSManaged public private(set) var blockedDomain: String
@NSManaged public private(set) var createAt: Date
@NSManaged public private(set) var domain: String
@NSManaged public private(set) var userID: String
override public func awakeFromInsert() {
super.awakeFromInsert()
setPrimitiveValue(Date(), forKey: #keyPath(DomainBlock.createAt))
}
}
extension DomainBlock {
@discardableResult
public static func insert(
into context: NSManagedObjectContext,
blockedDomain: String,
domain: String,
userID: String
) -> DomainBlock {
let domainBlock: DomainBlock = context.insertObject()
domainBlock.domain = domain
domainBlock.blockedDomain = blockedDomain
domainBlock.userID = userID
return domainBlock
}
}
extension DomainBlock: Managed {
public static var defaultSortDescriptors: [NSSortDescriptor] {
[NSSortDescriptor(keyPath: \DomainBlock.createAt, ascending: false)]
}
}
extension DomainBlock {
static func predicate(domain: String) -> NSPredicate {
NSPredicate(format: "%K == %@", #keyPath(DomainBlock.domain), domain)
}
static func predicate(userID: String) -> NSPredicate {
NSPredicate(format: "%K == %@", #keyPath(DomainBlock.userID), userID)
}
static func predicate(blockedDomain: String) -> NSPredicate {
NSPredicate(format: "%K == %@", #keyPath(DomainBlock.blockedDomain), blockedDomain)
}
public static func predicate(domain: String, userID: String) -> NSPredicate {
NSCompoundPredicate(andPredicateWithSubpredicates: [
DomainBlock.predicate(domain: domain),
DomainBlock.predicate(userID: userID)
])
}
public static func predicate(domain: String, userID: String, blockedDomain: String) -> NSPredicate {
NSCompoundPredicate(andPredicateWithSubpredicates: [
DomainBlock.predicate(domain: domain),
DomainBlock.predicate(userID: userID),
DomainBlock.predicate(blockedDomain: blockedDomain)
])
}
}

View File

@ -28,6 +28,10 @@
"message": "Are you sure you want to sign out?", "message": "Are you sure you want to sign out?",
"confirm": "Sign Out" "confirm": "Sign Out"
}, },
"block_domain": {
"message": "Are you really, really sure you want to block the entire %s? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
"block_entire_domain": "Block entire domain"
},
"save_photo_failure": { "save_photo_failure": {
"title": "Save Photo Failure", "title": "Save Photo Failure",
"message": "Please enable photo libaray access permission to save photo." "message": "Please enable photo libaray access permission to save photo."
@ -55,11 +59,14 @@
"preview": "Preview", "preview": "Preview",
"share": "Share", "share": "Share",
"share_user": "Share %s", "share_user": "Share %s",
"share_post": "Share post",
"open_in_safari": "Open in Safari", "open_in_safari": "Open in Safari",
"find_people": "Find people to follow", "find_people": "Find people to follow",
"manually_search": "Manually search instead", "manually_search": "Manually search instead",
"skip": "Skip", "skip": "Skip",
"report_user": "Report %s", "report_user": "Report %s",
"block_domain": "Block %s",
"unblock_domain": "Unblock %s",
"settings": "Settings" "settings": "Settings"
}, },
"status": { "status": {
@ -416,4 +423,4 @@
"text_placeholder": "Type or paste additional comments" "text_placeholder": "Type or paste additional comments"
} }
} }
} }

View File

@ -13,7 +13,7 @@
0F20220726134DA4000C64BF /* HashtagTimelineViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F20220626134DA4000C64BF /* HashtagTimelineViewModel+Diffable.swift */; }; 0F20220726134DA4000C64BF /* HashtagTimelineViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F20220626134DA4000C64BF /* HashtagTimelineViewModel+Diffable.swift */; };
0F20220D26134E3F000C64BF /* HashtagTimelineViewModel+LoadLatestState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F20220C26134E3F000C64BF /* HashtagTimelineViewModel+LoadLatestState.swift */; }; 0F20220D26134E3F000C64BF /* HashtagTimelineViewModel+LoadLatestState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F20220C26134E3F000C64BF /* HashtagTimelineViewModel+LoadLatestState.swift */; };
0F202213261351F5000C64BF /* APIService+HashtagTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F202212261351F5000C64BF /* APIService+HashtagTimeline.swift */; }; 0F202213261351F5000C64BF /* APIService+HashtagTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F202212261351F5000C64BF /* APIService+HashtagTimeline.swift */; };
0F202227261411BB000C64BF /* HashtagTimelineViewController+StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F202226261411BA000C64BF /* HashtagTimelineViewController+StatusProvider.swift */; }; 0F202227261411BB000C64BF /* HashtagTimelineViewController+Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F202226261411BA000C64BF /* HashtagTimelineViewController+Provider.swift */; };
0F20222D261457EE000C64BF /* HashtagTimelineViewModel+LoadOldestState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F20222C261457EE000C64BF /* HashtagTimelineViewModel+LoadOldestState.swift */; }; 0F20222D261457EE000C64BF /* HashtagTimelineViewModel+LoadOldestState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F20222C261457EE000C64BF /* HashtagTimelineViewModel+LoadOldestState.swift */; };
0F20223326145E51000C64BF /* HashtagTimelineViewModel+LoadMiddleState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F20223226145E51000C64BF /* HashtagTimelineViewModel+LoadMiddleState.swift */; }; 0F20223326145E51000C64BF /* HashtagTimelineViewModel+LoadMiddleState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F20223226145E51000C64BF /* HashtagTimelineViewModel+LoadMiddleState.swift */; };
0F20223926146553000C64BF /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F20223826146553000C64BF /* Array.swift */; }; 0F20223926146553000C64BF /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F20223826146553000C64BF /* Array.swift */; };
@ -59,7 +59,7 @@
2D364F7825E66D8300204FDC /* MastodonResendEmailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D364F7725E66D8300204FDC /* MastodonResendEmailViewModel.swift */; }; 2D364F7825E66D8300204FDC /* MastodonResendEmailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D364F7725E66D8300204FDC /* MastodonResendEmailViewModel.swift */; };
2D38F1C625CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1C525CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift */; }; 2D38F1C625CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1C525CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift */; };
2D38F1D525CD465300561493 /* HomeTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1D425CD465300561493 /* HomeTimelineViewController.swift */; }; 2D38F1D525CD465300561493 /* HomeTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1D425CD465300561493 /* HomeTimelineViewController.swift */; };
2D38F1DF25CD46A400561493 /* HomeTimelineViewController+StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1DE25CD46A400561493 /* HomeTimelineViewController+StatusProvider.swift */; }; 2D38F1DF25CD46A400561493 /* HomeTimelineViewController+Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1DE25CD46A400561493 /* HomeTimelineViewController+Provider.swift */; };
2D38F1E525CD46C100561493 /* HomeTimelineViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1E425CD46C100561493 /* HomeTimelineViewModel.swift */; }; 2D38F1E525CD46C100561493 /* HomeTimelineViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1E425CD46C100561493 /* HomeTimelineViewModel.swift */; };
2D38F1EB25CD477000561493 /* HomeTimelineViewModel+LoadLatestState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1EA25CD477000561493 /* HomeTimelineViewModel+LoadLatestState.swift */; }; 2D38F1EB25CD477000561493 /* HomeTimelineViewModel+LoadLatestState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1EA25CD477000561493 /* HomeTimelineViewModel+LoadLatestState.swift */; };
2D38F1F125CD477D00561493 /* HomeTimelineViewModel+LoadMiddleState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1F025CD477D00561493 /* HomeTimelineViewModel+LoadMiddleState.swift */; }; 2D38F1F125CD477D00561493 /* HomeTimelineViewModel+LoadMiddleState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1F025CD477D00561493 /* HomeTimelineViewModel+LoadMiddleState.swift */; };
@ -98,7 +98,7 @@
2D6DE40026141DF600A63F6A /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D6DE3FF26141DF600A63F6A /* SearchViewModel.swift */; }; 2D6DE40026141DF600A63F6A /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D6DE3FF26141DF600A63F6A /* SearchViewModel.swift */; };
2D76316525C14BD100929FB9 /* PublicTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76316425C14BD100929FB9 /* PublicTimelineViewController.swift */; }; 2D76316525C14BD100929FB9 /* PublicTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76316425C14BD100929FB9 /* PublicTimelineViewController.swift */; };
2D76316B25C14D4C00929FB9 /* PublicTimelineViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76316A25C14D4C00929FB9 /* PublicTimelineViewModel.swift */; }; 2D76316B25C14D4C00929FB9 /* PublicTimelineViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76316A25C14D4C00929FB9 /* PublicTimelineViewModel.swift */; };
2D76317D25C14DF500929FB9 /* PublicTimelineViewController+StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76317C25C14DF400929FB9 /* PublicTimelineViewController+StatusProvider.swift */; }; 2D76317D25C14DF500929FB9 /* PublicTimelineViewController+Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76317C25C14DF400929FB9 /* PublicTimelineViewController+Provider.swift */; };
2D76318325C14E8F00929FB9 /* PublicTimelineViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76318225C14E8F00929FB9 /* PublicTimelineViewModel+Diffable.swift */; }; 2D76318325C14E8F00929FB9 /* PublicTimelineViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76318225C14E8F00929FB9 /* PublicTimelineViewModel+Diffable.swift */; };
2D76319F25C1521200929FB9 /* StatusSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76319E25C1521200929FB9 /* StatusSection.swift */; }; 2D76319F25C1521200929FB9 /* StatusSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76319E25C1521200929FB9 /* StatusSection.swift */; };
2D7631A825C1535600929FB9 /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D7631A725C1535600929FB9 /* StatusTableViewCell.swift */; }; 2D7631A825C1535600929FB9 /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D7631A725C1535600929FB9 /* StatusTableViewCell.swift */; };
@ -118,6 +118,9 @@
2D939AB525EDD8A90076FA61 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D939AB425EDD8A90076FA61 /* String.swift */; }; 2D939AB525EDD8A90076FA61 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D939AB425EDD8A90076FA61 /* String.swift */; };
2D939AC825EE14620076FA61 /* CropViewController in Frameworks */ = {isa = PBXBuildFile; productRef = 2D939AC725EE14620076FA61 /* CropViewController */; }; 2D939AC825EE14620076FA61 /* CropViewController in Frameworks */ = {isa = PBXBuildFile; productRef = 2D939AC725EE14620076FA61 /* CropViewController */; };
2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D939AE725EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift */; }; 2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D939AE725EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift */; };
2D9DB967263A76FB007C1D71 /* BlockDomainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D9DB966263A76FB007C1D71 /* BlockDomainService.swift */; };
2D9DB969263A833E007C1D71 /* DomainBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D9DB968263A833E007C1D71 /* DomainBlock.swift */; };
2D9DB96B263A91D1007C1D71 /* APIService+DomainBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D9DB96A263A91D1007C1D71 /* APIService+DomainBlock.swift */; };
2DA504692601ADE7008F4E6C /* SawToothView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA504682601ADE7008F4E6C /* SawToothView.swift */; }; 2DA504692601ADE7008F4E6C /* SawToothView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA504682601ADE7008F4E6C /* SawToothView.swift */; };
2DA6054725F716A2006356F9 /* PlaybackState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA6054625F716A2006356F9 /* PlaybackState.swift */; }; 2DA6054725F716A2006356F9 /* PlaybackState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA6054625F716A2006356F9 /* PlaybackState.swift */; };
2DA6055125F74407006356F9 /* AudioContainerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA6055025F74407006356F9 /* AudioContainerViewModel.swift */; }; 2DA6055125F74407006356F9 /* AudioContainerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA6055025F74407006356F9 /* AudioContainerViewModel.swift */; };
@ -238,7 +241,7 @@
DB45FB1D25CA9D23005A8AC7 /* APIService+HomeTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FB1C25CA9D23005A8AC7 /* APIService+HomeTimeline.swift */; }; DB45FB1D25CA9D23005A8AC7 /* APIService+HomeTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FB1C25CA9D23005A8AC7 /* APIService+HomeTimeline.swift */; };
DB47229725F9EFAD00DA7F53 /* NSManagedObjectContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB47229625F9EFAD00DA7F53 /* NSManagedObjectContext.swift */; }; DB47229725F9EFAD00DA7F53 /* NSManagedObjectContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB47229625F9EFAD00DA7F53 /* NSManagedObjectContext.swift */; };
DB482A3F261331E8008AE74C /* UserTimelineViewModel+State.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB482A3E261331E8008AE74C /* UserTimelineViewModel+State.swift */; }; DB482A3F261331E8008AE74C /* UserTimelineViewModel+State.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB482A3E261331E8008AE74C /* UserTimelineViewModel+State.swift */; };
DB482A45261335BA008AE74C /* UserTimelineViewController+StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB482A44261335BA008AE74C /* UserTimelineViewController+StatusProvider.swift */; }; DB482A45261335BA008AE74C /* UserTimelineViewController+Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB482A44261335BA008AE74C /* UserTimelineViewController+Provider.swift */; };
DB482A4B261340A7008AE74C /* APIService+UserTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB482A4A261340A7008AE74C /* APIService+UserTimeline.swift */; }; DB482A4B261340A7008AE74C /* APIService+UserTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB482A4A261340A7008AE74C /* APIService+UserTimeline.swift */; };
DB4924E226312AB200E9DB22 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4924E126312AB200E9DB22 /* NotificationService.swift */; }; DB4924E226312AB200E9DB22 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4924E126312AB200E9DB22 /* NotificationService.swift */; };
DB49A61425FF2C5600B98345 /* EmojiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB49A61325FF2C5600B98345 /* EmojiService.swift */; }; DB49A61425FF2C5600B98345 /* EmojiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB49A61325FF2C5600B98345 /* EmojiService.swift */; };
@ -360,7 +363,7 @@
DB938F0F2624119800E5B6C1 /* ThreadViewModel+LoadThreadState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB938F0E2624119800E5B6C1 /* ThreadViewModel+LoadThreadState.swift */; }; DB938F0F2624119800E5B6C1 /* ThreadViewModel+LoadThreadState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB938F0E2624119800E5B6C1 /* ThreadViewModel+LoadThreadState.swift */; };
DB938F1526241FDF00E5B6C1 /* APIService+Thread.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB938F1426241FDF00E5B6C1 /* APIService+Thread.swift */; }; DB938F1526241FDF00E5B6C1 /* APIService+Thread.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB938F1426241FDF00E5B6C1 /* APIService+Thread.swift */; };
DB938F1F2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB938F1E2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift */; }; DB938F1F2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB938F1E2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift */; };
DB938F25262438D600E5B6C1 /* ThreadViewController+StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB938F24262438D600E5B6C1 /* ThreadViewController+StatusProvider.swift */; }; DB938F25262438D600E5B6C1 /* ThreadViewController+Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB938F24262438D600E5B6C1 /* ThreadViewController+Provider.swift */; };
DB938F3326243D6200E5B6C1 /* TimelineTopLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB938F3226243D6200E5B6C1 /* TimelineTopLoaderTableViewCell.swift */; }; DB938F3326243D6200E5B6C1 /* TimelineTopLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB938F3226243D6200E5B6C1 /* TimelineTopLoaderTableViewCell.swift */; };
DB98336B25C9420100AD9700 /* APIService+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98336A25C9420100AD9700 /* APIService+App.swift */; }; DB98336B25C9420100AD9700 /* APIService+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98336A25C9420100AD9700 /* APIService+App.swift */; };
DB98337125C9443200AD9700 /* APIService+Authentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98337025C9443200AD9700 /* APIService+Authentication.swift */; }; DB98337125C9443200AD9700 /* APIService+Authentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98337025C9443200AD9700 /* APIService+Authentication.swift */; };
@ -431,7 +434,7 @@
DBE3CDFB261C6CA500430CC6 /* FavoriteViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE3CDFA261C6CA500430CC6 /* FavoriteViewModel.swift */; }; DBE3CDFB261C6CA500430CC6 /* FavoriteViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE3CDFA261C6CA500430CC6 /* FavoriteViewModel.swift */; };
DBE3CE01261D623D00430CC6 /* FavoriteViewModel+State.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE3CE00261D623D00430CC6 /* FavoriteViewModel+State.swift */; }; DBE3CE01261D623D00430CC6 /* FavoriteViewModel+State.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE3CE00261D623D00430CC6 /* FavoriteViewModel+State.swift */; };
DBE3CE07261D6A0E00430CC6 /* FavoriteViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE3CE06261D6A0E00430CC6 /* FavoriteViewModel+Diffable.swift */; }; DBE3CE07261D6A0E00430CC6 /* FavoriteViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE3CE06261D6A0E00430CC6 /* FavoriteViewModel+Diffable.swift */; };
DBE3CE0D261D767100430CC6 /* FavoriteViewController+StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE3CE0C261D767100430CC6 /* FavoriteViewController+StatusProvider.swift */; }; DBE3CE0D261D767100430CC6 /* FavoriteViewController+Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE3CE0C261D767100430CC6 /* FavoriteViewController+Provider.swift */; };
DBE3CE13261D7D4200430CC6 /* StatusTableViewControllerAspect.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE3CE12261D7D4200430CC6 /* StatusTableViewControllerAspect.swift */; }; DBE3CE13261D7D4200430CC6 /* StatusTableViewControllerAspect.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE3CE12261D7D4200430CC6 /* StatusTableViewControllerAspect.swift */; };
DBE54ABF2636C889004E7C0B /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D1B23263684C600ACB481 /* UserDefaults.swift */; }; DBE54ABF2636C889004E7C0B /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D1B23263684C600ACB481 /* UserDefaults.swift */; };
DBE54AC62636C89F004E7C0B /* NotificationPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE54AC52636C89F004E7C0B /* NotificationPreference.swift */; }; DBE54AC62636C89F004E7C0B /* NotificationPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE54AC52636C89F004E7C0B /* NotificationPreference.swift */; };
@ -571,7 +574,7 @@
0F20220626134DA4000C64BF /* HashtagTimelineViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HashtagTimelineViewModel+Diffable.swift"; sourceTree = "<group>"; }; 0F20220626134DA4000C64BF /* HashtagTimelineViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HashtagTimelineViewModel+Diffable.swift"; sourceTree = "<group>"; };
0F20220C26134E3F000C64BF /* HashtagTimelineViewModel+LoadLatestState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HashtagTimelineViewModel+LoadLatestState.swift"; sourceTree = "<group>"; }; 0F20220C26134E3F000C64BF /* HashtagTimelineViewModel+LoadLatestState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HashtagTimelineViewModel+LoadLatestState.swift"; sourceTree = "<group>"; };
0F202212261351F5000C64BF /* APIService+HashtagTimeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+HashtagTimeline.swift"; sourceTree = "<group>"; }; 0F202212261351F5000C64BF /* APIService+HashtagTimeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+HashtagTimeline.swift"; sourceTree = "<group>"; };
0F202226261411BA000C64BF /* HashtagTimelineViewController+StatusProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HashtagTimelineViewController+StatusProvider.swift"; sourceTree = "<group>"; }; 0F202226261411BA000C64BF /* HashtagTimelineViewController+Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HashtagTimelineViewController+Provider.swift"; sourceTree = "<group>"; };
0F20222C261457EE000C64BF /* HashtagTimelineViewModel+LoadOldestState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HashtagTimelineViewModel+LoadOldestState.swift"; sourceTree = "<group>"; }; 0F20222C261457EE000C64BF /* HashtagTimelineViewModel+LoadOldestState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HashtagTimelineViewModel+LoadOldestState.swift"; sourceTree = "<group>"; };
0F20223226145E51000C64BF /* HashtagTimelineViewModel+LoadMiddleState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HashtagTimelineViewModel+LoadMiddleState.swift"; sourceTree = "<group>"; }; 0F20223226145E51000C64BF /* HashtagTimelineViewModel+LoadMiddleState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HashtagTimelineViewModel+LoadMiddleState.swift"; sourceTree = "<group>"; };
0F20223826146553000C64BF /* Array.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Array.swift; sourceTree = "<group>"; }; 0F20223826146553000C64BF /* Array.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Array.swift; sourceTree = "<group>"; };
@ -616,7 +619,7 @@
2D364F7725E66D8300204FDC /* MastodonResendEmailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonResendEmailViewModel.swift; sourceTree = "<group>"; }; 2D364F7725E66D8300204FDC /* MastodonResendEmailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonResendEmailViewModel.swift; sourceTree = "<group>"; };
2D38F1C525CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentOffsetAdjustableTimelineViewControllerDelegate.swift; sourceTree = "<group>"; }; 2D38F1C525CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentOffsetAdjustableTimelineViewControllerDelegate.swift; sourceTree = "<group>"; };
2D38F1D425CD465300561493 /* HomeTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTimelineViewController.swift; sourceTree = "<group>"; }; 2D38F1D425CD465300561493 /* HomeTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTimelineViewController.swift; sourceTree = "<group>"; };
2D38F1DE25CD46A400561493 /* HomeTimelineViewController+StatusProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeTimelineViewController+StatusProvider.swift"; sourceTree = "<group>"; }; 2D38F1DE25CD46A400561493 /* HomeTimelineViewController+Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeTimelineViewController+Provider.swift"; sourceTree = "<group>"; };
2D38F1E425CD46C100561493 /* HomeTimelineViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTimelineViewModel.swift; sourceTree = "<group>"; }; 2D38F1E425CD46C100561493 /* HomeTimelineViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTimelineViewModel.swift; sourceTree = "<group>"; };
2D38F1EA25CD477000561493 /* HomeTimelineViewModel+LoadLatestState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeTimelineViewModel+LoadLatestState.swift"; sourceTree = "<group>"; }; 2D38F1EA25CD477000561493 /* HomeTimelineViewModel+LoadLatestState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeTimelineViewModel+LoadLatestState.swift"; sourceTree = "<group>"; };
2D38F1F025CD477D00561493 /* HomeTimelineViewModel+LoadMiddleState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeTimelineViewModel+LoadMiddleState.swift"; sourceTree = "<group>"; }; 2D38F1F025CD477D00561493 /* HomeTimelineViewModel+LoadMiddleState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeTimelineViewModel+LoadMiddleState.swift"; sourceTree = "<group>"; };
@ -652,7 +655,7 @@
2D6DE3FF26141DF600A63F6A /* SearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewModel.swift; sourceTree = "<group>"; }; 2D6DE3FF26141DF600A63F6A /* SearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewModel.swift; sourceTree = "<group>"; };
2D76316425C14BD100929FB9 /* PublicTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicTimelineViewController.swift; sourceTree = "<group>"; }; 2D76316425C14BD100929FB9 /* PublicTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicTimelineViewController.swift; sourceTree = "<group>"; };
2D76316A25C14D4C00929FB9 /* PublicTimelineViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicTimelineViewModel.swift; sourceTree = "<group>"; }; 2D76316A25C14D4C00929FB9 /* PublicTimelineViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicTimelineViewModel.swift; sourceTree = "<group>"; };
2D76317C25C14DF400929FB9 /* PublicTimelineViewController+StatusProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PublicTimelineViewController+StatusProvider.swift"; sourceTree = "<group>"; }; 2D76317C25C14DF400929FB9 /* PublicTimelineViewController+Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PublicTimelineViewController+Provider.swift"; sourceTree = "<group>"; };
2D76318225C14E8F00929FB9 /* PublicTimelineViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PublicTimelineViewModel+Diffable.swift"; sourceTree = "<group>"; }; 2D76318225C14E8F00929FB9 /* PublicTimelineViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PublicTimelineViewModel+Diffable.swift"; sourceTree = "<group>"; };
2D76319E25C1521200929FB9 /* StatusSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusSection.swift; sourceTree = "<group>"; }; 2D76319E25C1521200929FB9 /* StatusSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusSection.swift; sourceTree = "<group>"; };
2D7631A725C1535600929FB9 /* StatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewCell.swift; sourceTree = "<group>"; }; 2D7631A725C1535600929FB9 /* StatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewCell.swift; sourceTree = "<group>"; };
@ -671,6 +674,9 @@
2D927F1325C7EDD9004F19B8 /* Emoji.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Emoji.swift; sourceTree = "<group>"; }; 2D927F1325C7EDD9004F19B8 /* Emoji.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Emoji.swift; sourceTree = "<group>"; };
2D939AB425EDD8A90076FA61 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = "<group>"; }; 2D939AB425EDD8A90076FA61 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = "<group>"; };
2D939AE725EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonRegisterViewController+Avatar.swift"; sourceTree = "<group>"; }; 2D939AE725EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonRegisterViewController+Avatar.swift"; sourceTree = "<group>"; };
2D9DB966263A76FB007C1D71 /* BlockDomainService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockDomainService.swift; sourceTree = "<group>"; };
2D9DB968263A833E007C1D71 /* DomainBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainBlock.swift; sourceTree = "<group>"; };
2D9DB96A263A91D1007C1D71 /* APIService+DomainBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+DomainBlock.swift"; sourceTree = "<group>"; };
2DA504682601ADE7008F4E6C /* SawToothView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SawToothView.swift; sourceTree = "<group>"; }; 2DA504682601ADE7008F4E6C /* SawToothView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SawToothView.swift; sourceTree = "<group>"; };
2DA6054625F716A2006356F9 /* PlaybackState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackState.swift; sourceTree = "<group>"; }; 2DA6054625F716A2006356F9 /* PlaybackState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackState.swift; sourceTree = "<group>"; };
2DA6055025F74407006356F9 /* AudioContainerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioContainerViewModel.swift; sourceTree = "<group>"; }; 2DA6055025F74407006356F9 /* AudioContainerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioContainerViewModel.swift; sourceTree = "<group>"; };
@ -804,7 +810,7 @@
DB45FB1C25CA9D23005A8AC7 /* APIService+HomeTimeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+HomeTimeline.swift"; sourceTree = "<group>"; }; DB45FB1C25CA9D23005A8AC7 /* APIService+HomeTimeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+HomeTimeline.swift"; sourceTree = "<group>"; };
DB47229625F9EFAD00DA7F53 /* NSManagedObjectContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSManagedObjectContext.swift; sourceTree = "<group>"; }; DB47229625F9EFAD00DA7F53 /* NSManagedObjectContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSManagedObjectContext.swift; sourceTree = "<group>"; };
DB482A3E261331E8008AE74C /* UserTimelineViewModel+State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserTimelineViewModel+State.swift"; sourceTree = "<group>"; }; DB482A3E261331E8008AE74C /* UserTimelineViewModel+State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserTimelineViewModel+State.swift"; sourceTree = "<group>"; };
DB482A44261335BA008AE74C /* UserTimelineViewController+StatusProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserTimelineViewController+StatusProvider.swift"; sourceTree = "<group>"; }; DB482A44261335BA008AE74C /* UserTimelineViewController+Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserTimelineViewController+Provider.swift"; sourceTree = "<group>"; };
DB482A4A261340A7008AE74C /* APIService+UserTimeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+UserTimeline.swift"; sourceTree = "<group>"; }; DB482A4A261340A7008AE74C /* APIService+UserTimeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+UserTimeline.swift"; sourceTree = "<group>"; };
DB4924E126312AB200E9DB22 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; }; DB4924E126312AB200E9DB22 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
DB49A61325FF2C5600B98345 /* EmojiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiService.swift; sourceTree = "<group>"; }; DB49A61325FF2C5600B98345 /* EmojiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiService.swift; sourceTree = "<group>"; };
@ -919,7 +925,7 @@
DB938F0E2624119800E5B6C1 /* ThreadViewModel+LoadThreadState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThreadViewModel+LoadThreadState.swift"; sourceTree = "<group>"; }; DB938F0E2624119800E5B6C1 /* ThreadViewModel+LoadThreadState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThreadViewModel+LoadThreadState.swift"; sourceTree = "<group>"; };
DB938F1426241FDF00E5B6C1 /* APIService+Thread.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Thread.swift"; sourceTree = "<group>"; }; DB938F1426241FDF00E5B6C1 /* APIService+Thread.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Thread.swift"; sourceTree = "<group>"; };
DB938F1E2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThreadViewModel+Diffable.swift"; sourceTree = "<group>"; }; DB938F1E2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThreadViewModel+Diffable.swift"; sourceTree = "<group>"; };
DB938F24262438D600E5B6C1 /* ThreadViewController+StatusProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThreadViewController+StatusProvider.swift"; sourceTree = "<group>"; }; DB938F24262438D600E5B6C1 /* ThreadViewController+Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThreadViewController+Provider.swift"; sourceTree = "<group>"; };
DB938F3226243D6200E5B6C1 /* TimelineTopLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineTopLoaderTableViewCell.swift; sourceTree = "<group>"; }; DB938F3226243D6200E5B6C1 /* TimelineTopLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineTopLoaderTableViewCell.swift; sourceTree = "<group>"; };
DB98336A25C9420100AD9700 /* APIService+App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+App.swift"; sourceTree = "<group>"; }; DB98336A25C9420100AD9700 /* APIService+App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+App.swift"; sourceTree = "<group>"; };
DB98337025C9443200AD9700 /* APIService+Authentication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Authentication.swift"; sourceTree = "<group>"; }; DB98337025C9443200AD9700 /* APIService+Authentication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Authentication.swift"; sourceTree = "<group>"; };
@ -989,7 +995,7 @@
DBE3CDFA261C6CA500430CC6 /* FavoriteViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteViewModel.swift; sourceTree = "<group>"; }; DBE3CDFA261C6CA500430CC6 /* FavoriteViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteViewModel.swift; sourceTree = "<group>"; };
DBE3CE00261D623D00430CC6 /* FavoriteViewModel+State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FavoriteViewModel+State.swift"; sourceTree = "<group>"; }; DBE3CE00261D623D00430CC6 /* FavoriteViewModel+State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FavoriteViewModel+State.swift"; sourceTree = "<group>"; };
DBE3CE06261D6A0E00430CC6 /* FavoriteViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FavoriteViewModel+Diffable.swift"; sourceTree = "<group>"; }; DBE3CE06261D6A0E00430CC6 /* FavoriteViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FavoriteViewModel+Diffable.swift"; sourceTree = "<group>"; };
DBE3CE0C261D767100430CC6 /* FavoriteViewController+StatusProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FavoriteViewController+StatusProvider.swift"; sourceTree = "<group>"; }; DBE3CE0C261D767100430CC6 /* FavoriteViewController+Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FavoriteViewController+Provider.swift"; sourceTree = "<group>"; };
DBE3CE12261D7D4200430CC6 /* StatusTableViewControllerAspect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewControllerAspect.swift; sourceTree = "<group>"; }; DBE3CE12261D7D4200430CC6 /* StatusTableViewControllerAspect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewControllerAspect.swift; sourceTree = "<group>"; };
DBE54AC52636C89F004E7C0B /* NotificationPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPreference.swift; sourceTree = "<group>"; }; DBE54AC52636C89F004E7C0B /* NotificationPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPreference.swift; sourceTree = "<group>"; };
DBF53F5F25C14E88008AAC7B /* Mastodon.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = Mastodon.xctestplan; path = Mastodon/Mastodon.xctestplan; sourceTree = "<group>"; }; DBF53F5F25C14E88008AAC7B /* Mastodon.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = Mastodon.xctestplan; path = Mastodon/Mastodon.xctestplan; sourceTree = "<group>"; };
@ -1086,7 +1092,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
0F2021FA2613262F000C64BF /* HashtagTimelineViewController.swift */, 0F2021FA2613262F000C64BF /* HashtagTimelineViewController.swift */,
0F202226261411BA000C64BF /* HashtagTimelineViewController+StatusProvider.swift */, 0F202226261411BA000C64BF /* HashtagTimelineViewController+Provider.swift */,
0F202200261326E6000C64BF /* HashtagTimelineViewModel.swift */, 0F202200261326E6000C64BF /* HashtagTimelineViewModel.swift */,
0F20220626134DA4000C64BF /* HashtagTimelineViewModel+Diffable.swift */, 0F20220626134DA4000C64BF /* HashtagTimelineViewModel+Diffable.swift */,
0F20220C26134E3F000C64BF /* HashtagTimelineViewModel+LoadLatestState.swift */, 0F20220C26134E3F000C64BF /* HashtagTimelineViewModel+LoadLatestState.swift */,
@ -1212,7 +1218,7 @@
children = ( children = (
DB1F239626117C360057430E /* View */, DB1F239626117C360057430E /* View */,
2D38F1D425CD465300561493 /* HomeTimelineViewController.swift */, 2D38F1D425CD465300561493 /* HomeTimelineViewController.swift */,
2D38F1DE25CD46A400561493 /* HomeTimelineViewController+StatusProvider.swift */, 2D38F1DE25CD46A400561493 /* HomeTimelineViewController+Provider.swift */,
2D5A3D6125CFD9CB002347D6 /* HomeTimelineViewController+DebugAction.swift */, 2D5A3D6125CFD9CB002347D6 /* HomeTimelineViewController+DebugAction.swift */,
2D38F1E425CD46C100561493 /* HomeTimelineViewModel.swift */, 2D38F1E425CD46C100561493 /* HomeTimelineViewModel.swift */,
2D5A3D2725CF8BC9002347D6 /* HomeTimelineViewModel+Diffable.swift */, 2D5A3D2725CF8BC9002347D6 /* HomeTimelineViewModel+Diffable.swift */,
@ -1300,6 +1306,7 @@
5DF1054025F886D400D6C0D4 /* ViedeoPlaybackService.swift */, 5DF1054025F886D400D6C0D4 /* ViedeoPlaybackService.swift */,
DB71FD4B25F8C80E00512AE1 /* StatusPrefetchingService.swift */, DB71FD4B25F8C80E00512AE1 /* StatusPrefetchingService.swift */,
DBC7A67B260DFADE00E57475 /* StatusPublishService.swift */, DBC7A67B260DFADE00E57475 /* StatusPublishService.swift */,
2D9DB966263A76FB007C1D71 /* BlockDomainService.swift */,
DB4924E126312AB200E9DB22 /* NotificationService.swift */, DB4924E126312AB200E9DB22 /* NotificationService.swift */,
DB6D9F6226357848008423CD /* SettingService.swift */, DB6D9F6226357848008423CD /* SettingService.swift */,
DBA5E7A2263AD0A3004598BB /* PhotoLibraryService.swift */, DBA5E7A2263AD0A3004598BB /* PhotoLibraryService.swift */,
@ -1338,7 +1345,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
2D76316425C14BD100929FB9 /* PublicTimelineViewController.swift */, 2D76316425C14BD100929FB9 /* PublicTimelineViewController.swift */,
2D76317C25C14DF400929FB9 /* PublicTimelineViewController+StatusProvider.swift */, 2D76317C25C14DF400929FB9 /* PublicTimelineViewController+Provider.swift */,
2D76316A25C14D4C00929FB9 /* PublicTimelineViewModel.swift */, 2D76316A25C14D4C00929FB9 /* PublicTimelineViewModel.swift */,
2D32EAD925CBCC3300C9ED86 /* PublicTimelineViewModel+LoadMiddleState.swift */, 2D32EAD925CBCC3300C9ED86 /* PublicTimelineViewModel+LoadMiddleState.swift */,
2D76318225C14E8F00929FB9 /* PublicTimelineViewModel+Diffable.swift */, 2D76318225C14E8F00929FB9 /* PublicTimelineViewModel+Diffable.swift */,
@ -1719,6 +1726,7 @@
DB98336A25C9420100AD9700 /* APIService+App.swift */, DB98336A25C9420100AD9700 /* APIService+App.swift */,
DB98337025C9443200AD9700 /* APIService+Authentication.swift */, DB98337025C9443200AD9700 /* APIService+Authentication.swift */,
DB98339B25C96DE600AD9700 /* APIService+Account.swift */, DB98339B25C96DE600AD9700 /* APIService+Account.swift */,
2D9DB96A263A91D1007C1D71 /* APIService+DomainBlock.swift */,
2D04F42425C255B9003F936F /* APIService+PublicTimeline.swift */, 2D04F42425C255B9003F936F /* APIService+PublicTimeline.swift */,
DB45FB1C25CA9D23005A8AC7 /* APIService+HomeTimeline.swift */, DB45FB1C25CA9D23005A8AC7 /* APIService+HomeTimeline.swift */,
DB482A4A261340A7008AE74C /* APIService+UserTimeline.swift */, DB482A4A261340A7008AE74C /* APIService+UserTimeline.swift */,
@ -1975,6 +1983,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DB89BA2625C110B4008580ED /* Status.swift */, DB89BA2625C110B4008580ED /* Status.swift */,
2D9DB968263A833E007C1D71 /* DomainBlock.swift */,
2D6125462625436B00299647 /* Notification.swift */, 2D6125462625436B00299647 /* Notification.swift */,
2D0B7A1C261D839600B44727 /* SearchHistory.swift */, 2D0B7A1C261D839600B44727 /* SearchHistory.swift */,
DB8AF52425C131D1002E6C99 /* MastodonUser.swift */, DB8AF52425C131D1002E6C99 /* MastodonUser.swift */,
@ -2098,7 +2107,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DB938EE52623F50700E5B6C1 /* ThreadViewController.swift */, DB938EE52623F50700E5B6C1 /* ThreadViewController.swift */,
DB938F24262438D600E5B6C1 /* ThreadViewController+StatusProvider.swift */, DB938F24262438D600E5B6C1 /* ThreadViewController+Provider.swift */,
DB938EEC2623F79B00E5B6C1 /* ThreadViewModel.swift */, DB938EEC2623F79B00E5B6C1 /* ThreadViewModel.swift */,
DB938F1E2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift */, DB938F1E2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift */,
DB938F0E2624119800E5B6C1 /* ThreadViewModel+LoadThreadState.swift */, DB938F0E2624119800E5B6C1 /* ThreadViewModel+LoadThreadState.swift */,
@ -2281,7 +2290,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DBB525352611ECEB002F1F29 /* UserTimelineViewController.swift */, DBB525352611ECEB002F1F29 /* UserTimelineViewController.swift */,
DB482A44261335BA008AE74C /* UserTimelineViewController+StatusProvider.swift */, DB482A44261335BA008AE74C /* UserTimelineViewController+Provider.swift */,
DBB525552611EDCA002F1F29 /* UserTimelineViewModel.swift */, DBB525552611EDCA002F1F29 /* UserTimelineViewModel.swift */,
DBCBED1626132DB500B49291 /* UserTimelineViewModel+Diffable.swift */, DBCBED1626132DB500B49291 /* UserTimelineViewModel+Diffable.swift */,
DB482A3E261331E8008AE74C /* UserTimelineViewModel+State.swift */, DB482A3E261331E8008AE74C /* UserTimelineViewModel+State.swift */,
@ -2334,7 +2343,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DBE3CDEB261C6B2900430CC6 /* FavoriteViewController.swift */, DBE3CDEB261C6B2900430CC6 /* FavoriteViewController.swift */,
DBE3CE0C261D767100430CC6 /* FavoriteViewController+StatusProvider.swift */, DBE3CE0C261D767100430CC6 /* FavoriteViewController+Provider.swift */,
DBE3CDFA261C6CA500430CC6 /* FavoriteViewModel.swift */, DBE3CDFA261C6CA500430CC6 /* FavoriteViewModel.swift */,
DBE3CE06261D6A0E00430CC6 /* FavoriteViewModel+Diffable.swift */, DBE3CE06261D6A0E00430CC6 /* FavoriteViewModel+Diffable.swift */,
DBE3CE00261D623D00430CC6 /* FavoriteViewModel+State.swift */, DBE3CE00261D623D00430CC6 /* FavoriteViewModel+State.swift */,
@ -2862,6 +2871,7 @@
DB6B35182601FA3400DC1E11 /* MastodonAttachmentService.swift in Sources */, DB6B35182601FA3400DC1E11 /* MastodonAttachmentService.swift in Sources */,
0FB3D2F725E4C24D00AAD544 /* MastodonPickServerViewModel.swift in Sources */, 0FB3D2F725E4C24D00AAD544 /* MastodonPickServerViewModel.swift in Sources */,
2D61335E25C1894B00CAE157 /* APIService.swift in Sources */, 2D61335E25C1894B00CAE157 /* APIService.swift in Sources */,
2D9DB967263A76FB007C1D71 /* BlockDomainService.swift in Sources */,
DB66729625F9F91600D60309 /* ComposeStatusSection.swift in Sources */, DB66729625F9F91600D60309 /* ComposeStatusSection.swift in Sources */,
DB482A3F261331E8008AE74C /* UserTimelineViewModel+State.swift in Sources */, DB482A3F261331E8008AE74C /* UserTimelineViewModel+State.swift in Sources */,
2D38F1F725CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift in Sources */, 2D38F1F725CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift in Sources */,
@ -2967,7 +2977,7 @@
DB6D9F9726367249008423CD /* SettingsViewController.swift in Sources */, DB6D9F9726367249008423CD /* SettingsViewController.swift in Sources */,
DBD9149025DF6D8D00903DFD /* APIService+Onboarding.swift in Sources */, DBD9149025DF6D8D00903DFD /* APIService+Onboarding.swift in Sources */,
DBAE3FAF26172FC0004B8251 /* RemoteProfileViewModel.swift in Sources */, DBAE3FAF26172FC0004B8251 /* RemoteProfileViewModel.swift in Sources */,
DBE3CE0D261D767100430CC6 /* FavoriteViewController+StatusProvider.swift in Sources */, DBE3CE0D261D767100430CC6 /* FavoriteViewController+Provider.swift in Sources */,
2D084B9326259545003AA3AF /* NotificationViewModel+LoadLatestState.swift in Sources */, 2D084B9326259545003AA3AF /* NotificationViewModel+LoadLatestState.swift in Sources */,
DB98337F25C9452D00AD9700 /* APIService+APIError.swift in Sources */, DB98337F25C9452D00AD9700 /* APIService+APIError.swift in Sources */,
DB9E0D6F25EE008500CFDD76 /* UIInterpolatingMotionEffect.swift in Sources */, DB9E0D6F25EE008500CFDD76 /* UIInterpolatingMotionEffect.swift in Sources */,
@ -3003,8 +3013,9 @@
2DA6055125F74407006356F9 /* AudioContainerViewModel.swift in Sources */, 2DA6055125F74407006356F9 /* AudioContainerViewModel.swift in Sources */,
0FB3D2FE25E4CB6400AAD544 /* PickServerTitleCell.swift in Sources */, 0FB3D2FE25E4CB6400AAD544 /* PickServerTitleCell.swift in Sources */,
5DA732CC2629CEF500A92342 /* UIView+Remove.swift in Sources */, 5DA732CC2629CEF500A92342 /* UIView+Remove.swift in Sources */,
2D38F1DF25CD46A400561493 /* HomeTimelineViewController+StatusProvider.swift in Sources */, 2D38F1DF25CD46A400561493 /* HomeTimelineViewController+Provider.swift in Sources */,
2D206B9225F60EA700143C56 /* UIControl.swift in Sources */, 2D206B9225F60EA700143C56 /* UIControl.swift in Sources */,
2D9DB96B263A91D1007C1D71 /* APIService+DomainBlock.swift in Sources */,
2D46975E25C2A54100CF4AA9 /* NSLayoutConstraint.swift in Sources */, 2D46975E25C2A54100CF4AA9 /* NSLayoutConstraint.swift in Sources */,
2D45E5BF25C9549700A6D639 /* PublicTimelineViewModel+State.swift in Sources */, 2D45E5BF25C9549700A6D639 /* PublicTimelineViewModel+State.swift in Sources */,
DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */, DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */,
@ -3083,7 +3094,7 @@
DB44384F25E8C1FA008912A2 /* CALayer.swift in Sources */, DB44384F25E8C1FA008912A2 /* CALayer.swift in Sources */,
2D34D9CB261489930081BFC0 /* SearchViewController+Recommend.swift in Sources */, 2D34D9CB261489930081BFC0 /* SearchViewController+Recommend.swift in Sources */,
DB938F1526241FDF00E5B6C1 /* APIService+Thread.swift in Sources */, DB938F1526241FDF00E5B6C1 /* APIService+Thread.swift in Sources */,
DB482A45261335BA008AE74C /* UserTimelineViewController+StatusProvider.swift in Sources */, DB482A45261335BA008AE74C /* UserTimelineViewController+Provider.swift in Sources */,
2D206B8625F5FB0900143C56 /* Double.swift in Sources */, 2D206B8625F5FB0900143C56 /* Double.swift in Sources */,
DB9A485C2603010E008B817C /* PHPickerResultLoader.swift in Sources */, DB9A485C2603010E008B817C /* PHPickerResultLoader.swift in Sources */,
2D76319F25C1521200929FB9 /* StatusSection.swift in Sources */, 2D76319F25C1521200929FB9 /* StatusSection.swift in Sources */,
@ -3127,7 +3138,7 @@
DB45FAF925CA80A2005A8AC7 /* APIService+CoreData+MastodonAuthentication.swift in Sources */, DB45FAF925CA80A2005A8AC7 /* APIService+CoreData+MastodonAuthentication.swift in Sources */,
2D04F42525C255B9003F936F /* APIService+PublicTimeline.swift in Sources */, 2D04F42525C255B9003F936F /* APIService+PublicTimeline.swift in Sources */,
2D34D9E226149C920081BFC0 /* SearchRecommendTagsCollectionViewCell.swift in Sources */, 2D34D9E226149C920081BFC0 /* SearchRecommendTagsCollectionViewCell.swift in Sources */,
2D76317D25C14DF500929FB9 /* PublicTimelineViewController+StatusProvider.swift in Sources */, 2D76317D25C14DF500929FB9 /* PublicTimelineViewController+Provider.swift in Sources */,
0F20223326145E51000C64BF /* HashtagTimelineViewModel+LoadMiddleState.swift in Sources */, 0F20223326145E51000C64BF /* HashtagTimelineViewModel+LoadMiddleState.swift in Sources */,
2D206B8025F5F45E00143C56 /* UIImage.swift in Sources */, 2D206B8025F5F45E00143C56 /* UIImage.swift in Sources */,
DB6D9F502635761F008423CD /* SubscriptionAlerts.swift in Sources */, DB6D9F502635761F008423CD /* SubscriptionAlerts.swift in Sources */,
@ -3144,7 +3155,7 @@
2D76318325C14E8F00929FB9 /* PublicTimelineViewModel+Diffable.swift in Sources */, 2D76318325C14E8F00929FB9 /* PublicTimelineViewModel+Diffable.swift in Sources */,
DB6180FA26391F2E0018D199 /* MediaPreviewViewModel.swift in Sources */, DB6180FA26391F2E0018D199 /* MediaPreviewViewModel.swift in Sources */,
2D198655261C3C4300F0B013 /* SearchViewModel+LoadOldestState.swift in Sources */, 2D198655261C3C4300F0B013 /* SearchViewModel+LoadOldestState.swift in Sources */,
0F202227261411BB000C64BF /* HashtagTimelineViewController+StatusProvider.swift in Sources */, 0F202227261411BB000C64BF /* HashtagTimelineViewController+Provider.swift in Sources */,
2D7631A825C1535600929FB9 /* StatusTableViewCell.swift in Sources */, 2D7631A825C1535600929FB9 /* StatusTableViewCell.swift in Sources */,
2D76316525C14BD100929FB9 /* PublicTimelineViewController.swift in Sources */, 2D76316525C14BD100929FB9 /* PublicTimelineViewController.swift in Sources */,
DB938F1F2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift in Sources */, DB938F1F2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift in Sources */,
@ -3178,7 +3189,7 @@
2D38F1FE25CD481700561493 /* StatusProvider.swift in Sources */, 2D38F1FE25CD481700561493 /* StatusProvider.swift in Sources */,
5B24BBE2262DB19100A9381B /* APIService+Report.swift in Sources */, 5B24BBE2262DB19100A9381B /* APIService+Report.swift in Sources */,
5BB04FF5262F0E6D0043BFF6 /* ReportSection.swift in Sources */, 5BB04FF5262F0E6D0043BFF6 /* ReportSection.swift in Sources */,
DB938F25262438D600E5B6C1 /* ThreadViewController+StatusProvider.swift in Sources */, DB938F25262438D600E5B6C1 /* ThreadViewController+Provider.swift in Sources */,
DB66729C25F9F91F00D60309 /* ComposeStatusItem.swift in Sources */, DB66729C25F9F91F00D60309 /* ComposeStatusItem.swift in Sources */,
DB6180E326391A4C0018D199 /* ViewControllerAnimatedTransitioning.swift in Sources */, DB6180E326391A4C0018D199 /* ViewControllerAnimatedTransitioning.swift in Sources */,
0FB3D31E25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift in Sources */, 0FB3D31E25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift in Sources */,
@ -3251,6 +3262,7 @@
2DF75BB925D1474100694EC8 /* ManagedObjectObserver.swift in Sources */, 2DF75BB925D1474100694EC8 /* ManagedObjectObserver.swift in Sources */,
2D927F0225C7E4F2004F19B8 /* Mention.swift in Sources */, 2D927F0225C7E4F2004F19B8 /* Mention.swift in Sources */,
DB89BA1D25C1107F008580ED /* URL.swift in Sources */, DB89BA1D25C1107F008580ED /* URL.swift in Sources */,
2D9DB969263A833E007C1D71 /* DomainBlock.swift in Sources */,
2D0B7A1D261D839600B44727 /* SearchHistory.swift in Sources */, 2D0B7A1D261D839600B44727 /* SearchHistory.swift in Sources */,
2D927F0825C7E9A8004F19B8 /* Tag.swift in Sources */, 2D927F0825C7E9A8004F19B8 /* Tag.swift in Sources */,
5B90C46E26259B2C0002E742 /* Subscription.swift in Sources */, 5B90C46E26259B2C0002E742 /* Subscription.swift in Sources */,

View File

@ -223,7 +223,6 @@ extension StatusSection {
meta.blurhashImagePublisher() meta.blurhashImagePublisher()
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak cell] image in .sink { [weak cell] image in
guard let cell = cell else { return }
blurhashOverlayImageView.image = image blurhashOverlayImageView.image = image
image?.pngData().flatMap { image?.pngData().flatMap {
blurhashImageCache.setObject($0 as NSData, forKey: blurhashImageDataKey) blurhashImageCache.setObject($0 as NSData, forKey: blurhashImageDataKey)
@ -401,16 +400,15 @@ extension StatusSection {
.store(in: &cell.disposeBag) .store(in: &cell.disposeBag)
} }
// toolbar
StatusSection.configureActionToolBar(
cell: cell,
dependency: dependency,
status: status,
requestUserID: requestUserID
)
// separator line
if let statusTableViewCell = cell as? StatusTableViewCell { if let statusTableViewCell = cell as? StatusTableViewCell {
// toolbar
StatusSection.configureActionToolBar(
cell: statusTableViewCell,
dependency: dependency,
status: status,
requestUserID: requestUserID
)
// separator line
statusTableViewCell.separatorLine.isHidden = statusItemAttribute.isSeparatorLineHidden statusTableViewCell.separatorLine.isHidden = statusItemAttribute.isSeparatorLineHidden
} }
@ -430,12 +428,12 @@ extension StatusSection {
.sink { _ in .sink { _ in
// do nothing // do nothing
} receiveValue: { [weak dependency, weak cell] change in } receiveValue: { [weak dependency, weak cell] change in
guard let cell = cell else { return }
guard let dependency = dependency else { return } guard let dependency = dependency else { return }
guard case .update(let object) = change.changeType, guard case .update(let object) = change.changeType,
let status = object as? Status else { return } let status = object as? Status else { return }
guard let statusTableViewCell = cell as? StatusTableViewCell else { return }
StatusSection.configureActionToolBar( StatusSection.configureActionToolBar(
cell: cell, cell: statusTableViewCell,
dependency: dependency, dependency: dependency,
status: status, status: status,
requestUserID: requestUserID requestUserID: requestUserID
@ -593,7 +591,7 @@ extension StatusSection {
} }
static func configureActionToolBar( static func configureActionToolBar(
cell: StatusCell, cell: StatusTableViewCell,
dependency: NeedsDependency, dependency: NeedsDependency,
status: Status, status: Status,
requestUserID: String requestUserID: String
@ -623,6 +621,26 @@ extension StatusSection {
cell.statusView.actionToolbarContainer.favoriteButton.setTitle(favoriteCountTitle, for: .normal) cell.statusView.actionToolbarContainer.favoriteButton.setTitle(favoriteCountTitle, for: .normal)
cell.statusView.actionToolbarContainer.isFavoriteButtonHighlight = isLike cell.statusView.actionToolbarContainer.isFavoriteButtonHighlight = isLike
Publishers.CombineLatest(
dependency.context.blockDomainService.blockedDomains,
ManagedObjectObserver.observe(object: status.authorForUserProvider)
.assertNoFailure()
)
.receive(on: DispatchQueue.main)
.sink { [weak dependency, weak cell] _,change in
guard let cell = cell else { return }
guard let dependency = dependency else { return }
switch change.changeType {
case .delete:
return
case .update(_):
break
case .none:
break
}
StatusSection.setupStatusMoreButtonMenu(cell: cell, dependency: dependency, status: status)
}
.store(in: &cell.disposeBag)
self.setupStatusMoreButtonMenu(cell: cell, dependency: dependency, status: status) self.setupStatusMoreButtonMenu(cell: cell, dependency: dependency, status: status)
} }
@ -752,37 +770,36 @@ extension StatusSection {
} }
private static func setupStatusMoreButtonMenu( private static func setupStatusMoreButtonMenu(
cell: StatusCell, cell: StatusTableViewCell,
dependency: NeedsDependency, dependency: NeedsDependency,
status: Status) { status: Status) {
cell.statusView.actionToolbarContainer.moreButton.menu = nil guard let userProvider = dependency as? UserProvider else { fatalError() }
guard let authenticationBox = dependency.context.authenticationService.activeMastodonAuthenticationBox.value else { guard let authenticationBox = dependency.context.authenticationService.activeMastodonAuthenticationBox.value else {
return return
} }
let author = (status.reblog ?? status).author let author = status.authorForUserProvider
guard authenticationBox.userID != author.id else { let isMyself = authenticationBox.userID == author.id
return let canReport = !isMyself
} let isInSameDomain = authenticationBox.domain == author.domainFromAcct
var children: [UIMenuElement] = [] let isMuting = (author.mutingBy ?? Set()).map(\.id).contains(authenticationBox.userID)
let name = author.displayNameWithFallback let isBlocking = (author.blockingBy ?? Set()).map(\.id).contains(authenticationBox.userID)
let reportAction = UIAction(title: L10n.Common.Controls.Actions.reportUser(name), image: UIImage(systemName: "exclamationmark.bubble"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { let isDomainBlocking = dependency.context.blockDomainService.blockedDomains.value.contains(author.domainFromAcct)
[weak dependency] _ in
guard let dependency = dependency else { return }
let viewModel = ReportViewModel(
context: dependency.context,
domain: authenticationBox.domain,
user: status.author,
status: status)
dependency.coordinator.present(
scene: .report(viewModel: viewModel),
from: nil,
transition: .modal(animated: true, completion: nil)
)
}
children.append(reportAction)
cell.statusView.actionToolbarContainer.moreButton.menu = UIMenu(title: "", options: [], children: children)
cell.statusView.actionToolbarContainer.moreButton.showsMenuAsPrimaryAction = true cell.statusView.actionToolbarContainer.moreButton.showsMenuAsPrimaryAction = true
cell.statusView.actionToolbarContainer.moreButton.menu = UserProviderFacade.createProfileActionMenu(
for: author,
isMyself: isMyself,
isMuting: isMuting,
isBlocking: isBlocking,
isInSameDomain: isInSameDomain,
isDomainBlocking: isDomainBlocking,
provider: userProvider,
cell: cell,
sourceView: cell.statusView.actionToolbarContainer.moreButton,
barButtonItem: nil,
shareUser: nil,
shareStatus: status
)
} }
} }

View File

@ -50,6 +50,15 @@ extension MastodonUser {
} }
} }
var domainFromAcct: String {
if !acct.contains("@") {
return domain
} else {
let domain = acct.split(separator: "@").last
return String(domain!)
}
}
} }
extension MastodonUser { extension MastodonUser {

View File

@ -5,8 +5,8 @@
// Created by MainasuK Cirno on 2021/2/4. // Created by MainasuK Cirno on 2021/2/4.
// //
import Foundation
import CoreDataStack import CoreDataStack
import Foundation
import MastodonSDK import MastodonSDK
extension Status.Property { extension Status.Property {
@ -34,7 +34,6 @@ extension Status.Property {
} }
extension Status { extension Status {
enum SensitiveType { enum SensitiveType {
case none case none
case all case all
@ -61,5 +60,29 @@ extension Status {
// not sensitive // not sensitive
return .none return .none
} }
}
extension Status {
var authorForUserProvider: MastodonUser {
let author = (reblog ?? self).author
return author
}
}
extension Status {
var statusURL: URL {
if let urlString = self.url,
let url = URL(string: urlString)
{
return url
} else {
return URL(string: "https://\(self.domain)/web/statuses/\(self.id)")!
}
}
var activityItems: [Any] {
var items: [Any] = []
items.append(self.statusURL)
return items
}
} }

View File

@ -13,6 +13,14 @@ internal enum L10n {
internal enum Common { internal enum Common {
internal enum Alerts { internal enum Alerts {
internal enum BlockDomain {
/// Block entire domain
internal static let blockEntireDomain = L10n.tr("Localizable", "Common.Alerts.BlockDomain.BlockEntireDomain")
/// Are you really, really sure you want to block the entire %@? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.
internal static func message(_ p1: Any) -> String {
return L10n.tr("Localizable", "Common.Alerts.BlockDomain.Message", String(describing: p1))
}
}
internal enum Common { internal enum Common {
/// Please try again. /// Please try again.
internal static let pleaseTryAgain = L10n.tr("Localizable", "Common.Alerts.Common.PleaseTryAgain") internal static let pleaseTryAgain = L10n.tr("Localizable", "Common.Alerts.Common.PleaseTryAgain")
@ -66,6 +74,10 @@ internal enum L10n {
internal static let add = L10n.tr("Localizable", "Common.Controls.Actions.Add") internal static let add = L10n.tr("Localizable", "Common.Controls.Actions.Add")
/// Back /// Back
internal static let back = L10n.tr("Localizable", "Common.Controls.Actions.Back") internal static let back = L10n.tr("Localizable", "Common.Controls.Actions.Back")
/// Block %@
internal static func blockDomain(_ p1: Any) -> String {
return L10n.tr("Localizable", "Common.Controls.Actions.BlockDomain", String(describing: p1))
}
/// Cancel /// Cancel
internal static let cancel = L10n.tr("Localizable", "Common.Controls.Actions.Cancel") internal static let cancel = L10n.tr("Localizable", "Common.Controls.Actions.Cancel")
/// Confirm /// Confirm
@ -104,6 +116,8 @@ internal enum L10n {
internal static let settings = L10n.tr("Localizable", "Common.Controls.Actions.Settings") internal static let settings = L10n.tr("Localizable", "Common.Controls.Actions.Settings")
/// Share /// Share
internal static let share = L10n.tr("Localizable", "Common.Controls.Actions.Share") internal static let share = L10n.tr("Localizable", "Common.Controls.Actions.Share")
/// Share post
internal static let sharePost = L10n.tr("Localizable", "Common.Controls.Actions.SharePost")
/// Share %@ /// Share %@
internal static func shareUser(_ p1: Any) -> String { internal static func shareUser(_ p1: Any) -> String {
return L10n.tr("Localizable", "Common.Controls.Actions.ShareUser", String(describing: p1)) return L10n.tr("Localizable", "Common.Controls.Actions.ShareUser", String(describing: p1))
@ -118,6 +132,10 @@ internal enum L10n {
internal static let takePhoto = L10n.tr("Localizable", "Common.Controls.Actions.TakePhoto") internal static let takePhoto = L10n.tr("Localizable", "Common.Controls.Actions.TakePhoto")
/// Try Again /// Try Again
internal static let tryAgain = L10n.tr("Localizable", "Common.Controls.Actions.TryAgain") internal static let tryAgain = L10n.tr("Localizable", "Common.Controls.Actions.TryAgain")
/// Unblock %@
internal static func unblockDomain(_ p1: Any) -> String {
return L10n.tr("Localizable", "Common.Controls.Actions.UnblockDomain", String(describing: p1))
}
} }
internal enum Firendship { internal enum Firendship {
/// Block /// Block

View File

@ -114,7 +114,7 @@ extension StatusTableViewCellDelegate where Self: StatusProvider {
guard let imagePreviewPresentableCell = tableView.cellForRow(at: indexPath) as? ImagePreviewPresentableCell else { return nil } guard let imagePreviewPresentableCell = tableView.cellForRow(at: indexPath) as? ImagePreviewPresentableCell else { return nil }
guard imagePreviewPresentableCell.isRevealing else { return nil } guard imagePreviewPresentableCell.isRevealing else { return nil }
let status = status(for: nil, indexPath: indexPath) let status = self.status(for: nil, indexPath: indexPath)
return contextMenuConfiguration(tableView, status: status, imagePreviewPresentableCell: imagePreviewPresentableCell, contextMenuConfigurationForRowAt: indexPath, point: point) return contextMenuConfiguration(tableView, status: status, imagePreviewPresentableCell: imagePreviewPresentableCell, contextMenuConfigurationForRowAt: indexPath, point: point)
} }
@ -260,7 +260,7 @@ extension StatusTableViewCellDelegate where Self: StatusProvider {
guard index < imageViews.count else { return } guard index < imageViews.count else { return }
let imageView = imageViews[index] let imageView = imageViews[index]
let status = status(for: nil, indexPath: indexPath) let status = self.status(for: nil, indexPath: indexPath)
let initialFrame: CGRect? = { let initialFrame: CGRect? = {
guard let previewViewController = animator.previewViewController else { return nil } guard let previewViewController = animator.previewViewController else { return nil }
return UIView.findContextMenuPreviewFrameInWindow(previewController: previewViewController) return UIView.findContextMenuPreviewFrameInWindow(previewController: previewViewController)

View File

@ -5,12 +5,33 @@
// Created by MainasuK Cirno on 2021-4-1. // Created by MainasuK Cirno on 2021-4-1.
// //
import UIKit
import Combine import Combine
import CoreData import CoreData
import CoreDataStack import CoreDataStack
import UIKit
protocol UserProvider: NeedsDependency & DisposeBagCollectable & UIViewController { protocol UserProvider: NeedsDependency & DisposeBagCollectable & UIViewController {
// async // async
func mastodonUser() -> Future<MastodonUser?, Never> func mastodonUser() -> Future<MastodonUser?, Never>
func mastodonUser(for cell: UITableViewCell?) -> Future<MastodonUser?, Never>
}
extension UserProvider where Self: StatusProvider {
func mastodonUser(for cell: UITableViewCell?) -> Future<MastodonUser?, Never> {
Future { [weak self] promise in
guard let self = self else { return }
self.status(for: cell, indexPath: nil)
.sink { status in
promise(.success(status?.authorForUserProvider))
}
.store(in: &self.disposeBag)
}
}
func mastodonUser() -> Future<MastodonUser?, Never> {
Future { promise in
promise(.success(nil))
}
}
} }

View File

@ -5,16 +5,15 @@
// Created by MainasuK Cirno on 2021-4-1. // Created by MainasuK Cirno on 2021-4-1.
// //
import UIKit
import Combine import Combine
import CoreData import CoreData
import CoreDataStack import CoreDataStack
import MastodonSDK import MastodonSDK
import UIKit
enum UserProviderFacade { } enum UserProviderFacade {}
extension UserProviderFacade { extension UserProviderFacade {
static func toggleUserFollowRelationship( static func toggleUserFollowRelationship(
provider: UserProvider provider: UserProvider
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> { ) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> {
@ -50,25 +49,31 @@ extension UserProviderFacade {
.switchToLatest() .switchToLatest()
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
} }
extension UserProviderFacade { extension UserProviderFacade {
static func toggleUserBlockRelationship( static func toggleUserBlockRelationship(
provider: UserProvider provider: UserProvider,
cell: UITableViewCell?
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> { ) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> {
// prepare authentication // prepare authentication
guard let activeMastodonAuthenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else { guard let activeMastodonAuthenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else {
assertionFailure() assertionFailure()
return Fail(error: APIService.APIError.implicit(.authenticationMissing)).eraseToAnyPublisher() return Fail(error: APIService.APIError.implicit(.authenticationMissing)).eraseToAnyPublisher()
} }
if let cell = cell {
return _toggleUserBlockRelationship( return _toggleUserBlockRelationship(
context: provider.context, context: provider.context,
activeMastodonAuthenticationBox: activeMastodonAuthenticationBox, activeMastodonAuthenticationBox: activeMastodonAuthenticationBox,
mastodonUser: provider.mastodonUser().eraseToAnyPublisher() mastodonUser: provider.mastodonUser(for: cell).eraseToAnyPublisher()
) )
} else {
return _toggleUserBlockRelationship(
context: provider.context,
activeMastodonAuthenticationBox: activeMastodonAuthenticationBox,
mastodonUser: provider.mastodonUser().eraseToAnyPublisher()
)
}
} }
private static func _toggleUserBlockRelationship( private static func _toggleUserBlockRelationship(
@ -90,25 +95,31 @@ extension UserProviderFacade {
.switchToLatest() .switchToLatest()
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
} }
extension UserProviderFacade { extension UserProviderFacade {
static func toggleUserMuteRelationship( static func toggleUserMuteRelationship(
provider: UserProvider provider: UserProvider,
cell: UITableViewCell?
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> { ) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> {
// prepare authentication // prepare authentication
guard let activeMastodonAuthenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else { guard let activeMastodonAuthenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else {
assertionFailure() assertionFailure()
return Fail(error: APIService.APIError.implicit(.authenticationMissing)).eraseToAnyPublisher() return Fail(error: APIService.APIError.implicit(.authenticationMissing)).eraseToAnyPublisher()
} }
if let cell = cell {
return _toggleUserMuteRelationship( return _toggleUserMuteRelationship(
context: provider.context, context: provider.context,
activeMastodonAuthenticationBox: activeMastodonAuthenticationBox, activeMastodonAuthenticationBox: activeMastodonAuthenticationBox,
mastodonUser: provider.mastodonUser().eraseToAnyPublisher() mastodonUser: provider.mastodonUser(for: cell).eraseToAnyPublisher()
) )
} else {
return _toggleUserMuteRelationship(
context: provider.context,
activeMastodonAuthenticationBox: activeMastodonAuthenticationBox,
mastodonUser: provider.mastodonUser().eraseToAnyPublisher()
)
}
} }
private static func _toggleUserMuteRelationship( private static func _toggleUserMuteRelationship(
@ -130,81 +141,135 @@ extension UserProviderFacade {
.switchToLatest() .switchToLatest()
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
} }
extension UserProviderFacade { extension UserProviderFacade {
static func createProfileActionMenu( static func createProfileActionMenu(
for mastodonUser: MastodonUser, for mastodonUser: MastodonUser,
isMyself: Bool,
isMuting: Bool, isMuting: Bool,
isBlocking: Bool, isBlocking: Bool,
needsShareAction: Bool, isInSameDomain: Bool,
isDomainBlocking: Bool,
provider: UserProvider, provider: UserProvider,
cell: UITableViewCell?,
sourceView: UIView?, sourceView: UIView?,
barButtonItem: UIBarButtonItem? barButtonItem: UIBarButtonItem?,
shareUser: MastodonUser?,
shareStatus: Status?
) -> UIMenu { ) -> UIMenu {
var children: [UIMenuElement] = [] var children: [UIMenuElement] = []
let name = mastodonUser.displayNameWithFallback let name = mastodonUser.displayNameWithFallback
// mute if !isMyself {
let muteAction = UIAction( // mute
title: isMuting ? L10n.Common.Controls.Firendship.unmuteUser(name) : L10n.Common.Controls.Firendship.mute, let muteAction = UIAction(
image: isMuting ? UIImage(systemName: "speaker") : UIImage(systemName: "speaker.slash"), title: isMuting ? L10n.Common.Controls.Firendship.unmuteUser(name) : L10n.Common.Controls.Firendship.mute,
discoverabilityTitle: isMuting ? nil : L10n.Common.Controls.Firendship.muteUser(name), image: isMuting ? UIImage(systemName: "speaker") : UIImage(systemName: "speaker.slash"),
attributes: isMuting ? [] : .destructive, discoverabilityTitle: isMuting ? nil : L10n.Common.Controls.Firendship.muteUser(name),
state: .off attributes: isMuting ? [] : .destructive,
) { [weak provider] _ in state: .off
guard let provider = provider else { return } ) { [weak provider] _ in
guard let provider = provider else { return }
UserProviderFacade.toggleUserMuteRelationship( UserProviderFacade.toggleUserMuteRelationship(
provider: provider provider: provider,
) cell: cell
.sink { _ in )
// do nothing .sink { _ in
} receiveValue: { _ in // do nothing
// do nothing } receiveValue: { _ in
// do nothing
}
.store(in: &provider.context.disposeBag)
}
if isMuting {
children.append(muteAction)
} else {
let muteMenu = UIMenu(title: L10n.Common.Controls.Firendship.muteUser(name), image: UIImage(systemName: "speaker.slash"), options: [], children: [muteAction])
children.append(muteMenu)
} }
.store(in: &provider.context.disposeBag)
}
if isMuting {
children.append(muteAction)
} else {
let muteMenu = UIMenu(title: L10n.Common.Controls.Firendship.muteUser(name), image: UIImage(systemName: "speaker.slash"), options: [], children: [muteAction])
children.append(muteMenu)
} }
// block if !isMyself {
let blockAction = UIAction( // block
title: isBlocking ? L10n.Common.Controls.Firendship.unblockUser(name) : L10n.Common.Controls.Firendship.block, let blockAction = UIAction(
image: isBlocking ? UIImage(systemName: "hand.raised.slash") : UIImage(systemName: "hand.raised"), title: isBlocking ? L10n.Common.Controls.Firendship.unblockUser(name) : L10n.Common.Controls.Firendship.block,
discoverabilityTitle: isBlocking ? nil : L10n.Common.Controls.Firendship.blockUser(name), image: isBlocking ? UIImage(systemName: "hand.raised.slash") : UIImage(systemName: "hand.raised"),
attributes: isBlocking ? [] : .destructive, discoverabilityTitle: isBlocking ? nil : L10n.Common.Controls.Firendship.blockUser(name),
state: .off attributes: isBlocking ? [] : .destructive,
) { [weak provider] _ in state: .off
guard let provider = provider else { return } ) { [weak provider] _ in
guard let provider = provider else { return }
UserProviderFacade.toggleUserBlockRelationship( UserProviderFacade.toggleUserBlockRelationship(
provider: provider provider: provider,
) cell: cell
.sink { _ in )
// do nothing .sink { _ in
} receiveValue: { _ in // do nothing
// do nothing } receiveValue: { _ in
// do nothing
}
.store(in: &provider.context.disposeBag)
}
if isBlocking {
children.append(blockAction)
} else {
let blockMenu = UIMenu(title: L10n.Common.Controls.Firendship.blockUser(name), image: UIImage(systemName: "hand.raised"), options: [], children: [blockAction])
children.append(blockMenu)
} }
.store(in: &provider.context.disposeBag)
}
if isBlocking {
children.append(blockAction)
} else {
let blockMenu = UIMenu(title: L10n.Common.Controls.Firendship.blockUser(name), image: UIImage(systemName: "hand.raised"), options: [], children: [blockAction])
children.append(blockMenu)
} }
if needsShareAction { if !isMyself {
let reportAction = UIAction(title: L10n.Common.Controls.Actions.reportUser(name), image: UIImage(systemName: "flag"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in
guard let provider = provider else { return }
guard let authenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else {
return
}
let viewModel = ReportViewModel(
context: provider.context,
domain: authenticationBox.domain,
user: mastodonUser,
status: nil
)
provider.coordinator.present(
scene: .report(viewModel: viewModel),
from: provider,
transition: .modal(animated: true, completion: nil)
)
}
children.append(reportAction)
}
if !isInSameDomain {
if isDomainBlocking {
let unblockDomainAction = UIAction(title: L10n.Common.Controls.Actions.unblockDomain(mastodonUser.domainFromAcct), image: UIImage(systemName: "nosign"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in
guard let provider = provider else { return }
provider.context.blockDomainService.unblockDomain(userProvider: provider, cell: cell)
}
children.append(unblockDomainAction)
} else {
let blockDomainAction = UIAction(title: L10n.Common.Controls.Actions.blockDomain(mastodonUser.domainFromAcct), image: UIImage(systemName: "nosign"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in
guard let provider = provider else { return }
let alertController = UIAlertController(title: "", message: L10n.Common.Alerts.BlockDomain.message(mastodonUser.domainFromAcct), preferredStyle: .alert)
let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .default) { _ in
}
alertController.addAction(cancelAction)
let blockDomainAction = UIAlertAction(title: L10n.Common.Alerts.BlockDomain.blockEntireDomain, style: .destructive) { _ in
provider.context.blockDomainService.blockDomain(userProvider: provider, cell: cell)
}
alertController.addAction(blockDomainAction)
provider.present(alertController, animated: true, completion: nil)
}
children.append(blockDomainAction)
}
}
if let shareUser = shareUser {
let shareAction = UIAction(title: L10n.Common.Controls.Actions.shareUser(name), image: UIImage(systemName: "square.and.arrow.up"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in let shareAction = UIAction(title: L10n.Common.Controls.Actions.shareUser(name), image: UIImage(systemName: "square.and.arrow.up"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in
guard let provider = provider else { return } guard let provider = provider else { return }
let activityViewController = createActivityViewControllerForMastodonUser(mastodonUser: mastodonUser, dependency: provider) let activityViewController = createActivityViewControllerForMastodonUser(mastodonUser: shareUser, dependency: provider)
provider.coordinator.present( provider.coordinator.present(
scene: .activityViewController( scene: .activityViewController(
activityViewController: activityViewController, activityViewController: activityViewController,
@ -218,23 +283,22 @@ extension UserProviderFacade {
children.append(shareAction) children.append(shareAction)
} }
let reportAction = UIAction(title: L10n.Common.Controls.Actions.reportUser(name), image: UIImage(systemName: "exclamationmark.bubble"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in if let shareStatus = shareStatus {
guard let provider = provider else { return } let shareAction = UIAction(title: L10n.Common.Controls.Actions.sharePost, image: UIImage(systemName: "square.and.arrow.up"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in
guard let authenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else { guard let provider = provider else { return }
return let activityViewController = createActivityViewControllerForMastodonUser(status: shareStatus, dependency: provider)
provider.coordinator.present(
scene: .activityViewController(
activityViewController: activityViewController,
sourceView: sourceView,
barButtonItem: barButtonItem
),
from: provider,
transition: .activityViewControllerPresent(animated: true, completion: nil)
)
} }
let viewModel = ReportViewModel( children.append(shareAction)
context: provider.context,
domain: authenticationBox.domain,
user: mastodonUser,
status: nil)
provider.coordinator.present(
scene: .report(viewModel: viewModel),
from: provider,
transition: .modal(animated: true, completion: nil)
)
} }
children.append(reportAction)
return UIMenu(title: "", options: [], children: children) return UIMenu(title: "", options: [], children: children)
} }
@ -246,5 +310,12 @@ extension UserProviderFacade {
) )
return activityViewController return activityViewController
} }
static func createActivityViewControllerForMastodonUser(status: Status, dependency: NeedsDependency) -> UIActivityViewController {
let activityViewController = UIActivityViewController(
activityItems: status.activityItems,
applicationActivities: [SafariActivity(sceneCoordinator: dependency.coordinator)]
)
return activityViewController
}
} }

View File

@ -1,3 +1,5 @@
"Common.Alerts.BlockDomain.BlockEntireDomain" = "Block entire domain";
"Common.Alerts.BlockDomain.Message" = "Are you really, really sure you want to block the entire %@? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.";
"Common.Alerts.Common.PleaseTryAgain" = "Please try again."; "Common.Alerts.Common.PleaseTryAgain" = "Please try again.";
"Common.Alerts.Common.PleaseTryAgainLater" = "Please try again later."; "Common.Alerts.Common.PleaseTryAgainLater" = "Please try again later.";
"Common.Alerts.DiscardPostContent.Message" = "Confirm discard composed post content."; "Common.Alerts.DiscardPostContent.Message" = "Confirm discard composed post content.";
@ -16,6 +18,7 @@ Please check your internet connection.";
"Common.Alerts.VoteFailure.Title" = "Vote Failure"; "Common.Alerts.VoteFailure.Title" = "Vote Failure";
"Common.Controls.Actions.Add" = "Add"; "Common.Controls.Actions.Add" = "Add";
"Common.Controls.Actions.Back" = "Back"; "Common.Controls.Actions.Back" = "Back";
"Common.Controls.Actions.BlockDomain" = "Block %@";
"Common.Controls.Actions.Cancel" = "Cancel"; "Common.Controls.Actions.Cancel" = "Cancel";
"Common.Controls.Actions.Confirm" = "Confirm"; "Common.Controls.Actions.Confirm" = "Confirm";
"Common.Controls.Actions.Continue" = "Continue"; "Common.Controls.Actions.Continue" = "Continue";
@ -34,12 +37,14 @@ Please check your internet connection.";
"Common.Controls.Actions.SeeMore" = "See More"; "Common.Controls.Actions.SeeMore" = "See More";
"Common.Controls.Actions.Settings" = "Settings"; "Common.Controls.Actions.Settings" = "Settings";
"Common.Controls.Actions.Share" = "Share"; "Common.Controls.Actions.Share" = "Share";
"Common.Controls.Actions.SharePost" = "Share post";
"Common.Controls.Actions.ShareUser" = "Share %@"; "Common.Controls.Actions.ShareUser" = "Share %@";
"Common.Controls.Actions.SignIn" = "Sign In"; "Common.Controls.Actions.SignIn" = "Sign In";
"Common.Controls.Actions.SignUp" = "Sign Up"; "Common.Controls.Actions.SignUp" = "Sign Up";
"Common.Controls.Actions.Skip" = "Skip"; "Common.Controls.Actions.Skip" = "Skip";
"Common.Controls.Actions.TakePhoto" = "Take photo"; "Common.Controls.Actions.TakePhoto" = "Take photo";
"Common.Controls.Actions.TryAgain" = "Try Again"; "Common.Controls.Actions.TryAgain" = "Try Again";
"Common.Controls.Actions.UnblockDomain" = "Unblock %@";
"Common.Controls.Firendship.Block" = "Block"; "Common.Controls.Firendship.Block" = "Block";
"Common.Controls.Firendship.BlockDomain" = "Block %@"; "Common.Controls.Firendship.BlockDomain" = "Block %@";
"Common.Controls.Firendship.BlockUser" = "Block %@"; "Common.Controls.Firendship.BlockUser" = "Block %@";

View File

@ -1,5 +1,5 @@
// //
// HashtagTimelineViewController+StatusProvider.swift // HashtagTimelineViewController+Provider.swift
// Mastodon // Mastodon
// //
// Created by BradGao on 2021/3/31. // Created by BradGao on 2021/3/31.
@ -86,3 +86,4 @@ extension HashtagTimelineViewController: StatusProvider {
} }
extension HashtagTimelineViewController: UserProvider {}

View File

@ -1,5 +1,5 @@
// //
// HomeTimelineViewController+StatusProvider.swift // HomeTimelineViewController+Provider.swift
// Mastodon // Mastodon
// //
// Created by sxiaojian on 2021/2/5. // Created by sxiaojian on 2021/2/5.
@ -85,3 +85,5 @@ extension HomeTimelineViewController: StatusProvider {
} }
} }
extension HomeTimelineViewController: UserProvider {}

View File

@ -85,3 +85,5 @@ extension FavoriteViewController: StatusProvider {
} }
} }
extension FavoriteViewController: UserProvider {}

View File

@ -8,8 +8,15 @@
import Foundation import Foundation
import Combine import Combine
import CoreDataStack import CoreDataStack
import UIKit
extension ProfileViewController: UserProvider { extension ProfileViewController: UserProvider {
func mastodonUser(for cell: UITableViewCell?) -> Future<MastodonUser?, Never> {
return Future { promise in
promise(.success(nil))
}
}
func mastodonUser() -> Future<MastodonUser?, Never> { func mastodonUser() -> Future<MastodonUser?, Never> {
return Future { promise in return Future { promise in

View File

@ -373,20 +373,45 @@ extension ProfileViewController {
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.assign(to: \.text, on: profileHeaderViewController.profileHeaderView.usernameLabel) .assign(to: \.text, on: profileHeaderViewController.profileHeaderView.usernameLabel)
.store(in: &disposeBag) .store(in: &disposeBag)
viewModel.relationshipActionOptionSet Publishers.CombineLatest(
.receive(on: DispatchQueue.main) viewModel.relationshipActionOptionSet,
.sink { [weak self] relationshipActionOptionSet in viewModel.context.blockDomainService.blockedDomains
guard let self = self else { return } )
guard let mastodonUser = self.viewModel.mastodonUser.value else { .receive(on: DispatchQueue.main)
self.moreMenuBarButtonItem.menu = nil .sink { [weak self] relationshipActionOptionSet,domains in
return guard let self = self else { return }
} guard let mastodonUser = self.viewModel.mastodonUser.value else {
let isMuting = relationshipActionOptionSet.contains(.muting) self.moreMenuBarButtonItem.menu = nil
let isBlocking = relationshipActionOptionSet.contains(.blocking) return
let needsShareAction = self.viewModel.isMeBarButtonItemsHidden.value
self.moreMenuBarButtonItem.menu = UserProviderFacade.createProfileActionMenu(for: mastodonUser, isMuting: isMuting, isBlocking: isBlocking, needsShareAction: needsShareAction, provider: self, sourceView: nil, barButtonItem: self.moreMenuBarButtonItem)
} }
.store(in: &disposeBag) guard let currentMastodonUser = self.viewModel.currentMastodonUser.value else {
self.moreMenuBarButtonItem.menu = nil
return
}
guard let currentDomain = self.viewModel.domain.value else { return }
let isMuting = relationshipActionOptionSet.contains(.muting)
let isBlocking = relationshipActionOptionSet.contains(.blocking)
let isDomainBlocking = domains.contains(mastodonUser.domainFromAcct)
let needsShareAction = self.viewModel.isMeBarButtonItemsHidden.value
let isInSameDomain = mastodonUser.domainFromAcct == currentDomain
let isMyself = currentMastodonUser.id == mastodonUser.id
self.moreMenuBarButtonItem.menu = UserProviderFacade.createProfileActionMenu(
for: mastodonUser,
isMyself: isMyself,
isMuting: isMuting,
isBlocking: isBlocking,
isInSameDomain: isInSameDomain,
isDomainBlocking: isDomainBlocking,
provider: self,
cell: nil,
sourceView: nil,
barButtonItem: self.moreMenuBarButtonItem,
shareUser: needsShareAction ? mastodonUser : nil,
shareStatus: nil)
}
.store(in: &disposeBag)
viewModel.isRelationshipActionButtonHidden viewModel.isRelationshipActionButtonHidden
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] isHidden in .sink { [weak self] isHidden in
@ -767,7 +792,7 @@ extension ProfileViewController: ProfileHeaderViewDelegate {
) )
let unmuteAction = UIAlertAction(title: L10n.Common.Controls.Firendship.unmute, style: .default) { [weak self] _ in let unmuteAction = UIAlertAction(title: L10n.Common.Controls.Firendship.unmute, style: .default) { [weak self] _ in
guard let self = self else { return } guard let self = self else { return }
UserProviderFacade.toggleUserMuteRelationship(provider: self) UserProviderFacade.toggleUserMuteRelationship(provider: self, cell: nil)
.sink { _ in .sink { _ in
// do nothing // do nothing
} receiveValue: { _ in } receiveValue: { _ in
@ -789,7 +814,7 @@ extension ProfileViewController: ProfileHeaderViewDelegate {
) )
let unblockAction = UIAlertAction(title: L10n.Common.Controls.Firendship.unblock, style: .default) { [weak self] _ in let unblockAction = UIAlertAction(title: L10n.Common.Controls.Firendship.unblock, style: .default) { [weak self] _ in
guard let self = self else { return } guard let self = self else { return }
UserProviderFacade.toggleUserBlockRelationship(provider: self) UserProviderFacade.toggleUserBlockRelationship(provider: self, cell: nil)
.sink { _ in .sink { _ in
// do nothing // do nothing
} receiveValue: { _ in } receiveValue: { _ in

View File

@ -1,5 +1,5 @@
// //
// UserTimelineViewController+StatusProvider.swift // UserTimelineViewController+Provider.swift
// Mastodon // Mastodon
// //
// Created by MainasuK Cirno on 2021-3-30. // Created by MainasuK Cirno on 2021-3-30.
@ -85,3 +85,5 @@ extension UserTimelineViewController: StatusProvider {
} }
} }
extension UserTimelineViewController: UserProvider {}

View File

@ -1,5 +1,5 @@
// //
// PublicTimelineViewController+StatusProvider.swift // PublicTimelineViewController+Provider.swift
// Mastodon // Mastodon
// //
// Created by sxiaojian on 2021/1/27. // Created by sxiaojian on 2021/1/27.
@ -85,3 +85,5 @@ extension PublicTimelineViewController: StatusProvider {
} }
} }
extension PublicTimelineViewController: UserProvider {}

View File

@ -11,6 +11,13 @@ import Foundation
import UIKit import UIKit
extension SearchViewController: UserProvider { extension SearchViewController: UserProvider {
func mastodonUser(for cell: UITableViewCell?) -> Future<MastodonUser?, Never> {
return Future { promise in
promise(.success(nil))
}
}
func mastodonUser() -> Future<MastodonUser?, Never> { func mastodonUser() -> Future<MastodonUser?, Never> {
Future { promise in Future { promise in
promise(.success(self.viewModel.mastodonUser.value)) promise(.success(self.viewModel.mastodonUser.value))
@ -47,7 +54,7 @@ extension SearchViewController: SearchRecommendAccountsCollectionViewCellDelegat
) )
let unmuteAction = UIAlertAction(title: L10n.Common.Controls.Firendship.unmute, style: .default) { [weak self] _ in let unmuteAction = UIAlertAction(title: L10n.Common.Controls.Firendship.unmute, style: .default) { [weak self] _ in
guard let self = self else { return } guard let self = self else { return }
UserProviderFacade.toggleUserMuteRelationship(provider: self) UserProviderFacade.toggleUserMuteRelationship(provider: self, cell: nil)
.sink { _ in .sink { _ in
// do nothing // do nothing
} receiveValue: { _ in } receiveValue: { _ in
@ -69,7 +76,7 @@ extension SearchViewController: SearchRecommendAccountsCollectionViewCellDelegat
) )
let unblockAction = UIAlertAction(title: L10n.Common.Controls.Firendship.unblock, style: .default) { [weak self] _ in let unblockAction = UIAlertAction(title: L10n.Common.Controls.Firendship.unblock, style: .default) { [weak self] _ in
guard let self = self else { return } guard let self = self else { return }
UserProviderFacade.toggleUserBlockRelationship(provider: self) UserProviderFacade.toggleUserBlockRelationship(provider: self, cell: nil)
.sink { _ in .sink { _ in
// do nothing // do nothing
} receiveValue: { _ in } receiveValue: { _ in

View File

@ -356,8 +356,4 @@ extension StatusTableViewCell: ActionToolbarContainerDelegate {
delegate?.statusTableViewCell(self, actionToolbarContainer: actionToolbarContainer, likeButtonDidPressed: sender) delegate?.statusTableViewCell(self, actionToolbarContainer: actionToolbarContainer, likeButtonDidPressed: sender)
} }
func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, moreButtonDidPressed sender: UIButton) {
}
} }

View File

@ -12,7 +12,6 @@ protocol ActionToolbarContainerDelegate: class {
func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, replayButtonDidPressed sender: UIButton) func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, replayButtonDidPressed sender: UIButton)
func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, reblogButtonDidPressed sender: UIButton) func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, reblogButtonDidPressed sender: UIButton)
func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, starButtonDidPressed sender: UIButton) func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, starButtonDidPressed sender: UIButton)
func actionToolbarContainer(_ actionToolbarContainer: ActionToolbarContainer, moreButtonDidPressed sender: UIButton)
} }
@ -63,7 +62,6 @@ extension ActionToolbarContainer {
replyButton.addTarget(self, action: #selector(ActionToolbarContainer.replyButtonDidPressed(_:)), for: .touchUpInside) replyButton.addTarget(self, action: #selector(ActionToolbarContainer.replyButtonDidPressed(_:)), for: .touchUpInside)
reblogButton.addTarget(self, action: #selector(ActionToolbarContainer.reblogButtonDidPressed(_:)), for: .touchUpInside) reblogButton.addTarget(self, action: #selector(ActionToolbarContainer.reblogButtonDidPressed(_:)), for: .touchUpInside)
favoriteButton.addTarget(self, action: #selector(ActionToolbarContainer.favoriteButtonDidPressed(_:)), for: .touchUpInside) favoriteButton.addTarget(self, action: #selector(ActionToolbarContainer.favoriteButtonDidPressed(_:)), for: .touchUpInside)
moreButton.addTarget(self, action: #selector(ActionToolbarContainer.moreButtonDidPressed(_:)), for: .touchUpInside)
} }
} }
@ -194,11 +192,6 @@ extension ActionToolbarContainer {
delegate?.actionToolbarContainer(self, starButtonDidPressed: sender) delegate?.actionToolbarContainer(self, starButtonDidPressed: sender)
} }
@objc private func moreButtonDidPressed(_ sender: UIButton) {
os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
delegate?.actionToolbarContainer(self, moreButtonDidPressed: sender)
}
} }
#if DEBUG #if DEBUG

View File

@ -1,5 +1,5 @@
// //
// ThreadViewController+StatusProvider.swift // ThreadViewController+Provider.swift
// Mastodon // Mastodon
// //
// Created by MainasuK Cirno on 2021-4-12. // Created by MainasuK Cirno on 2021-4-12.
@ -86,3 +86,5 @@ extension ThreadViewController: StatusProvider {
} }
} }
extension ThreadViewController: UserProvider {}

View File

@ -0,0 +1,139 @@
//
// APIService+DomainBlock.swift
// Mastodon
//
// Created by sxiaojian on 2021/4/29.
//
import Combine
import CommonOSLog
import CoreData
import CoreDataStack
import DateToolsSwift
import Foundation
import MastodonSDK
extension APIService {
func getDomainblocks(
domain: String,
limit: Int = onceRequestDomainBlocksMaxCount,
authorizationBox: AuthenticationService.MastodonAuthenticationBox
) -> AnyPublisher<Mastodon.Response.Content<[String]>, Error> {
let authorization = authorizationBox.userAuthorization
let query = Mastodon.API.DomainBlock.Query(
maxID: nil, sinceID: nil, limit: limit
)
return Mastodon.API.DomainBlock.getDomainblocks(
domain: domain,
session: session,
authorization: authorization,
query: query
)
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<[String]>, Error> in
self.backgroundManagedObjectContext.performChanges {
let blockedDomains: [DomainBlock] = {
let request = DomainBlock.sortedFetchRequest
request.predicate = DomainBlock.predicate(domain: authorizationBox.domain, userID: authorizationBox.userID)
request.returnsObjectsAsFaults = false
do {
return try self.backgroundManagedObjectContext.fetch(request)
} catch {
assertionFailure(error.localizedDescription)
return []
}
}()
blockedDomains.forEach { self.backgroundManagedObjectContext.delete($0) }
response.value.forEach { domain in
// use constrain to avoid repeated save
_ = DomainBlock.insert(
into: self.backgroundManagedObjectContext,
blockedDomain: domain,
domain: authorizationBox.domain,
userID: authorizationBox.userID
)
}
}
.setFailureType(to: Error.self)
.tryMap { result -> Mastodon.Response.Content<[String]> in
switch result {
case .success:
return response
case .failure(let error):
throw error
}
}
.eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}
func blockDomain(
user: MastodonUser,
authorizationBox: AuthenticationService.MastodonAuthenticationBox
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Empty>, Error> {
let authorization = authorizationBox.userAuthorization
return Mastodon.API.DomainBlock.blockDomain(
domain: authorizationBox.domain,
blockDomain: user.domainFromAcct,
session: session,
authorization: authorization
)
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Empty>, Error> in
self.backgroundManagedObjectContext.performChanges {
let requestMastodonUserRequest = MastodonUser.sortedFetchRequest
requestMastodonUserRequest.predicate = MastodonUser.predicate(domain: authorizationBox.domain, id: authorizationBox.userID)
requestMastodonUserRequest.fetchLimit = 1
guard let requestMastodonUser = self.backgroundManagedObjectContext.safeFetch(requestMastodonUserRequest).first else { return }
user.update(isDomainBlocking: true, by: requestMastodonUser)
}
.setFailureType(to: Error.self)
.tryMap { result -> Mastodon.Response.Content<Mastodon.Entity.Empty> in
switch result {
case .success:
return response
case .failure(let error):
throw error
}
}
.eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}
func unblockDomain(
user: MastodonUser,
authorizationBox: AuthenticationService.MastodonAuthenticationBox
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Empty>, Error> {
let authorization = authorizationBox.userAuthorization
return Mastodon.API.DomainBlock.unblockDomain(
domain: authorizationBox.domain,
blockDomain: user.domainFromAcct,
session: session,
authorization: authorization
)
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Empty>, Error> in
self.backgroundManagedObjectContext.performChanges {
let requestMastodonUserRequest = MastodonUser.sortedFetchRequest
requestMastodonUserRequest.predicate = MastodonUser.predicate(domain: authorizationBox.domain, id: authorizationBox.userID)
requestMastodonUserRequest.fetchLimit = 1
guard let requestMastodonUser = self.backgroundManagedObjectContext.safeFetch(requestMastodonUserRequest).first else { return }
user.update(isDomainBlocking: false, by: requestMastodonUser)
}
.setFailureType(to: Error.self)
.tryMap { result -> Mastodon.Response.Content<Mastodon.Entity.Empty> in
switch result {
case .success:
return response
case .failure(let error):
throw error
}
}
.eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}
}

View File

@ -46,6 +46,7 @@ final class APIService {
extension APIService { extension APIService {
public static let onceRequestStatusMaxCount = 100 public static let onceRequestStatusMaxCount = 100
public static let onceRequestUserMaxCount = 100 public static let onceRequestUserMaxCount = 100
public static let onceRequestDomainBlocksMaxCount = 100
} }
extension APIService { extension APIService {

View File

@ -0,0 +1,122 @@
//
// BlockDomainService.swift
// Mastodon
//
// Created by sxiaojian on 2021/4/29.
//
import Combine
import CoreData
import CoreDataStack
import Foundation
import MastodonSDK
import OSLog
import UIKit
final class BlockDomainService {
// input
weak var backgroundManagedObjectContext: NSManagedObjectContext?
weak var authenticationService: AuthenticationService?
// output
let blockedDomains = CurrentValueSubject<[String], Never>([])
init(
backgroundManagedObjectContext: NSManagedObjectContext,
authenticationService: AuthenticationService
) {
self.backgroundManagedObjectContext = backgroundManagedObjectContext
self.authenticationService = authenticationService
guard let authorizationBox = authenticationService.activeMastodonAuthenticationBox.value else { return }
backgroundManagedObjectContext.perform {
let _blockedDomains: [DomainBlock] = {
let request = DomainBlock.sortedFetchRequest
request.predicate = DomainBlock.predicate(domain: authorizationBox.domain, userID: authorizationBox.userID)
request.returnsObjectsAsFaults = false
do {
return try backgroundManagedObjectContext.fetch(request)
} catch {
assertionFailure(error.localizedDescription)
return []
}
}()
self.blockedDomains.value = _blockedDomains.map(\.blockedDomain)
}
}
func blockDomain(
userProvider: UserProvider,
cell: UITableViewCell?
) {
guard let activeMastodonAuthenticationBox = userProvider.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
guard let context = userProvider.context else {
return
}
var mastodonUser: AnyPublisher<MastodonUser?, Never>
if let cell = cell {
mastodonUser = userProvider.mastodonUser(for: cell).eraseToAnyPublisher()
} else {
mastodonUser = userProvider.mastodonUser().eraseToAnyPublisher()
}
mastodonUser
.compactMap { mastodonUser -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Empty>, Error>? in
guard let mastodonUser = mastodonUser else {
return nil
}
return context.apiService.blockDomain(user: mastodonUser, authorizationBox: activeMastodonAuthenticationBox)
}
.switchToLatest()
.flatMap { _ -> AnyPublisher<Mastodon.Response.Content<[String]>, Error> in
context.apiService.getDomainblocks(domain: activeMastodonAuthenticationBox.domain, authorizationBox: activeMastodonAuthenticationBox)
}
.sink { completion in
switch completion {
case .finished:
break
case .failure(let error):
print(error)
}
} receiveValue: { [weak self] response in
self?.blockedDomains.value = response.value
}
.store(in: &userProvider.disposeBag)
}
func unblockDomain(
userProvider: UserProvider,
cell: UITableViewCell?
) {
guard let activeMastodonAuthenticationBox = userProvider.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
guard let context = userProvider.context else {
return
}
var mastodonUser: AnyPublisher<MastodonUser?, Never>
if let cell = cell {
mastodonUser = userProvider.mastodonUser(for: cell).eraseToAnyPublisher()
} else {
mastodonUser = userProvider.mastodonUser().eraseToAnyPublisher()
}
mastodonUser
.compactMap { mastodonUser -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Empty>, Error>? in
guard let mastodonUser = mastodonUser else {
return nil
}
return context.apiService.unblockDomain(user: mastodonUser, authorizationBox: activeMastodonAuthenticationBox)
}
.switchToLatest()
.flatMap { _ -> AnyPublisher<Mastodon.Response.Content<[String]>, Error> in
context.apiService.getDomainblocks(domain: activeMastodonAuthenticationBox.domain, authorizationBox: activeMastodonAuthenticationBox)
}
.sink { completion in
switch completion {
case .finished:
break
case .failure(let error):
print(error)
}
} receiveValue: { [weak self] response in
self?.blockedDomains.value = response.value
}
.store(in: &userProvider.disposeBag)
}
}

View File

@ -30,6 +30,8 @@ class AppContext: ObservableObject {
let statusPublishService = StatusPublishService() let statusPublishService = StatusPublishService()
let notificationService: NotificationService let notificationService: NotificationService
let settingService: SettingService let settingService: SettingService
let blockDomainService: BlockDomainService
let photoLibraryService = PhotoLibraryService() let photoLibraryService = PhotoLibraryService()
let documentStore: DocumentStore let documentStore: DocumentStore
@ -73,6 +75,11 @@ class AppContext: ObservableObject {
notificationService: _notificationService notificationService: _notificationService
) )
blockDomainService = BlockDomainService(
backgroundManagedObjectContext: _backgroundManagedObjectContext,
authenticationService: _authenticationService
)
documentStore = DocumentStore() documentStore = DocumentStore()
documentStoreSubscription = documentStore.objectWillChange documentStoreSubscription = documentStore.objectWillChange
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)

View File

@ -0,0 +1,146 @@
//
// File.swift
//
//
// Created by sxiaojian on 2021/4/29.
//
import Foundation
import Combine
extension Mastodon.API.DomainBlock {
static func domainBlockEndpointURL(domain: String) -> URL {
Mastodon.API.endpointURL(domain: domain).appendingPathComponent("domain_blocks")
}
/// Fetch domain blocks
///
/// - Since: 1.4.0
/// # Reference
/// [Document](https://docs.joinmastodon.org/methods/accounts/domain_blocks/)
/// - Parameters:
/// - domain: Mastodon instance domain. e.g. "example.com"
/// - session: `URLSession`
/// - authorization: User token
/// - Returns: `AnyPublisher` contains `String` nested in the response
public static func getDomainblocks(
domain: String,
session: URLSession,
authorization: Mastodon.API.OAuth.Authorization,
query: Mastodon.API.DomainBlock.Query
) -> AnyPublisher<Mastodon.Response.Content<[String]>, Error> {
let url = domainBlockEndpointURL(domain: domain)
let request = Mastodon.API.get(url: url, query: query, authorization: authorization)
return session.dataTaskPublisher(for: request)
.tryMap { data, response in
let value = try Mastodon.API.decode(type: [String].self, from: data, response: response)
return Mastodon.Response.Content(value: value, response: response)
}
.eraseToAnyPublisher()
}
/// Block a domain
///
/// - Since: 1.4.0
/// # Reference
/// [Document](https://docs.joinmastodon.org/methods/accounts/domain_blocks/)
/// - Parameters:
/// - domain: Mastodon instance domain. e.g. "example.com"
/// - session: `URLSession`
/// - authorization: User token
/// - Returns: `AnyPublisher` contains `String` nested in the response
public static func blockDomain(
domain: String,
blockDomain:String,
session: URLSession,
authorization: Mastodon.API.OAuth.Authorization
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Empty>, Error> {
let query = Mastodon.API.DomainBlock.BlockQuery(domain: blockDomain)
let request = Mastodon.API.post(
url: domainBlockEndpointURL(domain: domain),
query: query,
authorization: authorization
)
return session.dataTaskPublisher(for: request)
.tryMap { data, response in
let value = try Mastodon.API.decode(type: Mastodon.Entity.Empty.self, from: data, response: response)
return Mastodon.Response.Content(value: value, response: response)
}
.eraseToAnyPublisher()
}
/// Unblock a domain
///
/// - Since: 1.4.0
/// # Reference
/// [Document](https://docs.joinmastodon.org/methods/accounts/domain_blocks/)
/// - Parameters:
/// - domain: Mastodon instance domain. e.g. "example.com"
/// - session: `URLSession`
/// - authorization: User token
/// - Returns: `AnyPublisher` contains `String` nested in the response
public static func unblockDomain(
domain: String,
blockDomain:String,
session: URLSession,
authorization: Mastodon.API.OAuth.Authorization
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Empty>, Error> {
let query = Mastodon.API.DomainBlock.BlockDeleteQuery(domain: blockDomain)
let request = Mastodon.API.delete(
url: domainBlockEndpointURL(domain: domain),
query: query,
authorization: authorization
)
return session.dataTaskPublisher(for: request)
.tryMap { data, response in
let value = try Mastodon.API.decode(type: Mastodon.Entity.Empty.self, from: data, response: response)
return Mastodon.Response.Content(value: value, response: response)
}
.eraseToAnyPublisher()
}
}
extension Mastodon.API.DomainBlock {
public struct Query: GetQuery {
public let maxID: Mastodon.Entity.Status.ID?
public let sinceID: Mastodon.Entity.Status.ID?
public let limit: Int?
public init(
maxID: Mastodon.Entity.Status.ID?,
sinceID: Mastodon.Entity.Status.ID?,
limit: Int?
) {
self.maxID = maxID
self.sinceID = sinceID
self.limit = limit
}
var queryItems: [URLQueryItem]? {
var items: [URLQueryItem] = []
maxID.flatMap { items.append(URLQueryItem(name: "max_id", value: $0)) }
sinceID.flatMap { items.append(URLQueryItem(name: "since_id", value: $0)) }
limit.flatMap { items.append(URLQueryItem(name: "limit", value: String($0))) }
guard !items.isEmpty else { return nil }
return items
}
}
public struct BlockDeleteQuery: Codable, DeleteQuery {
public let domain: String
public init(domain: String) {
self.domain = domain
}
}
public struct BlockQuery: Codable, PostQuery {
public let domain: String
public init(domain: String) {
self.domain = domain
}
}
}

View File

@ -117,6 +117,7 @@ extension Mastodon.API {
public enum Notifications { } public enum Notifications { }
public enum Subscriptions { } public enum Subscriptions { }
public enum Reports { } public enum Reports { }
public enum DomainBlock { }
} }
extension Mastodon.API.V2 { extension Mastodon.API.V2 {

View File

@ -0,0 +1,14 @@
//
// File.swift
//
//
// Created by sxiaojian on 2021/4/30.
//
import Foundation
extension Mastodon.Entity {
public struct Empty: Codable {
}
}

View File

@ -60,3 +60,8 @@ protocol PutQuery: RequestQuery { }
// DELETE // DELETE
protocol DeleteQuery: RequestQuery { } protocol DeleteQuery: RequestQuery { }
extension DeleteQuery {
// By default a `DeleteQuery` does not has query items
var queryItems: [URLQueryItem]? { nil }
}