mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2025-03-28 04:25:28 +00:00
Progress flow
This commit is contained in:
parent
409680e9b8
commit
a705a71c45
10 changed files with 72 additions and 17 deletions
|
@ -5,6 +5,7 @@ import Observation
|
||||||
import SafariServices
|
import SafariServices
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import AppAccount
|
import AppAccount
|
||||||
|
import WebKit
|
||||||
|
|
||||||
extension View {
|
extension View {
|
||||||
@MainActor func withSafariRouter() -> some View {
|
@MainActor func withSafariRouter() -> some View {
|
||||||
|
@ -19,6 +20,7 @@ private struct SafariRouter: ViewModifier {
|
||||||
@Environment(UserPreferences.self) private var preferences
|
@Environment(UserPreferences.self) private var preferences
|
||||||
@Environment(RouterPath.self) private var routerPath
|
@Environment(RouterPath.self) private var routerPath
|
||||||
@Environment(AppAccountsManager.self) private var appAccount
|
@Environment(AppAccountsManager.self) private var appAccount
|
||||||
|
@Environment(TipedUsers.self) private var tipedUsers
|
||||||
|
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
@State private var safariManager = InAppSafariManager()
|
@State private var safariManager = InAppSafariManager()
|
||||||
|
@ -34,8 +36,9 @@ private struct SafariRouter: ViewModifier {
|
||||||
.onOpenURL { url in
|
.onOpenURL { url in
|
||||||
// Open external URL (from icecubesapp://)
|
// Open external URL (from icecubesapp://)
|
||||||
guard !isSecondaryColumn else { return }
|
guard !isSecondaryColumn else { return }
|
||||||
if url.lastPathComponent == "socialproxy" {
|
if url.absoluteString == "icecubesapp://socialproxy" {
|
||||||
safariManager.dismiss()
|
safariManager.dismiss()
|
||||||
|
TipedUsers.shared.tipedUserCount += 1
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let urlString = url.absoluteString.replacingOccurrences(of: AppInfo.scheme, with: "https://")
|
let urlString = url.absoluteString.replacingOccurrences(of: AppInfo.scheme, with: "https://")
|
||||||
|
@ -55,8 +58,8 @@ private struct SafariRouter: ViewModifier {
|
||||||
}
|
}
|
||||||
} else if url.host() == "social-proxy.com", let accountName = appAccount.currentAccount.accountName {
|
} else if url.host() == "social-proxy.com", let accountName = appAccount.currentAccount.accountName {
|
||||||
let newURL = url.appending(queryItems: [
|
let newURL = url.appending(queryItems: [
|
||||||
.init(name: "callback", value: "icecubesapp"),
|
.init(name: "callback", value: "icecubesapp://socialproxy"),
|
||||||
.init(name: "id", value: accountName)
|
.init(name: "id", value: "@\(accountName)")
|
||||||
])
|
])
|
||||||
return safariManager.open(newURL)
|
return safariManager.open(newURL)
|
||||||
}
|
}
|
||||||
|
@ -116,6 +119,9 @@ private struct SafariRouter: ViewModifier {
|
||||||
|
|
||||||
func dismiss() {
|
func dismiss() {
|
||||||
viewController.presentedViewController?.dismiss(animated: true)
|
viewController.presentedViewController?.dismiss(animated: true)
|
||||||
|
window?.resignKey()
|
||||||
|
window?.isHidden = false
|
||||||
|
window = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupWindow(windowScene: UIWindowScene) -> UIWindow {
|
func setupWindow(windowScene: UIWindowScene) -> UIWindow {
|
||||||
|
|
|
@ -22,6 +22,7 @@ let package = Package(
|
||||||
.package(name: "StatusKit", path: "../StatusKit"),
|
.package(name: "StatusKit", path: "../StatusKit"),
|
||||||
.package(name: "Env", path: "../Env"),
|
.package(name: "Env", path: "../Env"),
|
||||||
.package(url: "https://github.com/Dean151/ButtonKit", from: "0.1.1"),
|
.package(url: "https://github.com/Dean151/ButtonKit", from: "0.1.1"),
|
||||||
|
.package(url: "https://github.com/dkk/WrappingHStack", from: "2.2.11"),
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
.target(
|
.target(
|
||||||
|
@ -32,6 +33,7 @@ let package = Package(
|
||||||
.product(name: "StatusKit", package: "StatusKit"),
|
.product(name: "StatusKit", package: "StatusKit"),
|
||||||
.product(name: "Env", package: "Env"),
|
.product(name: "Env", package: "Env"),
|
||||||
.product(name: "ButtonKit", package: "ButtonKit"),
|
.product(name: "ButtonKit", package: "ButtonKit"),
|
||||||
|
.product(name: "WrappingHStack", package: "WrappingHStack"),
|
||||||
],
|
],
|
||||||
swiftSettings: [
|
swiftSettings: [
|
||||||
.enableExperimentalFeature("StrictConcurrency"),
|
.enableExperimentalFeature("StrictConcurrency"),
|
||||||
|
|
|
@ -16,6 +16,7 @@ struct AccountDetailHeaderView: View {
|
||||||
@Environment(QuickLook.self) private var quickLook
|
@Environment(QuickLook.self) private var quickLook
|
||||||
@Environment(RouterPath.self) private var routerPath
|
@Environment(RouterPath.self) private var routerPath
|
||||||
@Environment(CurrentAccount.self) private var currentAccount
|
@Environment(CurrentAccount.self) private var currentAccount
|
||||||
|
@Environment(TipedUsers.self) private var tipedUsers
|
||||||
@Environment(\.redactionReasons) private var reasons
|
@Environment(\.redactionReasons) private var reasons
|
||||||
@Environment(\.isSupporter) private var isSupporter: Bool
|
@Environment(\.isSupporter) private var isSupporter: Bool
|
||||||
|
|
||||||
|
@ -45,6 +46,13 @@ struct AccountDetailHeaderView: View {
|
||||||
}
|
}
|
||||||
accountInfoView
|
accountInfoView
|
||||||
}
|
}
|
||||||
|
.onChange(of: tipedUsers.tipedUserCount, { _, _ in
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
|
||||||
|
Task {
|
||||||
|
try? await viewModel.followButtonViewModel?.refreshRelationship()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private var headerImageView: some View {
|
private var headerImageView: some View {
|
||||||
|
@ -211,20 +219,17 @@ struct AccountDetailHeaderView: View {
|
||||||
.accessibilityRespondsToUserInteraction(false)
|
.accessibilityRespondsToUserInteraction(false)
|
||||||
movedToView
|
movedToView
|
||||||
joinedAtView
|
joinedAtView
|
||||||
tipView
|
if viewModel.isProAccount {
|
||||||
|
tipView
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.accessibilityElement(children: .contain)
|
.accessibilityElement(children: .contain)
|
||||||
.accessibilitySortPriority(1)
|
.accessibilitySortPriority(1)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
if let relationship = viewModel.relationship, !viewModel.isCurrentUser {
|
if let followButtonViewModel = viewModel.followButtonViewModel, !viewModel.isCurrentUser {
|
||||||
HStack {
|
HStack {
|
||||||
FollowButton(viewModel: .init(accountId: account.id,
|
FollowButton(viewModel: followButtonViewModel)
|
||||||
relationship: relationship,
|
|
||||||
shouldDisplayNotify: true,
|
|
||||||
relationshipUpdated: { relationship in
|
|
||||||
viewModel.relationship = relationship
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
} else if !viewModel.isCurrentUser {
|
} else if !viewModel.isCurrentUser {
|
||||||
ProgressView()
|
ProgressView()
|
||||||
|
@ -319,6 +324,9 @@ struct AccountDetailHeaderView: View {
|
||||||
private var tipView: some View {
|
private var tipView: some View {
|
||||||
Button {
|
Button {
|
||||||
isTipSheetPresented = true
|
isTipSheetPresented = true
|
||||||
|
Task {
|
||||||
|
try? await viewModel.followButtonViewModel?.follow()
|
||||||
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Text("$ Send tip")
|
Text("$ Send tip")
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,6 +79,12 @@ import SwiftUI
|
||||||
|
|
||||||
var translation: Translation?
|
var translation: Translation?
|
||||||
var isLoadingTranslation = false
|
var isLoadingTranslation = false
|
||||||
|
|
||||||
|
var followButtonViewModel: FollowButtonViewModel?
|
||||||
|
|
||||||
|
var isProAccount: Bool {
|
||||||
|
account?.url?.host() == "social-proxy.com"
|
||||||
|
}
|
||||||
|
|
||||||
private(set) var account: Account?
|
private(set) var account: Account?
|
||||||
private var tabTask: Task<Void, Never>?
|
private var tabTask: Task<Void, Never>?
|
||||||
|
@ -117,6 +123,14 @@ import SwiftUI
|
||||||
featuredTags = data.featuredTags
|
featuredTags = data.featuredTags
|
||||||
featuredTags.sort { $0.statusesCountInt > $1.statusesCountInt }
|
featuredTags.sort { $0.statusesCountInt > $1.statusesCountInt }
|
||||||
relationship = data.relationships.first
|
relationship = data.relationships.first
|
||||||
|
if let relationship {
|
||||||
|
followButtonViewModel = .init(accountId: accountId,
|
||||||
|
relationship: relationship,
|
||||||
|
shouldDisplayNotify: true,
|
||||||
|
relationshipUpdated: { [weak self] relationship in
|
||||||
|
self?.relationship = relationship
|
||||||
|
})
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
if let account {
|
if let account {
|
||||||
accountState = .data(account: account)
|
accountState = .data(account: account)
|
||||||
|
|
|
@ -30,7 +30,8 @@ import SwiftUI
|
||||||
func follow() async throws {
|
func follow() async throws {
|
||||||
guard let client else { return }
|
guard let client else { return }
|
||||||
do {
|
do {
|
||||||
relationship = try await client.post(endpoint: Accounts.follow(id: accountId, notify: false, reblogs: true))
|
_ = try await client.post(endpoint: Accounts.follow(id: accountId, notify: false, reblogs: true))
|
||||||
|
try await refreshRelationship()
|
||||||
relationshipUpdated(relationship)
|
relationshipUpdated(relationship)
|
||||||
} catch {
|
} catch {
|
||||||
throw error
|
throw error
|
||||||
|
@ -40,12 +41,21 @@ import SwiftUI
|
||||||
func unfollow() async throws {
|
func unfollow() async throws {
|
||||||
guard let client else { return }
|
guard let client else { return }
|
||||||
do {
|
do {
|
||||||
relationship = try await client.post(endpoint: Accounts.unfollow(id: accountId))
|
_ = try await client.post(endpoint: Accounts.unfollow(id: accountId))
|
||||||
|
try await refreshRelationship()
|
||||||
relationshipUpdated(relationship)
|
relationshipUpdated(relationship)
|
||||||
} catch {
|
} catch {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func refreshRelationship() async throws {
|
||||||
|
guard let client else { return }
|
||||||
|
let relationships: [Relationship] = try await client.get(endpoint: Accounts.relationships(ids: [accountId]))
|
||||||
|
if let relationship = relationships.first {
|
||||||
|
self.relationship = relationship
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func toggleNotify() async throws {
|
func toggleNotify() async throws {
|
||||||
guard let client else { return }
|
guard let client else { return }
|
||||||
|
@ -83,7 +93,7 @@ public struct FollowButton: View {
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
VStack(alignment: .trailing) {
|
VStack(alignment: .trailing) {
|
||||||
AsyncButton {
|
AsyncButton {
|
||||||
if viewModel.relationship.following {
|
if viewModel.relationship.following || viewModel.relationship.requested {
|
||||||
try await viewModel.unfollow()
|
try await viewModel.unfollow()
|
||||||
} else {
|
} else {
|
||||||
try await viewModel.follow()
|
try await viewModel.follow()
|
||||||
|
|
|
@ -3,6 +3,7 @@ import Models
|
||||||
import Env
|
import Env
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
import WrappingHStack
|
import WrappingHStack
|
||||||
|
import AppAccount
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
struct TipSheetView: View {
|
struct TipSheetView: View {
|
||||||
|
@ -10,6 +11,8 @@ struct TipSheetView: View {
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
@Environment(Theme.self) private var theme: Theme
|
@Environment(Theme.self) private var theme: Theme
|
||||||
@Environment(TipedUsers.self) private var tipedUSers: TipedUsers
|
@Environment(TipedUsers.self) private var tipedUSers: TipedUsers
|
||||||
|
@Environment(\.openURL) private var openURL
|
||||||
|
@Environment(AppAccountsManager.self) private var appAccount: AppAccountsManager
|
||||||
|
|
||||||
@State private var selectedTip: String?
|
@State private var selectedTip: String?
|
||||||
|
|
||||||
|
@ -122,5 +125,13 @@ struct TipSheetView: View {
|
||||||
}
|
}
|
||||||
.font(.title)
|
.font(.title)
|
||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
|
.onAppear {
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||||
|
if let accountName = appAccount.currentAccount.accountName,
|
||||||
|
let url = URL(string: "https://social-proxy.com/subscribe/to/\(account.username)?callback=icecubesapp://socialproxy&id=@\(accountName)") {
|
||||||
|
openURL(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ let package = Package(
|
||||||
.package(name: "Env", path: "../Env"),
|
.package(name: "Env", path: "../Env"),
|
||||||
.package(url: "https://github.com/kean/Nuke", from: "12.4.0"),
|
.package(url: "https://github.com/kean/Nuke", from: "12.4.0"),
|
||||||
.package(url: "https://github.com/divadretlaw/EmojiText", from: "4.0.0"),
|
.package(url: "https://github.com/divadretlaw/EmojiText", from: "4.0.0"),
|
||||||
.package(url: "https://github.com/dkk/WrappingHStack", from: "2.2.11"),
|
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
.target(
|
.target(
|
||||||
|
@ -32,7 +31,6 @@ let package = Package(
|
||||||
.product(name: "NukeUI", package: "Nuke"),
|
.product(name: "NukeUI", package: "Nuke"),
|
||||||
.product(name: "Nuke", package: "Nuke"),
|
.product(name: "Nuke", package: "Nuke"),
|
||||||
.product(name: "EmojiText", package: "EmojiText"),
|
.product(name: "EmojiText", package: "EmojiText"),
|
||||||
.product(name: "WrappingHStack", package: "WrappingHStack"),
|
|
||||||
],
|
],
|
||||||
swiftSettings: [
|
swiftSettings: [
|
||||||
.enableExperimentalFeature("StrictConcurrency"),
|
.enableExperimentalFeature("StrictConcurrency"),
|
||||||
|
|
|
@ -5,6 +5,8 @@ import Foundation
|
||||||
public class TipedUsers {
|
public class TipedUsers {
|
||||||
public var usersIds: [String] = []
|
public var usersIds: [String] = []
|
||||||
|
|
||||||
|
public var tipedUserCount: Int = 0
|
||||||
|
|
||||||
static public let shared = TipedUsers()
|
static public let shared = TipedUsers()
|
||||||
|
|
||||||
private init() { }
|
private init() { }
|
||||||
|
|
|
@ -70,6 +70,10 @@ public final class Account: Codable, Identifiable, Hashable, Sendable, Equatable
|
||||||
public var haveHeader: Bool {
|
public var haveHeader: Bool {
|
||||||
header.lastPathComponent != "missing.png"
|
header.lastPathComponent != "missing.png"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var fullAccountName: String {
|
||||||
|
"\(acct)@\(url?.host() ?? "")"
|
||||||
|
}
|
||||||
|
|
||||||
public init(id: String, username: String, displayName: String?, avatar: URL, header: URL, acct: String, note: HTMLString, createdAt: ServerDate, followersCount: Int, followingCount: Int, statusesCount: Int, lastStatusAt: String? = nil, fields: [Account.Field], locked: Bool, emojis: [Emoji], url: URL? = nil, source: Account.Source? = nil, bot: Bool, discoverable: Bool? = nil, moved: Account? = nil) {
|
public init(id: String, username: String, displayName: String?, avatar: URL, header: URL, acct: String, note: HTMLString, createdAt: ServerDate, followersCount: Int, followingCount: Int, statusesCount: Int, lastStatusAt: String? = nil, fields: [Account.Field], locked: Bool, emojis: [Emoji], url: URL? = nil, source: Account.Source? = nil, bot: Bool, discoverable: Bool? = nil, moved: Account? = nil) {
|
||||||
self.id = id
|
self.id = id
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public struct Relationship: Codable {
|
public struct Relationship: Codable, Equatable, Identifiable {
|
||||||
public let id: String
|
public let id: String
|
||||||
public let following: Bool
|
public let following: Bool
|
||||||
public let showingReblogs: Bool
|
public let showingReblogs: Bool
|
||||||
|
|
Loading…
Reference in a new issue