Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ios/happwn/UI/AddSubscriptionView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ struct AddSubscriptionView: View {
}
.navigationTitle("Новая подписка")
.navigationBarTitleDisplayMode(.inline)
.keyboardDoneButton()
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Отмена") { dismiss() }
Expand Down
1 change: 1 addition & 0 deletions ios/happwn/UI/ExtractView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ struct ExtractView: View {
}
.background(Color(.systemGroupedBackground))
.navigationTitle("happwn")
.keyboardDoneButton()
}

private var linkSection: some View {
Expand Down
50 changes: 49 additions & 1 deletion ios/happwn/UI/SettingsView.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import SwiftUI
import UserNotifications
import UIKit

struct SettingsView: View {
@EnvironmentObject private var settings: Settings
@Environment(\.openURL) private var openURL
@Environment(\.scenePhase) private var scenePhase
@State private var notifStatus: UNAuthorizationStatus = .notDetermined

private let columns = [GridItem(.adaptive(minimum: 44), spacing: 14)]

Expand Down Expand Up @@ -34,9 +39,33 @@ struct SettingsView: View {
Toggle("Уведомления об обновлениях", isOn: $settings.notificationsEnabled)
.onChange(of: settings.notificationsEnabled) { enabled in
if enabled {
Task { await NotificationService().requestAuthorization() }
Task {
await NotificationService().requestAuthorization()
await refreshNotifStatus()
}
}
}
HStack {
Text("Разрешение").foregroundStyle(.secondary)
Spacer()
Text(notifStatusText)
.foregroundStyle(notifStatus == .authorized ? .green : .secondary)
}
if settings.notificationsEnabled && (notifStatus == .denied || notifStatus == .notDetermined) {
Button {
if notifStatus == .notDetermined {
Task {
await NotificationService().requestAuthorization()
await refreshNotifStatus()
}
} else if let url = URL(string: UIApplication.openSettingsURLString) {
openURL(url)
}
} label: {
Label(notifStatus == .notDetermined ? "Запросить разрешение" : "Включить в Настройках iOS",
systemImage: "bell.badge")
}
}
} header: {
Text("Обновления")
} footer: {
Expand All @@ -55,6 +84,25 @@ struct SettingsView: View {
}
}
.navigationTitle("Настройки")
.keyboardDoneButton()
.task { await refreshNotifStatus() }
.onChange(of: scenePhase) { phase in
if phase == .active { Task { await refreshNotifStatus() } }
}
}

private var notifStatusText: String {
switch notifStatus {
case .authorized, .provisional, .ephemeral: return "Разрешены"
case .denied: return "Запрещены"
case .notDetermined: return "Не запрошены"
@unknown default: return "—"
}
}

@MainActor
private func refreshNotifStatus() async {
notifStatus = await NotificationService().authorizationStatus()
}

private func labeledField(icon: String, tint: Color, title: String,
Expand Down
1 change: 1 addition & 0 deletions ios/happwn/UI/SubscriptionDetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ struct SubscriptionDetailView: View {
}
.navigationTitle(sub?.name ?? "Подписка")
.navigationBarTitleDisplayMode(.inline)
.keyboardDoneButton()
.onAppear { store.markSeen(id) }
}

Expand Down
23 changes: 23 additions & 0 deletions ios/happwn/UI/Theme.swift
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,29 @@ struct PrimaryButton: View {
}
}

// MARK: - Keyboard dismissal

/// Adds a "Готово" button above the keyboard to dismiss it — needed for
/// multiline fields where Return can't close the keyboard.
struct KeyboardDoneToolbar: ViewModifier {
func body(content: Content) -> some View {
content.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button("Готово") {
UIApplication.shared.sendAction(
#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
.fontWeight(.semibold)
}
}
}
}

extension View {
func keyboardDoneButton() -> some View { modifier(KeyboardDoneToolbar()) }
}

// MARK: - Haptics

enum Haptics {
Expand Down
4 changes: 2 additions & 2 deletions ios/project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ settings:
GENERATE_INFOPLIST_FILE: YES
INFOPLIST_KEY_UILaunchScreen_Generation: YES
INFOPLIST_KEY_CFBundleDisplayName: happwn
MARKETING_VERSION: "1.0.3"
CURRENT_PROJECT_VERSION: "4"
MARKETING_VERSION: "1.0.4"
CURRENT_PROJECT_VERSION: "5"
SWIFT_VERSION: "5.0"
TARGETED_DEVICE_FAMILY: "1,2"
targets:
Expand Down
Loading