mirror of
https://github.com/mastodon/mastodon-ios.git
synced 2025-02-02 10:27:08 +01:00
feat: implement auto refresh logic for Poll
This commit is contained in:
parent
1e691a2a76
commit
30c035e09a
@ -56,6 +56,34 @@ extension Poll {
|
||||
return poll
|
||||
}
|
||||
|
||||
public func update(expiresAt: Date?) {
|
||||
if self.expiresAt != expiresAt {
|
||||
self.expiresAt = expiresAt
|
||||
}
|
||||
}
|
||||
|
||||
public func update(expired: Bool) {
|
||||
if self.expired != expired {
|
||||
self.expired = expired
|
||||
}
|
||||
}
|
||||
|
||||
public func update(votesCount: Int) {
|
||||
if self.votesCount.intValue != votesCount {
|
||||
self.votesCount = NSNumber(value: votesCount)
|
||||
}
|
||||
}
|
||||
|
||||
public func update(votersCount: Int?) {
|
||||
if self.votersCount?.intValue != votersCount {
|
||||
self.votersCount = votersCount.flatMap { NSNumber(value: $0) }
|
||||
}
|
||||
}
|
||||
|
||||
public func didUpdate(at networkDate: Date) {
|
||||
self.updatedAt = networkDate
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Poll {
|
||||
|
@ -50,6 +50,22 @@ extension PollOption {
|
||||
return option
|
||||
}
|
||||
|
||||
public func update(votesCount: Int?) {
|
||||
if self.votesCount?.intValue != votesCount {
|
||||
self.votesCount = votesCount.flatMap { NSNumber(value: $0) }
|
||||
}
|
||||
}
|
||||
|
||||
public func update(votedBy: MastodonUser) {
|
||||
if !(self.votedBy ?? Set()).contains(votedBy) {
|
||||
self.mutableSetValue(forKey: #keyPath(PollOption.votedBy)).add(votedBy)
|
||||
}
|
||||
}
|
||||
|
||||
public func didUpdate(at networkDate: Date) {
|
||||
self.updatedAt = networkDate
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension PollOption {
|
||||
|
@ -60,10 +60,15 @@
|
||||
"status_content_warning": "content warning",
|
||||
"media_content_warning": "Tap to reveal that may be sensitive",
|
||||
"poll": {
|
||||
"vote": "Vote",
|
||||
"vote_count": {
|
||||
"single": "%d vote",
|
||||
"multiple": "%d votes",
|
||||
},
|
||||
"voter_count": {
|
||||
"single": "%d voter",
|
||||
"multiple": "%d voters",
|
||||
},
|
||||
"time_left": "%s left"
|
||||
}
|
||||
},
|
||||
|
@ -77,7 +77,7 @@
|
||||
2DA7D05725CA693F00804E11 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA7D05625CA693F00804E11 /* Application.swift */; };
|
||||
2DF123A725C3B0210020F248 /* ActiveLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF123A625C3B0210020F248 /* ActiveLabel.swift */; };
|
||||
2DF75B9B25D0E27500694EC8 /* StatusProviderFacade.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF75B9A25D0E27500694EC8 /* StatusProviderFacade.swift */; };
|
||||
2DF75BA125D0E29D00694EC8 /* StatusProvider+TimelinePostTableViewCellDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF75BA025D0E29D00694EC8 /* StatusProvider+TimelinePostTableViewCellDelegate.swift */; };
|
||||
2DF75BA125D0E29D00694EC8 /* StatusProvider+StatusTableViewCellDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF75BA025D0E29D00694EC8 /* StatusProvider+StatusTableViewCellDelegate.swift */; };
|
||||
2DF75BA725D10E1000694EC8 /* APIService+Favorite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF75BA625D10E1000694EC8 /* APIService+Favorite.swift */; };
|
||||
2DF75BB925D1474100694EC8 /* ManagedObjectObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF75BB825D1474100694EC8 /* ManagedObjectObserver.swift */; };
|
||||
2DF75BC725D1475D00694EC8 /* ManagedObjectContextObjectsDidChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF75BC625D1475D00694EC8 /* ManagedObjectContextObjectsDidChange.swift */; };
|
||||
@ -94,6 +94,7 @@
|
||||
DB0AC6FC25CD02E600D75117 /* APIService+Instance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */; };
|
||||
DB118A8225E4B6E600FAB162 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */; };
|
||||
DB118A8C25E4BFB500FAB162 /* HighlightDimmableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */; };
|
||||
DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1D186B25EF5BA7003F1F23 /* PollTableView.swift */; };
|
||||
DB2B3ABC25E37E15007045F9 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DB2B3ABE25E37E15007045F9 /* InfoPlist.strings */; };
|
||||
DB2B3AE925E38850007045F9 /* UIViewPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2B3AE825E38850007045F9 /* UIViewPreview.swift */; };
|
||||
DB2F073525E8ECF000957B2D /* AuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2F073325E8ECF000957B2D /* AuthenticationViewModel.swift */; };
|
||||
@ -125,6 +126,9 @@
|
||||
DB5086AB25CC0BBB00C2C187 /* AvatarConfigurableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5086AA25CC0BBB00C2C187 /* AvatarConfigurableView.swift */; };
|
||||
DB5086B825CC0D6400C2C187 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = DB5086B725CC0D6400C2C187 /* Kingfisher */; };
|
||||
DB5086BE25CC0D9900C2C187 /* SplashPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5086BD25CC0D9900C2C187 /* SplashPreference.swift */; };
|
||||
DB59F0FE25EF5D96001F1DAB /* StatusProvider+UITableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB59F0FD25EF5D96001F1DAB /* StatusProvider+UITableViewDelegate.swift */; };
|
||||
DB59F10425EF5EBC001F1DAB /* TableViewCellHeightCacheableContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB59F10325EF5EBC001F1DAB /* TableViewCellHeightCacheableContainer.swift */; };
|
||||
DB59F10E25EF724F001F1DAB /* APIService+Poll.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB59F10D25EF724F001F1DAB /* APIService+Poll.swift */; };
|
||||
DB68586425E619B700F0A850 /* NSKeyValueObservation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68586325E619B700F0A850 /* NSKeyValueObservation.swift */; };
|
||||
DB68A04A25E9027700CFDF14 /* DarkContentStatusBarStyleNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68A04925E9027700CFDF14 /* DarkContentStatusBarStyleNavigationController.swift */; };
|
||||
DB68A05D25E9055900CFDF14 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = DB68A05C25E9055900CFDF14 /* Settings.bundle */; };
|
||||
@ -292,7 +296,7 @@
|
||||
2DA7D05625CA693F00804E11 /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
|
||||
2DF123A625C3B0210020F248 /* ActiveLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveLabel.swift; sourceTree = "<group>"; };
|
||||
2DF75B9A25D0E27500694EC8 /* StatusProviderFacade.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusProviderFacade.swift; sourceTree = "<group>"; };
|
||||
2DF75BA025D0E29D00694EC8 /* StatusProvider+TimelinePostTableViewCellDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusProvider+TimelinePostTableViewCellDelegate.swift"; sourceTree = "<group>"; };
|
||||
2DF75BA025D0E29D00694EC8 /* StatusProvider+StatusTableViewCellDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusProvider+StatusTableViewCellDelegate.swift"; sourceTree = "<group>"; };
|
||||
2DF75BA625D10E1000694EC8 /* APIService+Favorite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Favorite.swift"; sourceTree = "<group>"; };
|
||||
2DF75BB825D1474100694EC8 /* ManagedObjectObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedObjectObserver.swift; sourceTree = "<group>"; };
|
||||
2DF75BC625D1475D00694EC8 /* ManagedObjectContextObjectsDidChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedObjectContextObjectsDidChange.swift; sourceTree = "<group>"; };
|
||||
@ -313,6 +317,7 @@
|
||||
DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Instance.swift"; sourceTree = "<group>"; };
|
||||
DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||
DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightDimmableButton.swift; sourceTree = "<group>"; };
|
||||
DB1D186B25EF5BA7003F1F23 /* PollTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollTableView.swift; sourceTree = "<group>"; };
|
||||
DB2B3ABD25E37E15007045F9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
DB2B3AE825E38850007045F9 /* UIViewPreview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewPreview.swift; sourceTree = "<group>"; };
|
||||
DB2F073325E8ECF000957B2D /* AuthenticationViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationViewModel.swift; sourceTree = "<group>"; };
|
||||
@ -349,6 +354,9 @@
|
||||
DB5086A425CC0B7000C2C187 /* AvatarBarButtonItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarBarButtonItem.swift; sourceTree = "<group>"; };
|
||||
DB5086AA25CC0BBB00C2C187 /* AvatarConfigurableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarConfigurableView.swift; sourceTree = "<group>"; };
|
||||
DB5086BD25CC0D9900C2C187 /* SplashPreference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SplashPreference.swift; sourceTree = "<group>"; };
|
||||
DB59F0FD25EF5D96001F1DAB /* StatusProvider+UITableViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusProvider+UITableViewDelegate.swift"; sourceTree = "<group>"; };
|
||||
DB59F10325EF5EBC001F1DAB /* TableViewCellHeightCacheableContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewCellHeightCacheableContainer.swift; sourceTree = "<group>"; };
|
||||
DB59F10D25EF724F001F1DAB /* APIService+Poll.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Poll.swift"; sourceTree = "<group>"; };
|
||||
DB68586325E619B700F0A850 /* NSKeyValueObservation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSKeyValueObservation.swift; sourceTree = "<group>"; };
|
||||
DB68A04925E9027700CFDF14 /* DarkContentStatusBarStyleNavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DarkContentStatusBarStyleNavigationController.swift; sourceTree = "<group>"; };
|
||||
DB68A05C25E9055900CFDF14 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
|
||||
@ -551,7 +559,8 @@
|
||||
children = (
|
||||
2D38F1FD25CD481700561493 /* StatusProvider.swift */,
|
||||
2DF75B9A25D0E27500694EC8 /* StatusProviderFacade.swift */,
|
||||
2DF75BA025D0E29D00694EC8 /* StatusProvider+TimelinePostTableViewCellDelegate.swift */,
|
||||
2DF75BA025D0E29D00694EC8 /* StatusProvider+StatusTableViewCellDelegate.swift */,
|
||||
DB59F0FD25EF5D96001F1DAB /* StatusProvider+UITableViewDelegate.swift */,
|
||||
);
|
||||
path = StatusProvider;
|
||||
sourceTree = "<group>";
|
||||
@ -617,9 +626,10 @@
|
||||
2D38F1FC25CD47D900561493 /* StatusProvider */,
|
||||
DB5086AA25CC0BBB00C2C187 /* AvatarConfigurableView.swift */,
|
||||
2D69CFF325CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift */,
|
||||
2D5A3D3725CF8D9F002347D6 /* ScrollViewContainer.swift */,
|
||||
DB59F10325EF5EBC001F1DAB /* TableViewCellHeightCacheableContainer.swift */,
|
||||
2D38F1C525CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift */,
|
||||
2D38F20725CD491300561493 /* DisposeBagCollectable.swift */,
|
||||
2D5A3D3725CF8D9F002347D6 /* ScrollViewContainer.swift */,
|
||||
);
|
||||
path = Protocol;
|
||||
sourceTree = "<group>";
|
||||
@ -672,6 +682,7 @@
|
||||
2D42FF7C25C82207004A627A /* ToolBar */,
|
||||
DB9D6C1325E4F97A0051B173 /* Container */,
|
||||
2D152A8A25C295B8009AA50C /* Content */,
|
||||
DB1D187125EF5BBD003F1F23 /* TableView */,
|
||||
2D7631A625C1533800929FB9 /* TableviewCell */,
|
||||
);
|
||||
path = View;
|
||||
@ -752,6 +763,14 @@
|
||||
path = CoreDataStack;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DB1D187125EF5BBD003F1F23 /* TableView */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DB1D186B25EF5BA7003F1F23 /* PollTableView.swift */,
|
||||
);
|
||||
path = TableView;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DB3D0FF725BAA68500EAA174 /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -850,15 +869,16 @@
|
||||
DB45FB0925CA87BC005A8AC7 /* CoreData */,
|
||||
2D61335625C1887F00CAE157 /* Persist */,
|
||||
2D61335D25C1894B00CAE157 /* APIService.swift */,
|
||||
DB98337E25C9452D00AD9700 /* APIService+APIError.swift */,
|
||||
DBD9148F25DF6D8D00903DFD /* APIService+Onboarding.swift */,
|
||||
2DF75BA625D10E1000694EC8 /* APIService+Favorite.swift */,
|
||||
DB98337E25C9452D00AD9700 /* APIService+APIError.swift */,
|
||||
DB98336A25C9420100AD9700 /* APIService+App.swift */,
|
||||
DB98337025C9443200AD9700 /* APIService+Authentication.swift */,
|
||||
DB98339B25C96DE600AD9700 /* APIService+Account.swift */,
|
||||
2D04F42425C255B9003F936F /* APIService+PublicTimeline.swift */,
|
||||
DB45FB1C25CA9D23005A8AC7 /* APIService+HomeTimeline.swift */,
|
||||
DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */,
|
||||
DB59F10D25EF724F001F1DAB /* APIService+Poll.swift */,
|
||||
);
|
||||
path = APIService;
|
||||
sourceTree = "<group>";
|
||||
@ -1480,6 +1500,7 @@
|
||||
DB72601C25E36A2100235243 /* MastodonServerRulesViewController.swift in Sources */,
|
||||
2D42FF8F25C8228A004A627A /* UIButton.swift in Sources */,
|
||||
0FAA102725E1126A0017CCDE /* MastodonPickServerViewController.swift in Sources */,
|
||||
DB59F0FE25EF5D96001F1DAB /* StatusProvider+UITableViewDelegate.swift in Sources */,
|
||||
DB68586425E619B700F0A850 /* NSKeyValueObservation.swift in Sources */,
|
||||
2D61335825C188A000CAE157 /* APIService+Persist+Timeline.swift in Sources */,
|
||||
DB45FAE325CA7181005A8AC7 /* MastodonUser.swift in Sources */,
|
||||
@ -1499,6 +1520,7 @@
|
||||
DBD9149025DF6D8D00903DFD /* APIService+Onboarding.swift in Sources */,
|
||||
DB98337F25C9452D00AD9700 /* APIService+APIError.swift in Sources */,
|
||||
2DF123A725C3B0210020F248 /* ActiveLabel.swift in Sources */,
|
||||
DB59F10425EF5EBC001F1DAB /* TableViewCellHeightCacheableContainer.swift in Sources */,
|
||||
DBE0822425CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift in Sources */,
|
||||
2DF75B9B25D0E27500694EC8 /* StatusProviderFacade.swift in Sources */,
|
||||
DB5086A525CC0B7000C2C187 /* AvatarBarButtonItem.swift in Sources */,
|
||||
@ -1515,9 +1537,11 @@
|
||||
DB4481CC25EE2AFE00BEFB67 /* PollItem.swift in Sources */,
|
||||
DB4563BD25E11A24004DA0B9 /* KeyboardResponderService.swift in Sources */,
|
||||
DB5086BE25CC0D9900C2C187 /* SplashPreference.swift in Sources */,
|
||||
2DF75BA125D0E29D00694EC8 /* StatusProvider+TimelinePostTableViewCellDelegate.swift in Sources */,
|
||||
2DF75BA125D0E29D00694EC8 /* StatusProvider+StatusTableViewCellDelegate.swift in Sources */,
|
||||
DB59F10E25EF724F001F1DAB /* APIService+Poll.swift in Sources */,
|
||||
2D42FF7E25C82218004A627A /* ActionToolBarContainer.swift in Sources */,
|
||||
DB0140A125C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift in Sources */,
|
||||
DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */,
|
||||
2D5981A125E4A593000FB903 /* MastodonConfirmEmailViewModel.swift in Sources */,
|
||||
DB8AF55025C13703002E6C99 /* MainTabBarController.swift in Sources */,
|
||||
DB9D6BE925E4F5340051B173 /* SearchViewController.swift in Sources */,
|
||||
|
@ -15,18 +15,20 @@ enum PollItem {
|
||||
|
||||
extension PollItem {
|
||||
class Attribute: Hashable {
|
||||
var voted: Bool = false
|
||||
// var pollVotable: Bool
|
||||
var isOptionVoted: Bool
|
||||
|
||||
init(voted: Bool = false) {
|
||||
self.voted = voted
|
||||
init(isOptionVoted: Bool) {
|
||||
// self.pollVotable = pollVotable
|
||||
self.isOptionVoted = isOptionVoted
|
||||
}
|
||||
|
||||
static func == (lhs: PollItem.Attribute, rhs: PollItem.Attribute) -> Bool {
|
||||
return lhs.voted == rhs.voted
|
||||
return lhs.isOptionVoted == rhs.isOptionVoted
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(voted)
|
||||
hasher.combine(isOptionVoted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,6 @@ extension PollSection {
|
||||
itemAttribute: PollItem.Attribute
|
||||
) {
|
||||
cell.optionLabel.text = pollOption.title
|
||||
cell.configureCheckmark(state: itemAttribute.voted ? .on : .off)
|
||||
|
||||
cell.configure(state: itemAttribute.isOptionVoted ? .on : .off)
|
||||
}
|
||||
}
|
||||
|
@ -21,11 +21,11 @@ extension StatusSection {
|
||||
dependency: NeedsDependency,
|
||||
managedObjectContext: NSManagedObjectContext,
|
||||
timestampUpdatePublisher: AnyPublisher<Date, Never>,
|
||||
timelinePostTableViewCellDelegate: StatusTableViewCellDelegate,
|
||||
statusTableViewCellDelegate: StatusTableViewCellDelegate,
|
||||
timelineMiddleLoaderTableViewCellDelegate: TimelineMiddleLoaderTableViewCellDelegate?
|
||||
) -> UITableViewDiffableDataSource<StatusSection, Item> {
|
||||
UITableViewDiffableDataSource(tableView: tableView) { [weak timelinePostTableViewCellDelegate, weak timelineMiddleLoaderTableViewCellDelegate] tableView, indexPath, item -> UITableViewCell? in
|
||||
guard let timelinePostTableViewCellDelegate = timelinePostTableViewCellDelegate else { return UITableViewCell() }
|
||||
UITableViewDiffableDataSource(tableView: tableView) { [weak statusTableViewCellDelegate, weak timelineMiddleLoaderTableViewCellDelegate] tableView, indexPath, item -> UITableViewCell? in
|
||||
guard let statusTableViewCellDelegate = statusTableViewCellDelegate else { return UITableViewCell() }
|
||||
|
||||
switch item {
|
||||
case .homeTimelineIndex(objectID: let objectID, let attribute):
|
||||
@ -36,7 +36,7 @@ extension StatusSection {
|
||||
let timelineIndex = managedObjectContext.object(with: objectID) as! HomeTimelineIndex
|
||||
StatusSection.configure(cell: cell, readableLayoutFrame: tableView.readableContentGuide.layoutFrame, timestampUpdatePublisher: timestampUpdatePublisher, toot: timelineIndex.toot, requestUserID: timelineIndex.userID, statusContentWarningAttribute: attribute)
|
||||
}
|
||||
cell.delegate = timelinePostTableViewCellDelegate
|
||||
cell.delegate = statusTableViewCellDelegate
|
||||
return cell
|
||||
case .toot(let objectID, let attribute):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StatusTableViewCell.self), for: indexPath) as! StatusTableViewCell
|
||||
@ -47,7 +47,7 @@ extension StatusSection {
|
||||
let toot = managedObjectContext.object(with: objectID) as! Toot
|
||||
StatusSection.configure(cell: cell, readableLayoutFrame: tableView.readableContentGuide.layoutFrame, timestampUpdatePublisher: timestampUpdatePublisher, toot: toot, requestUserID: requestUserID, statusContentWarningAttribute: attribute)
|
||||
}
|
||||
cell.delegate = timelinePostTableViewCellDelegate
|
||||
cell.delegate = statusTableViewCellDelegate
|
||||
return cell
|
||||
case .publicMiddleLoader(let upperTimelineTootID):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineMiddleLoaderTableViewCell.self), for: indexPath) as! TimelineMiddleLoaderTableViewCell
|
||||
@ -158,9 +158,27 @@ extension StatusSection {
|
||||
if let poll = (toot.reblog ?? toot).poll {
|
||||
cell.statusView.pollTableView.isHidden = false
|
||||
cell.statusView.pollStatusStackView.isHidden = false
|
||||
cell.statusView.pollVoteButton.isHidden = !poll.multiple
|
||||
cell.statusView.pollVoteCountLabel.text = {
|
||||
if poll.multiple {
|
||||
let count = poll.votersCount?.intValue ?? 0
|
||||
if count > 1 {
|
||||
return L10n.Common.Controls.Status.Poll.VoterCount.single(count)
|
||||
} else {
|
||||
return L10n.Common.Controls.Status.Poll.VoterCount.multiple(count)
|
||||
}
|
||||
} else {
|
||||
let count = poll.votesCount.intValue
|
||||
if count > 1 {
|
||||
return L10n.Common.Controls.Status.Poll.VoteCount.single(count)
|
||||
} else {
|
||||
return L10n.Common.Controls.Status.Poll.VoteCount.multiple(count)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
let managedObjectContext = toot.managedObjectContext!
|
||||
cell.statusView.statusPollTableViewDataSource = PollSection.tableViewDiffableDataSource(
|
||||
cell.statusView.pollTableViewDataSource = PollSection.tableViewDiffableDataSource(
|
||||
for: cell.statusView.pollTableView,
|
||||
managedObjectContext: managedObjectContext
|
||||
)
|
||||
@ -171,15 +189,16 @@ extension StatusSection {
|
||||
.sorted(by: { $0.index.intValue < $1.index.intValue })
|
||||
.map { option -> PollItem in
|
||||
let isVoted = (option.votedBy ?? Set()).map { $0.id }.contains(requestUserID)
|
||||
let attribute = PollItem.Attribute(voted: isVoted)
|
||||
let attribute = PollItem.Attribute(isOptionVoted: isVoted)
|
||||
let option = PollItem.opion(objectID: option.objectID, attribute: attribute)
|
||||
return option
|
||||
}
|
||||
snapshot.appendItems(pollItems, toSection: .main)
|
||||
cell.statusView.statusPollTableViewDataSource?.apply(snapshot, animatingDifferences: false, completion: nil)
|
||||
cell.statusView.pollTableViewDataSource?.apply(snapshot, animatingDifferences: false, completion: nil)
|
||||
} else {
|
||||
cell.statusView.pollTableView.isHidden = true
|
||||
cell.statusView.pollStatusStackView.isHidden = true
|
||||
cell.statusView.pollVoteButton.isHidden = true
|
||||
}
|
||||
|
||||
// toolbar
|
||||
|
@ -73,6 +73,8 @@ internal enum L10n {
|
||||
internal static func timeLeft(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "Common.Controls.Status.Poll.TimeLeft", String(describing: p1))
|
||||
}
|
||||
/// Vote
|
||||
internal static let vote = L10n.tr("Localizable", "Common.Controls.Status.Poll.Vote")
|
||||
internal enum VoteCount {
|
||||
/// %d votes
|
||||
internal static func multiple(_ p1: Int) -> String {
|
||||
@ -83,6 +85,16 @@ internal enum L10n {
|
||||
return L10n.tr("Localizable", "Common.Controls.Status.Poll.VoteCount.Single", p1)
|
||||
}
|
||||
}
|
||||
internal enum VoterCount {
|
||||
/// %d voters
|
||||
internal static func multiple(_ p1: Int) -> String {
|
||||
return L10n.tr("Localizable", "Common.Controls.Status.Poll.VoterCount.Multiple", p1)
|
||||
}
|
||||
/// %d voter
|
||||
internal static func single(_ p1: Int) -> String {
|
||||
return L10n.tr("Localizable", "Common.Controls.Status.Poll.VoterCount.Single", p1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
internal enum Timeline {
|
||||
|
@ -0,0 +1,71 @@
|
||||
//
|
||||
// StatusProvider+StatusTableViewCellDelegate.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by sxiaojian on 2021/2/8.
|
||||
//
|
||||
|
||||
import os.log
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
import ActiveLabel
|
||||
|
||||
// MARK: - ActionToolbarContainerDelegate
|
||||
extension StatusTableViewCellDelegate where Self: StatusProvider {
|
||||
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, actionToolbarContainer: ActionToolbarContainer, likeButtonDidPressed sender: UIButton) {
|
||||
StatusProviderFacade.responseToStatusLikeAction(provider: self, cell: cell)
|
||||
}
|
||||
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, contentWarningActionButtonPressed button: UIButton) {
|
||||
guard let diffableDataSource = self.tableViewDiffableDataSource else { return }
|
||||
guard let item = item(for: cell, indexPath: nil) else { return }
|
||||
|
||||
switch item {
|
||||
case .homeTimelineIndex(_, let attribute):
|
||||
attribute.isStatusTextSensitive = false
|
||||
case .toot(_, let attribute):
|
||||
attribute.isStatusTextSensitive = false
|
||||
default:
|
||||
return
|
||||
}
|
||||
var snapshot = diffableDataSource.snapshot()
|
||||
snapshot.reloadItems([item])
|
||||
diffableDataSource.apply(snapshot)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension StatusTableViewCellDelegate where Self: StatusProvider {
|
||||
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, mosaicImageViewContainer: MosaicImageViewContainer, didTapImageView imageView: UIImageView, atIndex index: Int) {
|
||||
|
||||
}
|
||||
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, mosaicImageViewContainer: MosaicImageViewContainer, didTapContentWarningVisualEffectView visualEffectView: UIVisualEffectView) {
|
||||
guard let diffableDataSource = self.tableViewDiffableDataSource else { return }
|
||||
guard let item = item(for: cell, indexPath: nil) else { return }
|
||||
|
||||
switch item {
|
||||
case .homeTimelineIndex(_, let attribute):
|
||||
attribute.isStatusSensitive = false
|
||||
case .toot(_, let attribute):
|
||||
attribute.isStatusSensitive = false
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
var snapshot = diffableDataSource.snapshot()
|
||||
snapshot.reloadItems([item])
|
||||
UIView.animate(withDuration: 0.33) {
|
||||
cell.statusView.statusMosaicImageViewContainer.blurVisualEffectView.effect = nil
|
||||
cell.statusView.statusMosaicImageViewContainer.vibrancyVisualEffectView.alpha = 0.0
|
||||
} completion: { _ in
|
||||
diffableDataSource.apply(snapshot, animatingDifferences: false, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
//
|
||||
// StatusProvider+TimelinePostTableViewCellDelegate.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by sxiaojian on 2021/2/8.
|
||||
//
|
||||
|
||||
import os.log
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
import ActiveLabel
|
||||
|
||||
// MARK: - ActionToolbarContainerDelegate
|
||||
extension StatusTableViewCellDelegate where Self: StatusProvider {
|
||||
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, actionToolbarContainer: ActionToolbarContainer, likeButtonDidPressed sender: UIButton) {
|
||||
StatusProviderFacade.responseToStatusLikeAction(provider: self, cell: cell)
|
||||
}
|
||||
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, contentWarningActionButtonPressed button: UIButton) {
|
||||
guard let diffableDataSource = self.tableViewDiffableDataSource else { return }
|
||||
item(for: cell, indexPath: nil)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] item in
|
||||
guard let _ = self else { return }
|
||||
guard let item = item else { return }
|
||||
switch item {
|
||||
case .homeTimelineIndex(_, let attribute):
|
||||
attribute.isStatusTextSensitive = false
|
||||
case .toot(_, let attribute):
|
||||
attribute.isStatusTextSensitive = false
|
||||
default:
|
||||
return
|
||||
}
|
||||
var snapshot = diffableDataSource.snapshot()
|
||||
snapshot.reloadItems([item])
|
||||
diffableDataSource.apply(snapshot)
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension StatusTableViewCellDelegate where Self: StatusProvider {
|
||||
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, mosaicImageViewContainer: MosaicImageViewContainer, didTapImageView imageView: UIImageView, atIndex index: Int) {
|
||||
|
||||
}
|
||||
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, mosaicImageViewContainer: MosaicImageViewContainer, didTapContentWarningVisualEffectView visualEffectView: UIVisualEffectView) {
|
||||
guard let diffableDataSource = self.tableViewDiffableDataSource else { return }
|
||||
item(for: cell, indexPath: nil)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] item in
|
||||
guard let _ = self else { return }
|
||||
guard let item = item else { return }
|
||||
switch item {
|
||||
case .homeTimelineIndex(_, let attribute):
|
||||
attribute.isStatusSensitive = false
|
||||
case .toot(_, let attribute):
|
||||
attribute.isStatusSensitive = false
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
var snapshot = diffableDataSource.snapshot()
|
||||
snapshot.reloadItems([item])
|
||||
UIView.animate(withDuration: 0.33) {
|
||||
cell.statusView.statusMosaicImageViewContainer.blurVisualEffectView.effect = nil
|
||||
cell.statusView.statusMosaicImageViewContainer.vibrancyVisualEffectView.alpha = 0.0
|
||||
} completion: { _ in
|
||||
diffableDataSource.apply(snapshot, animatingDifferences: false, completion: nil)
|
||||
}
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
//
|
||||
// StatusProvider+UITableViewDelegate.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-3-3.
|
||||
//
|
||||
|
||||
import os.log
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
extension StatusTableViewCellDelegate where Self: StatusProvider {
|
||||
// TODO:
|
||||
// func handleTableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
|
||||
// }
|
||||
|
||||
func handleTableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
||||
let now = Date()
|
||||
var pollID: Mastodon.Entity.Poll.ID?
|
||||
toot(for: cell, indexPath: indexPath)
|
||||
.compactMap { [weak self] toot -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Poll>, Error>? in
|
||||
guard let self = self else { return nil }
|
||||
guard let authenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else { return nil }
|
||||
guard let toot = (toot?.reblog ?? toot) else { return nil }
|
||||
guard let poll = toot.poll else { return nil }
|
||||
pollID = poll.id
|
||||
|
||||
// not expired AND last update > 60s
|
||||
guard !poll.expired else {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: poll %s expired. Skip for update", ((#file as NSString).lastPathComponent), #line, #function, poll.id)
|
||||
return nil
|
||||
}
|
||||
let timeIntervalSinceUpdate = now.timeIntervalSince(poll.updatedAt)
|
||||
guard timeIntervalSinceUpdate > 60 else {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: poll %s updated in the %.2fs. Skip for update", ((#file as NSString).lastPathComponent), #line, #function, poll.id, timeIntervalSinceUpdate)
|
||||
return nil
|
||||
}
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: poll %s info update…", ((#file as NSString).lastPathComponent), #line, #function, poll.id)
|
||||
|
||||
return self.context.apiService.poll(
|
||||
domain: toot.domain,
|
||||
pollID: poll.id,
|
||||
pollObjectID: poll.objectID,
|
||||
mastodonAuthenticationBox: authenticationBox
|
||||
)
|
||||
}
|
||||
.setFailureType(to: Error.self)
|
||||
.switchToLatest()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveCompletion: { completion in
|
||||
switch completion {
|
||||
case .failure(let error):
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: poll %s info fail to update: %s", ((#file as NSString).lastPathComponent), #line, #function, pollID ?? "?", error.localizedDescription)
|
||||
case .finished:
|
||||
break
|
||||
}
|
||||
}, receiveValue: { response in
|
||||
let poll = response.value
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: poll %s info updated", ((#file as NSString).lastPathComponent), #line, #function, poll.id)
|
||||
})
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension StatusTableViewCellDelegate where Self: StatusProvider {
|
||||
|
||||
|
||||
}
|
@ -7,13 +7,17 @@
|
||||
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
|
||||
protocol StatusProvider: NeedsDependency & DisposeBagCollectable & UIViewController {
|
||||
// async
|
||||
func toot() -> Future<Toot?, Never>
|
||||
func toot(for cell: UITableViewCell, indexPath: IndexPath?) -> Future<Toot?, Never>
|
||||
func toot(for cell: UICollectionViewCell) -> Future<Toot?, Never>
|
||||
|
||||
// sync
|
||||
var managedObjectContext: NSManagedObjectContext { get }
|
||||
var tableViewDiffableDataSource: UITableViewDiffableDataSource<StatusSection, Item>? { get }
|
||||
func item(for cell: UITableViewCell, indexPath: IndexPath?) -> Future<Item?, Never>
|
||||
func item(for cell: UITableViewCell?, indexPath: IndexPath?) -> Item?
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
//
|
||||
// TableViewCellHeightCacheableContainer.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-3-3.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol TableViewCellHeightCacheableContainer: UIViewController {
|
||||
// TODO:
|
||||
}
|
@ -18,8 +18,11 @@
|
||||
"Common.Controls.Actions.TakePhoto" = "Take photo";
|
||||
"Common.Controls.Status.MediaContentWarning" = "Tap to reveal that may be sensitive";
|
||||
"Common.Controls.Status.Poll.TimeLeft" = "%@ left";
|
||||
"Common.Controls.Status.Poll.Vote" = "Vote";
|
||||
"Common.Controls.Status.Poll.VoteCount.Multiple" = "%d votes";
|
||||
"Common.Controls.Status.Poll.VoteCount.Single" = "%d vote";
|
||||
"Common.Controls.Status.Poll.VoterCount.Multiple" = "%d voters";
|
||||
"Common.Controls.Status.Poll.VoterCount.Single" = "%d voter";
|
||||
"Common.Controls.Status.ShowPost" = "Show Post";
|
||||
"Common.Controls.Status.StatusContentWarning" = "content warning";
|
||||
"Common.Controls.Status.UserBoosted" = "%@ boosted";
|
||||
|
@ -8,6 +8,7 @@
|
||||
import os.log
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
|
||||
// MARK: - StatusProvider
|
||||
@ -47,25 +48,26 @@ extension HomeTimelineViewController: StatusProvider {
|
||||
return Future { promise in promise(.success(nil)) }
|
||||
}
|
||||
|
||||
var managedObjectContext: NSManagedObjectContext {
|
||||
return viewModel.fetchedResultsController.managedObjectContext
|
||||
}
|
||||
|
||||
var tableViewDiffableDataSource: UITableViewDiffableDataSource<StatusSection, Item>? {
|
||||
return viewModel.diffableDataSource
|
||||
}
|
||||
|
||||
func item(for cell: UITableViewCell, indexPath: IndexPath?) -> Future<Item?, Never> {
|
||||
return Future { promise in
|
||||
func item(for cell: UITableViewCell?, indexPath: IndexPath?) -> Item? {
|
||||
guard let diffableDataSource = self.viewModel.diffableDataSource else {
|
||||
assertionFailure()
|
||||
promise(.success(nil))
|
||||
return
|
||||
return nil
|
||||
}
|
||||
guard let indexPath = indexPath ?? self.tableView.indexPath(for: cell),
|
||||
|
||||
guard let indexPath = indexPath ?? cell.flatMap({ self.tableView.indexPath(for: $0) }),
|
||||
let item = diffableDataSource.itemIdentifier(for: indexPath) else {
|
||||
promise(.success(nil))
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
promise(.success(item))
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ extension HomeTimelineViewController {
|
||||
viewModel.setupDiffableDataSource(
|
||||
for: tableView,
|
||||
dependency: self,
|
||||
timelinePostTableViewCellDelegate: self,
|
||||
statusTableViewCellDelegate: self,
|
||||
timelineMiddleLoaderTableViewCellDelegate: self
|
||||
)
|
||||
|
||||
@ -220,16 +220,21 @@ extension HomeTimelineViewController: LoadMoreConfigurableTableViewContainer {
|
||||
// MARK: - UITableViewDelegate
|
||||
extension HomeTimelineViewController: UITableViewDelegate {
|
||||
|
||||
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
|
||||
guard let diffableDataSource = viewModel.diffableDataSource else { return 100 }
|
||||
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return 100 }
|
||||
// TODO:
|
||||
// func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
|
||||
// guard let diffableDataSource = viewModel.diffableDataSource else { return 100 }
|
||||
// guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return 100 }
|
||||
//
|
||||
// guard let frame = viewModel.cellFrameCache.object(forKey: NSNumber(value: item.hashValue))?.cgRectValue else {
|
||||
// return 200
|
||||
// }
|
||||
// // os_log("%{public}s[%{public}ld], %{public}s: cache cell frame %s", ((#file as NSString).lastPathComponent), #line, #function, frame.debugDescription)
|
||||
//
|
||||
// return ceil(frame.height)
|
||||
// }
|
||||
|
||||
guard let frame = viewModel.cellFrameCache.object(forKey: NSNumber(value: item.hashValue))?.cgRectValue else {
|
||||
return 200
|
||||
}
|
||||
// os_log("%{public}s[%{public}ld], %{public}s: cache cell frame %s", ((#file as NSString).lastPathComponent), #line, #function, frame.debugDescription)
|
||||
|
||||
return ceil(frame.height)
|
||||
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
||||
handleTableView(tableView, willDisplay: cell, forRowAt: indexPath)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ extension HomeTimelineViewModel {
|
||||
func setupDiffableDataSource(
|
||||
for tableView: UITableView,
|
||||
dependency: NeedsDependency,
|
||||
timelinePostTableViewCellDelegate: StatusTableViewCellDelegate,
|
||||
statusTableViewCellDelegate: StatusTableViewCellDelegate,
|
||||
timelineMiddleLoaderTableViewCellDelegate: TimelineMiddleLoaderTableViewCellDelegate
|
||||
) {
|
||||
let timestampUpdatePublisher = Timer.publish(every: 1.0, on: .main, in: .common)
|
||||
@ -28,7 +28,7 @@ extension HomeTimelineViewModel {
|
||||
dependency: dependency,
|
||||
managedObjectContext: fetchedResultsController.managedObjectContext,
|
||||
timestampUpdatePublisher: timestampUpdatePublisher,
|
||||
timelinePostTableViewCellDelegate: timelinePostTableViewCellDelegate,
|
||||
statusTableViewCellDelegate: statusTableViewCellDelegate,
|
||||
timelineMiddleLoaderTableViewCellDelegate: timelineMiddleLoaderTableViewCellDelegate
|
||||
)
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
import os.log
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
@ -48,25 +49,25 @@ extension PublicTimelineViewController: StatusProvider {
|
||||
return Future { promise in promise(.success(nil)) }
|
||||
}
|
||||
|
||||
var managedObjectContext: NSManagedObjectContext {
|
||||
return viewModel.fetchedResultsController.managedObjectContext
|
||||
}
|
||||
|
||||
var tableViewDiffableDataSource: UITableViewDiffableDataSource<StatusSection, Item>? {
|
||||
return viewModel.diffableDataSource
|
||||
}
|
||||
|
||||
func item(for cell: UITableViewCell, indexPath: IndexPath?) -> Future<Item?, Never> {
|
||||
return Future { promise in
|
||||
func item(for cell: UITableViewCell?, indexPath: IndexPath?) -> Item? {
|
||||
guard let diffableDataSource = self.viewModel.diffableDataSource else {
|
||||
assertionFailure()
|
||||
promise(.success(nil))
|
||||
return
|
||||
return nil
|
||||
}
|
||||
guard let indexPath = indexPath ?? self.tableView.indexPath(for: cell),
|
||||
guard let indexPath = indexPath ?? cell.flatMap({ self.tableView.indexPath(for: $0) }),
|
||||
let item = diffableDataSource.itemIdentifier(for: indexPath) else {
|
||||
promise(.success(nil))
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
promise(.success(item))
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ extension PublicTimelineViewController {
|
||||
viewModel.setupDiffableDataSource(
|
||||
for: tableView,
|
||||
dependency: self,
|
||||
timelinePostTableViewCellDelegate: self,
|
||||
statusTableViewCellDelegate: self,
|
||||
timelineMiddleLoaderTableViewCellDelegate: self
|
||||
)
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ extension PublicTimelineViewModel {
|
||||
func setupDiffableDataSource(
|
||||
for tableView: UITableView,
|
||||
dependency: NeedsDependency,
|
||||
timelinePostTableViewCellDelegate: StatusTableViewCellDelegate,
|
||||
statusTableViewCellDelegate: StatusTableViewCellDelegate,
|
||||
timelineMiddleLoaderTableViewCellDelegate: TimelineMiddleLoaderTableViewCellDelegate
|
||||
) {
|
||||
let timestampUpdatePublisher = Timer.publish(every: 1.0, on: .main, in: .common)
|
||||
@ -27,7 +27,7 @@ extension PublicTimelineViewModel {
|
||||
dependency: dependency,
|
||||
managedObjectContext: fetchedResultsController.managedObjectContext,
|
||||
timestampUpdatePublisher: timestampUpdatePublisher,
|
||||
timelinePostTableViewCellDelegate: timelinePostTableViewCellDelegate,
|
||||
statusTableViewCellDelegate: statusTableViewCellDelegate,
|
||||
timelineMiddleLoaderTableViewCellDelegate: timelineMiddleLoaderTableViewCellDelegate
|
||||
)
|
||||
items.value = []
|
||||
|
@ -25,8 +25,8 @@ final class StatusView: UIView {
|
||||
|
||||
weak var delegate: StatusViewDelegate?
|
||||
var isStatusTextSensitive = false
|
||||
var statusPollTableViewDataSource: UITableViewDiffableDataSource<PollSection, PollItem>?
|
||||
var statusPollTableViewHeightLaoutConstraint: NSLayoutConstraint!
|
||||
var pollTableViewDataSource: UITableViewDiffableDataSource<PollSection, PollItem>?
|
||||
var pollTableViewHeightLaoutConstraint: NSLayoutConstraint!
|
||||
|
||||
let headerContainerStackView = UIStackView()
|
||||
|
||||
@ -105,8 +105,8 @@ final class StatusView: UIView {
|
||||
}()
|
||||
let statusMosaicImageViewContainer = MosaicImageViewContainer()
|
||||
|
||||
let pollTableView: UITableView = {
|
||||
let tableView = UITableView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
|
||||
let pollTableView: PollTableView = {
|
||||
let tableView = PollTableView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
|
||||
tableView.register(PollOptionTableViewCell.self, forCellReuseIdentifier: String(describing: PollOptionTableViewCell.self))
|
||||
tableView.isScrollEnabled = false
|
||||
tableView.separatorStyle = .none
|
||||
@ -136,6 +136,15 @@ final class StatusView: UIView {
|
||||
label.text = L10n.Common.Controls.Status.Poll.timeLeft("6 hours")
|
||||
return label
|
||||
}()
|
||||
let pollVoteButton: UIButton = {
|
||||
let button = HitTestExpandedButton()
|
||||
button.titleLabel?.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 14, weight: .regular))
|
||||
button.setTitle(L10n.Common.Controls.Status.Poll.vote, for: .normal)
|
||||
button.setTitleColor(Asset.Colors.Button.highlight.color, for: .normal)
|
||||
button.setTitleColor(Asset.Colors.Button.highlight.color.withAlphaComponent(0.8), for: .highlighted)
|
||||
button.setTitleColor(Asset.Colors.Button.disabled.color, for: .disabled)
|
||||
return button
|
||||
}()
|
||||
|
||||
// do not use visual effect view due to we blur text only without background
|
||||
let contentWarningBlurContentImageView: UIImageView = {
|
||||
@ -302,18 +311,18 @@ extension StatusView {
|
||||
statusContainerStackView.addArrangedSubview(statusMosaicImageViewContainer)
|
||||
pollTableView.translatesAutoresizingMaskIntoConstraints = false
|
||||
statusContainerStackView.addArrangedSubview(pollTableView)
|
||||
statusPollTableViewHeightLaoutConstraint = pollTableView.heightAnchor.constraint(equalToConstant: 44.0).priority(.required - 1)
|
||||
pollTableViewHeightLaoutConstraint = pollTableView.heightAnchor.constraint(equalToConstant: 44.0).priority(.required - 1)
|
||||
NSLayoutConstraint.activate([
|
||||
statusPollTableViewHeightLaoutConstraint,
|
||||
pollTableViewHeightLaoutConstraint,
|
||||
])
|
||||
|
||||
statusPollTableViewHeightObservation = pollTableView.observe(\.contentSize, options: .new, changeHandler: { [weak self] tableView, _ in
|
||||
guard let self = self else { return }
|
||||
guard self.pollTableView.contentSize.height != .zero else {
|
||||
self.statusPollTableViewHeightLaoutConstraint.constant = 44
|
||||
self.pollTableViewHeightLaoutConstraint.constant = 44
|
||||
return
|
||||
}
|
||||
self.statusPollTableViewHeightLaoutConstraint.constant = self.pollTableView.contentSize.height
|
||||
self.pollTableViewHeightLaoutConstraint.constant = self.pollTableView.contentSize.height
|
||||
})
|
||||
|
||||
statusContainerStackView.addArrangedSubview(pollStatusStackView)
|
||||
@ -321,9 +330,11 @@ extension StatusView {
|
||||
pollStatusStackView.addArrangedSubview(pollVoteCountLabel)
|
||||
pollStatusStackView.addArrangedSubview(pollStatusDotLabel)
|
||||
pollStatusStackView.addArrangedSubview(pollCountdownLabel)
|
||||
pollStatusStackView.addArrangedSubview(pollVoteButton)
|
||||
pollVoteCountLabel.setContentHuggingPriority(.defaultHigh + 2, for: .horizontal)
|
||||
pollStatusDotLabel.setContentHuggingPriority(.defaultHigh + 1, for: .horizontal)
|
||||
pollCountdownLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
||||
pollVoteButton.setContentHuggingPriority(.defaultHigh + 3, for: .horizontal)
|
||||
|
||||
// action toolbar container
|
||||
containerStackView.addArrangedSubview(actionToolbarContainer)
|
||||
|
10
Mastodon/Scene/Share/View/TableView/PollTableView.swift
Normal file
10
Mastodon/Scene/Share/View/TableView/PollTableView.swift
Normal file
@ -0,0 +1,10 @@
|
||||
//
|
||||
// PollTableView.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-3-3.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
final class PollTableView: UITableView { }
|
@ -6,6 +6,7 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Combine
|
||||
|
||||
final class PollOptionTableViewCell: UITableViewCell {
|
||||
|
||||
@ -14,6 +15,9 @@ final class PollOptionTableViewCell: UITableViewCell {
|
||||
static let verticalMargin: CGFloat = 5
|
||||
static let checkmarkImageSize = CGSize(width: 26, height: 26)
|
||||
|
||||
private var viewStateDisposeBag = Set<AnyCancellable>()
|
||||
private(set) var pollState: PollState = .off
|
||||
|
||||
let roundedBackgroundView = UIView()
|
||||
|
||||
let checkmarkBackgroundView: UIView = {
|
||||
@ -58,6 +62,22 @@ final class PollOptionTableViewCell: UITableViewCell {
|
||||
_init()
|
||||
}
|
||||
|
||||
override func setSelected(_ selected: Bool, animated: Bool) {
|
||||
super.setSelected(selected, animated: animated)
|
||||
}
|
||||
|
||||
override func setHighlighted(_ highlighted: Bool, animated: Bool) {
|
||||
super.setHighlighted(highlighted, animated: animated)
|
||||
|
||||
switch pollState {
|
||||
case .off, .none:
|
||||
let color = Asset.Colors.Background.systemGroupedBackground.color
|
||||
self.roundedBackgroundView.backgroundColor = isHighlighted ? color.withAlphaComponent(0.8) : color
|
||||
case .on:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension PollOptionTableViewCell {
|
||||
@ -113,7 +133,7 @@ extension PollOptionTableViewCell {
|
||||
optionPercentageLabel.setContentHuggingPriority(.required - 1, for: .horizontal)
|
||||
optionPercentageLabel.setContentCompressionResistancePriority(.required - 1, for: .horizontal)
|
||||
|
||||
configureCheckmark(state: .none)
|
||||
configure(state: .none)
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
@ -136,13 +156,13 @@ extension PollOptionTableViewCell {
|
||||
|
||||
extension PollOptionTableViewCell {
|
||||
|
||||
enum CheckmarkState {
|
||||
enum PollState {
|
||||
case none
|
||||
case off
|
||||
case on
|
||||
}
|
||||
|
||||
func configureCheckmark(state: CheckmarkState) {
|
||||
func configure(state: PollState) {
|
||||
switch state {
|
||||
case .none:
|
||||
checkmarkBackgroundView.backgroundColor = .clear
|
||||
@ -185,13 +205,13 @@ struct PollTableViewCell_Previews: PreviewProvider {
|
||||
.previewLayout(.fixed(width: 375, height: 44 + 10))
|
||||
UIViewPreview() {
|
||||
let cell = PollOptionTableViewCell()
|
||||
cell.configureCheckmark(state: .off)
|
||||
cell.configure(state: .off)
|
||||
return cell
|
||||
}
|
||||
.previewLayout(.fixed(width: 375, height: 44 + 10))
|
||||
UIViewPreview() {
|
||||
let cell = PollOptionTableViewCell()
|
||||
cell.configureCheckmark(state: .on)
|
||||
cell.configure(state: .on)
|
||||
return cell
|
||||
}
|
||||
.previewLayout(.fixed(width: 375, height: 44 + 10))
|
||||
|
@ -9,13 +9,15 @@ import os.log
|
||||
import UIKit
|
||||
import AVKit
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
|
||||
protocol StatusTableViewCellDelegate: class {
|
||||
var managedObjectContext: NSManagedObjectContext { get }
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, actionToolbarContainer: ActionToolbarContainer, likeButtonDidPressed sender: UIButton)
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, statusView: StatusView, contentWarningActionButtonPressed button: UIButton)
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, mosaicImageViewContainer: MosaicImageViewContainer, didTapImageView imageView: UIImageView, atIndex index: Int)
|
||||
func statusTableViewCell(_ cell: StatusTableViewCell, mosaicImageViewContainer: MosaicImageViewContainer, didTapContentWarningVisualEffectView visualEffectView: UIVisualEffectView)
|
||||
|
||||
}
|
||||
|
||||
final class StatusTableViewCell: UITableViewCell {
|
||||
@ -32,8 +34,8 @@ final class StatusTableViewCell: UITableViewCell {
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
statusView.isStatusTextSensitive = false
|
||||
statusView.pollTableView.dataSource = nil
|
||||
statusView.cleanUpContentWarning()
|
||||
statusView.pollTableView.dataSource = nil
|
||||
disposeBag.removeAll()
|
||||
observations.removeAll()
|
||||
}
|
||||
@ -85,12 +87,30 @@ extension StatusTableViewCell {
|
||||
bottomPaddingView.backgroundColor = Asset.Colors.Background.systemGroupedBackground.color
|
||||
|
||||
statusView.delegate = self
|
||||
statusView.pollTableView.delegate = self
|
||||
statusView.statusMosaicImageViewContainer.delegate = self
|
||||
statusView.actionToolbarContainer.delegate = self
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDelegate
|
||||
extension StatusTableViewCell: UITableViewDelegate {
|
||||
func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
|
||||
if tableView === statusView.pollTableView, let diffableDataSource = statusView.pollTableViewDataSource {
|
||||
guard let item = diffableDataSource.itemIdentifier(for: indexPath),
|
||||
case let .opion(objectID, _) = item,
|
||||
let option = delegate?.managedObjectContext.object(with: objectID) as? PollOption else {
|
||||
return false
|
||||
}
|
||||
|
||||
return !option.poll.expired
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - StatusViewDelegate
|
||||
extension StatusTableViewCell: StatusViewDelegate {
|
||||
func statusView(_ statusView: StatusView, contentWarningActionButtonPressed button: UIButton) {
|
||||
|
@ -94,7 +94,7 @@ extension APIService {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
APIService.CoreData.mergeToot(for: requestMastodonUser, old: oldToot, in: mastodonAuthenticationBox.domain, entity: entity, networkDate: response.networkDate)
|
||||
APIService.CoreData.merge(toot: oldToot, entity: entity, requestMastodonUser: requestMastodonUser, domain: mastodonAuthenticationBox.domain, networkDate: response.networkDate)
|
||||
os_log(.info, log: log, "%{public}s[%{public}ld], %{public}s: did update toot %{public}s like status to: %{public}s. now %ld likes", ((#file as NSString).lastPathComponent), #line, #function, entity.id, entity.favourited.flatMap { $0 ? "like" : "unlike" } ?? "<nil>", entity.favouritesCount )
|
||||
}
|
||||
.setFailureType(to: Error.self)
|
||||
@ -132,7 +132,7 @@ extension APIService {
|
||||
|
||||
let requestMastodonUserID = mastodonAuthenticationBox.userID
|
||||
let query = Mastodon.API.Favorites.ListQuery(limit: limit, minID: nil, maxID: maxID)
|
||||
return Mastodon.API.Favorites.getFavoriteStatus(domain: mastodonAuthenticationBox.domain, session: session, authorization: mastodonAuthenticationBox.userAuthorization, query: query)
|
||||
return Mastodon.API.Favorites.favoritedStatus(domain: mastodonAuthenticationBox.domain, session: session, authorization: mastodonAuthenticationBox.userAuthorization, query: query)
|
||||
.map { response -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Status]>, Error> in
|
||||
let log = OSLog.api
|
||||
|
||||
|
71
Mastodon/Service/APIService/APIService+Poll.swift
Normal file
71
Mastodon/Service/APIService/APIService+Poll.swift
Normal file
@ -0,0 +1,71 @@
|
||||
//
|
||||
// APIService+Poll.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-3-3.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import CommonOSLog
|
||||
import DateToolsSwift
|
||||
import MastodonSDK
|
||||
|
||||
extension APIService {
|
||||
|
||||
func poll(
|
||||
domain: String,
|
||||
pollID: Mastodon.Entity.Poll.ID,
|
||||
pollObjectID: NSManagedObjectID,
|
||||
mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox
|
||||
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Poll>, Error> {
|
||||
let authorization = mastodonAuthenticationBox.userAuthorization
|
||||
let requestMastodonUserID = mastodonAuthenticationBox.userID
|
||||
|
||||
return Mastodon.API.Polls.poll(
|
||||
session: session,
|
||||
domain: domain,
|
||||
pollID: pollID,
|
||||
authorization: authorization
|
||||
)
|
||||
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Poll>, Error> in
|
||||
let entity = response.value
|
||||
let managedObjectContext = self.backgroundManagedObjectContext
|
||||
|
||||
return managedObjectContext.performChanges {
|
||||
let _requestMastodonUser: MastodonUser? = {
|
||||
let request = MastodonUser.sortedFetchRequest
|
||||
request.predicate = MastodonUser.predicate(domain: mastodonAuthenticationBox.domain, id: requestMastodonUserID)
|
||||
request.fetchLimit = 1
|
||||
request.returnsObjectsAsFaults = false
|
||||
do {
|
||||
return try managedObjectContext.fetch(request).first
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
guard let requestMastodonUser = _requestMastodonUser else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
guard let poll = managedObjectContext.object(with: pollObjectID) as? Poll else { return }
|
||||
APIService.CoreData.merge(poll: poll, entity: entity, requestMastodonUser: requestMastodonUser, domain: domain, networkDate: response.networkDate)
|
||||
}
|
||||
.setFailureType(to: Error.self)
|
||||
.tryMap { result -> Mastodon.Response.Content<Mastodon.Entity.Poll> in
|
||||
switch result {
|
||||
case .success:
|
||||
return response
|
||||
case .failure(let error):
|
||||
throw error
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
}
|
@ -44,7 +44,7 @@ extension APIService.CoreData {
|
||||
|
||||
if let oldToot = oldToot {
|
||||
// merge old Toot
|
||||
APIService.CoreData.mergeToot(for: requestMastodonUser, old: oldToot,in: domain, entity: entity, networkDate: networkDate)
|
||||
APIService.CoreData.merge(toot: oldToot, entity: entity, requestMastodonUser: requestMastodonUser, domain: domain, networkDate: networkDate)
|
||||
return (oldToot, false, false)
|
||||
} else {
|
||||
let (mastodonUser, isMastodonUserCreated) = createOrMergeMastodonUser(into: managedObjectContext, for: requestMastodonUser,in: domain, entity: entity.account, networkDate: networkDate, log: log)
|
||||
@ -106,10 +106,34 @@ extension APIService.CoreData {
|
||||
}
|
||||
}
|
||||
|
||||
static func mergeToot(for requestMastodonUser: MastodonUser?, old toot: Toot,in domain: String, entity: Mastodon.Entity.Status, networkDate: Date) {
|
||||
static func merge(
|
||||
toot: Toot,
|
||||
entity: Mastodon.Entity.Status,
|
||||
requestMastodonUser: MastodonUser?,
|
||||
domain: String,
|
||||
networkDate: Date
|
||||
) {
|
||||
guard networkDate > toot.updatedAt else { return }
|
||||
|
||||
// merge
|
||||
// merge poll
|
||||
if let poll = entity.poll, let oldPoll = toot.poll, poll.options.count == oldPoll.options.count {
|
||||
oldPoll.update(expiresAt: poll.expiresAt)
|
||||
oldPoll.update(expired: poll.expired)
|
||||
oldPoll.update(votesCount: poll.votesCount)
|
||||
oldPoll.update(votersCount: poll.votersCount)
|
||||
|
||||
let oldOptions = oldPoll.options.sorted(by: { $0.index.intValue < $1.index.intValue })
|
||||
for (i, (option, oldOption)) in zip(poll.options, oldOptions).enumerated() {
|
||||
let votedBy: MastodonUser? = (poll.ownVotes ?? []).contains(i) ? requestMastodonUser : nil
|
||||
oldOption.update(votesCount: option.votesCount)
|
||||
votedBy.flatMap { oldOption.update(votedBy: $0) }
|
||||
oldOption.didUpdate(at: networkDate)
|
||||
}
|
||||
|
||||
oldPoll.didUpdate(at: networkDate)
|
||||
}
|
||||
|
||||
// merge metrics
|
||||
if entity.favouritesCount != toot.favouritesCount.intValue {
|
||||
toot.update(favouritesCount:NSNumber(value: entity.favouritesCount))
|
||||
}
|
||||
@ -122,6 +146,7 @@ extension APIService.CoreData {
|
||||
toot.update(reblogsCount:NSNumber(value: entity.reblogsCount))
|
||||
}
|
||||
|
||||
// merge relationship
|
||||
if let mastodonUser = requestMastodonUser {
|
||||
if let favourited = entity.favourited {
|
||||
toot.update(liked: favourited, mastodonUser: mastodonUser)
|
||||
@ -142,10 +167,36 @@ extension APIService.CoreData {
|
||||
|
||||
// merge user
|
||||
mergeMastodonUser(for: requestMastodonUser, old: toot.author, in: domain, entity: entity.account, networkDate: networkDate)
|
||||
// merge indirect reblog & quote
|
||||
|
||||
// merge indirect reblog
|
||||
if let reblog = toot.reblog, let reblogEntity = entity.reblog {
|
||||
mergeToot(for: requestMastodonUser, old: reblog,in: domain, entity: reblogEntity, networkDate: networkDate)
|
||||
merge(toot: reblog, entity: reblogEntity, requestMastodonUser: requestMastodonUser, domain: domain, networkDate: networkDate)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension APIService.CoreData {
|
||||
static func merge(
|
||||
poll: Poll,
|
||||
entity: Mastodon.Entity.Poll,
|
||||
requestMastodonUser: MastodonUser?,
|
||||
domain: String,
|
||||
networkDate: Date
|
||||
) {
|
||||
poll.update(expiresAt: entity.expiresAt)
|
||||
poll.update(expired: entity.expired)
|
||||
poll.update(votesCount: entity.votesCount)
|
||||
poll.update(votersCount: entity.votersCount)
|
||||
|
||||
let oldOptions = poll.options.sorted(by: { $0.index.intValue < $1.index.intValue })
|
||||
for (i, (optionEntity, option)) in zip(entity.options, oldOptions).enumerated() {
|
||||
let votedBy: MastodonUser? = (entity.ownVotes ?? []).contains(i) ? requestMastodonUser : nil
|
||||
option.update(votesCount: optionEntity.votesCount)
|
||||
votedBy.flatMap { option.update(votedBy: $0) }
|
||||
option.didUpdate(at: networkDate)
|
||||
}
|
||||
|
||||
poll.didUpdate(at: networkDate)
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,20 @@ extension Mastodon.API.Favorites {
|
||||
return Mastodon.API.endpointURL(domain: domain).appendingPathComponent(pathComponent)
|
||||
}
|
||||
|
||||
/// Favourite / Undo Favourite
|
||||
///
|
||||
/// Add a status to your favourites list / Remove a status from your favourites list
|
||||
///
|
||||
/// # Last Update
|
||||
/// 2021/3/3
|
||||
/// # Reference
|
||||
/// [Document](https://docs.joinmastodon.org/methods/statuses/)
|
||||
/// - Parameters:
|
||||
/// - domain: Mastodon instance domain. e.g. "example.com"
|
||||
/// - statusID: Mastodon status id
|
||||
/// - session: `URLSession`
|
||||
/// - authorization: User token
|
||||
/// - Returns: `AnyPublisher` contains `Server` nested in the response
|
||||
public static func favorites(domain: String, statusID: String, session: URLSession, authorization: Mastodon.API.OAuth.Authorization, favoriteKind: FavoriteKind) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Status>, Error> {
|
||||
let url: URL = favoriteActionEndpointURL(domain: domain, statusID: statusID, favoriteKind: favoriteKind)
|
||||
var request = Mastodon.API.post(url: url, query: nil, authorization: authorization)
|
||||
@ -42,7 +56,21 @@ extension Mastodon.API.Favorites {
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
public static func getFavoriteByUserLists(domain: String, statusID: String, session: URLSession, authorization: Mastodon.API.OAuth.Authorization) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Account]>, Error> {
|
||||
/// Favourited by
|
||||
///
|
||||
/// View who favourited a given status.
|
||||
///
|
||||
/// # Last Update
|
||||
/// 2021/3/3
|
||||
/// # Reference
|
||||
/// [Document](https://docs.joinmastodon.org/methods/statuses/)
|
||||
/// - Parameters:
|
||||
/// - domain: Mastodon instance domain. e.g. "example.com"
|
||||
/// - statusID: Mastodon status id
|
||||
/// - session: `URLSession`
|
||||
/// - authorization: User token
|
||||
/// - Returns: `AnyPublisher` contains `Server` nested in the response
|
||||
public static func favoriteBy(domain: String, statusID: String, session: URLSession, authorization: Mastodon.API.OAuth.Authorization) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Account]>, Error> {
|
||||
let url = favoriteByUserListsEndpointURL(domain: domain, statusID: statusID)
|
||||
let request = Mastodon.API.get(url: url, query: nil, authorization: authorization)
|
||||
return session.dataTaskPublisher(for: request)
|
||||
@ -53,7 +81,20 @@ extension Mastodon.API.Favorites {
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
public static func getFavoriteStatus(domain: String, session: URLSession, authorization: Mastodon.API.OAuth.Authorization, query: Mastodon.API.Favorites.ListQuery) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Status]>, Error> {
|
||||
/// Favourited statuses
|
||||
///
|
||||
/// Using this endpoint to view the favourited list for user
|
||||
///
|
||||
/// # Last Update
|
||||
/// 2021/3/3
|
||||
/// # Reference
|
||||
/// [Document](https://docs.joinmastodon.org/methods/accounts/favourites/)
|
||||
/// - Parameters:
|
||||
/// - domain: Mastodon instance domain. e.g. "example.com"
|
||||
/// - session: `URLSession`
|
||||
/// - authorization: User token
|
||||
/// - Returns: `AnyPublisher` contains `Server` nested in the response
|
||||
public static func favoritedStatus(domain: String, session: URLSession, authorization: Mastodon.API.OAuth.Authorization, query: Mastodon.API.Favorites.ListQuery) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Status]>, Error> {
|
||||
let url = favoritesStatusesEndpointURL(domain: domain)
|
||||
let request = Mastodon.API.get(url: url, query: query, authorization: authorization)
|
||||
return session.dataTaskPublisher(for: request)
|
||||
|
51
MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Polls.swift
Normal file
51
MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Polls.swift
Normal file
@ -0,0 +1,51 @@
|
||||
//
|
||||
// Mastodon+API+Polls.swift
|
||||
//
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-3-3.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
extension Mastodon.API.Polls {
|
||||
|
||||
static func viewPollEndpointURL(domain: String, pollID: Mastodon.Entity.Poll.ID) -> URL {
|
||||
let pathComponent = "polls/" + pollID
|
||||
return Mastodon.API.endpointURL(domain: domain).appendingPathComponent(pathComponent)
|
||||
}
|
||||
|
||||
/// View a poll
|
||||
///
|
||||
/// Using this endpoint to view the poll of status
|
||||
///
|
||||
/// # Last Update
|
||||
/// 2021/3/3
|
||||
/// # Reference
|
||||
/// [Document](https://docs.joinmastodon.org/methods/statuses/polls/)
|
||||
/// - Parameters:
|
||||
/// - session: `URLSession`
|
||||
/// - domain: Mastodon instance domain. e.g. "example.com"
|
||||
/// - pollID: id for poll
|
||||
/// - authorization: User token. Could be nil if status is public
|
||||
/// - Returns: `AnyPublisher` contains `Server` nested in the response
|
||||
public static func poll(
|
||||
session: URLSession,
|
||||
domain: String,
|
||||
pollID: Mastodon.Entity.Poll.ID,
|
||||
authorization: Mastodon.API.OAuth.Authorization?
|
||||
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Poll>, Error> {
|
||||
let request = Mastodon.API.get(
|
||||
url: viewPollEndpointURL(domain: domain, pollID: pollID),
|
||||
query: nil,
|
||||
authorization: authorization
|
||||
)
|
||||
return session.dataTaskPublisher(for: request)
|
||||
.tryMap { data, response in
|
||||
let value = try Mastodon.API.decode(type: Mastodon.Entity.Poll.self, from: data, response: response)
|
||||
return Mastodon.Response.Content(value: value, response: response)
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
}
|
@ -53,13 +53,14 @@ extension Mastodon.API.Timeline {
|
||||
/// - Since: 0.0.0
|
||||
/// - Version: 3.3.0
|
||||
/// # Last Update
|
||||
/// 2021/2/19
|
||||
/// 2021/3/3
|
||||
/// # Reference
|
||||
/// [Document](https://https://docs.joinmastodon.org/methods/timelines/)
|
||||
/// - Parameters:
|
||||
/// - session: `URLSession`
|
||||
/// - domain: Mastodon instance domain. e.g. "example.com"
|
||||
/// - query: `PublicTimelineQuery` with query parameters
|
||||
/// - authorization: User token
|
||||
/// - Returns: `AnyPublisher` contains `Token` nested in the response
|
||||
public static func home(
|
||||
session: URLSession,
|
||||
|
@ -5,6 +5,7 @@
|
||||
// Created by xiaojian sun on 2021/1/25.
|
||||
//
|
||||
|
||||
import os.log
|
||||
import Foundation
|
||||
import enum NIOHTTP1.HTTPResponseStatus
|
||||
|
||||
@ -93,6 +94,7 @@ extension Mastodon.API {
|
||||
public enum Instance { }
|
||||
public enum OAuth { }
|
||||
public enum Onboarding { }
|
||||
public enum Polls { }
|
||||
public enum Timeline { }
|
||||
public enum Favorites { }
|
||||
}
|
||||
@ -155,6 +157,7 @@ extension Mastodon.API {
|
||||
return try Mastodon.API.decoder.decode(type, from: data)
|
||||
} catch let decodeError {
|
||||
#if DEBUG
|
||||
os_log(.info, "%{public}s[%{public}ld], %{public}s: decode fail. content %s", ((#file as NSString).lastPathComponent), #line, #function, String(data: data, encoding: .utf8) ?? "<nil>")
|
||||
debugPrint(decodeError)
|
||||
#endif
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user