Skip to content

Commit

Permalink
Added search tab for communitiyes (fixes #88) (#228)
Browse files Browse the repository at this point in the history
  • Loading branch information
JakeShirley authored Jul 7, 2023
1 parent 4ef7a44 commit 19d7b6b
Show file tree
Hide file tree
Showing 14 changed files with 349 additions and 16 deletions.
20 changes: 20 additions & 0 deletions Mlem.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@
6D8F08FF2A4029AE003EB4FD /* Community List View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8F08FE2A4029AE003EB4FD /* Community List View.swift */; };
6D91D4552A415994006B8F9A /* CommunityListSidebarEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D91D4542A415994006B8F9A /* CommunityListSidebarEntry.swift */; };
6D91D4582A4159D8006B8F9A /* CommunityListRowViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D91D4572A4159D8006B8F9A /* CommunityListRowViews.swift */; };
6DA61F812A55B83F001EA633 /* Search View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA61F802A55B83F001EA633 /* Search View.swift */; };
6DA61F852A568F99001EA633 /* Int.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA61F842A568F99001EA633 /* Int.swift */; };
6DA61F872A5720EA001EA633 /* RecentSearchesTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA61F862A5720EA001EA633 /* RecentSearchesTracker.swift */; };
6DA61F892A575DF1001EA633 /* URL - Lemmy Image Parameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA61F882A575DF1001EA633 /* URL - Lemmy Image Parameters.swift */; };
6DA7E9A22A50764E0095AB68 /* UserViewTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA7E9A12A50764E0095AB68 /* UserViewTab.swift */; };
6DCE71292A53C26600CFEB5E /* ServerInstanceLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DCE71282A53C26600CFEB5E /* ServerInstanceLocation.swift */; };
Expand Down Expand Up @@ -473,6 +476,9 @@
6D8F08FE2A4029AE003EB4FD /* Community List View.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Community List View.swift"; sourceTree = "<group>"; };
6D91D4542A415994006B8F9A /* CommunityListSidebarEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommunityListSidebarEntry.swift; sourceTree = "<group>"; };
6D91D4572A4159D8006B8F9A /* CommunityListRowViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommunityListRowViews.swift; sourceTree = "<group>"; };
6DA61F802A55B83F001EA633 /* Search View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Search View.swift"; sourceTree = "<group>"; };
6DA61F842A568F99001EA633 /* Int.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Int.swift; sourceTree = "<group>"; };
6DA61F862A5720EA001EA633 /* RecentSearchesTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentSearchesTracker.swift; sourceTree = "<group>"; };
6DA61F882A575DF1001EA633 /* URL - Lemmy Image Parameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL - Lemmy Image Parameters.swift"; sourceTree = "<group>"; };
6DA7E9A12A50764E0095AB68 /* UserViewTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserViewTab.swift; sourceTree = "<group>"; };
6DCE71282A53C26600CFEB5E /* ServerInstanceLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerInstanceLocation.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -762,6 +768,7 @@
B1A26FE02A44AAB200B91A32 /* Navigation getter.swift */,
B1A26FE22A45B11800B91A32 /* View - Handle Lemmy Links.swift */,
B1CB6E742A4C729D00DA9675 /* Bundle - Current App Icon.swift */,
6DA61F842A568F99001EA633 /* Int.swift */,
6DA61F882A575DF1001EA633 /* URL - Lemmy Image Parameters.swift */,
);
path = Extensions;
Expand Down Expand Up @@ -944,6 +951,7 @@
6363D5F427EE1BAE00E34822 /* Tabs */ = {
isa = PBXGroup;
children = (
6DA61F7F2A55B831001EA633 /* Search */,
6DE1183A2A4A215F00810C7E /* Profile */,
6DFF50412A48DEC0001E648D /* Inbox */,
6363D5F627EE1BBC00E34822 /* Settings */,
Expand Down Expand Up @@ -1194,6 +1202,7 @@
63F0C7A72A0522FC00A18C5D /* Saved Account Tracker.swift */,
631711562A27C70E00FE08C4 /* Comment Reply Tracker.swift */,
63AF452C2A2F37EB00E0266F /* Own Account Tracker.swift */,
6DA61F862A5720EA001EA633 /* RecentSearchesTracker.swift */,
);
path = Trackers;
sourceTree = "<group>";
Expand Down Expand Up @@ -1295,6 +1304,14 @@
path = Components;
sourceTree = "<group>";
};
6DA61F7F2A55B831001EA633 /* Search */ = {
isa = PBXGroup;
children = (
6DA61F802A55B83F001EA633 /* Search View.swift */,
);
path = Search;
sourceTree = "<group>";
};
6DA7E9A02A50763B0095AB68 /* User */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1790,6 +1807,7 @@
637218692A3A2AAD008C4816 /* CreateCommentLike.swift in Sources */,
B157E0C42A507B8000B02C8B /* Window.swift in Sources */,
6372185A2A3A2AAD008C4816 /* APISubscribedStatus.swift in Sources */,
6DA61F872A5720EA001EA633 /* RecentSearchesTracker.swift in Sources */,
CD3FBCD32A4A4B8B00B2063F /* Messages Tracker.swift in Sources */,
637218762A3A2AAD008C4816 /* BlockCommunity.swift in Sources */,
CD69F5752A42479A0028D4F7 /* Comment Item Logic.swift in Sources */,
Expand Down Expand Up @@ -1874,6 +1892,7 @@
6D479FD32A4F1F2D00125C2F /* Block Community Button.swift in Sources */,
CD04D5E72A3636FB008EF95B /* Compact Post.swift in Sources */,
6386E0342A042C44006B3C1D /* Contributors View.swift in Sources */,
6DA61F852A568F99001EA633 /* Int.swift in Sources */,
6363D5C527EE196700E34822 /* MlemApp.swift in Sources */,
637218542A3A2AAD008C4816 /* APILanguage.swift in Sources */,
6372186E2A3A2AAD008C4816 /* DeleteComment.swift in Sources */,
Expand All @@ -1890,6 +1909,7 @@
63F0C7B92A0533C700A18C5D /* Add Account View.swift in Sources */,
63E5D3922A13CF2300EC1FBD /* Favorite Community Tracker.swift in Sources */,
B1B78D642A51D53900F72485 /* AppDelegate.swift in Sources */,
6DA61F812A55B83F001EA633 /* Search View.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
6 changes: 5 additions & 1 deletion Mlem/API/Models/Community/APICommunityView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ struct APICommunityView: Decodable {
let counts: APICommunityAggregates
}

extension APICommunityView: Hashable, Equatable {
extension APICommunityView: Hashable, Equatable, Identifiable {
var id: Int {
return self.community.id
}

static func == (lhs: APICommunityView, rhs: APICommunityView) -> Bool {
return lhs.community.id == rhs.community.id
}
Expand Down
4 changes: 4 additions & 0 deletions Mlem/App Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ struct AppConstants {
static let favoriteCommunitiesFilePath = { applicationSupportDirectoryPath
.appendingPathComponent("Favorite Communities", conformingTo: .json)
}()

static let recentSearchesFilePath = { applicationSupportDirectoryPath
.appendingPathComponent("Recent Searches", conformingTo: .json)
}()

// MARK: - Haptics
static let hapticManager: UINotificationFeedbackGenerator = UINotificationFeedbackGenerator()
Expand Down
8 changes: 7 additions & 1 deletion Mlem/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,19 @@ struct ContentView: View {
Label(computeUsername(account: currentActiveAccount), systemImage: "person.circle")
.environment(\.symbolVariants, tabSelection == 3 ? .fill : .none)
}.tag(3)

NavigationView {
SearchView(account: currentActiveAccount)
} .tabItem {
Label("Search", systemImage: tabSelection == 4 ? "text.magnifyingglass" : "magnifyingglass")
}.tag(4)
}

SettingsView()
.tabItem {
Label("Settings", systemImage: "gear")
.environment(\.symbolVariants, tabSelection == 4 ? .fill : .none)
}.tag(4)
}.tag(5)
}
.onAppear {
if appState.currentActiveAccount == nil,
Expand Down
23 changes: 23 additions & 0 deletions Mlem/Extensions/Int.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// Int.swift
// Mlem
//
// Created by Jake Shirley on 7/5/23.
//

import Foundation

extension Int {
var roundedWithAbbreviations: String {
let number = Double(self)
let thousand = number / 1000
let million = number / 1000000
if million >= 1.0 {
return "\(round(million*10)/10)m"
} else if thousand >= 1.0 {
return "\(round(thousand*10)/10)k"
} else {
return self.description
}
}
}
4 changes: 3 additions & 1 deletion Mlem/Logic/File Management/Decode Data from File.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ internal enum DecodingError: Error {
}

internal enum WhatToDecode {
case accounts, filteredKeywords, favoriteCommunities
case accounts, filteredKeywords, favoriteCommunities, recentSearches
}

func decodeFromFile(fromURL: URL, whatToDecode: WhatToDecode) throws -> any Codable {
Expand All @@ -27,6 +27,8 @@ func decodeFromFile(fromURL: URL, whatToDecode: WhatToDecode) throws -> any Coda
return try JSONDecoder().decode([String].self, from: rawData)
case .favoriteCommunities:
return try JSONDecoder().decode([FavoriteCommunity].self, from: rawData)
case .recentSearches:
return try JSONDecoder().decode([String].self, from: rawData)
}
} catch let decodingError {
print("Failed to decode loaded data: \(decodingError.localizedDescription)")
Expand Down
2 changes: 1 addition & 1 deletion Mlem/Models/Trackers/Community Search Result Tracker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
import Foundation

class CommunitySearchResultsTracker: ObservableObject {
@Published var foundCommunities: [APICommunity] = .init()
@Published var foundCommunities: [APICommunityView] = .init()
@Published var isLoading: Bool = false
}
77 changes: 77 additions & 0 deletions Mlem/Models/Trackers/RecentSearchesTracker.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//
// RecentSearchesTracker.swift
// Mlem
//
// Created by Jake Shirley on 7/6/23.
//

import Foundation

class RecentSearchesTracker: ObservableObject {
@Published var recentSearches: [String] = .init()

init() {
loadFromDisk()
}

func loadFromDisk() {
if FileManager.default.fileExists(atPath: AppConstants.recentSearchesFilePath.path) {
print("Favorite communities file exists, will attempt to load favorite communities")
do {
recentSearches = try decodeFromFile(
fromURL: AppConstants.recentSearchesFilePath,
whatToDecode: .recentSearches
) as? [String] ?? []
} catch let decodingError {
print("Failed while decoding recent searches, erasing file: \(decodingError)")
}
} else {
print("Recent searches file does not exist, will try to create it")

do {
try createEmptyFile(at: AppConstants.recentSearchesFilePath)
} catch let emptyFileCreationError {
print("Failed while creating empty file: \(emptyFileCreationError)")
}
}
}

// Lazy save in the background
func saveToDisk() {
Task(priority: .background) { [recentSearches] in
do {
let encodedSearches: Data = try encodeForSaving(object: recentSearches)

do {
try writeDataToFile(data: encodedSearches, fileURL: AppConstants.recentSearchesFilePath)
} catch let writingError {
print("Failed while saving data to file: \(writingError)")
clearRecentSearches()
}
} catch let encodingError {
print("Failed while encoding recent searches to data: \(encodingError)")
}
}
}

func addRecentSearch(_ searchText: String) {
// don't insert duplicates
guard !recentSearches.contains(searchText) else {
return
}

recentSearches.insert(searchText, at: 0)

// Limit results to 5
while recentSearches.count > 5 {
recentSearches.remove(at: 5)
}

saveToDisk()
}

func clearRecentSearches() {
recentSearches = []
saveToDisk()
}
}
20 changes: 15 additions & 5 deletions Mlem/Views/Shared/Links/Community Link View.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,32 @@ func shouldClipAvatar(url: URL?) -> Bool {
struct CommunityLinkView: View {
let community: APICommunity
let serverInstanceLocation: ServerInstanceLocation
let extraText: String?
let overrideShowAvatar: Bool? // if present, shows or hides avatar according to value; otherwise uses system setting

init(community: APICommunity,
serverInstanceLocation: ServerInstanceLocation = .bottom,
overrideShowAvatar: Bool? = nil) {
overrideShowAvatar: Bool? = nil,
extraText: String? = nil
) {
self.community = community
self.serverInstanceLocation = serverInstanceLocation
self.extraText = extraText
self.overrideShowAvatar = overrideShowAvatar
}

var body: some View {
NavigationLink(value: community) {
CommunityLabel(community: community,
serverInstanceLocation: serverInstanceLocation,
overrideShowAvatar: overrideShowAvatar
)
HStack {
CommunityLabel(community: community,
serverInstanceLocation: serverInstanceLocation,
overrideShowAvatar: overrideShowAvatar
)
Spacer()
if let text = extraText {
Text(text)
}
}
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion Mlem/Views/Shared/Loading View.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import SwiftUI

struct LoadingView: View {
enum PossibleThingsToLoad {
case posts, image, comments, inbox, replies, mentions, messages, communityDetails
case posts, image, comments, inbox, replies, mentions, messages, communityDetails, search
}

let whatIsLoading: PossibleThingsToLoad
Expand Down Expand Up @@ -37,6 +37,8 @@ struct LoadingView: View {
Text("Loading messages")
case .communityDetails:
Text("Loading community details")
case .search:
Text("Searching...")
}

Spacer()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,16 +113,16 @@ struct CommunitySearchResultsView: View {
} else {
Section {
ForEach(communitySearchResultsTracker.foundCommunities) { foundCommunity in
NavigationLink(value: foundCommunity) {
communityNameView(for: foundCommunity)
NavigationLink(value: foundCommunity.community) {
communityNameView(for: foundCommunity.community)
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
// This is when a community is already favorited
if favoritedCommunitiesTracker.favoriteCommunities
.contains(where: { $0.community.id == foundCommunity.id }) {
Button(role: .destructive) {
unfavoriteCommunity(
account: account,
community: foundCommunity,
community: foundCommunity.community,
favoritedCommunitiesTracker: favoritedCommunitiesTracker
)
} label: {
Expand All @@ -132,7 +132,7 @@ struct CommunitySearchResultsView: View {
Button {
favoriteCommunity(
account: account,
community: foundCommunity,
community: foundCommunity.community,
favoritedCommunitiesTracker: favoritedCommunitiesTracker
)
} label: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,7 @@ struct CommunitySearchField: View {
)

let response = try await APIClient().perform(request: request)
let communities = response.communities.map { $0.community }
communitySearchResultsTracker.foundCommunities = communities
communitySearchResultsTracker.foundCommunities = response.communities
} catch {
print("Search command error: \(error)")
errorAlert = .init(
Expand Down
Loading

0 comments on commit 19d7b6b

Please sign in to comment.