瀏覽代碼

Testflight Expiration Date

Jonas Björkert 2 年之前
父節點
當前提交
fd467eae07

+ 0 - 2
.gitignore

@@ -80,5 +80,3 @@ fastlane/test_output
 fastlane/FastlaneRunner
 
 ConfigOverride.xcconfig
-
-branch.txt

文件差異過大導致無法顯示
+ 16 - 11
FreeAPS.xcodeproj/project.pbxproj


+ 2 - 22
FreeAPS/Sources/APS/APSManager.swift

@@ -941,31 +941,11 @@ final class BaseAPSManager: APSManager, Injectable {
                 }
                 let af = preferences.sigmoid ? preferences.adjustmentFactorSigmoid : preferences.adjustmentFactor
                 let insulin_type = preferences.curve
-                let buildDate = Bundle.main.buildDate
+                let buildDate = BuildDetails.default.buildDate() ?? Date()
                 let version = Bundle.main.releaseVersionNumber
                 let build = Bundle.main.buildVersionNumber
 
-                // Read branch information from branch.txt instead of infoDictionary
-                var branch = "Unknown"
-                if let branchFileURL = Bundle.main.url(forResource: "branch", withExtension: "txt"),
-                   let branchFileContent = try? String(contentsOf: branchFileURL)
-                {
-                    let lines = branchFileContent.components(separatedBy: .newlines)
-                    for line in lines {
-                        let components = line.components(separatedBy: "=")
-                        if components.count == 2 {
-                            let key = components[0].trimmingCharacters(in: .whitespaces)
-                            let value = components[1].trimmingCharacters(in: .whitespaces)
-
-                            if key == "BRANCH" {
-                                branch = value
-                                break
-                            }
-                        }
-                    }
-                } else {
-                    branch = "Unknown"
-                }
+                let branch = BuildDetails.default.branchAndSha
 
                 let copyrightNotice_ = Bundle.main.infoDictionary?["NSHumanReadableCopyright"] as? String ?? ""
                 let pump_ = pumpManager?.localizedTitle ?? ""

+ 1 - 1
FreeAPS/Sources/Application/FreeAPSApp.swift

@@ -55,7 +55,7 @@ import Swinject
     init() {
         debug(
             .default,
-            "Trio Started: v\(Bundle.main.releaseVersionNumber ?? "")(\(Bundle.main.buildVersionNumber ?? "")) [buildDate: \(Bundle.main.buildDate)] [buildExpires: \(Bundle.main.profileExpiration)]"
+            "Trio Started: v\(Bundle.main.releaseVersionNumber ?? "")(\(Bundle.main.buildVersionNumber ?? "")) [buildDate: \(BuildDetails.default.buildDate())] [buildExpires: \(BuildDetails.default.calculateExpirationDate())]"
         )
         loadServices()
     }

+ 83 - 0
FreeAPS/Sources/Helpers/BuildDetails.swift

@@ -0,0 +1,83 @@
+//
+//  BuildDetails.swift
+//  Trio
+//
+//  Created by Jonas Björkert on 2024-05-09.
+//
+import Foundation
+
+class BuildDetails {
+    static var `default` = BuildDetails()
+
+    let dict: [String: Any]
+
+    init() {
+        guard let url = Bundle.main.url(forResource: "BuildDetails", withExtension: "plist"),
+              let data = try? Data(contentsOf: url),
+              let parsed = try? PropertyListSerialization.propertyList(from: data, format: nil) as? [String: Any]
+        else {
+            dict = [:]
+            return
+        }
+        dict = parsed
+    }
+
+    var buildDateString: String? {
+        dict["com-trio-build-date"] as? String
+    }
+
+    var branchAndSha: String {
+        let branch = dict["com-trio-branch"] as? String ?? "Unknown"
+        let sha = dict["com-trio-commit-sha"] as? String ?? "Unknown"
+        return "\(branch) \(sha)"
+    }
+
+    // Determine if the build is from TestFlight
+    func isTestFlightBuild() -> Bool {
+        #if targetEnvironment(simulator)
+            return false
+        #else
+            if Bundle.main.url(forResource: "embedded", withExtension: "mobileprovision") != nil {
+                return false
+            }
+            guard let receiptName = Bundle.main.appStoreReceiptURL?.lastPathComponent else {
+                return false
+            }
+            return "sandboxReceipt".caseInsensitiveCompare(receiptName) == .orderedSame
+        #endif
+    }
+
+    // Parse the build date string into a Date object
+    func buildDate() -> Date? {
+        let dateFormatter = DateFormatter()
+        dateFormatter.dateFormat = "EEE MMM d HH:mm:ss 'UTC' yyyy"
+        dateFormatter.locale = Locale(identifier: "en_US_POSIX")
+        dateFormatter.timeZone = TimeZone(identifier: "UTC")
+
+        guard let dateString = buildDateString,
+              let date = dateFormatter.date(from: dateString)
+        else {
+            return nil
+        }
+        return date
+    }
+
+    // Calculate the expiration date based on the build type
+    func calculateExpirationDate() -> Date? {
+        if isTestFlightBuild(), let buildDate = buildDate() {
+            // For TestFlight, add 90 days to the build date
+            return Calendar.current.date(byAdding: .day, value: 90, to: buildDate)!
+        } else {
+            return Bundle.main.profileExpirationDate
+        }
+    }
+
+    // Expiration header based on build type
+    var expirationHeaderString: String {
+        if isTestFlightBuild() {
+            return "Beta (TestFlight) Expiration"
+        } else {
+            return "App Expiration"
+        }
+    }
+}

+ 19 - 31
FreeAPS/Sources/Helpers/Bundle+Extensions.swift

@@ -9,17 +9,7 @@ extension Bundle {
         infoDictionary?["CFBundleVersion"] as? String
     }
 
-    var buildDate: Date {
-        if let infoPath = Bundle.main.path(forResource: "Info", ofType: "plist"),
-           let infoAttr = try? FileManager.default.attributesOfItem(atPath: infoPath),
-           let infoDate = infoAttr[.modificationDate] as? Date
-        {
-            return infoDate
-        }
-        return Date()
-    }
-
-    var profileExpiration: String? {
+    var profileExpirationDateString: String? {
         guard
             let profilePath = Bundle.main.path(forResource: "embedded", ofType: "mobileprovision"),
             let profileData = try? Data(contentsOf: URL(fileURLWithPath: profilePath)),
@@ -32,30 +22,28 @@ extension Bundle {
             return nil
         }
 
-        // NOTE: We have the `[\\W]*?` check to make sure that variations in number of tabs or new lines in the future does not influence the result.
-        guard let regex = try? NSRegularExpression(pattern: "<key>ExpirationDate</key>[\\W]*?<date>(.*?)</date>", options: [])
+        let regexPattern = "<key>ExpirationDate</key>[\\W]*?<date>(.*?)</date>"
+        guard let regex = try? NSRegularExpression(pattern: regexPattern, options: []),
+              let match = regex.firstMatch(
+                  in: profileNSString as String,
+                  options: [],
+                  range: NSRange(location: 0, length: profileNSString.length)
+              ),
+              let range = Range(match.range(at: 1), in: profileNSString as String)
         else {
-            print("Warning: Could not create regex.")
-            return nil
-        }
-
-        let regExMatches = regex.matches(
-            in: profileNSString as String,
-            options: [],
-            range: NSRange(location: 0, length: profileNSString.length)
-        )
-
-        // NOTE: range `0` corresponds to the full regex match, so to get the first capture group, we use range `1`
-        guard let rangeOfCapturedGroupForDate = regExMatches.first?.range(at: 1) else {
-            print("Warning: Could not find regex match or capture group.")
+            print("Warning: Could not create regex or find match.")
             return nil
         }
 
-        let dateWithTimeAsString = profileNSString.substring(with: rangeOfCapturedGroupForDate)
+        return String(profileNSString.substring(with: NSRange(range, in: profileNSString as String)))
+    }
 
-        guard let dateAsStringIndex = dateWithTimeAsString.firstIndex(of: "T") else {
-            return nil
-        }
-        return String(dateWithTimeAsString[..<dateAsStringIndex])
+    var profileExpirationDate: Date? {
+        guard let dateString = profileExpirationDateString else { return nil }
+        let dateFormatter = DateFormatter()
+        dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"
+        dateFormatter.locale = Locale(identifier: "en_US_POSIX")
+        dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
+        return dateFormatter.date(from: dateString)
     }
 }

+ 1 - 20
FreeAPS/Sources/Modules/Settings/SettingsStateModel.swift

