Files
iOS/Sources/App/Settings/AppIconShortcuts/AppIconShortcutsConfigurationViewModel.swift
Bruno Pantaleão Gonçalves f5f32ef40c Add app icon shortcuts configurator (#4588)
<!-- Thank you for submitting a Pull Request and helping to improve Home
Assistant. Please complete the following sections to help the processing
and review of your changes. Please do not delete anything from this
template. -->

## Summary
<!-- Provide a brief summary of the changes you have made and most
importantly what they aim to achieve -->
As part of removing legacy iOS actions from the app completely, this PR
adds a way for the user to choose what to display in app icon long press
menu.
## Screenshots
<!-- If this is a user-facing change not in the frontend, please include
screenshots in light and dark mode. -->
<img width="3216" height="1890" alt="CleanShot 2026-04-30 at 11 35
28@2x"
src="https://github.com/user-attachments/assets/4680d7e4-4790-4f21-b4b4-b93f641ce769"
/>

## Link to pull request in Documentation repository
<!-- Pull requests that add, change or remove functionality must have a
corresponding pull request in the Companion App Documentation repository
(https://github.com/home-assistant/companion.home-assistant). Please add
the number of this pull request after the "#" -->
Documentation: home-assistant/companion.home-assistant#

## Any other notes
<!-- If there is any other information of note, like if this Pull
Request is part of a bigger change, please include it here. -->
2026-04-30 17:26:44 +02:00

127 lines
3.9 KiB
Swift

import Combine
import Foundation
import Shared
final class AppIconShortcutsConfigurationViewModel: ObservableObject {
@Published private(set) var config = AppIconShortcutConfig()
@Published var showAddItem = false
@Published var showError = false
@Published private(set) var errorMessage: String?
private let magicItemProvider = Current.magicItemProvider()
private var cancellables = Set<AnyCancellable>()
private var isInitialLoad = true
init() {
setupAutoSave()
}
@MainActor
func loadConfig() {
magicItemProvider.loadInformation { [weak self] _ in
guard let self else { return }
loadDatabase()
}
}
func magicItemInfo(for item: MagicItem) -> MagicItem.Info? {
magicItemProvider.getInfo(for: item)
}
func addItem(_ item: MagicItem) {
let isDuplicate = config.items.contains(where: {
$0.id == item.id && $0.serverId == item.serverId && $0.type == item.type
})
guard !isDuplicate else {
showError(message: L10n.Settings.AppIconShortcuts.duplicateError)
return
}
config.items.append(item)
}
func updateItem(_ item: MagicItem) {
if let indexToUpdate = config.items
.firstIndex(where: { $0.id == item.id && $0.serverId == item.serverId && $0.type == item.type }) {
config.items[indexToUpdate] = item
}
}
func deleteItem(at offsets: IndexSet) {
config.items.remove(atOffsets: offsets)
}
func moveItem(from source: IndexSet, to destination: Int) {
config.items.move(fromOffsets: source, toOffset: destination)
}
func deleteConfiguration(completion: (Bool) -> Void) {
do {
_ = try Current.database().write { db in
try AppIconShortcutConfig.deleteAll(db)
}
AppIconShortcutItemsUpdater.update()
completion(true)
} catch {
showError(message: L10n.Grdb.Config.MigrationError.failedToSave(error.localizedDescription))
completion(false)
}
}
private func setupAutoSave() {
$config
.dropFirst()
.sink { [weak self] _ in
guard let self, !self.isInitialLoad else { return }
Task { @MainActor in
self.save()
}
}
.store(in: &cancellables)
}
@discardableResult
@MainActor
private func save() -> Bool {
do {
try Current.database().write { db in
try config.insert(db, onConflict: .replace)
}
AppIconShortcutItemsUpdater.update()
return true
} catch {
Current.Log.error("Failed to save App Icon Shortcuts config, error: \(error.localizedDescription)")
showError(message: L10n.Grdb.Config.MigrationError.failedToSave(error.localizedDescription))
return false
}
}
@MainActor
private func loadDatabase() {
do {
if let config = try AppIconShortcutConfig.config() {
setConfig(config)
Current.Log.info("App Icon Shortcuts configuration exists")
} else {
setConfig(AppIconShortcutConfig())
Current.Log.info("No App Icon Shortcuts config found, initializing default configuration")
}
} catch {
Current.Log.error("Failed to access database (GRDB), error: \(error.localizedDescription)")
showError(message: L10n.Grdb.Config.MigrationError.failedAccessGrdb(error.localizedDescription))
}
}
@MainActor
private func setConfig(_ config: AppIconShortcutConfig) {
self.config = config
isInitialLoad = false
}
private func showError(message: String) {
DispatchQueue.main.async { [weak self] in
self?.errorMessage = message
self?.showError = true
}
}
}