Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CommunityModel improvements #729

Merged
merged 10 commits into from
Oct 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 21 additions & 13 deletions Mlem.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
03EEEAF32AB8DCDF0087F8D8 /* CommunityResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03EEEAF22AB8DCDF0087F8D8 /* CommunityResultView.swift */; };
03EEEAF72AB8ED3C0087F8D8 /* SearchTabPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03EEEAF62AB8ED3C0087F8D8 /* SearchTabPicker.swift */; };
03EEEAF92ABB985D0087F8D8 /* CommunityModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03EEEAF82ABB985D0087F8D8 /* CommunityModel.swift */; };
03FD64FF2AE53D0E00957AA9 /* CommunityModel+ContentModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03FD64FE2AE53D0E00957AA9 /* CommunityModel+ContentModel.swift */; };
500C168E2A66FAAB006F243B /* HapticManager+Dependency.swift in Sources */ = {isa = PBXBuildFile; fileRef = 500C168D2A66FAAB006F243B /* HapticManager+Dependency.swift */; };
5016A2B12A67EB8600B257E8 /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5016A2B02A67EB8600B257E8 /* UIViewController.swift */; };
5016A2B32A67EC0700B257E8 /* NotificationDisplayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5016A2B22A67EC0700B257E8 /* NotificationDisplayer.swift */; };
Expand Down Expand Up @@ -243,7 +244,6 @@
63D24EDC2A169F12005CCA81 /* MarkdownUI in Frameworks */ = {isa = PBXBuildFile; productRef = 63D24EDB2A169F12005CCA81 /* MarkdownUI */; };
63D24EDE2A169F2A005CCA81 /* Markdown View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63D24EDD2A169F2A005CCA81 /* Markdown View.swift */; };
63DF71F12A02999C002AC14E /* App Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63DF71F02A02999C002AC14E /* App Constants.swift */; };
63E5D38D2A13BCDE00EC1FBD /* Community Search Result Tracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63E5D38C2A13BCDE00EC1FBD /* Community Search Result Tracker.swift */; };
63E5D3922A13CF2300EC1FBD /* Favorite Community Tracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63E5D3912A13CF2300EC1FBD /* Favorite Community Tracker.swift */; };
63E5D3942A13CF3600EC1FBD /* Favorite Community.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63E5D3932A13CF3600EC1FBD /* Favorite Community.swift */; };
63F0C7A22A0519BA00A18C5D /* PostSortType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63F0C7A12A0519BA00A18C5D /* PostSortType.swift */; };
Expand Down Expand Up @@ -550,6 +550,7 @@
03EEEAF22AB8DCDF0087F8D8 /* CommunityResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommunityResultView.swift; sourceTree = "<group>"; };
03EEEAF62AB8ED3C0087F8D8 /* SearchTabPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTabPicker.swift; sourceTree = "<group>"; };
03EEEAF82ABB985D0087F8D8 /* CommunityModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommunityModel.swift; sourceTree = "<group>"; };
03FD64FE2AE53D0E00957AA9 /* CommunityModel+ContentModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CommunityModel+ContentModel.swift"; sourceTree = "<group>"; };
500C168D2A66FAAB006F243B /* HapticManager+Dependency.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HapticManager+Dependency.swift"; sourceTree = "<group>"; };
5016A2B02A67EB8600B257E8 /* UIViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewController.swift; sourceTree = "<group>"; };
5016A2B22A67EC0700B257E8 /* NotificationDisplayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationDisplayer.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -729,7 +730,6 @@
63D24ED82A169A5F005CCA81 /* UIApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIApplication.swift; sourceTree = "<group>"; };
63D24EDD2A169F2A005CCA81 /* Markdown View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Markdown View.swift"; sourceTree = "<group>"; };
63DF71F02A02999C002AC14E /* App Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "App Constants.swift"; sourceTree = "<group>"; };
63E5D38C2A13BCDE00EC1FBD /* Community Search Result Tracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Community Search Result Tracker.swift"; sourceTree = "<group>"; };
63E5D3912A13CF2300EC1FBD /* Favorite Community Tracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Favorite Community Tracker.swift"; sourceTree = "<group>"; };
63E5D3932A13CF3600EC1FBD /* Favorite Community.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Favorite Community.swift"; sourceTree = "<group>"; };
63F0C7A12A0519BA00A18C5D /* PostSortType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostSortType.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1088,22 +1088,22 @@
path = User;
sourceTree = "<group>";
};
030D00862AD1BB0D00953B1D /* User */ = {
030D00832AD0842900953B1D /* Results */ = {
isa = PBXGroup;
children = (
03B7AAF22ABEF85200068B23 /* UserModel.swift */,
030D00872AD1BB2600953B1D /* UserModel+ContentModel.swift */,
03EEEAF22AB8DCDF0087F8D8 /* CommunityResultView.swift */,
03B7AAF42ABEFA7A00068B23 /* UserResultView.swift */,
);
path = User;
path = Results;
sourceTree = "<group>";
};
030D00832AD0842900953B1D /* Results */ = {
030D00862AD1BB0D00953B1D /* User */ = {
isa = PBXGroup;
children = (
03EEEAF22AB8DCDF0087F8D8 /* CommunityResultView.swift */,
03B7AAF42ABEFA7A00068B23 /* UserResultView.swift */,
03B7AAF22ABEF85200068B23 /* UserModel.swift */,
030D00872AD1BB2600953B1D /* UserModel+ContentModel.swift */,
);
path = Results;
path = User;
sourceTree = "<group>";
};
030E86422AC6F6CB000283A6 /* Search Bar */ = {
Expand Down Expand Up @@ -1189,6 +1189,15 @@
path = TabBar;
sourceTree = "<group>";
};
03FD64FD2AE538C600957AA9 /* Community */ = {
isa = PBXGroup;
children = (
03EEEAF82ABB985D0087F8D8 /* CommunityModel.swift */,
03FD64FE2AE53D0E00957AA9 /* CommunityModel+ContentModel.swift */,
);
path = Community;
sourceTree = "<group>";
};
504ECBA82AB27C4C006C0B96 /* Onboarding */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1876,7 +1885,6 @@
CDF8425F2A49EA2A00723DA0 /* Inbox */,
6386E02E2A03ED39006B3C1D /* Comment Tracker.swift */,
63344C4E2A07BD2A001BC616 /* Filters Tracker.swift */,
63E5D38C2A13BCDE00EC1FBD /* Community Search Result Tracker.swift */,
63E5D3912A13CF2300EC1FBD /* Favorite Community Tracker.swift */,
63F0C7A72A0522FC00A18C5D /* Saved Account Tracker.swift */,
6DA61F862A5720EA001EA633 /* RecentSearchesTracker.swift */,
Expand Down Expand Up @@ -2350,7 +2358,7 @@
children = (
CDEBC3272A9A57F200518D9D /* Content Model Identifier.swift */,
CDEBC3292A9A580B00518D9D /* Post Model.swift */,
03EEEAF82ABB985D0087F8D8 /* CommunityModel.swift */,
03FD64FD2AE538C600957AA9 /* Community */,
030D00862AD1BB0D00953B1D /* User */,
03B7AAF02ABE404300068B23 /* ContentModel.swift */,
CDEBC32B2A9A582500518D9D /* Votes Model.swift */,
Expand Down Expand Up @@ -2979,7 +2987,6 @@
03B643572A6864CD00F65700 /* TabBarSettingsView.swift in Sources */,
CDF842642A49EAFA00723DA0 /* GetPersonMentions.swift in Sources */,
6D405B052A43E82300C65F9C /* Sidebar Header.swift in Sources */,
63E5D38D2A13BCDE00EC1FBD /* Community Search Result Tracker.swift in Sources */,
50811B302A92049B006BA3F2 /* APICommunityView+Mock.swift in Sources */,
CDA217F32A63202600BDA173 /* NSFW Overlay.swift in Sources */,
6372184E2A3A2AAD008C4816 /* APIPersonView.swift in Sources */,
Expand Down Expand Up @@ -3062,6 +3069,7 @@
63E5D3922A13CF2300EC1FBD /* Favorite Community Tracker.swift in Sources */,
B1B78D642A51D53900F72485 /* AppDelegate.swift in Sources */,
03A1B3F42A83F46200AB0DE0 /* ShareButtonView.swift in Sources */,
03FD64FF2AE53D0E00957AA9 /* CommunityModel+ContentModel.swift in Sources */,
6DA61F812A55B83F001EA633 /* SearchView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
13 changes: 2 additions & 11 deletions Mlem/Extensions/View - Handle Lemmy Links.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,28 +25,19 @@ struct HandleLemmyLinksDisplay: ViewModifier {
content
.navigationDestination(for: AppRoute.self) { route in
switch route {
case .apiCommunity(let community):
case .community(let community):
FeedView(community: community, feedType: .all, sortType: defaultPostSorting)
.environmentObject(appState)
.environmentObject(filtersTracker)
.environmentObject(CommunitySearchResultsTracker())
case .apiCommunityView(let context):
FeedView(community: context.community, feedType: .all, sortType: defaultPostSorting)
.environmentObject(appState)
.environmentObject(filtersTracker)
.environmentObject(CommunitySearchResultsTracker())
case .communityLinkWithContext(let context):
FeedView(community: context.community, feedType: context.feedType, sortType: defaultPostSorting)
.environmentObject(appState)
.environmentObject(filtersTracker)
.environmentObject(CommunitySearchResultsTracker())
case .communitySidebarLinkWithContext(let context):
CommunitySidebarView(
community: context.community,
communityDetails: context.communityDetails
community: context.community
)
.environmentObject(filtersTracker)
.environmentObject(CommunitySearchResultsTracker())
case .apiPostView(let post):
let postModel = PostModel(from: post)
let postTracker = PostTracker(
Expand Down
27 changes: 20 additions & 7 deletions Mlem/Models/Composers/PostEditor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,36 @@ import Foundation
import SwiftUI

struct PostEditorModel: Identifiable {
var id: Int { community.id }
var id: Int { community.communityId }

let community: APICommunity
let postTracker: PostTracker
let community: CommunityModel
var postTracker: PostTracker!
let editPost: PostModel?
var responseCallback: ((PostModel) -> Void)?

init(
community: APICommunity,
community: CommunityModel,
postTracker: PostTracker? = nil,
editPost: PostModel? = nil,
responseCallback: ((PostModel) -> Void)? = nil
) {
self.community = community
self.editPost = editPost
self.editPost = nil
self.responseCallback = responseCallback

self.initialiseTracker(postTracker)
}

init(
post: PostModel,
postTracker: PostTracker? = nil,
responseCallback: ((PostModel) -> Void)? = nil
) {
self.editPost = post
self.community = post.community
self.responseCallback = responseCallback
self.initialiseTracker(postTracker)
}

private mutating func initialiseTracker(_ postTracker: PostTracker?) {
@AppStorage("upvoteOnSave") var upvoteOnSave = false
if let postTracker {
self.postTracker = postTracker
Expand Down
19 changes: 19 additions & 0 deletions Mlem/Models/Content/Community/CommunityModel+ContentModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// CommunityModel+ContentModel.swift
// Mlem
//
// Created by Sjmarf on 22/10/2023.
//

import Foundation

extension CommunityModel: ContentModel {
var uid: ContentModelIdentifier { .init(contentType: .community, contentId: communityId) }
var imageUrls: [URL] {
if let url = avatar {
return [url.withIcon64Parameters]
}
return []
}
var searchResultScore: Int { self.subscriberCount ?? 0 }
}
182 changes: 182 additions & 0 deletions Mlem/Models/Content/Community/CommunityModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
//
// CommunityModel.swift
// Mlem
//
// Created by Sjmarf on 20/09/2023.
//

import Dependencies
import Foundation

struct CommunityModel {
@Dependency(\.apiClient) private var apiClient
@Dependency(\.errorHandler) var errorHandler
@Dependency(\.hapticManager) var hapticManager
@Dependency(\.communityRepository) var communityRepository

enum CommunityError: Error {
case noData
}

@available(*, deprecated, message: "Use attributes of the CommunityModel directly instead.")
var community: APICommunity

// Ids
let communityId: Int
let instanceId: Int

Sjmarf marked this conversation as resolved.
Show resolved Hide resolved
// Text
let name: String
let displayName: String
let description: String?

// Images
let avatar: URL?
let banner: URL?

// State
var nsfw: Bool
var local: Bool
var removed: Bool
var deleted: Bool
var hidden: Bool
var postingRestrictedToMods: Bool

// From APICommunityView
var blocked: Bool?
var subscribed: Bool?
var subscriberCount: Int?
Sjmarf marked this conversation as resolved.
Show resolved Hide resolved

// Dates
let creationDate: Date
let updatedDate: Date?

// URLs
let communityUrl: URL

// These values are only available via GetCommunityResponse
var site: APISite?
var moderators: [APICommunityModeratorView]?
var discussionLanguages: [Int]?
Sjmarf marked this conversation as resolved.
Show resolved Hide resolved
var defaultPostLanguage: Int?

init(from response: GetCommunityResponse) {
self.init(from: response.communityView)
self.site = response.site
self.moderators = response.moderators
self.discussionLanguages = response.discussionLanguages
self.defaultPostLanguage = response.defaultPostLanguage
}

init(from response: CommunityResponse) {
self.init(from: response.communityView)
self.discussionLanguages = response.discussionLanguages
}

init(from communityView: APICommunityView) {
self.init(from: communityView.community)
self.subscriberCount = communityView.counts.subscribers
self.subscribed = communityView.subscribed != .notSubscribed ? true : false
self.blocked = communityView.blocked
}

init(from community: APICommunity) {
self.community = community

self.communityId = community.id
self.instanceId = community.instanceId

self.name = community.name
self.displayName = community.title
self.description = community.description

self.avatar = community.iconUrl
self.banner = community.bannerUrl

self.nsfw = community.nsfw
self.local = community.local
self.removed = community.removed
self.deleted = community.deleted
self.hidden = community.hidden
self.postingRestrictedToMods = community.postingRestrictedToMods

self.creationDate = community.published
self.updatedDate = community.updated

self.communityUrl = community.actorId
}

mutating func toggleSubscribe(_ callback: @escaping (_ item: Self) -> Void = { _ in }) async throws {
guard let subscribed, let subscriberCount else {
throw CommunityError.noData
}
self.subscribed = !subscribed
if subscribed {
self.subscriberCount = subscriberCount + 1
} else {
self.subscriberCount = subscriberCount - 1
}
RunLoop.main.perform { [self] in
callback(self)
}
do {
let response = try await apiClient.followCommunity(id: communityId, shouldFollow: !subscribed)
RunLoop.main.perform {
callback(CommunityModel(from: response))
}
} catch {
hapticManager.play(haptic: .failure, priority: .high)
let phrase = (self.subscribed ?? false) ? "unsubscribe from" : "subscribe to"
errorHandler.handle(
.init(title: "Failed to \(phrase) community", style: .toast, underlyingError: error)
)
}
}

mutating func toggleBlock(_ callback: @escaping (_ item: Self) -> Void = { _ in }) async throws {
guard let blocked else {
throw CommunityError.noData
}
self.blocked = !blocked
RunLoop.main.perform { [self] in
callback(self)
}
do {
let response: BlockCommunityResponse
if !blocked {
response = try await communityRepository.blockCommunity(id: communityId)
} else {
response = try await communityRepository.unblockCommunity(id: communityId)
}
RunLoop.main.perform {
callback(CommunityModel(from: response.communityView))
}
} catch {
hapticManager.play(haptic: .failure, priority: .high)
errorHandler.handle(error)
let phrase = !blocked ? "block" : "unblock"
errorHandler.handle(
.init(title: "Failed to \(phrase) community", style: .toast, underlyingError: error)
)
}
}
}

extension CommunityModel: Identifiable {
var id: Int { hashValue }
}

extension CommunityModel: Hashable {
static func == (lhs: CommunityModel, rhs: CommunityModel) -> Bool {
return lhs.hashValue == rhs.hashValue
}

/// Hashes all fields for which state changes should trigger view updates.
func hash(into hasher: inout Hasher) {
hasher.combine(uid)
hasher.combine(subscribed)
hasher.combine(subscriberCount)
hasher.combine(blocked)
hasher.combine(moderators?.map { $0.moderator.id } ?? [])
}
}
Loading