mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2025-02-01 18:52:20 +00:00
Follow / Unfollow
This commit is contained in:
parent
8def548913
commit
60a963441c
7 changed files with 169 additions and 28 deletions
|
@ -382,8 +382,9 @@
|
|||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = IceCubesApp/IceCubesApp.entitlements;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 200;
|
||||
CURRENT_PROJECT_VERSION = 250;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"IceCubesApp/Resources\"";
|
||||
DEVELOPMENT_TEAM = Z6P74P6T99;
|
||||
|
@ -405,7 +406,7 @@
|
|||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 0.0.3;
|
||||
MARKETING_VERSION = 0.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.thomasricouard.IceCubesApp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
|
@ -425,8 +426,9 @@
|
|||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = IceCubesApp/IceCubesApp.entitlements;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 200;
|
||||
CURRENT_PROJECT_VERSION = 250;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"IceCubesApp/Resources\"";
|
||||
DEVELOPMENT_TEAM = Z6P74P6T99;
|
||||
|
@ -448,7 +450,7 @@
|
|||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 0.0.3;
|
||||
MARKETING_VERSION = 0.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.thomasricouard.IceCubesApp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
|
|
|
@ -7,7 +7,10 @@ struct AccountDetailHeaderView: View {
|
|||
@EnvironmentObject private var routeurPath: RouterPath
|
||||
@Environment(\.redactionReasons) private var reasons
|
||||
|
||||
let isCurrentUser: Bool
|
||||
let account: Account
|
||||
@Binding var relationship: Relationshionship?
|
||||
@Binding var following: Bool
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
|
@ -18,20 +21,31 @@ struct AccountDetailHeaderView: View {
|
|||
|
||||
private var headerImageView: some View {
|
||||
GeometryReader { proxy in
|
||||
AsyncImage(
|
||||
url: account.header,
|
||||
content: { image in
|
||||
image.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(height: 200)
|
||||
.frame(width: proxy.frame(in: .local).width)
|
||||
.clipped()
|
||||
},
|
||||
placeholder: {
|
||||
Color.gray
|
||||
.frame(height: 200)
|
||||
ZStack(alignment: .bottomTrailing) {
|
||||
AsyncImage(
|
||||
url: account.header,
|
||||
content: { image in
|
||||
image.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(height: 200)
|
||||
.frame(width: proxy.frame(in: .local).width)
|
||||
.clipped()
|
||||
},
|
||||
placeholder: {
|
||||
Color.gray
|
||||
.frame(height: 200)
|
||||
}
|
||||
)
|
||||
if relationship?.followedBy == true {
|
||||
Text("Follows You")
|
||||
.font(.footnote)
|
||||
.fontWeight(.semibold)
|
||||
.padding(4)
|
||||
.background(.ultraThinMaterial)
|
||||
.cornerRadius(4)
|
||||
.padding(8)
|
||||
}
|
||||
)
|
||||
}
|
||||
.background(Color.gray)
|
||||
}
|
||||
.frame(height: 200)
|
||||
|
@ -76,11 +90,27 @@ struct AccountDetailHeaderView: View {
|
|||
private var accountInfoView: some View {
|
||||
Group {
|
||||
accountAvatarView
|
||||
Text(account.displayName)
|
||||
.font(.headline)
|
||||
Text(account.acct)
|
||||
.font(.callout)
|
||||
.foregroundColor(.gray)
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text(account.displayName)
|
||||
.font(.headline)
|
||||
Text(account.acct)
|
||||
.font(.callout)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
Spacer()
|
||||
if relationship != nil && !isCurrentUser {
|
||||
Button {
|
||||
following.toggle()
|
||||
} label: {
|
||||
if relationship?.requested == true {
|
||||
Text("Requested")
|
||||
} else {
|
||||
Text(following ? "Following" : "Follow")
|
||||
}
|
||||
}.buttonStyle(.bordered)
|
||||
}
|
||||
}
|
||||
Text(account.note.asSafeAttributedString)
|
||||
.font(.body)
|
||||
.padding(.top, 8)
|
||||
|
@ -102,6 +132,9 @@ struct AccountDetailHeaderView: View {
|
|||
|
||||
struct AccountDetailHeaderView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
AccountDetailHeaderView(account: .placeholder())
|
||||
AccountDetailHeaderView(isCurrentUser: false,
|
||||
account: .placeholder(),
|
||||
relationship: .constant(.placeholder()),
|
||||
following: .constant(true))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,8 @@ public struct AccountDetailView: View {
|
|||
}
|
||||
|
||||
public init(account: Account, isCurrentUser: Bool = false) {
|
||||
_viewModel = StateObject(wrappedValue: .init(account: account))
|
||||
_viewModel = StateObject(wrappedValue: .init(account: account,
|
||||
isCurrentUser: isCurrentUser))
|
||||
self.isCurrentUser = isCurrentUser
|
||||
}
|
||||
|
||||
|
@ -54,14 +55,30 @@ public struct AccountDetailView: View {
|
|||
private var headerView: some View {
|
||||
switch viewModel.state {
|
||||
case .loading:
|
||||
AccountDetailHeaderView(account: .placeholder())
|
||||
AccountDetailHeaderView(isCurrentUser: isCurrentUser,
|
||||
account: .placeholder(),
|
||||
relationship: .constant(.placeholder()),
|
||||
following: .constant(false))
|
||||
.redacted(reason: .placeholder)
|
||||
case let .data(account):
|
||||
AccountDetailHeaderView(account: account)
|
||||
AccountDetailHeaderView(isCurrentUser: isCurrentUser,
|
||||
account: account,
|
||||
relationship: $viewModel.relationship,
|
||||
following:
|
||||
.init(get: {
|
||||
viewModel.relationship?.following ?? false
|
||||
}, set: { following in
|
||||
Task {
|
||||
if following {
|
||||
await viewModel.follow()
|
||||
} else {
|
||||
await viewModel.unfollow()
|
||||
}
|
||||
}
|
||||
}))
|
||||
case let .error(error):
|
||||
Text("Error: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,23 +16,32 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
|
|||
@Published var state: State = .loading
|
||||
@Published var statusesState: StatusesState = .loading
|
||||
@Published var title: String = ""
|
||||
@Published var relationship: Relationshionship?
|
||||
|
||||
private var account: Account?
|
||||
|
||||
private(set) var statuses: [Status] = []
|
||||
private let isCurrentUser: Bool
|
||||
|
||||
init(accountId: String) {
|
||||
self.accountId = accountId
|
||||
self.isCurrentUser = false
|
||||
}
|
||||
|
||||
init(account: Account) {
|
||||
init(account: Account, isCurrentUser: Bool) {
|
||||
self.accountId = account.id
|
||||
self.state = .data(account: account)
|
||||
self.isCurrentUser = isCurrentUser
|
||||
}
|
||||
|
||||
func fetchAccount() async {
|
||||
guard let client else { return }
|
||||
do {
|
||||
let account: Account = try await client.get(endpoint: Accounts.accounts(id: accountId))
|
||||
if !isCurrentUser {
|
||||
let relationships: [Relationshionship] = try await client.get(endpoint: Accounts.relationships(id: accountId))
|
||||
self.relationship = relationships.first
|
||||
}
|
||||
self.title = account.displayName
|
||||
state = .data(account: account)
|
||||
} catch {
|
||||
|
@ -63,4 +72,22 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
|
|||
statusesState = .error(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
func follow() async {
|
||||
guard let client else { return }
|
||||
do {
|
||||
relationship = try await client.post(endpoint: Accounts.follow(id: accountId))
|
||||
} catch {
|
||||
print("Error while following: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
func unfollow() async {
|
||||
guard let client else { return }
|
||||
do {
|
||||
relationship = try await client.post(endpoint: Accounts.unfollow(id: accountId))
|
||||
} catch {
|
||||
print("Error while unfollowing: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "1.000",
|
||||
"green" : "0.353",
|
||||
"red" : "0.349"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
31
Packages/Models/Sources/Models/Relationshionship.swift
Normal file
31
Packages/Models/Sources/Models/Relationshionship.swift
Normal file
|
@ -0,0 +1,31 @@
|
|||
import Foundation
|
||||
|
||||
public struct Relationshionship: Codable {
|
||||
public let id: String
|
||||
public let following: Bool
|
||||
public let showingReblogs: Bool
|
||||
public let followedBy: Bool
|
||||
public let blocking: Bool
|
||||
public let blockedBy: Bool
|
||||
public let muting: Bool
|
||||
public let mutingNotifications: Bool
|
||||
public let requested: Bool
|
||||
public let domainBlocking: Bool
|
||||
public let endorsed: Bool
|
||||
public let note: String
|
||||
|
||||
static public func placeholder() -> Relationshionship {
|
||||
.init(id: UUID().uuidString,
|
||||
following: false,
|
||||
showingReblogs: false,
|
||||
followedBy: false,
|
||||
blocking: false,
|
||||
blockedBy: false,
|
||||
muting: false,
|
||||
mutingNotifications: false,
|
||||
requested: false,
|
||||
domainBlocking: false,
|
||||
endorsed: false,
|
||||
note: "")
|
||||
}
|
||||
}
|
|
@ -4,6 +4,9 @@ public enum Accounts: Endpoint {
|
|||
case accounts(id: String)
|
||||
case verifyCredentials
|
||||
case statuses(id: String, sinceId: String?)
|
||||
case relationships(id: String)
|
||||
case follow(id: String)
|
||||
case unfollow(id: String)
|
||||
|
||||
public func path() -> String {
|
||||
switch self {
|
||||
|
@ -13,6 +16,12 @@ public enum Accounts: Endpoint {
|
|||
return "accounts/verify_credentials"
|
||||
case .statuses(let id, _):
|
||||
return "accounts/\(id)/statuses"
|
||||
case .relationships:
|
||||
return "accounts/relationships"
|
||||
case .follow(let id):
|
||||
return "accounts/\(id)/follow"
|
||||
case .unfollow(let id):
|
||||
return "accounts/\(id)/unfollow"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,6 +30,8 @@ public enum Accounts: Endpoint {
|
|||
case .statuses(_, let sinceId):
|
||||
guard let sinceId else { return nil }
|
||||
return [.init(name: "max_id", value: sinceId)]
|
||||
case let .relationships(id):
|
||||
return [.init(name: "id", value: id)]
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue