krz/hutch

an ios client for sourcehut

clone: git clone https://gitbay.org/krz/hutch.git

10d68934d4f45bf60e1736371ff790c50600198d

verified · cmc

author: Christian Cleberg <hello@cleberg.net> · 2026-03-19T22:05:16Z

fix README height update and settings save alerts
 Hutch.xcodeproj/project.pbxproj                    |  8 +++---
 .../Repositories/HgRepositorySettingsView.swift    | 31 ++++++++++++++++++++--
 .../HgRepositorySettingsViewModel.swift            |  4 ++-
 Hutch/Views/Repositories/ReadmeView.swift          |  6 ++++-
 .../Repositories/RepositorySettingsView.swift      | 23 +++++++++++++++-
 .../Repositories/RepositorySettingsViewModel.swift |  4 ++-
 6 files changed, 66 insertions(+), 10 deletions(-)

diff --git a/Hutch.xcodeproj/project.pbxproj b/Hutch.xcodeproj/project.pbxproj
index 08b7a12..b42764c 100644
--- a/Hutch.xcodeproj/project.pbxproj
+++ b/Hutch.xcodeproj/project.pbxproj
@@ -359,7 +359,7 @@
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 5;
+				CURRENT_PROJECT_VERSION = 6;
 				DEVELOPMENT_TEAM = ZCNAX3VL9D;
 				ENABLE_PREVIEWS = YES;
 				GENERATE_INFOPLIST_FILE = YES;
@@ -376,7 +376,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 2.0;
+				MARKETING_VERSION = 2.1;
 				PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -395,7 +395,7 @@
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 5;
+				CURRENT_PROJECT_VERSION = 6;
 				DEVELOPMENT_TEAM = ZCNAX3VL9D;
 				ENABLE_PREVIEWS = YES;
 				GENERATE_INFOPLIST_FILE = YES;
@@ -412,7 +412,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 2.0;
+				MARKETING_VERSION = 2.1;
 				PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				STRING_CATALOG_GENERATE_SYMBOLS = YES;
