mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2025-02-09 16:08:47 +01:00
Follow / Unfollow
This commit is contained in:
parent
8def548913
commit
60a963441c
@ -382,8 +382,9 @@
|
|||||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = IceCubesApp/IceCubesApp.entitlements;
|
CODE_SIGN_ENTITLEMENTS = IceCubesApp/IceCubesApp.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "-";
|
CODE_SIGN_IDENTITY = "-";
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 200;
|
CURRENT_PROJECT_VERSION = 250;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"IceCubesApp/Resources\"";
|
DEVELOPMENT_ASSET_PATHS = "\"IceCubesApp/Resources\"";
|
||||||
DEVELOPMENT_TEAM = Z6P74P6T99;
|
DEVELOPMENT_TEAM = Z6P74P6T99;
|
||||||
@ -405,7 +406,7 @@
|
|||||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||||
MARKETING_VERSION = 0.0.3;
|
MARKETING_VERSION = 0.2;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.thomasricouard.IceCubesApp;
|
PRODUCT_BUNDLE_IDENTIFIER = com.thomasricouard.IceCubesApp;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = auto;
|
SDKROOT = auto;
|
||||||
@ -425,8 +426,9 @@
|
|||||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = IceCubesApp/IceCubesApp.entitlements;
|
CODE_SIGN_ENTITLEMENTS = IceCubesApp/IceCubesApp.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "-";
|
CODE_SIGN_IDENTITY = "-";
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 200;
|
CURRENT_PROJECT_VERSION = 250;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"IceCubesApp/Resources\"";
|
DEVELOPMENT_ASSET_PATHS = "\"IceCubesApp/Resources\"";
|
||||||
DEVELOPMENT_TEAM = Z6P74P6T99;
|
DEVELOPMENT_TEAM = Z6P74P6T99;
|
||||||
@ -448,7 +450,7 @@
|
|||||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||||
MARKETING_VERSION = 0.0.3;
|
MARKETING_VERSION = 0.2;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.thomasricouard.IceCubesApp;
|
PRODUCT_BUNDLE_IDENTIFIER = com.thomasricouard.IceCubesApp;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = auto;
|
SDKROOT = auto;
|
||||||
|
@ -7,7 +7,10 @@ struct AccountDetailHeaderView: View {
|
|||||||
@EnvironmentObject private var routeurPath: RouterPath
|
@EnvironmentObject private var routeurPath: RouterPath
|
||||||
@Environment(\.redactionReasons) private var reasons
|
@Environment(\.redactionReasons) private var reasons
|
||||||
|
|
||||||
|
let isCurrentUser: Bool
|
||||||
let account: Account
|
let account: Account
|
||||||
|
@Binding var relationship: Relationshionship?
|
||||||
|
@Binding var following: Bool
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
@ -18,20 +21,31 @@ struct AccountDetailHeaderView: View {
|
|||||||
|
|
||||||
private var headerImageView: some View {
|
private var headerImageView: some View {
|
||||||
GeometryReader { proxy in
|
GeometryReader { proxy in
|
||||||
AsyncImage(
|
ZStack(alignment: .bottomTrailing) {
|
||||||
url: account.header,
|
AsyncImage(
|
||||||
content: { image in
|
url: account.header,
|
||||||
image.resizable()
|
content: { image in
|
||||||
.aspectRatio(contentMode: .fill)
|
image.resizable()
|
||||||
.frame(height: 200)
|
.aspectRatio(contentMode: .fill)
|
||||||
.frame(width: proxy.frame(in: .local).width)
|
.frame(height: 200)
|
||||||
.clipped()
|
.frame(width: proxy.frame(in: .local).width)
|
||||||
},
|
.clipped()
|
||||||
placeholder: {
|
},
|
||||||
Color.gray
|
placeholder: {
|
||||||
.frame(height: 200)
|
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)
|
.background(Color.gray)
|
||||||
}
|
}
|
||||||
.frame(height: 200)
|
.frame(height: 200)
|
||||||
@ -76,11 +90,27 @@ struct AccountDetailHeaderView: View {
|
|||||||
private var accountInfoView: some View {
|
private var accountInfoView: some View {
|
||||||
Group {
|
Group {
|
||||||
accountAvatarView
|
accountAvatarView
|
||||||
Text(account.displayName)
|
HStack {
|
||||||
.font(.headline)
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
Text(account.acct)
|
Text(account.displayName)
|
||||||
.font(.callout)
|
.font(.headline)
|
||||||
.foregroundColor(.gray)
|
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)
|
Text(account.note.asSafeAttributedString)
|
||||||
.font(.body)
|
.font(.body)
|
||||||
.padding(.top, 8)
|
.padding(.top, 8)
|
||||||
@ -102,6 +132,9 @@ struct AccountDetailHeaderView: View {
|
|||||||
|
|
||||||
struct AccountDetailHeaderView_Previews: PreviewProvider {
|
struct AccountDetailHeaderView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
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) {
|
public init(account: Account, isCurrentUser: Bool = false) {
|
||||||
_viewModel = StateObject(wrappedValue: .init(account: account))
|
_viewModel = StateObject(wrappedValue: .init(account: account,
|
||||||
|
isCurrentUser: isCurrentUser))
|
||||||
self.isCurrentUser = isCurrentUser
|
self.isCurrentUser = isCurrentUser
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,14 +55,30 @@ public struct AccountDetailView: View {
|
|||||||
private var headerView: some View {
|
private var headerView: some View {
|
||||||
switch viewModel.state {
|
switch viewModel.state {
|
||||||
case .loading:
|
case .loading:
|
||||||
AccountDetailHeaderView(account: .placeholder())
|
AccountDetailHeaderView(isCurrentUser: isCurrentUser,
|
||||||
|
account: .placeholder(),
|
||||||
|
relationship: .constant(.placeholder()),
|
||||||
|
following: .constant(false))
|
||||||
.redacted(reason: .placeholder)
|
.redacted(reason: .placeholder)
|
||||||
case let .data(account):
|
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):
|
case let .error(error):
|
||||||
Text("Error: \(error.localizedDescription)")
|
Text("Error: \(error.localizedDescription)")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,23 +16,32 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
|
|||||||
@Published var state: State = .loading
|
@Published var state: State = .loading
|
||||||
@Published var statusesState: StatusesState = .loading
|
@Published var statusesState: StatusesState = .loading
|
||||||
@Published var title: String = ""
|
@Published var title: String = ""
|
||||||
|
@Published var relationship: Relationshionship?
|
||||||
|
|
||||||
private var account: Account?
|
private var account: Account?
|
||||||
|
|
||||||
private(set) var statuses: [Status] = []
|
private(set) var statuses: [Status] = []
|
||||||
|
private let isCurrentUser: Bool
|
||||||
|
|
||||||
init(accountId: String) {
|
init(accountId: String) {
|
||||||
self.accountId = accountId
|
self.accountId = accountId
|
||||||
|
self.isCurrentUser = false
|
||||||
}
|
}
|
||||||
|
|
||||||
init(account: Account) {
|
init(account: Account, isCurrentUser: Bool) {
|
||||||
self.accountId = account.id
|
self.accountId = account.id
|
||||||
self.state = .data(account: account)
|
self.state = .data(account: account)
|
||||||
|
self.isCurrentUser = isCurrentUser
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchAccount() async {
|
func fetchAccount() async {
|
||||||
guard let client else { return }
|
guard let client else { return }
|
||||||
do {
|
do {
|
||||||
let account: Account = try await client.get(endpoint: Accounts.accounts(id: accountId))
|
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
|
self.title = account.displayName
|
||||||
state = .data(account: account)
|
state = .data(account: account)
|
||||||
} catch {
|
} catch {
|
||||||
@ -63,4 +72,22 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
|
|||||||
statusesState = .error(error: error)
|
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 accounts(id: String)
|
||||||
case verifyCredentials
|
case verifyCredentials
|
||||||
case statuses(id: String, sinceId: String?)
|
case statuses(id: String, sinceId: String?)
|
||||||
|
case relationships(id: String)
|
||||||
|
case follow(id: String)
|
||||||
|
case unfollow(id: String)
|
||||||
|
|
||||||
public func path() -> String {
|
public func path() -> String {
|
||||||
switch self {
|
switch self {
|
||||||
@ -13,6 +16,12 @@ public enum Accounts: Endpoint {
|
|||||||
return "accounts/verify_credentials"
|
return "accounts/verify_credentials"
|
||||||
case .statuses(let id, _):
|
case .statuses(let id, _):
|
||||||
return "accounts/\(id)/statuses"
|
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):
|
case .statuses(_, let sinceId):
|
||||||
guard let sinceId else { return nil }
|
guard let sinceId else { return nil }
|
||||||
return [.init(name: "max_id", value: sinceId)]
|
return [.init(name: "max_id", value: sinceId)]
|
||||||
|
case let .relationships(id):
|
||||||
|
return [.init(name: "id", value: id)]
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user