Merge branch 'main' of https://github.com/Dimillian/IceCubesApp
This commit is contained in:
commit
3c9b0af3dd
|
@ -140,9 +140,7 @@ struct IceCubesApp: App {
|
|||
}
|
||||
}
|
||||
selectedTab = newTab
|
||||
if userPreferences.hapticTabSelectionEnabled {
|
||||
HapticManager.shared.selectionChanged()
|
||||
}
|
||||
HapticManager.shared.fireHaptic(of: .tabSelection)
|
||||
})) {
|
||||
ForEach(availableTabs) { tab in
|
||||
tab.makeContentView(popToRootTab: $popToRootTab)
|
||||
|
|
|
@ -102,9 +102,11 @@ struct SettingsTabs: View {
|
|||
NavigationLink(destination: DisplaySettingsView()) {
|
||||
Label("settings.general.display", systemImage: "paintpalette")
|
||||
}
|
||||
NavigationLink(destination: HapticSettingsView()) {
|
||||
Label("settings.general.haptic", systemImage: "waveform.path")
|
||||
}
|
||||
if HapticManager.shared.supportsHaptics {
|
||||
NavigationLink(destination: HapticSettingsView()) {
|
||||
Label("settings.general.haptic", systemImage: "waveform.path")
|
||||
}
|
||||
}
|
||||
NavigationLink(destination: remoteLocalTimelinesView) {
|
||||
Label("settings.general.remote-timelines", systemImage: "dot.radiowaves.right")
|
||||
}
|
||||
|
|
|
@ -24,14 +24,14 @@
|
|||
"enum.avatar-position.top" = "본문 위";
|
||||
"enum.avatar-shape.circle" = "원";
|
||||
"enum.avatar-shape.rounded" = "둥근 사각형";
|
||||
"enum.durations.infinite" = "infinite";
|
||||
"enum.durations.fiveMinutes" = "5 minutes";
|
||||
"enum.durations.thirtyMinutes" = "30 minutes";
|
||||
"enum.durations.oneHour" = "1 hour";
|
||||
"enum.durations.sixHours" = "6 hours";
|
||||
"enum.durations.oneDay" = "1 day";
|
||||
"enum.durations.threeDays" = "3 days";
|
||||
"enum.durations.sevenDays" = "7 days";
|
||||
"enum.durations.infinite" = "해제할 때까지";
|
||||
"enum.durations.fiveMinutes" = "5분";
|
||||
"enum.durations.thirtyMinutes" = "30분";
|
||||
"enum.durations.oneHour" = "1시간";
|
||||
"enum.durations.sixHours" = "6시간";
|
||||
"enum.durations.oneDay" = "1일";
|
||||
"enum.durations.threeDays" = "3일";
|
||||
"enum.durations.sevenDays" = "7일";
|
||||
"enum.status-actions-display.all" = "모두 표시";
|
||||
"enum.status-actions-display.no-buttons" = "표시하지 않음";
|
||||
"enum.status-actions-display.only-buttons" = "버튼만 표시";
|
||||
|
@ -193,7 +193,7 @@
|
|||
"account.action.unblock" = "차단 해제";
|
||||
"account.action.mute" = "뮤트";
|
||||
"account.action.unmute" = "뮤트 해제";
|
||||
"account.action.share" = "Share this account";
|
||||
"account.action.share" = "공유";
|
||||
"account.boosted-by" = "부스트한 사용자";
|
||||
"account.detail.about" = "정보";
|
||||
"account.detail.familiar-followers" = "내가 아는 팔로워";
|
||||
|
@ -315,7 +315,7 @@
|
|||
"timeline.local" = "로컬";
|
||||
"timeline.n-recent-from-n-participants %lld %lld" = "%lld개의 최근 글 (%lld명의 사용자가 이야기 중)";
|
||||
"timeline.trending" = "뜨고 있는";
|
||||
"timeline.add.url" = "Instance URL";
|
||||
"timeline.add.url" = "인스턴스 URL";
|
||||
|
||||
// MARK: Package: Status
|
||||
"status.action.translate" = "번역";
|
||||
|
@ -340,8 +340,8 @@
|
|||
"status.action.unfavorite" = "좋아요 취소";
|
||||
"status.action.unpin" = "고정 해제";
|
||||
"status.action.view-in-browser" = "브라우저에서 보기";
|
||||
"status.card.share" = "Share this link";
|
||||
"status.card.copy" = "Copy this link";
|
||||
"status.card.share" = "링크 공유";
|
||||
"status.card.copy" = "링크 복사";
|
||||
"status.draft.delete" = "삭제";
|
||||
"status.draft.save" = "임시 보관함에 저장";
|
||||
"status.editor.ai-prompt.correct" = "맞게 고치기";
|
||||
|
|
|
@ -24,14 +24,14 @@
|
|||
"enum.avatar-position.top" = "Boven";
|
||||
"enum.avatar-shape.circle" = "Cirkel";
|
||||
"enum.avatar-shape.rounded" = "Afgerond";
|
||||
"enum.durations.infinite" = "infinite";
|
||||
"enum.durations.fiveMinutes" = "5 minutes";
|
||||
"enum.durations.thirtyMinutes" = "30 minutes";
|
||||
"enum.durations.oneHour" = "1 hour";
|
||||
"enum.durations.sixHours" = "6 hours";
|
||||
"enum.durations.oneDay" = "1 day";
|
||||
"enum.durations.threeDays" = "3 days";
|
||||
"enum.durations.sevenDays" = "7 days";
|
||||
"enum.durations.infinite" = "Onbepaald";
|
||||
"enum.durations.fiveMinutes" = "5 minuten";
|
||||
"enum.durations.thirtyMinutes" = "30 minuten";
|
||||
"enum.durations.oneHour" = "1 uur";
|
||||
"enum.durations.sixHours" = "6 uur";
|
||||
"enum.durations.oneDay" = "1 dag";
|
||||
"enum.durations.threeDays" = "3 dagen";
|
||||
"enum.durations.sevenDays" = "7 dagen";
|
||||
"enum.status-actions-display.all" = "Met tekst";
|
||||
"enum.status-actions-display.no-buttons" = "Geen knoppen";
|
||||
"enum.status-actions-display.only-buttons" = "Zonder tekst";
|
||||
|
@ -189,7 +189,7 @@
|
|||
"account.action.unblock" = "Deblokkeer";
|
||||
"account.action.mute" = "Dempen";
|
||||
"account.action.unmute" = "Dempen opheffen";
|
||||
"account.action.share" = "Share this account";
|
||||
"account.action.share" = "Deel dit account";
|
||||
"account.boosted-by" = "Geboost door";
|
||||
"account.detail.about" = "Over";
|
||||
"account.detail.familiar-followers" = "Ook gevolgd door";
|
||||
|
@ -332,8 +332,8 @@
|
|||
"status.action.unfavorite" = "Verwijder favoriet";
|
||||
"status.action.unpin" = "Maak los";
|
||||
"status.action.view-in-browser" = "Open in browser";
|
||||
"status.card.share" = "Share this link";
|
||||
"status.card.copy" = "Copy this link";
|
||||
"status.card.share" = "Deel deze link";
|
||||
"status.card.copy" = "Kopieer deze link";
|
||||
"status.draft.delete" = "Verwijder concept";
|
||||
"status.draft.save" = "Bewaar concept";
|
||||
"status.editor.ai-prompt.correct" = "Corrigeer tekst";
|
||||
|
|
|
@ -145,7 +145,7 @@
|
|||
"settings.display.font.system" = "Systemowa";
|
||||
"settings.display.font.custom" = "Własna";
|
||||
"settings.display.font.scaling-%@" = "Skalowanie czcionki: %@";
|
||||
"settings.about.built-with" = "Ice Cubes is built with the following Open Source software:";
|
||||
"settings.about.built-with" = "Ice Cubes zbudowano z wykorzystaniem następującego oprogramowania Open Source:";
|
||||
"settings.about.title" = "Ice Cubes";
|
||||
"settings.account.cached-posts-%@" = "Liczba postów w buforze: %@";
|
||||
"settings.account.action.delete-cache" = "Wyczyść bufor";
|
||||
|
|
|
@ -38,9 +38,7 @@ public struct AppAccountsSelectorView: View {
|
|||
}
|
||||
}
|
||||
.onTapGesture {
|
||||
if UserPreferences.shared.hapticButtonPressEnabled {
|
||||
HapticManager.shared.impact()
|
||||
}
|
||||
HapticManager.shared.fireHaptic(of: .buttonPress)
|
||||
}
|
||||
.onAppear {
|
||||
refreshAccounts()
|
||||
|
@ -80,9 +78,7 @@ public struct AppAccountsSelectorView: View {
|
|||
appAccounts.currentAccount = viewModel.appAccount
|
||||
}
|
||||
|
||||
if UserPreferences.shared.hapticButtonPressEnabled {
|
||||
HapticManager.shared.impact()
|
||||
}
|
||||
HapticManager.shared.fireHaptic(of: .buttonPress)
|
||||
} label: {
|
||||
HStack {
|
||||
if viewModel.account?.id == currentAccount.account?.id {
|
||||
|
@ -96,9 +92,7 @@ public struct AppAccountsSelectorView: View {
|
|||
if accountCreationEnabled {
|
||||
Divider()
|
||||
Button {
|
||||
if UserPreferences.shared.hapticButtonPressEnabled {
|
||||
HapticManager.shared.impact()
|
||||
}
|
||||
HapticManager.shared.fireHaptic(of: .buttonPress)
|
||||
routerPath.presentedSheet = .addAccount
|
||||
} label: {
|
||||
Label("app-account.button.add", systemImage: "person.badge.plus")
|
||||
|
@ -108,9 +102,7 @@ public struct AppAccountsSelectorView: View {
|
|||
if UIDevice.current.userInterfaceIdiom == .phone {
|
||||
Divider()
|
||||
Button {
|
||||
if UserPreferences.shared.hapticButtonPressEnabled {
|
||||
HapticManager.shared.impact()
|
||||
}
|
||||
HapticManager.shared.fireHaptic(of: .buttonPress)
|
||||
routerPath.presentedSheet = .settings
|
||||
} label: {
|
||||
Label("tab.settings", systemImage: "gear")
|
||||
|
|
|
@ -35,7 +35,7 @@ public struct FollowRequestButtons: View {
|
|||
}
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.disabled(currentAccount.isUpdating)
|
||||
.disabled(currentAccount.updatingFollowRequestAccountIds.contains(account.id))
|
||||
.padding(.top, 4)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,7 @@ public extension View {
|
|||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button {
|
||||
routerPath.presentedSheet = .newStatusEditor(visibility: visibility)
|
||||
if UserPreferences.shared.hapticButtonPressEnabled {
|
||||
HapticManager.shared.impact()
|
||||
}
|
||||
HapticManager.shared.fireHaptic(of: .buttonPress)
|
||||
} label: {
|
||||
Image(systemName: "square.and.pencil")
|
||||
}
|
||||
|
@ -31,9 +29,7 @@ public struct StatusEditorToolbarItem: ToolbarContent {
|
|||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button {
|
||||
routerPath.presentedSheet = .newStatusEditor(visibility: visibility)
|
||||
if UserPreferences.shared.hapticButtonPressEnabled {
|
||||
HapticManager.shared.impact()
|
||||
}
|
||||
HapticManager.shared.fireHaptic(of: .buttonPress)
|
||||
} label: {
|
||||
Image(systemName: "square.and.pencil")
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ public class CurrentAccount: ObservableObject {
|
|||
@Published public private(set) var tags: [Tag] = []
|
||||
@Published public private(set) var followRequests: [Account] = []
|
||||
@Published public private(set) var isUpdating: Bool = false
|
||||
@Published public private(set) var updatingFollowRequestAccountIds = Set<String>()
|
||||
@Published public private(set) var isLoadingAccount: Bool = false
|
||||
|
||||
private var client: Client?
|
||||
|
@ -122,9 +123,9 @@ public class CurrentAccount: ObservableObject {
|
|||
public func acceptFollowerRequest(id: String) async {
|
||||
guard let client else { return }
|
||||
do {
|
||||
isUpdating = true
|
||||
updatingFollowRequestAccountIds.insert(id)
|
||||
defer {
|
||||
isUpdating = false
|
||||
updatingFollowRequestAccountIds.remove(id)
|
||||
}
|
||||
_ = try await client.post(endpoint: FollowRequests.accept(id: id))
|
||||
await fetchFollowerRequests()
|
||||
|
@ -134,9 +135,9 @@ public class CurrentAccount: ObservableObject {
|
|||
public func rejectFollowerRequest(id: String) async {
|
||||
guard let client else { return }
|
||||
do {
|
||||
isUpdating = true
|
||||
updatingFollowRequestAccountIds.insert(id)
|
||||
defer {
|
||||
isUpdating = false
|
||||
updatingFollowRequestAccountIds.remove(id)
|
||||
}
|
||||
_ = try await client.post(endpoint: FollowRequests.reject(id: id))
|
||||
await fetchFollowerRequests()
|
||||
|
|
|
@ -1,30 +1,57 @@
|
|||
import CoreHaptics
|
||||
import UIKit
|
||||
|
||||
public class HapticManager {
|
||||
public static let shared: HapticManager = .init()
|
||||
|
||||
public enum HapticType {
|
||||
case buttonPress
|
||||
case dataRefresh(intensity: CGFloat)
|
||||
case notification(_ type: UINotificationFeedbackGenerator.FeedbackType)
|
||||
case tabSelection
|
||||
case timeline
|
||||
}
|
||||
|
||||
private let selectionGenerator = UISelectionFeedbackGenerator()
|
||||
private let impactGenerator = UIImpactFeedbackGenerator(style: .heavy)
|
||||
private let notificationGenerator = UINotificationFeedbackGenerator()
|
||||
|
||||
private let userPreferences = UserPreferences.shared
|
||||
|
||||
private init() {
|
||||
selectionGenerator.prepare()
|
||||
impactGenerator.prepare()
|
||||
}
|
||||
|
||||
public func selectionChanged() {
|
||||
selectionGenerator.selectionChanged()
|
||||
@MainActor
|
||||
public func fireHaptic(of type: HapticType) {
|
||||
guard supportsHaptics else { return }
|
||||
|
||||
switch type {
|
||||
case .buttonPress:
|
||||
if userPreferences.hapticButtonPressEnabled {
|
||||
impactGenerator.impactOccurred()
|
||||
}
|
||||
case let .dataRefresh(intensity):
|
||||
if userPreferences.hapticTimelineEnabled {
|
||||
impactGenerator.impactOccurred(intensity: intensity)
|
||||
}
|
||||
case let .notification(type):
|
||||
if userPreferences.hapticButtonPressEnabled {
|
||||
notificationGenerator.notificationOccurred(type)
|
||||
}
|
||||
case .tabSelection:
|
||||
if userPreferences.hapticTabSelectionEnabled {
|
||||
selectionGenerator.selectionChanged()
|
||||
}
|
||||
case .timeline:
|
||||
if userPreferences.hapticTimelineEnabled {
|
||||
selectionGenerator.selectionChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func impact() {
|
||||
impactGenerator.impactOccurred()
|
||||
}
|
||||
|
||||
public func impact(intensity: CGFloat) {
|
||||
impactGenerator.impactOccurred(intensity: intensity)
|
||||
}
|
||||
|
||||
public func notification(type: UINotificationFeedbackGenerator.FeedbackType) {
|
||||
notificationGenerator.notificationOccurred(type)
|
||||
public var supportsHaptics: Bool {
|
||||
CHHapticEngine.capabilitiesForHardware().supportsHaptics
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,6 +52,7 @@ public struct NotificationsListView: View {
|
|||
.background(theme.primaryBackgroundColor)
|
||||
.task {
|
||||
viewModel.client = client
|
||||
viewModel.currentAccount = account
|
||||
if let lockedType {
|
||||
viewModel.selectedType = lockedType
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import Env
|
||||
import Foundation
|
||||
import Models
|
||||
import Network
|
||||
|
@ -27,6 +28,7 @@ class NotificationsViewModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
}
|
||||
var currentAccount: CurrentAccount?
|
||||
|
||||
@Published var state: State = .loading
|
||||
@Published var selectedType: Models.Notification.NotificationType? {
|
||||
|
@ -52,7 +54,7 @@ class NotificationsViewModel: ObservableObject {
|
|||
private var consolidatedNotifications: [ConsolidatedNotification] = []
|
||||
|
||||
func fetchNotifications() async {
|
||||
guard let client else { return }
|
||||
guard let client, let currentAccount else { return }
|
||||
do {
|
||||
var nextPageState: State.PagingState = .hasNextPage
|
||||
if consolidatedNotifications.isEmpty {
|
||||
|
@ -77,6 +79,9 @@ class NotificationsViewModel: ObservableObject {
|
|||
at: 0
|
||||
)
|
||||
}
|
||||
|
||||
await currentAccount.fetchFollowerRequests()
|
||||
|
||||
withAnimation {
|
||||
state = .display(notifications: consolidatedNotifications,
|
||||
nextPageState: consolidatedNotifications.isEmpty ? .none : nextPageState)
|
||||
|
@ -96,6 +101,7 @@ class NotificationsViewModel: ObservableObject {
|
|||
maxId: lastId,
|
||||
types: queryTypes))
|
||||
consolidatedNotifications.append(contentsOf: newNotifications.consolidated(selectedType: selectedType))
|
||||
await currentAccount?.fetchFollowerRequests()
|
||||
state = .display(notifications: consolidatedNotifications, nextPageState: newNotifications.count < 15 ? .none : .hasNextPage)
|
||||
} catch {
|
||||
state = .error(error: error)
|
||||
|
@ -136,6 +142,10 @@ class NotificationsViewModel: ObservableObject {
|
|||
)
|
||||
}
|
||||
|
||||
if event.notification.supportedType == .follow_request, let currentAccount {
|
||||
await currentAccount.fetchFollowerRequests()
|
||||
}
|
||||
|
||||
withAnimation {
|
||||
state = .display(notifications: consolidatedNotifications, nextPageState: .hasNextPage)
|
||||
}
|
||||
|
|
|
@ -174,9 +174,7 @@ public class StatusEditorViewModel: ObservableObject {
|
|||
case let .edit(status):
|
||||
postStatus = try await client.put(endpoint: Statuses.editStatus(id: status.id, json: data))
|
||||
}
|
||||
if UserPreferences.shared.hapticButtonPressEnabled {
|
||||
HapticManager.shared.notification(type: .success)
|
||||
}
|
||||
HapticManager.shared.fireHaptic(of: .notification(.success))
|
||||
if hasExplicitlySelectedLanguage, let selectedLanguage {
|
||||
preferences?.markLanguageAsSelected(isoCode: selectedLanguage)
|
||||
}
|
||||
|
@ -188,9 +186,7 @@ public class StatusEditorViewModel: ObservableObject {
|
|||
showPostingErrorAlert = true
|
||||
}
|
||||
isPosting = false
|
||||
if UserPreferences.shared.hapticButtonPressEnabled {
|
||||
HapticManager.shared.notification(type: .error)
|
||||
}
|
||||
HapticManager.shared.fireHaptic(of: .notification(.error))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -179,9 +179,7 @@ struct StatusActionsView: View {
|
|||
|
||||
private func handleAction(action: Actions) {
|
||||
Task {
|
||||
if UserPreferences.shared.hapticButtonPressEnabled {
|
||||
HapticManager.shared.notification(type: .success)
|
||||
}
|
||||
HapticManager.shared.fireHaptic(of: .notification(.success))
|
||||
switch action {
|
||||
case .respond:
|
||||
routerPath.presentedSheet = .replyToStatusEditor(status: viewModel.status)
|
||||
|
|
|
@ -425,9 +425,7 @@ public struct StatusRowView: View {
|
|||
private var trailinSwipeActions: some View {
|
||||
Button {
|
||||
Task {
|
||||
if UserPreferences.shared.hapticButtonPressEnabled {
|
||||
HapticManager.shared.notification(type: .success)
|
||||
}
|
||||
HapticManager.shared.fireHaptic(of: .notification(.success))
|
||||
if viewModel.isFavorited {
|
||||
await viewModel.unFavorite()
|
||||
} else {
|
||||
|
@ -440,9 +438,7 @@ public struct StatusRowView: View {
|
|||
.tint(.yellow)
|
||||
Button {
|
||||
Task {
|
||||
if UserPreferences.shared.hapticButtonPressEnabled {
|
||||
HapticManager.shared.notification(type: .success)
|
||||
}
|
||||
HapticManager.shared.fireHaptic(of: .notification(.success))
|
||||
if viewModel.isReblogged {
|
||||
await viewModel.unReblog()
|
||||
} else {
|
||||
|
@ -458,9 +454,7 @@ public struct StatusRowView: View {
|
|||
@ViewBuilder
|
||||
private var leadingSwipeActions: some View {
|
||||
Button {
|
||||
if UserPreferences.shared.hapticButtonPressEnabled {
|
||||
HapticManager.shared.notification(type: .success)
|
||||
}
|
||||
HapticManager.shared.fireHaptic(of: .notification(.success))
|
||||
routerPath.presentedSheet = .replyToStatusEditor(status: viewModel.status)
|
||||
} label: {
|
||||
Image(systemName: "arrowshape.turn.up.left")
|
||||
|
|
|
@ -19,9 +19,7 @@ class PendingStatusesObserver: ObservableObject {
|
|||
func removeStatus(status: Status) {
|
||||
if !disableUpdate, let index = pendingStatuses.firstIndex(of: status.id) {
|
||||
pendingStatuses.removeSubrange(index ... (pendingStatuses.count - 1))
|
||||
if UserPreferences.shared.hapticTimelineEnabled {
|
||||
HapticManager.shared.selectionChanged()
|
||||
}
|
||||
HapticManager.shared.fireHaptic(of: .timeline)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -112,13 +112,9 @@ public struct TimelineView: View {
|
|||
viewModel.isTimelineVisible = false
|
||||
}
|
||||
.refreshable {
|
||||
if UserPreferences.shared.hapticTimelineEnabled {
|
||||
HapticManager.shared.impact(intensity: 0.3)
|
||||
}
|
||||
HapticManager.shared.fireHaptic(of: .dataRefresh(intensity: 0.3))
|
||||
await viewModel.fetchStatuses()
|
||||
if UserPreferences.shared.hapticTimelineEnabled {
|
||||
HapticManager.shared.impact(intensity: 0.7)
|
||||
}
|
||||
HapticManager.shared.fireHaptic(of: .dataRefresh(intensity: 0.7))
|
||||
}
|
||||
.onChange(of: watcher.latestEvent?.id) { _ in
|
||||
if let latestEvent = watcher.latestEvent {
|
||||
|
|
Loading…
Reference in New Issue