Align iOS home common apps and all-functions page with Android.
Add unified HomeCommonMenu/HomeAllFunctions diagnostics, rebuild all-functions from top-level menuList permissions, and document Home and AllFunctions module logic. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -49,25 +49,26 @@ struct HomeCommonMenuStore {
|
||||
self.defaults = defaults
|
||||
}
|
||||
|
||||
/// 按 Android 规则生成默认常用 URI:顶层权限顺序、可用权限过滤、别名去重,超过 4 个取前 4 个。
|
||||
static func defaultCommonURIs(
|
||||
orderedTopLevelURIs: [String],
|
||||
availableURIs: Set<String>
|
||||
) -> [String] {
|
||||
let resolved = orderedTopLevelURIs.compactMap { uri -> String? in
|
||||
/// 按 Android 规则生成默认常用 URI:先取顶层前 4 个 URI,再与 menuList 白名单求交。
|
||||
static func defaultCommonURIs(orderedTopLevelURIs: [String]) -> [String] {
|
||||
let rawTopLevel = orderedTopLevelURIs.compactMap { uri -> String? in
|
||||
let trimmed = uri.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !trimmed.isEmpty else { return nil }
|
||||
let canonical = HomeMenuRouter.canonicalURI(for: trimmed, availableURIs: availableURIs)
|
||||
return Self.isAndroidHomeMenuURI(canonical) && availableURIs.contains(canonical) ? canonical : nil
|
||||
return trimmed.isEmpty ? nil : trimmed
|
||||
}
|
||||
let deduplicated = unique(resolved)
|
||||
if deduplicated.count > 4 {
|
||||
return Array(deduplicated.prefix(4))
|
||||
}
|
||||
return deduplicated
|
||||
let candidates = rawTopLevel.count > 4 ? Array(rawTopLevel.prefix(4)) : rawTopLevel
|
||||
return candidates.filter { isAndroidHomeMenuURI($0) }
|
||||
}
|
||||
|
||||
/// 读取常用 URI;无角色时返回空,无保存或过滤后为空时写入 Android 对齐默认值。
|
||||
/// 返回顶层权限中属于 Android menuList 的 URI,顺序与 API 一致。
|
||||
static func availableTopLevelMenuURIs(from orderedTopLevelURIs: [String]) -> [String] {
|
||||
orderedTopLevelURIs.compactMap { uri -> String? in
|
||||
let trimmed = uri.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !trimmed.isEmpty, isAndroidHomeMenuURI(trimmed) else { return nil }
|
||||
return trimmed
|
||||
}
|
||||
}
|
||||
|
||||
/// 读取常用 URI;无角色时返回空,有 saved 时精确匹配且不回退默认。
|
||||
func load(
|
||||
menuItems: [HomeMenuItem],
|
||||
roleCode: String?,
|
||||
@ -78,55 +79,99 @@ struct HomeCommonMenuStore {
|
||||
guard let roleCode, !roleCode.isEmpty else { return [] }
|
||||
migrateLegacyStorageIfNeeded(roleCode: roleCode, accountScope: accountScope, legacyRoleId: legacyRoleId)
|
||||
|
||||
let availableURIs = Set(menuItems.map(\.uri))
|
||||
let rawTopLevel = topLevelPermissionURIs.compactMap { uri -> String? in
|
||||
let trimmed = uri.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return trimmed.isEmpty ? nil : trimmed
|
||||
}
|
||||
let availableTopLevel = Self.availableTopLevelMenuURIs(from: rawTopLevel)
|
||||
let availableTopLevelSet = Set(availableTopLevel)
|
||||
let key = Self.storageKey(roleCode: roleCode, accountScope: accountScope)
|
||||
let saved = defaults.stringArray(forKey: key)
|
||||
let saved = defaults.stringArray(forKey: key) ?? []
|
||||
let defaultCommon = Self.defaultCommonURIs(orderedTopLevelURIs: rawTopLevel)
|
||||
|
||||
if let saved, !saved.isEmpty {
|
||||
let filtered = Self.unique(saved.compactMap { uri -> String? in
|
||||
let resolved = HomeMenuRouter.canonicalURI(for: uri, availableURIs: availableURIs)
|
||||
return Self.isAndroidHomeMenuURI(resolved) && availableURIs.contains(resolved) ? resolved : nil
|
||||
})
|
||||
if !filtered.isEmpty {
|
||||
save(filtered, roleCode: roleCode, accountScope: accountScope)
|
||||
return filtered
|
||||
}
|
||||
HomeCommonMenuDiagnostics.log(
|
||||
step: "context",
|
||||
fields: [
|
||||
"roleCode": roleCode,
|
||||
"accountScope": accountScope ?? "",
|
||||
"storageKey": key
|
||||
]
|
||||
)
|
||||
HomeCommonMenuDiagnostics.log(
|
||||
step: "rawTopLevel",
|
||||
fields: [
|
||||
"count": String(rawTopLevel.count),
|
||||
"uris": HomeCommonMenuDiagnostics.formatURIList(rawTopLevel)
|
||||
]
|
||||
)
|
||||
HomeCommonMenuDiagnostics.log(
|
||||
step: "availableFunctions",
|
||||
fields: [
|
||||
"count": String(availableTopLevel.count),
|
||||
"uris": HomeCommonMenuDiagnostics.formatURIList(availableTopLevel)
|
||||
]
|
||||
)
|
||||
HomeCommonMenuDiagnostics.log(
|
||||
step: "savedCommon",
|
||||
fields: [
|
||||
"uris": HomeCommonMenuDiagnostics.formatURIList(saved)
|
||||
]
|
||||
)
|
||||
HomeCommonMenuDiagnostics.log(
|
||||
step: "defaultCommon",
|
||||
fields: [
|
||||
"uris": HomeCommonMenuDiagnostics.formatURIList(defaultCommon)
|
||||
]
|
||||
)
|
||||
|
||||
let finalCommon: [String]
|
||||
if !saved.isEmpty {
|
||||
finalCommon = saved.filter { availableTopLevelSet.contains($0) }
|
||||
} else {
|
||||
finalCommon = defaultCommon
|
||||
save(finalCommon, roleCode: roleCode, accountScope: accountScope)
|
||||
}
|
||||
|
||||
let defaultURIs = Self.defaultCommonURIs(
|
||||
orderedTopLevelURIs: topLevelPermissionURIs,
|
||||
availableURIs: availableURIs
|
||||
HomeCommonMenuDiagnostics.log(
|
||||
step: "finalCommon",
|
||||
fields: [
|
||||
"count": String(finalCommon.count),
|
||||
"uris": HomeCommonMenuDiagnostics.formatURIList(finalCommon)
|
||||
]
|
||||
)
|
||||
save(defaultURIs, roleCode: roleCode, accountScope: accountScope)
|
||||
return defaultURIs
|
||||
_ = menuItems
|
||||
return finalCommon
|
||||
}
|
||||
|
||||
/// 将指定 URI 加入常用应用,按别名避免重复添加。
|
||||
func add(_ uri: String, current: [String], menuItems: [HomeMenuItem], roleCode: String?, accountScope: String? = nil) -> [String] {
|
||||
/// 将指定 URI 加入常用应用,使用精确 URI 匹配。
|
||||
func add(
|
||||
_ uri: String,
|
||||
current: [String],
|
||||
topLevelPermissionURIs: [String],
|
||||
roleCode: String?,
|
||||
accountScope: String? = nil
|
||||
) -> [String] {
|
||||
guard let roleCode, !roleCode.isEmpty else { return current }
|
||||
let availableURIs = Set(menuItems.map(\.uri))
|
||||
let resolved = HomeMenuRouter.canonicalURI(for: uri, availableURIs: availableURIs)
|
||||
guard Self.isAndroidHomeMenuURI(resolved), availableURIs.contains(resolved) else { return current }
|
||||
guard !current.contains(where: { HomeMenuRouter.menuAliasKey(for: $0) == HomeMenuRouter.menuAliasKey(for: resolved) }) else {
|
||||
return current
|
||||
}
|
||||
let next = current + [resolved]
|
||||
let availableTopLevel = Set(Self.availableTopLevelMenuURIs(from: topLevelPermissionURIs))
|
||||
let trimmed = uri.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard availableTopLevel.contains(trimmed), !current.contains(trimmed) else { return current }
|
||||
let next = current + [trimmed]
|
||||
save(next, roleCode: roleCode, accountScope: accountScope)
|
||||
return next
|
||||
}
|
||||
|
||||
/// 从常用应用中移除指定 URI,同义 URI 会一起移除。
|
||||
/// 从常用应用中移除指定 URI,使用精确 URI 匹配。
|
||||
func remove(_ uri: String, current: [String], roleCode: String?, accountScope: String? = nil) -> [String] {
|
||||
guard let roleCode, !roleCode.isEmpty else { return current }
|
||||
let aliasKey = HomeMenuRouter.menuAliasKey(for: uri)
|
||||
let next = current.filter { HomeMenuRouter.menuAliasKey(for: $0) != aliasKey }
|
||||
let trimmed = uri.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let next = current.filter { $0 != trimmed }
|
||||
save(next, roleCode: roleCode, accountScope: accountScope)
|
||||
return next
|
||||
}
|
||||
|
||||
/// 保存指定角色的常用 URI。
|
||||
func save(_ uris: [String], roleCode: String, accountScope: String? = nil) {
|
||||
defaults.set(Self.unique(uris), forKey: Self.storageKey(roleCode: roleCode, accountScope: accountScope))
|
||||
defaults.set(uris, forKey: Self.storageKey(roleCode: roleCode, accountScope: accountScope))
|
||||
}
|
||||
|
||||
/// 若账号级 key 无数据,则从旧 roleCode 或 legacy role id key 一次性迁移常用菜单。
|
||||
@ -170,12 +215,4 @@ struct HomeCommonMenuStore {
|
||||
private static func isAndroidHomeMenuURI(_ uri: String) -> Bool {
|
||||
androidHomeMenuURIs.contains(uri)
|
||||
}
|
||||
|
||||
/// 按 URI 别名去重,保留首次出现的 URI。
|
||||
private static func unique(_ uris: [String]) -> [String] {
|
||||
var seen = Set<String>()
|
||||
return uris.filter { uri in
|
||||
seen.insert(HomeMenuRouter.menuAliasKey(for: uri)).inserted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user