@@ -31,26 +31,7 @@ extension Settings {
 
             versionNumber = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown"
 
-            // Read branch information from the branch.txt instead of infoDictionary
-            if let branchFileURL = Bundle.main.url(forResource: "branch", withExtension: "txt"),
-               let branchFileContent = try? String(contentsOf: branchFileURL)
-            {
-                let lines = branchFileContent.components(separatedBy: .newlines)
-                for line in lines {
-                    let components = line.components(separatedBy: "=")
-                    if components.count == 2 {
-                        let key = components[0].trimmingCharacters(in: .whitespaces)
-                        let value = components[1].trimmingCharacters(in: .whitespaces)
-
-                        if key == "BRANCH" {
-                            branch = value
-                            break
-                        }
-                    }
-                }
-            } else {
-                branch = "Unknown"
-            }
+            branch = BuildDetails.default.branchAndSha
 
             copyrightNotice = Bundle.main.infoDictionary?["NSHumanReadableCopyright"] as? String ?? ""
 

+ 2 - 10
FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift

@@ -9,6 +9,7 @@ extension Settings {
         let resolver: Resolver
         @StateObject var state = StateModel()
         @State private var showShareSheet = false
+        @StateObject private var viewModel = SettingsRootViewModel()
 
         var body: some View {
             Form {
@@ -16,16 +17,7 @@ extension Settings {
                     Toggle("Closed loop", isOn: $state.closedLoop)
                 }
                 header: {
-                    if let expirationDate = Bundle.main.profileExpiration {
-                        Text(
-                            "Trio v\(state.versionNumber) (\(state.buildNumber))\nBranch: \(state.branch) \(state.copyrightNotice)" +
-                                "\nBuild Expires: " + expirationDate
-                        ).textCase(nil)
-                    } else {
-                        Text(
-                            "Trio v\(state.versionNumber) (\(state.buildNumber))\nBranch: \(state.branch) \(state.copyrightNotice)"
-                        )
-                    }
+                    Text(viewModel.headerText).textCase(nil)
                 }
 
                 Section {

+ 30 - 0
FreeAPS/Sources/Modules/Settings/View/SettingsRootViewModel.swift

@@ -0,0 +1,30 @@
+//
+//  SettingsRootViewModel.swift
+//  FreeAPS
+//
+//  Created by Jonas Björkert on 2024-05-09.
+//
+
+import Foundation
+import SwiftUI
+import Swinject
+
+class SettingsRootViewModel: ObservableObject {
+    @Published var headerText: String = ""
+
+    init() {
+        let buildDetails = BuildDetails.default
+        let versionNumber = Bundle.main.releaseVersionNumber ?? "Unknown"
+        let buildNumber = Bundle.main.buildVersionNumber ?? "Unknown"
+        let branch = buildDetails.branchAndSha
+
+        let headerBase = "Trio v\(versionNumber) (\(buildNumber))\nBranch: \(branch)"
+
+        if let expirationDate = buildDetails.calculateExpirationDate() {
+            let formattedDate = DateFormatter.localizedString(from: expirationDate, dateStyle: .medium, timeStyle: .none)
+            headerText = "\(headerBase)\nBuild Expires: \(formattedDate)"
+        } else {
+            headerText = headerBase
+        }
+    }
+}

+ 40 - 0
scripts/capture-build-details.sh

@@ -0,0 +1,40 @@
+#!/bin/sh -e
+
+#  capture-build-details.sh
+#  Trio
+#
+#  Created by Jonas Björkert on 2024-05-08.
+
+# Enable debugging if needed
+#set -x
+
+info_plist_path="${BUILT_PRODUCTS_DIR}/${CONTENTS_FOLDER_PATH}/BuildDetails.plist"
+
+# Ensure the path to BuildDetails.plist is valid.
+if [ "${info_plist_path}" == "/" -o ! -e "${info_plist_path}" ]; then
+    echo "BuildDetails.plist file does not exist at path: ${info_plist_path}" >&2
+    exit 1
+else
+    echo "Gathering build details..."
+
+    # Capture the current date and write it to BuildDetails.plist
+    plutil -replace com-trio-build-date -string "$(date)" "${info_plist_path}"
+
+    # Retrieve the current branch
+    git_branch=$(git symbolic-ref --short -q HEAD)
+
+    # Attempt to retrieve the current tag
+    git_tag=$(git describe --tags --exact-match 2>/dev/null || echo "")
+
+    # Retrieve the current SHA of the latest commit
+    git_commit_sha=$(git log -1 --format="%h" --abbrev=7)
+
+    # Determine the branch or tag information
+    git_branch_or_tag="${git_branch:-${git_tag}}"
+
+    # Update BuildDetails.plist with the branch or tag information
+    plutil -replace com-trio-branch -string "${git_branch_or_tag}" "${info_plist_path}"
+
+    # Update BuildDetails.plist with the SHA information
+    plutil -replace com-trio-commit-sha -string "${git_commit_sha}" "${info_plist_path}"
+fi