diff --git a/Hutch/Views/Repositories/HgRepositorySettingsView.swift b/Hutch/Views/Repositories/HgRepositorySettingsView.swift
index 3894b9d..5f45da8 100644
--- a/Hutch/Views/Repositories/HgRepositorySettingsView.swift
+++ b/Hutch/Views/Repositories/HgRepositorySettingsView.swift
@@ -9,6 +9,7 @@ struct HgRepositorySettingsView: View {
     @State private var viewModel: HgRepositorySettingsViewModel?
     @State private var showDeleteConfirmation = false
     @State private var pendingACLDeletion: HgACLEntry?
+    @State private var saveResultAlert: SaveResultAlert?
 
     var body: some View {
         NavigationStack {
@@ -88,6 +89,13 @@ struct HgRepositorySettingsView: View {
                 Text("\(entry.entity.canonicalName) will lose \(entry.mode) access to this repository.")
             }
         }
+        .alert(item: $saveResultAlert) { alert in
+            Alert(
+                title: Text(alert.title),
+                message: Text(alert.message),
+                dismissButton: .default(Text("OK"))
+            )
+        }
     }
 
     @ViewBuilder
@@ -108,7 +116,13 @@ struct HgRepositorySettingsView: View {
             }
 
             Button {
-                Task { await viewModel.saveInfo() }
+                Task {
+                    let didSave = await viewModel.saveInfo()
+                    saveResultAlert = SaveResultAlert(
+                        title: didSave ? "Settings Updated" : "Couldn't Update Settings",
+                        message: didSave ? "Repository settings were saved." : (viewModel.error ?? "Please try again.")
+                    )
+                }
             } label: {
                 if viewModel.isSavingInfo {
                     ProgressView()
@@ -188,7 +202,13 @@ struct HgRepositorySettingsView: View {
             Toggle("Hide this repository from public listings", isOn: Bindable(viewModel).editedNonPublishing)
 
             Button {
-                Task { await viewModel.saveInfo() }
+                Task {
+                    let didSave = await viewModel.saveInfo()
+                    saveResultAlert = SaveResultAlert(
+                        title: didSave ? "Settings Updated" : "Couldn't Update Settings",
+                        message: didSave ? "Repository settings were saved." : (viewModel.error ?? "Please try again.")
+                    )
+                }
             } label: {
                 if viewModel.isSavingInfo {
                     ProgressView()
@@ -236,4 +256,11 @@ struct HgRepositorySettingsView: View {
             .disabled(viewModel.isDeleting)
         }
     }
+
+    private struct SaveResultAlert: Identifiable {
+        let title: String
+        let message: String
+
+        var id: String { "\(title)-\(message)" }
+    }
 }
diff --git a/Hutch/Views/Repositories/HgRepositorySettingsViewModel.swift b/Hutch/Views/Repositories/HgRepositorySettingsViewModel.swift
index 6cb6e36..a1a11ae 100644
--- a/Hutch/Views/Repositories/HgRepositorySettingsViewModel.swift
+++ b/Hutch/Views/Repositories/HgRepositorySettingsViewModel.swift
@@ -168,7 +168,7 @@ final class HgRepositorySettingsViewModel {
         }
     }
 
-    func saveInfo() async {
+    func saveInfo() async -> Bool {
         isSavingInfo = true
         defer { isSavingInfo = false }
         error = nil
@@ -185,8 +185,10 @@ final class HgRepositorySettingsViewModel {
                 variables: ["id": repositoryId, "input": input],
                 responseType: HgUpdateRepositoryResponse.self
             )
+            return true
         } catch {
             self.error = error.userFacingMessage
+            return false
         }
     }
 
diff --git a/Hutch/Views/Repositories/ReadmeView.swift b/Hutch/Views/Repositories/ReadmeView.swift
index 576adfc..3777cdb 100644
--- a/Hutch/Views/Repositories/ReadmeView.swift
+++ b/Hutch/Views/Repositories/ReadmeView.swift
@@ -1108,7 +1108,11 @@ private struct HTMLWebViewRepresentable: UIViewRepresentable {
         if let cachedHeight = HTMLWebViewCoordinator.heightCache.object(forKey: wrapped as NSString)?.doubleValue {
             let height = CGFloat(cachedHeight)
             if abs(dynamicHeight - height) > 0.5 {
-                dynamicHeight = height
+                DispatchQueue.main.async {
+                    if abs(self.dynamicHeight - height) > 0.5 {
+                        self.dynamicHeight = height
+                    }
+                }
             }
         }
 
diff --git a/Hutch/Views/Repositories/RepositorySettingsView.swift b/Hutch/Views/Repositories/RepositorySettingsView.swift
index 5074138..5606ec6 100644
--- a/Hutch/Views/Repositories/RepositorySettingsView.swift
+++ b/Hutch/Views/Repositories/RepositorySettingsView.swift
@@ -11,6 +11,7 @@ struct RepositorySettingsView: View {
     @State private var viewModel: RepositorySettingsViewModel?
     @State private var showDeleteConfirmation = false
     @State private var pendingACLDeletion: ACLEntry?
+    @State private var saveResultAlert: SaveResultAlert?
 
     var body: some View {
         NavigationStack {
@@ -91,6 +92,13 @@ struct RepositorySettingsView: View {
                 Text("\(entry.entity.canonicalName) will lose \(entry.mode) access to this repository.")
             }
         }
+        .alert(item: $saveResultAlert) { alert in
+            Alert(
+                title: Text(alert.title),
+                message: Text(alert.message),
+                dismissButton: .default(Text("OK"))
+            )
+        }
     }
 
     // MARK: - Info Section
@@ -122,7 +130,13 @@ struct RepositorySettingsView: View {
             }
 
             Button {
-                Task { await viewModel.saveInfo() }
+                Task {
+                    let didSave = await viewModel.saveInfo()
+                    saveResultAlert = SaveResultAlert(
+                        title: didSave ? "Settings Updated" : "Couldn't Update Settings",
+                        message: didSave ? "Repository settings were saved." : (viewModel.error ?? "Please try again.")
+                    )
+                }
             } label: {
                 if viewModel.isSavingInfo {
                     ProgressView()
@@ -256,4 +270,11 @@ struct RepositorySettingsView: View {
             .disabled(viewModel.isDeleting)
         }
     }
+
+    private struct SaveResultAlert: Identifiable {
+        let title: String
+        let message: String
+
+        var id: String { "\(title)-\(message)" }
+    }
 }
diff --git a/Hutch/Views/Repositories/RepositorySettingsViewModel.swift b/Hutch/Views/Repositories/RepositorySettingsViewModel.swift
index ca8e501..2847cb7 100644
--- a/Hutch/Views/Repositories/RepositorySettingsViewModel.swift
+++ b/Hutch/Views/Repositories/RepositorySettingsViewModel.swift
@@ -153,7 +153,7 @@ final class RepositorySettingsViewModel {
     }
     """
 
-    func saveInfo() async {
+    func saveInfo() async -> Bool {
         isSavingInfo = true
         defer { isSavingInfo = false }
         error = nil
@@ -172,8 +172,10 @@ final class RepositorySettingsViewModel {
                 variables: ["id": repositoryId, "input": input],
                 responseType: UpdateRepoInfoResponse.self
             )
+            return true
         } catch {
             self.error = error.userFacingMessage
+            return false
         }
     }