|
|
@ -47,4 +47,5 @@ npm-debug.log
|
||||||
screenshot/
|
screenshot/
|
||||||
.opencode/
|
.opencode/
|
||||||
.cursor/
|
.cursor/
|
||||||
|
.codex/
|
||||||
openspec/
|
openspec/
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,264 @@
|
||||||
|
//
|
||||||
|
// AppRestrictShared.swift
|
||||||
|
// Shared between main app and Screen Time extensions.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import FamilyControls
|
||||||
|
import ManagedSettings
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
enum AppRestrictShared {
|
||||||
|
static let appGroupId = "group.cn.zuomeng.jisuloca"
|
||||||
|
static let storeName = ManagedSettingsStore.Name("appRestrict")
|
||||||
|
static let activityName = "appRestrict.monitoring"
|
||||||
|
|
||||||
|
static let selectionKey = "appRestrict.selection"
|
||||||
|
static let enabledTokensKey = "appRestrict.enabledTokens"
|
||||||
|
static let linksKey = "appRestrict.links"
|
||||||
|
static let shieldConfigKey = "appRestrict.shieldConfig"
|
||||||
|
static let customImageFileName = "shield_custom.jpg"
|
||||||
|
|
||||||
|
static var defaults: UserDefaults {
|
||||||
|
UserDefaults(suiteName: appGroupId) ?? .standard
|
||||||
|
}
|
||||||
|
|
||||||
|
static var containerURL: URL? {
|
||||||
|
FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupId)
|
||||||
|
}
|
||||||
|
|
||||||
|
static var customImageURL: URL? {
|
||||||
|
containerURL?.appendingPathComponent(customImageFileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func makeStore() -> ManagedSettingsStore {
|
||||||
|
ManagedSettingsStore(named: storeName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AppRestrictLinkRecord: Codable, Equatable {
|
||||||
|
let catalogId: String
|
||||||
|
let tokenData: Data
|
||||||
|
let displayName: String?
|
||||||
|
let iconURL: String?
|
||||||
|
|
||||||
|
init(catalogId: String, tokenData: Data, displayName: String? = nil, iconURL: String? = nil) {
|
||||||
|
self.catalogId = catalogId
|
||||||
|
self.tokenData = tokenData
|
||||||
|
self.displayName = displayName
|
||||||
|
self.iconURL = iconURL
|
||||||
|
}
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
catalogId = try container.decode(String.self, forKey: .catalogId)
|
||||||
|
tokenData = try container.decode(Data.self, forKey: .tokenData)
|
||||||
|
displayName = try container.decodeIfPresent(String.self, forKey: .displayName)
|
||||||
|
iconURL = try container.decodeIfPresent(String.self, forKey: .iconURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AppRestrictShieldConfig: Codable, Equatable {
|
||||||
|
enum ImageSource: String, Codable {
|
||||||
|
case presetDefault
|
||||||
|
case presetFocus
|
||||||
|
case presetCalm
|
||||||
|
case album
|
||||||
|
}
|
||||||
|
|
||||||
|
var title: String
|
||||||
|
var subtitle: String
|
||||||
|
var primaryButtonLabel: String
|
||||||
|
var imageSource: ImageSource
|
||||||
|
|
||||||
|
static let `default` = AppRestrictShieldConfig(
|
||||||
|
title: "该应用已被锁定",
|
||||||
|
subtitle: "专注当下,稍后再回来吧",
|
||||||
|
primaryButtonLabel: "知道了",
|
||||||
|
imageSource: .presetDefault
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AppRestrictTokenCodec {
|
||||||
|
static func encode(_ token: ApplicationToken) -> Data? {
|
||||||
|
try? PropertyListEncoder().encode(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func decode(_ data: Data) -> ApplicationToken? {
|
||||||
|
try? PropertyListDecoder().decode(ApplicationToken.self, from: data)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func encodeSelection(_ selection: FamilyActivitySelection) -> Data? {
|
||||||
|
try? PropertyListEncoder().encode(selection)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func decodeSelection(_ data: Data) -> FamilyActivitySelection? {
|
||||||
|
try? PropertyListDecoder().decode(FamilyActivitySelection.self, from: data)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func encodeTokenSet(_ tokens: Set<ApplicationToken>) -> Data? {
|
||||||
|
let datas = tokens.compactMap { encode($0) }
|
||||||
|
return try? PropertyListEncoder().encode(datas)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func decodeTokenSet(_ data: Data) -> Set<ApplicationToken> {
|
||||||
|
guard let datas = try? PropertyListDecoder().decode([Data].self, from: data) else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return Set(datas.compactMap { decode($0) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AppRestrictSharedStore {
|
||||||
|
static var selection: FamilyActivitySelection {
|
||||||
|
get {
|
||||||
|
guard let data = AppRestrictShared.defaults.data(forKey: AppRestrictShared.selectionKey),
|
||||||
|
let value = AppRestrictTokenCodec.decodeSelection(data) else {
|
||||||
|
return FamilyActivitySelection()
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if let data = AppRestrictTokenCodec.encodeSelection(newValue) {
|
||||||
|
AppRestrictShared.defaults.set(data, forKey: AppRestrictShared.selectionKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static var enabledTokens: Set<ApplicationToken> {
|
||||||
|
get {
|
||||||
|
guard let data = AppRestrictShared.defaults.data(forKey: AppRestrictShared.enabledTokensKey) else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return AppRestrictTokenCodec.decodeTokenSet(data)
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if let data = AppRestrictTokenCodec.encodeTokenSet(newValue) {
|
||||||
|
AppRestrictShared.defaults.set(data, forKey: AppRestrictShared.enabledTokensKey)
|
||||||
|
} else {
|
||||||
|
AppRestrictShared.defaults.removeObject(forKey: AppRestrictShared.enabledTokensKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static var links: [AppRestrictLinkRecord] {
|
||||||
|
get {
|
||||||
|
guard let data = AppRestrictShared.defaults.data(forKey: AppRestrictShared.linksKey),
|
||||||
|
let value = try? JSONDecoder().decode([AppRestrictLinkRecord].self, from: data) else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if let data = try? JSONEncoder().encode(newValue) {
|
||||||
|
AppRestrictShared.defaults.set(data, forKey: AppRestrictShared.linksKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static var shieldConfig: AppRestrictShieldConfig {
|
||||||
|
get {
|
||||||
|
guard let data = AppRestrictShared.defaults.data(forKey: AppRestrictShared.shieldConfigKey),
|
||||||
|
let value = try? JSONDecoder().decode(AppRestrictShieldConfig.self, from: data) else {
|
||||||
|
return .default
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if let data = try? JSONEncoder().encode(newValue) {
|
||||||
|
AppRestrictShared.defaults.set(data, forKey: AppRestrictShared.shieldConfigKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func linkRecord(for token: ApplicationToken) -> AppRestrictLinkRecord? {
|
||||||
|
guard let data = AppRestrictTokenCodec.encode(token) else { return nil }
|
||||||
|
return links.first(where: { $0.tokenData == data })
|
||||||
|
}
|
||||||
|
|
||||||
|
static func catalogId(for token: ApplicationToken) -> String? {
|
||||||
|
linkRecord(for: token)?.catalogId
|
||||||
|
}
|
||||||
|
|
||||||
|
static func setLink(
|
||||||
|
catalogId: String,
|
||||||
|
token: ApplicationToken,
|
||||||
|
displayName: String? = nil,
|
||||||
|
iconURL: String? = nil
|
||||||
|
) {
|
||||||
|
guard let data = AppRestrictTokenCodec.encode(token) else { return }
|
||||||
|
var list = links.filter { $0.tokenData != data && $0.catalogId != catalogId }
|
||||||
|
list.append(AppRestrictLinkRecord(
|
||||||
|
catalogId: catalogId,
|
||||||
|
tokenData: data,
|
||||||
|
displayName: displayName,
|
||||||
|
iconURL: iconURL
|
||||||
|
))
|
||||||
|
links = list
|
||||||
|
}
|
||||||
|
|
||||||
|
static func removeLink(for token: ApplicationToken) {
|
||||||
|
guard let data = AppRestrictTokenCodec.encode(token) else { return }
|
||||||
|
links = links.filter { $0.tokenData != data }
|
||||||
|
}
|
||||||
|
|
||||||
|
static func applyShield(for tokens: Set<ApplicationToken>) {
|
||||||
|
let store = AppRestrictShared.makeStore()
|
||||||
|
if tokens.isEmpty {
|
||||||
|
store.clearAllSettings()
|
||||||
|
} else {
|
||||||
|
store.shield.applications = tokens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func clearShield() {
|
||||||
|
AppRestrictShared.makeStore().clearAllSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
static func loadShieldImage() -> UIImage? {
|
||||||
|
let config = shieldConfig
|
||||||
|
switch config.imageSource {
|
||||||
|
case .album:
|
||||||
|
if let url = AppRestrictShared.customImageURL,
|
||||||
|
let data = try? Data(contentsOf: url),
|
||||||
|
let image = UIImage(data: data) {
|
||||||
|
return image
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case .presetDefault:
|
||||||
|
return UIImage(named: "AppRestrict/shield_preset_default")
|
||||||
|
?? UIImage(systemName: "lock.shield.fill")
|
||||||
|
case .presetFocus:
|
||||||
|
return UIImage(named: "AppRestrict/shield_preset_focus")
|
||||||
|
?? UIImage(systemName: "brain.head.profile")
|
||||||
|
case .presetCalm:
|
||||||
|
return UIImage(named: "AppRestrict/shield_preset_calm")
|
||||||
|
?? UIImage(systemName: "leaf.fill")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func saveCustomImage(_ image: UIImage) -> Bool {
|
||||||
|
guard let url = AppRestrictShared.customImageURL else { return false }
|
||||||
|
let resized = image.appRestrictResized(maxSide: 720)
|
||||||
|
guard let data = resized.jpegData(compressionQuality: 0.82) else { return false }
|
||||||
|
do {
|
||||||
|
try data.write(to: url, options: .atomic)
|
||||||
|
return true
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension UIImage {
|
||||||
|
func appRestrictResized(maxSide: CGFloat) -> UIImage {
|
||||||
|
let maxCurrent = max(size.width, size.height)
|
||||||
|
guard maxCurrent > maxSide, maxCurrent > 0 else { return self }
|
||||||
|
let scale = maxSide / maxCurrent
|
||||||
|
let newSize = CGSize(width: size.width * scale, height: size.height * scale)
|
||||||
|
let renderer = UIGraphicsImageRenderer(size: newSize)
|
||||||
|
return renderer.image { _ in
|
||||||
|
draw(in: CGRect(origin: .zero, size: newSize))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.developer.family-controls</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.application-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>group.cn.zuomeng.jisuloca</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
import DeviceActivity
|
||||||
|
import FamilyControls
|
||||||
|
import Foundation
|
||||||
|
import ManagedSettings
|
||||||
|
|
||||||
|
final class DeviceActivityMonitorExtension: DeviceActivityMonitor {
|
||||||
|
override func intervalDidStart(for activity: DeviceActivityName) {
|
||||||
|
super.intervalDidStart(for: activity)
|
||||||
|
let tokens = AppRestrictSharedStore.enabledTokens
|
||||||
|
AppRestrictSharedStore.applyShield(for: tokens)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func intervalDidEnd(for activity: DeviceActivityName) {
|
||||||
|
super.intervalDidEnd(for: activity)
|
||||||
|
// Keep shields while user still has enabled tokens; only clear when empty.
|
||||||
|
let tokens = AppRestrictSharedStore.enabledTokens
|
||||||
|
if tokens.isEmpty {
|
||||||
|
AppRestrictSharedStore.clearShield()
|
||||||
|
} else {
|
||||||
|
AppRestrictSharedStore.applyShield(for: tokens)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func eventDidReachThreshold(_ event: DeviceActivityEvent.Name, activity: DeviceActivityName) {
|
||||||
|
super.eventDidReachThreshold(event, activity: activity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>DeviceActivityMonitor</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>$(PRODUCT_NAME)</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>XPC!</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1</string>
|
||||||
|
<key>NSExtension</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSExtensionPointIdentifier</key>
|
||||||
|
<string>com.apple.deviceactivity.monitor-extension</string>
|
||||||
|
<key>NSExtensionPrincipalClass</key>
|
||||||
|
<string>$(PRODUCT_MODULE_NAME).DeviceActivityMonitorExtension</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
2
Podfile
|
|
@ -1,6 +1,6 @@
|
||||||
# Uncomment the next line to define a global platform for your project
|
# Uncomment the next line to define a global platform for your project
|
||||||
source 'https://gitee.com/mirrors/CocoaPods-Specs.git'
|
source 'https://gitee.com/mirrors/CocoaPods-Specs.git'
|
||||||
platform :ios, '15.0'
|
platform :ios, '16.0'
|
||||||
use_frameworks!
|
use_frameworks!
|
||||||
target 'QuickLocation' do
|
target 'QuickLocation' do
|
||||||
# Comment the next line if you don't want to use dynamic frameworks
|
# Comment the next line if you don't want to use dynamic frameworks
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
04532656C70B8C5AD6A8161E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 226CA98495C2884EBB1D1372 /* Foundation.framework */; };
|
||||||
|
071EBEFAB7A70D8554B38024 /* DeviceActivity.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88CC9D32980864EAD9F72004 /* DeviceActivity.framework */; };
|
||||||
|
091AC874363B91A91D717758 /* ManagedSettings.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2EE79029677394A46B143E75 /* ManagedSettings.framework */; };
|
||||||
305A76882FCA8C7000227D26 /* MoyaProvider+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A74C62FCA8C7000227D26 /* MoyaProvider+Rx.swift */; };
|
305A76882FCA8C7000227D26 /* MoyaProvider+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A74C62FCA8C7000227D26 /* MoyaProvider+Rx.swift */; };
|
||||||
305A76892FCA8C7000227D26 /* Observable+Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A74C72FCA8C7000227D26 /* Observable+Response.swift */; };
|
305A76892FCA8C7000227D26 /* Observable+Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A74C72FCA8C7000227D26 /* Observable+Response.swift */; };
|
||||||
305A768A2FCA8C7000227D26 /* Single+Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A74C82FCA8C7000227D26 /* Single+Response.swift */; };
|
305A768A2FCA8C7000227D26 /* Single+Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A74C82FCA8C7000227D26 /* Single+Response.swift */; };
|
||||||
|
|
@ -47,7 +50,6 @@
|
||||||
305A76AD2FCA8C7000227D26 /* UIApplicationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A74F62FCA8C7000227D26 /* UIApplicationExtension.swift */; };
|
305A76AD2FCA8C7000227D26 /* UIApplicationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A74F62FCA8C7000227D26 /* UIApplicationExtension.swift */; };
|
||||||
305A76AE2FCA8C7000227D26 /* UIButton+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A74F72FCA8C7000227D26 /* UIButton+Extension.swift */; };
|
305A76AE2FCA8C7000227D26 /* UIButton+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A74F72FCA8C7000227D26 /* UIButton+Extension.swift */; };
|
||||||
305A76AF2FCA8C7000227D26 /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A74F82FCA8C7000227D26 /* UIColor+Extension.swift */; };
|
305A76AF2FCA8C7000227D26 /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A74F82FCA8C7000227D26 /* UIColor+Extension.swift */; };
|
||||||
55B217A33022F0B100784706 /* UIDevice+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B217A33022F0B100784707 /* UIDevice+Extension.swift */; };
|
|
||||||
305A76B02FCA8C7000227D26 /* UIFont+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A74F92FCA8C7000227D26 /* UIFont+Extension.swift */; };
|
305A76B02FCA8C7000227D26 /* UIFont+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A74F92FCA8C7000227D26 /* UIFont+Extension.swift */; };
|
||||||
305A76B12FCA8C7000227D26 /* UIImage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A74FA2FCA8C7000227D26 /* UIImage+Extension.swift */; };
|
305A76B12FCA8C7000227D26 /* UIImage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A74FA2FCA8C7000227D26 /* UIImage+Extension.swift */; };
|
||||||
305A76B22FCA8C7000227D26 /* UIImage+Resource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A74FB2FCA8C7000227D26 /* UIImage+Resource.swift */; };
|
305A76B22FCA8C7000227D26 /* UIImage+Resource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305A74FB2FCA8C7000227D26 /* UIImage+Resource.swift */; };
|
||||||
|
|
@ -187,11 +189,6 @@
|
||||||
30A87A642FEE75520095E7C6 /* CreateBubbleTipsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A632FEE75520095E7C6 /* CreateBubbleTipsView.swift */; };
|
30A87A642FEE75520095E7C6 /* CreateBubbleTipsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A632FEE75520095E7C6 /* CreateBubbleTipsView.swift */; };
|
||||||
30A87A662FEE843E0095E7C6 /* CreateBubbleDoneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A652FEE843E0095E7C6 /* CreateBubbleDoneView.swift */; };
|
30A87A662FEE843E0095E7C6 /* CreateBubbleDoneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A652FEE843E0095E7C6 /* CreateBubbleDoneView.swift */; };
|
||||||
30A87A682FEE86560095E7C6 /* CreateBubblePopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A672FEE86560095E7C6 /* CreateBubblePopView.swift */; };
|
30A87A682FEE86560095E7C6 /* CreateBubblePopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A672FEE86560095E7C6 /* CreateBubblePopView.swift */; };
|
||||||
55B219C13024C00100784722 /* BubbleHeroView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B219C13024C00100784721 /* BubbleHeroView.swift */; };
|
|
||||||
55B219C13024C00100784724 /* CreateBubbleSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B219C13024C00100784723 /* CreateBubbleSetupView.swift */; };
|
|
||||||
55B219C13024C00100784726 /* BubbleKnowledgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B219C13024C00100784725 /* BubbleKnowledgeView.swift */; };
|
|
||||||
55B219C13024C00100784728 /* BubbleKnowledgeVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B219C13024C00100784727 /* BubbleKnowledgeVC.swift */; };
|
|
||||||
55C219C13024C00100784731 /* SearchLocationHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55C219C13024C00100784730 /* SearchLocationHeaderView.swift */; };
|
|
||||||
30A87A6B2FEF5B950095E7C6 /* SearchLocationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A6A2FEF5B950095E7C6 /* SearchLocationView.swift */; };
|
30A87A6B2FEF5B950095E7C6 /* SearchLocationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A6A2FEF5B950095E7C6 /* SearchLocationView.swift */; };
|
||||||
30A87A6D2FEF5BA10095E7C6 /* SearchLocationVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A6C2FEF5BA10095E7C6 /* SearchLocationVC.swift */; };
|
30A87A6D2FEF5BA10095E7C6 /* SearchLocationVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A6C2FEF5BA10095E7C6 /* SearchLocationVC.swift */; };
|
||||||
30A87A6F2FEF7BE40095E7C6 /* SearchLocationResultVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A6E2FEF7BE40095E7C6 /* SearchLocationResultVC.swift */; };
|
30A87A6F2FEF7BE40095E7C6 /* SearchLocationResultVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A87A6E2FEF7BE40095E7C6 /* SearchLocationResultVC.swift */; };
|
||||||
|
|
@ -239,8 +236,6 @@
|
||||||
30D74AAB2FE8C7700050EB2C /* GPSSignalHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D74AAA2FE8C7700050EB2C /* GPSSignalHelper.swift */; };
|
30D74AAB2FE8C7700050EB2C /* GPSSignalHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D74AAA2FE8C7700050EB2C /* GPSSignalHelper.swift */; };
|
||||||
30D74AAE2FEA13E00050EB2C /* ScheduleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D74AAD2FEA13E00050EB2C /* ScheduleView.swift */; };
|
30D74AAE2FEA13E00050EB2C /* ScheduleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D74AAD2FEA13E00050EB2C /* ScheduleView.swift */; };
|
||||||
30D74AB02FEA13ED0050EB2C /* ScheduleVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D74AAF2FEA13ED0050EB2C /* ScheduleVC.swift */; };
|
30D74AB02FEA13ED0050EB2C /* ScheduleVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D74AAF2FEA13ED0050EB2C /* ScheduleVC.swift */; };
|
||||||
55B218B13024B00100784722 /* FeatureIntroVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B218B13024B00100784721 /* FeatureIntroVC.swift */; };
|
|
||||||
55B218B13024B00100784724 /* FeatureIntroView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B218B13024B00100784723 /* FeatureIntroView.swift */; };
|
|
||||||
30D74AB22FEA1D5D0050EB2C /* ScheduleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D74AB12FEA1D5D0050EB2C /* ScheduleViewModel.swift */; };
|
30D74AB22FEA1D5D0050EB2C /* ScheduleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D74AB12FEA1D5D0050EB2C /* ScheduleViewModel.swift */; };
|
||||||
30D74AB42FEA25B90050EB2C /* ViewedModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D74AB32FEA25B90050EB2C /* ViewedModel.swift */; };
|
30D74AB42FEA25B90050EB2C /* ViewedModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D74AB32FEA25B90050EB2C /* ViewedModel.swift */; };
|
||||||
30D74AB62FEA34FF0050EB2C /* ItineraryAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D74AB52FEA34FF0050EB2C /* ItineraryAPI.swift */; };
|
30D74AB62FEA34FF0050EB2C /* ItineraryAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D74AB52FEA34FF0050EB2C /* ItineraryAPI.swift */; };
|
||||||
|
|
@ -300,11 +295,15 @@
|
||||||
30EFF3E52FDAA93400EB35D4 /* PrivacyPolicyVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EFF3E42FDAA93300EB35D4 /* PrivacyPolicyVC.swift */; };
|
30EFF3E52FDAA93400EB35D4 /* PrivacyPolicyVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EFF3E42FDAA93300EB35D4 /* PrivacyPolicyVC.swift */; };
|
||||||
30EFF3E72FDAA93D00EB35D4 /* PrivacyPolicyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EFF3E62FDAA93D00EB35D4 /* PrivacyPolicyView.swift */; };
|
30EFF3E72FDAA93D00EB35D4 /* PrivacyPolicyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EFF3E62FDAA93D00EB35D4 /* PrivacyPolicyView.swift */; };
|
||||||
30EFF3E82FCA8C7000227D26 /* AuthenticationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 30EFF3E92FCA8C7000227D26 /* AuthenticationServices.framework */; };
|
30EFF3E82FCA8C7000227D26 /* AuthenticationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 30EFF3E92FCA8C7000227D26 /* AuthenticationServices.framework */; };
|
||||||
|
4392B1D9C125D873FCFCDDC9 /* app_catalog.json in Resources */ = {isa = PBXBuildFile; fileRef = 3C79865C3ED165AFDD091C29 /* app_catalog.json */; };
|
||||||
|
488FF21773B8E5021D8662B5 /* AppRestrictView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 787F87DD1CEBED1A4E1E9FC7 /* AppRestrictView.swift */; };
|
||||||
|
4AF6E5954D73E21F31F7A16B /* AppRestrictVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0112B7865829D5B6A4A0BD0 /* AppRestrictVC.swift */; };
|
||||||
557E8D5330187A5C0032AB51 /* BaseNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 557E8D5230187A5C0032AB51 /* BaseNavigationView.swift */; };
|
557E8D5330187A5C0032AB51 /* BaseNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 557E8D5230187A5C0032AB51 /* BaseNavigationView.swift */; };
|
||||||
55B2179130217D6600784774 /* HomeView2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B2179030217D6600784774 /* HomeView2.swift */; };
|
55B2179130217D6600784774 /* HomeView2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B2179030217D6600784774 /* HomeView2.swift */; };
|
||||||
55B2179330218CE100784774 /* GroupMemberView2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B2179230218CE100784774 /* GroupMemberView2.swift */; };
|
55B2179330218CE100784774 /* GroupMemberView2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B2179230218CE100784774 /* GroupMemberView2.swift */; };
|
||||||
55B217953022DD6B00784774 /* 荆南波波黑-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 55B217943022DD6B00784774 /* 荆南波波黑-Bold.ttf */; };
|
55B217953022DD6B00784774 /* 荆南波波黑-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 55B217943022DD6B00784774 /* 荆南波波黑-Bold.ttf */; };
|
||||||
55B217A13022F0A000784702 /* FontManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B217A03022F0A000784701 /* FontManager.swift */; };
|
55B217A13022F0A000784702 /* FontManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B217A03022F0A000784701 /* FontManager.swift */; };
|
||||||
|
55B217A33022F0B100784706 /* UIDevice+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B217A33022F0B100784707 /* UIDevice+Extension.swift */; };
|
||||||
55B217B13022F0A000784711 /* GroupItineraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B217B03022F0A000784710 /* GroupItineraryView.swift */; };
|
55B217B13022F0A000784711 /* GroupItineraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B217B03022F0A000784710 /* GroupItineraryView.swift */; };
|
||||||
55B217C13022F0A000784721 /* MinePhotoWallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B217C03022F0A000784720 /* MinePhotoWallView.swift */; };
|
55B217C13022F0A000784721 /* MinePhotoWallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B217C03022F0A000784720 /* MinePhotoWallView.swift */; };
|
||||||
55B217C33022F0A000784723 /* MemberPhoneReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B217C23022F0A000784722 /* MemberPhoneReportView.swift */; };
|
55B217C33022F0A000784723 /* MemberPhoneReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B217C23022F0A000784722 /* MemberPhoneReportView.swift */; };
|
||||||
|
|
@ -322,14 +321,86 @@
|
||||||
55B218A13024A00100784708 /* CancelAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B218A13024A00100784707 /* CancelAccountView.swift */; };
|
55B218A13024A00100784708 /* CancelAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B218A13024A00100784707 /* CancelAccountView.swift */; };
|
||||||
55B218A13024A0010078470A /* MemberInfoVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B218A13024A00100784709 /* MemberInfoVC.swift */; };
|
55B218A13024A0010078470A /* MemberInfoVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B218A13024A00100784709 /* MemberInfoVC.swift */; };
|
||||||
55B218A13024A0010078470C /* MemberInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B218A13024A0010078470B /* MemberInfoView.swift */; };
|
55B218A13024A0010078470C /* MemberInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B218A13024A0010078470B /* MemberInfoView.swift */; };
|
||||||
|
55B218B13024B00100784722 /* FeatureIntroVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B218B13024B00100784721 /* FeatureIntroVC.swift */; };
|
||||||
|
55B218B13024B00100784724 /* FeatureIntroView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B218B13024B00100784723 /* FeatureIntroView.swift */; };
|
||||||
|
55B219C13024C00100784722 /* BubbleHeroView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B219C13024C00100784721 /* BubbleHeroView.swift */; };
|
||||||
|
55B219C13024C00100784724 /* CreateBubbleSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B219C13024C00100784723 /* CreateBubbleSetupView.swift */; };
|
||||||
|
55B219C13024C00100784726 /* BubbleKnowledgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B219C13024C00100784725 /* BubbleKnowledgeView.swift */; };
|
||||||
|
55B219C13024C00100784728 /* BubbleKnowledgeVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B219C13024C00100784727 /* BubbleKnowledgeVC.swift */; };
|
||||||
55B21D6E3023117900784774 /* 优设标题黑_猫啃网.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 55B21D6D3023117900784774 /* 优设标题黑_猫啃网.ttf */; };
|
55B21D6E3023117900784774 /* 优设标题黑_猫啃网.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 55B21D6D3023117900784774 /* 优设标题黑_猫啃网.ttf */; };
|
||||||
55BF752D2FFE53F70055DA57 /* LocationPermissionPopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55BF752C2FFE53F70055DA57 /* LocationPermissionPopView.swift */; };
|
55BF752D2FFE53F70055DA57 /* LocationPermissionPopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55BF752C2FFE53F70055DA57 /* LocationPermissionPopView.swift */; };
|
||||||
55BF75352FFF91690055DA57 /* InteractionEmojiCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55BF75342FFF91690055DA57 /* InteractionEmojiCell.swift */; };
|
55BF75352FFF91690055DA57 /* InteractionEmojiCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55BF75342FFF91690055DA57 /* InteractionEmojiCell.swift */; };
|
||||||
|
55C219C13024C00100784731 /* SearchLocationHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55C219C13024C00100784730 /* SearchLocationHeaderView.swift */; };
|
||||||
|
60BEE25E61F3DE24976A7A47 /* ManagedSettingsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FE44DF1C46BDA7B98BC7859 /* ManagedSettingsUI.framework */; };
|
||||||
|
68F0D48EB76039050533197C /* AppRestrictShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB29D0618C4BE76F2CA5261 /* AppRestrictShared.swift */; };
|
||||||
|
6D90E8FFE4D597E888E6A224 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E326B06736FE04DD7A5E96D /* UIKit.framework */; };
|
||||||
|
77731F107613C247B7FE98C8 /* ManagedSettings.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2EE79029677394A46B143E75 /* ManagedSettings.framework */; };
|
||||||
|
7B9D095A034C9FA5E69F55BA /* AppRestrictShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB29D0618C4BE76F2CA5261 /* AppRestrictShared.swift */; };
|
||||||
|
80F749310651808BF00F95CF /* DeviceActivityMonitorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64156B4A0E806901AB223FA /* DeviceActivityMonitorExtension.swift */; };
|
||||||
|
830C70B1B53657BB020721DA /* AppRestrictManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AEEA6CEA0CEAAB42C22752B /* AppRestrictManager.swift */; };
|
||||||
|
89647BC98E0FF342511D42CF /* FamilyControls.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4CD6B965A124276661A84C4C /* FamilyControls.framework */; };
|
||||||
A1B2C3D42FDABC8C009215C1 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = A1B2C3D52FDABC8C009215C1 /* PrivacyInfo.xcprivacy */; };
|
A1B2C3D42FDABC8C009215C1 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = A1B2C3D52FDABC8C009215C1 /* PrivacyInfo.xcprivacy */; };
|
||||||
|
A42DCB2A66E694B7CC50033B /* AppRestrictCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89519661B70A0ADEC2A76032 /* AppRestrictCell.swift */; };
|
||||||
|
A55D8DE8A1F679EBF7B562D1 /* AppCatalogStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47922567FCD34DBE9186F8FD /* AppCatalogStore.swift */; };
|
||||||
|
A97A15CB71FDCA432974A768 /* AppRestrictShieldSettingsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFBDE92B94E7004F6B642AD /* AppRestrictShieldSettingsVC.swift */; };
|
||||||
|
B38B130801018F93E81D52C5 /* ManagedSettings.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2EE79029677394A46B143E75 /* ManagedSettings.framework */; };
|
||||||
|
B52F13A261ED273A3A58183F /* SelectActivityVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BAE595D39202A2958737BD3 /* SelectActivityVC.swift */; };
|
||||||
|
C15D41A1770F5B4A02E5FC59 /* AppRestrictShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB29D0618C4BE76F2CA5261 /* AppRestrictShared.swift */; };
|
||||||
|
C3C4ACC1AE3CAF5BA21328B7 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 226CA98495C2884EBB1D1372 /* Foundation.framework */; };
|
||||||
|
C6A99DEE361AB31477F88475 /* LockDistractView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC46E80B848E4012D192E4CE /* LockDistractView.swift */; };
|
||||||
|
CC53DE2CA88E529129547A52 /* ShieldConfigurationExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 0C47E0012491DB1371FD5E53 /* ShieldConfigurationExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
|
D3C101B198090A9F4D93E44F /* FamilyControls.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4CD6B965A124276661A84C4C /* FamilyControls.framework */; };
|
||||||
|
D60B79067C80CD6AF50D63FE /* FamilyControls.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4CD6B965A124276661A84C4C /* FamilyControls.framework */; };
|
||||||
D698E9C56D6F2C152772131D /* Pods_QuickLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 475D33CAFA1E1911EB1F8D9F /* Pods_QuickLocation.framework */; };
|
D698E9C56D6F2C152772131D /* Pods_QuickLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 475D33CAFA1E1911EB1F8D9F /* Pods_QuickLocation.framework */; };
|
||||||
|
D765496DAEBAE6A8C98DB843 /* ShieldConfigurationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08002267FC4390B20D3510B3 /* ShieldConfigurationExtension.swift */; };
|
||||||
|
DB67C2EB2521042829A3DBA5 /* PairGuideStepsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E5F5DC8694A001BF0C47147 /* PairGuideStepsView.swift */; };
|
||||||
|
DBFE43A8D2406BF4EF92C2EF /* DeviceActivityMonitorExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 98E91E51811235EAD0985421 /* DeviceActivityMonitorExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
|
E3D4129BDA33540BA4E1292E /* LockDistractVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9CFFB14BA2ECC259151BCDA /* LockDistractVC.swift */; };
|
||||||
|
E6E5C711C9BD9F6C929BB062 /* DeviceActivity.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88CC9D32980864EAD9F72004 /* DeviceActivity.framework */; };
|
||||||
|
E866CAAE7D4E74EB7AD608B9 /* ITunesSearchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE3D39F325FC7624C1D914D6 /* ITunesSearchService.swift */; };
|
||||||
|
EA13C1E21EC94958AC9755A8 /* FamilyActivityPickerHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF9D223AB35744F92E0E3E77 /* FamilyActivityPickerHost.swift */; };
|
||||||
|
F53E5BD11D671E5CED4E7408 /* ManagedSettingsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FE44DF1C46BDA7B98BC7859 /* ManagedSettingsUI.framework */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
1B096A922D2FECE87D42D1D4 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 3E4359002FC48D26003470A5 /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = E4257D157F2E24904E9B6630;
|
||||||
|
remoteInfo = ShieldConfigurationExtension;
|
||||||
|
};
|
||||||
|
BCE404021C2F9BD8BCC26E94 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 3E4359002FC48D26003470A5 /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = B626CA1007794E780B5B1CB3;
|
||||||
|
remoteInfo = DeviceActivityMonitorExtension;
|
||||||
|
};
|
||||||
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
63D74ED0B1CEAFA8DAC0A6C0 /* Embed Foundation Extensions */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "";
|
||||||
|
dstSubfolderSpec = 13;
|
||||||
|
files = (
|
||||||
|
DBFE43A8D2406BF4EF92C2EF /* DeviceActivityMonitorExtension.appex in Embed Foundation Extensions */,
|
||||||
|
CC53DE2CA88E529129547A52 /* ShieldConfigurationExtension.appex in Embed Foundation Extensions */,
|
||||||
|
);
|
||||||
|
name = "Embed Foundation Extensions";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
08002267FC4390B20D3510B3 /* ShieldConfigurationExtension.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ShieldConfigurationExtension.swift; path = ShieldConfigurationExtension/ShieldConfigurationExtension.swift; sourceTree = "<group>"; };
|
||||||
|
0C47E0012491DB1371FD5E53 /* ShieldConfigurationExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShieldConfigurationExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
1AB29D0618C4BE76F2CA5261 /* AppRestrictShared.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AppRestrictShared.swift; path = AppRestrictShared/AppRestrictShared.swift; sourceTree = "<group>"; };
|
||||||
|
1E326B06736FE04DD7A5E96D /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
|
||||||
|
226CA98495C2884EBB1D1372 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
|
||||||
|
2EE79029677394A46B143E75 /* ManagedSettings.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ManagedSettings.framework; path = System/Library/Frameworks/ManagedSettings.framework; sourceTree = SDKROOT; };
|
||||||
305A74C62FCA8C7000227D26 /* MoyaProvider+Rx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MoyaProvider+Rx.swift"; sourceTree = "<group>"; };
|
305A74C62FCA8C7000227D26 /* MoyaProvider+Rx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MoyaProvider+Rx.swift"; sourceTree = "<group>"; };
|
||||||
305A74C72FCA8C7000227D26 /* Observable+Response.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Observable+Response.swift"; sourceTree = "<group>"; };
|
305A74C72FCA8C7000227D26 /* Observable+Response.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Observable+Response.swift"; sourceTree = "<group>"; };
|
||||||
305A74C82FCA8C7000227D26 /* Single+Response.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Single+Response.swift"; sourceTree = "<group>"; };
|
305A74C82FCA8C7000227D26 /* Single+Response.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Single+Response.swift"; sourceTree = "<group>"; };
|
||||||
|
|
@ -372,7 +443,6 @@
|
||||||
305A74F62FCA8C7000227D26 /* UIApplicationExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIApplicationExtension.swift; sourceTree = "<group>"; };
|
305A74F62FCA8C7000227D26 /* UIApplicationExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIApplicationExtension.swift; sourceTree = "<group>"; };
|
||||||
305A74F72FCA8C7000227D26 /* UIButton+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+Extension.swift"; sourceTree = "<group>"; };
|
305A74F72FCA8C7000227D26 /* UIButton+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+Extension.swift"; sourceTree = "<group>"; };
|
||||||
305A74F82FCA8C7000227D26 /* UIColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = "<group>"; };
|
305A74F82FCA8C7000227D26 /* UIColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = "<group>"; };
|
||||||
55B217A33022F0B100784707 /* UIDevice+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+Extension.swift"; sourceTree = "<group>"; };
|
|
||||||
305A74F92FCA8C7000227D26 /* UIFont+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+Extension.swift"; sourceTree = "<group>"; };
|
305A74F92FCA8C7000227D26 /* UIFont+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+Extension.swift"; sourceTree = "<group>"; };
|
||||||
305A74FA2FCA8C7000227D26 /* UIImage+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Extension.swift"; sourceTree = "<group>"; };
|
305A74FA2FCA8C7000227D26 /* UIImage+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Extension.swift"; sourceTree = "<group>"; };
|
||||||
305A74FB2FCA8C7000227D26 /* UIImage+Resource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Resource.swift"; sourceTree = "<group>"; };
|
305A74FB2FCA8C7000227D26 /* UIImage+Resource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Resource.swift"; sourceTree = "<group>"; };
|
||||||
|
|
@ -518,11 +588,6 @@
|
||||||
30A87A632FEE75520095E7C6 /* CreateBubbleTipsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateBubbleTipsView.swift; sourceTree = "<group>"; };
|
30A87A632FEE75520095E7C6 /* CreateBubbleTipsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateBubbleTipsView.swift; sourceTree = "<group>"; };
|
||||||
30A87A652FEE843E0095E7C6 /* CreateBubbleDoneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateBubbleDoneView.swift; sourceTree = "<group>"; };
|
30A87A652FEE843E0095E7C6 /* CreateBubbleDoneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateBubbleDoneView.swift; sourceTree = "<group>"; };
|
||||||
30A87A672FEE86560095E7C6 /* CreateBubblePopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateBubblePopView.swift; sourceTree = "<group>"; };
|
30A87A672FEE86560095E7C6 /* CreateBubblePopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateBubblePopView.swift; sourceTree = "<group>"; };
|
||||||
55B219C13024C00100784721 /* BubbleHeroView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BubbleHeroView.swift; sourceTree = "<group>"; };
|
|
||||||
55B219C13024C00100784723 /* CreateBubbleSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateBubbleSetupView.swift; sourceTree = "<group>"; };
|
|
||||||
55B219C13024C00100784725 /* BubbleKnowledgeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BubbleKnowledgeView.swift; sourceTree = "<group>"; };
|
|
||||||
55B219C13024C00100784727 /* BubbleKnowledgeVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BubbleKnowledgeVC.swift; sourceTree = "<group>"; };
|
|
||||||
55C219C13024C00100784730 /* SearchLocationHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchLocationHeaderView.swift; sourceTree = "<group>"; };
|
|
||||||
30A87A6A2FEF5B950095E7C6 /* SearchLocationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchLocationView.swift; sourceTree = "<group>"; };
|
30A87A6A2FEF5B950095E7C6 /* SearchLocationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchLocationView.swift; sourceTree = "<group>"; };
|
||||||
30A87A6C2FEF5BA10095E7C6 /* SearchLocationVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchLocationVC.swift; sourceTree = "<group>"; };
|
30A87A6C2FEF5BA10095E7C6 /* SearchLocationVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchLocationVC.swift; sourceTree = "<group>"; };
|
||||||
30A87A6E2FEF7BE40095E7C6 /* SearchLocationResultVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchLocationResultVC.swift; sourceTree = "<group>"; };
|
30A87A6E2FEF7BE40095E7C6 /* SearchLocationResultVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchLocationResultVC.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -571,8 +636,6 @@
|
||||||
30D74AAA2FE8C7700050EB2C /* GPSSignalHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GPSSignalHelper.swift; sourceTree = "<group>"; };
|
30D74AAA2FE8C7700050EB2C /* GPSSignalHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GPSSignalHelper.swift; sourceTree = "<group>"; };
|
||||||
30D74AAD2FEA13E00050EB2C /* ScheduleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleView.swift; sourceTree = "<group>"; };
|
30D74AAD2FEA13E00050EB2C /* ScheduleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleView.swift; sourceTree = "<group>"; };
|
||||||
30D74AAF2FEA13ED0050EB2C /* ScheduleVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleVC.swift; sourceTree = "<group>"; };
|
30D74AAF2FEA13ED0050EB2C /* ScheduleVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleVC.swift; sourceTree = "<group>"; };
|
||||||
55B218B13024B00100784721 /* FeatureIntroVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureIntroVC.swift; sourceTree = "<group>"; };
|
|
||||||
55B218B13024B00100784723 /* FeatureIntroView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureIntroView.swift; sourceTree = "<group>"; };
|
|
||||||
30D74AB12FEA1D5D0050EB2C /* ScheduleViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleViewModel.swift; sourceTree = "<group>"; };
|
30D74AB12FEA1D5D0050EB2C /* ScheduleViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleViewModel.swift; sourceTree = "<group>"; };
|
||||||
30D74AB32FEA25B90050EB2C /* ViewedModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewedModel.swift; sourceTree = "<group>"; };
|
30D74AB32FEA25B90050EB2C /* ViewedModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewedModel.swift; sourceTree = "<group>"; };
|
||||||
30D74AB52FEA34FF0050EB2C /* ItineraryAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItineraryAPI.swift; sourceTree = "<group>"; };
|
30D74AB52FEA34FF0050EB2C /* ItineraryAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItineraryAPI.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -634,13 +697,17 @@
|
||||||
30EFF3E42FDAA93300EB35D4 /* PrivacyPolicyVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyPolicyVC.swift; sourceTree = "<group>"; };
|
30EFF3E42FDAA93300EB35D4 /* PrivacyPolicyVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyPolicyVC.swift; sourceTree = "<group>"; };
|
||||||
30EFF3E62FDAA93D00EB35D4 /* PrivacyPolicyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyPolicyView.swift; sourceTree = "<group>"; };
|
30EFF3E62FDAA93D00EB35D4 /* PrivacyPolicyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyPolicyView.swift; sourceTree = "<group>"; };
|
||||||
30EFF3E92FCA8C7000227D26 /* AuthenticationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AuthenticationServices.framework; path = System/Library/Frameworks/AuthenticationServices.framework; sourceTree = SDKROOT; };
|
30EFF3E92FCA8C7000227D26 /* AuthenticationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AuthenticationServices.framework; path = System/Library/Frameworks/AuthenticationServices.framework; sourceTree = SDKROOT; };
|
||||||
|
3C79865C3ED165AFDD091C29 /* app_catalog.json */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.json; name = app_catalog.json; path = AppRestrict/app_catalog.json; sourceTree = "<group>"; };
|
||||||
3E4359082FC48D26003470A5 /* QuickLocation.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = QuickLocation.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
3E4359082FC48D26003470A5 /* QuickLocation.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = QuickLocation.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
475D33CAFA1E1911EB1F8D9F /* Pods_QuickLocation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_QuickLocation.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
475D33CAFA1E1911EB1F8D9F /* Pods_QuickLocation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_QuickLocation.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
47922567FCD34DBE9186F8FD /* AppCatalogStore.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AppCatalogStore.swift; path = AppRestrict/AppCatalogStore.swift; sourceTree = "<group>"; };
|
||||||
|
4CD6B965A124276661A84C4C /* FamilyControls.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FamilyControls.framework; path = System/Library/Frameworks/FamilyControls.framework; sourceTree = SDKROOT; };
|
||||||
557E8D5230187A5C0032AB51 /* BaseNavigationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseNavigationView.swift; sourceTree = "<group>"; };
|
557E8D5230187A5C0032AB51 /* BaseNavigationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseNavigationView.swift; sourceTree = "<group>"; };
|
||||||
55B2179030217D6600784774 /* HomeView2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView2.swift; sourceTree = "<group>"; };
|
55B2179030217D6600784774 /* HomeView2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView2.swift; sourceTree = "<group>"; };
|
||||||
55B2179230218CE100784774 /* GroupMemberView2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMemberView2.swift; sourceTree = "<group>"; };
|
55B2179230218CE100784774 /* GroupMemberView2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMemberView2.swift; sourceTree = "<group>"; };
|
||||||
55B217943022DD6B00784774 /* 荆南波波黑-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "荆南波波黑-Bold.ttf"; sourceTree = "<group>"; };
|
55B217943022DD6B00784774 /* 荆南波波黑-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "荆南波波黑-Bold.ttf"; sourceTree = "<group>"; };
|
||||||
55B217A03022F0A000784701 /* FontManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontManager.swift; sourceTree = "<group>"; };
|
55B217A03022F0A000784701 /* FontManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontManager.swift; sourceTree = "<group>"; };
|
||||||
|
55B217A33022F0B100784707 /* UIDevice+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+Extension.swift"; sourceTree = "<group>"; };
|
||||||
55B217B03022F0A000784710 /* GroupItineraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupItineraryView.swift; sourceTree = "<group>"; };
|
55B217B03022F0A000784710 /* GroupItineraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupItineraryView.swift; sourceTree = "<group>"; };
|
||||||
55B217C03022F0A000784720 /* MinePhotoWallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MinePhotoWallView.swift; sourceTree = "<group>"; };
|
55B217C03022F0A000784720 /* MinePhotoWallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MinePhotoWallView.swift; sourceTree = "<group>"; };
|
||||||
55B217C23022F0A000784722 /* MemberPhoneReportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberPhoneReportView.swift; sourceTree = "<group>"; };
|
55B217C23022F0A000784722 /* MemberPhoneReportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberPhoneReportView.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -658,32 +725,62 @@
|
||||||
55B218A13024A00100784707 /* CancelAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancelAccountView.swift; sourceTree = "<group>"; };
|
55B218A13024A00100784707 /* CancelAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancelAccountView.swift; sourceTree = "<group>"; };
|
||||||
55B218A13024A00100784709 /* MemberInfoVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberInfoVC.swift; sourceTree = "<group>"; };
|
55B218A13024A00100784709 /* MemberInfoVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberInfoVC.swift; sourceTree = "<group>"; };
|
||||||
55B218A13024A0010078470B /* MemberInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberInfoView.swift; sourceTree = "<group>"; };
|
55B218A13024A0010078470B /* MemberInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberInfoView.swift; sourceTree = "<group>"; };
|
||||||
|
55B218B13024B00100784721 /* FeatureIntroVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureIntroVC.swift; sourceTree = "<group>"; };
|
||||||
|
55B218B13024B00100784723 /* FeatureIntroView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureIntroView.swift; sourceTree = "<group>"; };
|
||||||
|
55B219C13024C00100784721 /* BubbleHeroView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BubbleHeroView.swift; sourceTree = "<group>"; };
|
||||||
|
55B219C13024C00100784723 /* CreateBubbleSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateBubbleSetupView.swift; sourceTree = "<group>"; };
|
||||||
|
55B219C13024C00100784725 /* BubbleKnowledgeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BubbleKnowledgeView.swift; sourceTree = "<group>"; };
|
||||||
|
55B219C13024C00100784727 /* BubbleKnowledgeVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BubbleKnowledgeVC.swift; sourceTree = "<group>"; };
|
||||||
55B21D6D3023117900784774 /* 优设标题黑_猫啃网.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "优设标题黑_猫啃网.ttf"; sourceTree = "<group>"; };
|
55B21D6D3023117900784774 /* 优设标题黑_猫啃网.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "优设标题黑_猫啃网.ttf"; sourceTree = "<group>"; };
|
||||||
55BF752C2FFE53F70055DA57 /* LocationPermissionPopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationPermissionPopView.swift; sourceTree = "<group>"; };
|
55BF752C2FFE53F70055DA57 /* LocationPermissionPopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationPermissionPopView.swift; sourceTree = "<group>"; };
|
||||||
55BF75342FFF91690055DA57 /* InteractionEmojiCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractionEmojiCell.swift; sourceTree = "<group>"; };
|
55BF75342FFF91690055DA57 /* InteractionEmojiCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractionEmojiCell.swift; sourceTree = "<group>"; };
|
||||||
|
55C219C13024C00100784730 /* SearchLocationHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchLocationHeaderView.swift; sourceTree = "<group>"; };
|
||||||
|
5E5F5DC8694A001BF0C47147 /* PairGuideStepsView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PairGuideStepsView.swift; path = AppRestrict/PairGuideStepsView.swift; sourceTree = "<group>"; };
|
||||||
|
6BAE595D39202A2958737BD3 /* SelectActivityVC.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SelectActivityVC.swift; path = AppRestrict/SelectActivityVC.swift; sourceTree = "<group>"; };
|
||||||
|
787F87DD1CEBED1A4E1E9FC7 /* AppRestrictView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AppRestrictView.swift; path = AppRestrict/AppRestrictView.swift; sourceTree = "<group>"; };
|
||||||
|
7AEEA6CEA0CEAAB42C22752B /* AppRestrictManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AppRestrictManager.swift; path = AppRestrict/AppRestrictManager.swift; sourceTree = "<group>"; };
|
||||||
|
7FE44DF1C46BDA7B98BC7859 /* ManagedSettingsUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ManagedSettingsUI.framework; path = System/Library/Frameworks/ManagedSettingsUI.framework; sourceTree = SDKROOT; };
|
||||||
|
88CC9D32980864EAD9F72004 /* DeviceActivity.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DeviceActivity.framework; path = System/Library/Frameworks/DeviceActivity.framework; sourceTree = SDKROOT; };
|
||||||
|
89519661B70A0ADEC2A76032 /* AppRestrictCell.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AppRestrictCell.swift; path = AppRestrict/AppRestrictCell.swift; sourceTree = "<group>"; };
|
||||||
|
98E91E51811235EAD0985421 /* DeviceActivityMonitorExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = DeviceActivityMonitorExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
A1B2C3D52FDABC8C009215C1 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
A1B2C3D52FDABC8C009215C1 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||||
|
AC46E80B848E4012D192E4CE /* LockDistractView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LockDistractView.swift; sourceTree = "<group>"; };
|
||||||
|
BE3D39F325FC7624C1D914D6 /* ITunesSearchService.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ITunesSearchService.swift; path = AppRestrict/ITunesSearchService.swift; sourceTree = "<group>"; };
|
||||||
|
BFFBDE92B94E7004F6B642AD /* AppRestrictShieldSettingsVC.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AppRestrictShieldSettingsVC.swift; path = AppRestrict/AppRestrictShieldSettingsVC.swift; sourceTree = "<group>"; };
|
||||||
|
D0112B7865829D5B6A4A0BD0 /* AppRestrictVC.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AppRestrictVC.swift; path = AppRestrict/AppRestrictVC.swift; sourceTree = "<group>"; };
|
||||||
|
D64156B4A0E806901AB223FA /* DeviceActivityMonitorExtension.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DeviceActivityMonitorExtension.swift; path = DeviceActivityMonitorExtension/DeviceActivityMonitorExtension.swift; sourceTree = "<group>"; };
|
||||||
DA16D49AA46D4F6838340B55 /* Pods-QuickLocation.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-QuickLocation.debug.xcconfig"; path = "Target Support Files/Pods-QuickLocation/Pods-QuickLocation.debug.xcconfig"; sourceTree = "<group>"; };
|
DA16D49AA46D4F6838340B55 /* Pods-QuickLocation.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-QuickLocation.debug.xcconfig"; path = "Target Support Files/Pods-QuickLocation/Pods-QuickLocation.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
E483B9929C03809ADEDE8341 /* Pods-QuickLocation.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-QuickLocation.release.xcconfig"; path = "Target Support Files/Pods-QuickLocation/Pods-QuickLocation.release.xcconfig"; sourceTree = "<group>"; };
|
E483B9929C03809ADEDE8341 /* Pods-QuickLocation.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-QuickLocation.release.xcconfig"; path = "Target Support Files/Pods-QuickLocation/Pods-QuickLocation.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
EF9D223AB35744F92E0E3E77 /* FamilyActivityPickerHost.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FamilyActivityPickerHost.swift; path = AppRestrict/FamilyActivityPickerHost.swift; sourceTree = "<group>"; };
|
||||||
|
F9CFFB14BA2ECC259151BCDA /* LockDistractVC.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LockDistractVC.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||||
30CCDF8E2FE3E63B00F5214A /* sound */ = {
|
30CCDF8E2FE3E63B00F5214A /* sound */ = {
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
);
|
||||||
path = sound;
|
path = sound;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
30CCDF902FE3E63B00F5214A /* video */ = {
|
30CCDF902FE3E63B00F5214A /* video */ = {
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
);
|
||||||
path = video;
|
path = video;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
30CCE01E2FE3E64700F5214A /* lotties */ = {
|
30CCE01E2FE3E64700F5214A /* lotties */ = {
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
);
|
||||||
path = lotties;
|
path = lotties;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
30D87CEF2FDFF52100E958FD /* TTGTagCollectionView */ = {
|
30D87CEF2FDFF52100E958FD /* TTGTagCollectionView */ = {
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
);
|
||||||
path = TTGTagCollectionView;
|
path = TTGTagCollectionView;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
|
@ -696,12 +793,63 @@
|
||||||
files = (
|
files = (
|
||||||
30EFF3E82FCA8C7000227D26 /* AuthenticationServices.framework in Frameworks */,
|
30EFF3E82FCA8C7000227D26 /* AuthenticationServices.framework in Frameworks */,
|
||||||
D698E9C56D6F2C152772131D /* Pods_QuickLocation.framework in Frameworks */,
|
D698E9C56D6F2C152772131D /* Pods_QuickLocation.framework in Frameworks */,
|
||||||
|
89647BC98E0FF342511D42CF /* FamilyControls.framework in Frameworks */,
|
||||||
|
77731F107613C247B7FE98C8 /* ManagedSettings.framework in Frameworks */,
|
||||||
|
E6E5C711C9BD9F6C929BB062 /* DeviceActivity.framework in Frameworks */,
|
||||||
|
60BEE25E61F3DE24976A7A47 /* ManagedSettingsUI.framework in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
57D97F1D073CB957F5B3FC99 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
C3C4ACC1AE3CAF5BA21328B7 /* Foundation.framework in Frameworks */,
|
||||||
|
071EBEFAB7A70D8554B38024 /* DeviceActivity.framework in Frameworks */,
|
||||||
|
D60B79067C80CD6AF50D63FE /* FamilyControls.framework in Frameworks */,
|
||||||
|
091AC874363B91A91D717758 /* ManagedSettings.framework in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
6314CDE3DFF338A21DC2A612 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
04532656C70B8C5AD6A8161E /* Foundation.framework in Frameworks */,
|
||||||
|
B38B130801018F93E81D52C5 /* ManagedSettings.framework in Frameworks */,
|
||||||
|
F53E5BD11D671E5CED4E7408 /* ManagedSettingsUI.framework in Frameworks */,
|
||||||
|
D3C101B198090A9F4D93E44F /* FamilyControls.framework in Frameworks */,
|
||||||
|
6D90E8FFE4D597E888E6A224 /* UIKit.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
|
2234314E8DD57630628CC5C3 /* ShieldConfigurationExtension */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
08002267FC4390B20D3510B3 /* ShieldConfigurationExtension.swift */,
|
||||||
|
);
|
||||||
|
name = ShieldConfigurationExtension;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
2509CBAFBD63A140B359BD7F /* DeviceActivityMonitorExtension */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
D64156B4A0E806901AB223FA /* DeviceActivityMonitorExtension.swift */,
|
||||||
|
);
|
||||||
|
name = DeviceActivityMonitorExtension;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
2F518AB6587CC227CC515D8C /* AppRestrict */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
7AEEA6CEA0CEAAB42C22752B /* AppRestrictManager.swift */,
|
||||||
|
);
|
||||||
|
name = AppRestrict;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
305A74C92FCA8C7000227D26 /* RxMoya */ = {
|
305A74C92FCA8C7000227D26 /* RxMoya */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
|
@ -981,6 +1129,7 @@
|
||||||
305A752C2FCA8C7000227D26 /* URL */,
|
305A752C2FCA8C7000227D26 /* URL */,
|
||||||
30D87CD52FDF9F1900E958FD /* MQTT */,
|
30D87CD52FDF9F1900E958FD /* MQTT */,
|
||||||
30C6666A2FFB7C3000E62B25 /* IAPManager */,
|
30C6666A2FFB7C3000E62B25 /* IAPManager */,
|
||||||
|
2F518AB6587CC227CC515D8C /* AppRestrict */,
|
||||||
);
|
);
|
||||||
path = Manager;
|
path = Manager;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -1156,6 +1305,8 @@
|
||||||
30ACD54A2FF6332200174861 /* PopupWindow */,
|
30ACD54A2FF6332200174861 /* PopupWindow */,
|
||||||
30EFF3AD2FD7FF1400EB35D4 /* TextInput */,
|
30EFF3AD2FD7FF1400EB35D4 /* TextInput */,
|
||||||
30C6679D2FFB7FEF00E62B25 /* Share */,
|
30C6679D2FFB7FEF00E62B25 /* Share */,
|
||||||
|
F49062649CA8E5F9FA2F22A5 /* AppRestrict */,
|
||||||
|
B79BE06D49443FE82A811D5C /* LockDistract */,
|
||||||
);
|
);
|
||||||
path = Section;
|
path = Section;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -1564,15 +1715,6 @@
|
||||||
path = SOS;
|
path = SOS;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
55B218B13024B00100784720 /* Explore */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
55B218B13024B00100784721 /* FeatureIntroVC.swift */,
|
|
||||||
55B218B13024B00100784723 /* FeatureIntroView.swift */,
|
|
||||||
);
|
|
||||||
path = Explore;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
30D74AAC2FEA13BD0050EB2C /* Schedule */ = {
|
30D74AAC2FEA13BD0050EB2C /* Schedule */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
|
@ -1728,6 +1870,9 @@
|
||||||
3E4359092FC48D26003470A5 /* Products */,
|
3E4359092FC48D26003470A5 /* Products */,
|
||||||
B07D45692FCECE07570D9B73 /* Pods */,
|
B07D45692FCECE07570D9B73 /* Pods */,
|
||||||
47CD8471BE2146A2656CF27E /* Frameworks */,
|
47CD8471BE2146A2656CF27E /* Frameworks */,
|
||||||
|
DA2257EC3F3167F4A6AFE759 /* AppRestrictShared */,
|
||||||
|
2509CBAFBD63A140B359BD7F /* DeviceActivityMonitorExtension */,
|
||||||
|
2234314E8DD57630628CC5C3 /* ShieldConfigurationExtension */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
|
@ -1735,6 +1880,8 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
3E4359082FC48D26003470A5 /* QuickLocation.app */,
|
3E4359082FC48D26003470A5 /* QuickLocation.app */,
|
||||||
|
98E91E51811235EAD0985421 /* DeviceActivityMonitorExtension.appex */,
|
||||||
|
0C47E0012491DB1371FD5E53 /* ShieldConfigurationExtension.appex */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -1744,6 +1891,12 @@
|
||||||
children = (
|
children = (
|
||||||
30EFF3E92FCA8C7000227D26 /* AuthenticationServices.framework */,
|
30EFF3E92FCA8C7000227D26 /* AuthenticationServices.framework */,
|
||||||
475D33CAFA1E1911EB1F8D9F /* Pods_QuickLocation.framework */,
|
475D33CAFA1E1911EB1F8D9F /* Pods_QuickLocation.framework */,
|
||||||
|
4CD6B965A124276661A84C4C /* FamilyControls.framework */,
|
||||||
|
2EE79029677394A46B143E75 /* ManagedSettings.framework */,
|
||||||
|
88CC9D32980864EAD9F72004 /* DeviceActivity.framework */,
|
||||||
|
7FE44DF1C46BDA7B98BC7859 /* ManagedSettingsUI.framework */,
|
||||||
|
74B9CD2ABDBC4C8CE3107CDE /* iOS */,
|
||||||
|
1E326B06736FE04DD7A5E96D /* UIKit.framework */,
|
||||||
);
|
);
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -1774,6 +1927,16 @@
|
||||||
path = TodayTrackDetail;
|
path = TodayTrackDetail;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
55B217DE302301000078473D /* PhoneReportDetail */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
55B217D73023010000784736 /* PhoneReportDetailVC.swift */,
|
||||||
|
55B217D93023010000784738 /* PhoneReportDetailView.swift */,
|
||||||
|
55B217DB302301000078473A /* PhoneReportDetailViewModel.swift */,
|
||||||
|
);
|
||||||
|
path = PhoneReportDetail;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
55B218A13024A00100784710 /* About */ = {
|
55B218A13024A00100784710 /* About */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
|
@ -1801,14 +1964,21 @@
|
||||||
path = MemberInfo;
|
path = MemberInfo;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
55B217DE302301000078473D /* PhoneReportDetail */ = {
|
55B218B13024B00100784720 /* Explore */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
55B217D73023010000784736 /* PhoneReportDetailVC.swift */,
|
55B218B13024B00100784721 /* FeatureIntroVC.swift */,
|
||||||
55B217D93023010000784738 /* PhoneReportDetailView.swift */,
|
55B218B13024B00100784723 /* FeatureIntroView.swift */,
|
||||||
55B217DB302301000078473A /* PhoneReportDetailViewModel.swift */,
|
|
||||||
);
|
);
|
||||||
path = PhoneReportDetail;
|
path = Explore;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
74B9CD2ABDBC4C8CE3107CDE /* iOS */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
226CA98495C2884EBB1D1372 /* Foundation.framework */,
|
||||||
|
);
|
||||||
|
name = iOS;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
B07D45692FCECE07570D9B73 /* Pods */ = {
|
B07D45692FCECE07570D9B73 /* Pods */ = {
|
||||||
|
|
@ -1820,6 +1990,40 @@
|
||||||
path = Pods;
|
path = Pods;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
B79BE06D49443FE82A811D5C /* LockDistract */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
F9CFFB14BA2ECC259151BCDA /* LockDistractVC.swift */,
|
||||||
|
AC46E80B848E4012D192E4CE /* LockDistractView.swift */,
|
||||||
|
);
|
||||||
|
path = LockDistract;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
DA2257EC3F3167F4A6AFE759 /* AppRestrictShared */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
1AB29D0618C4BE76F2CA5261 /* AppRestrictShared.swift */,
|
||||||
|
);
|
||||||
|
name = AppRestrictShared;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
F49062649CA8E5F9FA2F22A5 /* AppRestrict */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
47922567FCD34DBE9186F8FD /* AppCatalogStore.swift */,
|
||||||
|
89519661B70A0ADEC2A76032 /* AppRestrictCell.swift */,
|
||||||
|
D0112B7865829D5B6A4A0BD0 /* AppRestrictVC.swift */,
|
||||||
|
787F87DD1CEBED1A4E1E9FC7 /* AppRestrictView.swift */,
|
||||||
|
BFFBDE92B94E7004F6B642AD /* AppRestrictShieldSettingsVC.swift */,
|
||||||
|
EF9D223AB35744F92E0E3E77 /* FamilyActivityPickerHost.swift */,
|
||||||
|
BE3D39F325FC7624C1D914D6 /* ITunesSearchService.swift */,
|
||||||
|
6BAE595D39202A2958737BD3 /* SelectActivityVC.swift */,
|
||||||
|
3C79865C3ED165AFDD091C29 /* app_catalog.json */,
|
||||||
|
5E5F5DC8694A001BF0C47147 /* PairGuideStepsView.swift */,
|
||||||
|
);
|
||||||
|
name = AppRestrict;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
|
|
@ -1833,10 +2037,13 @@
|
||||||
3E4359062FC48D26003470A5 /* Resources */,
|
3E4359062FC48D26003470A5 /* Resources */,
|
||||||
22E76BAEDC74B6177770F922 /* [CP] Embed Pods Frameworks */,
|
22E76BAEDC74B6177770F922 /* [CP] Embed Pods Frameworks */,
|
||||||
84E518A2C7E1AE699B07AC0D /* [CP] Copy Pods Resources */,
|
84E518A2C7E1AE699B07AC0D /* [CP] Copy Pods Resources */,
|
||||||
|
63D74ED0B1CEAFA8DAC0A6C0 /* Embed Foundation Extensions */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
|
0950A334CDF61EB8C1C94506 /* PBXTargetDependency */,
|
||||||
|
DCCA06707CFDC3C0D07BD8B0 /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
fileSystemSynchronizedGroups = (
|
fileSystemSynchronizedGroups = (
|
||||||
30CCDF8E2FE3E63B00F5214A /* sound */,
|
30CCDF8E2FE3E63B00F5214A /* sound */,
|
||||||
|
|
@ -1849,6 +2056,40 @@
|
||||||
productReference = 3E4359082FC48D26003470A5 /* QuickLocation.app */;
|
productReference = 3E4359082FC48D26003470A5 /* QuickLocation.app */;
|
||||||
productType = "com.apple.product-type.application";
|
productType = "com.apple.product-type.application";
|
||||||
};
|
};
|
||||||
|
B626CA1007794E780B5B1CB3 /* DeviceActivityMonitorExtension */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 1A6BE372491582A933CD3ECE /* Build configuration list for PBXNativeTarget "DeviceActivityMonitorExtension" */;
|
||||||
|
buildPhases = (
|
||||||
|
933B6388A2F481EE1DF7A99C /* Sources */,
|
||||||
|
57D97F1D073CB957F5B3FC99 /* Frameworks */,
|
||||||
|
7FA03AF172854DD407AFED91 /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = DeviceActivityMonitorExtension;
|
||||||
|
productName = DeviceActivityMonitorExtension;
|
||||||
|
productReference = 98E91E51811235EAD0985421 /* DeviceActivityMonitorExtension.appex */;
|
||||||
|
productType = "com.apple.product-type.app-extension";
|
||||||
|
};
|
||||||
|
E4257D157F2E24904E9B6630 /* ShieldConfigurationExtension */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = BAA60EF75453DD3B5A4775AF /* Build configuration list for PBXNativeTarget "ShieldConfigurationExtension" */;
|
||||||
|
buildPhases = (
|
||||||
|
EF5DDEE45DD2FBD2EA2AAB8D /* Sources */,
|
||||||
|
6314CDE3DFF338A21DC2A612 /* Frameworks */,
|
||||||
|
6E86F27A156DD5A139D94E1B /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = ShieldConfigurationExtension;
|
||||||
|
productName = ShieldConfigurationExtension;
|
||||||
|
productReference = 0C47E0012491DB1371FD5E53 /* ShieldConfigurationExtension.appex */;
|
||||||
|
productType = "com.apple.product-type.app-extension";
|
||||||
|
};
|
||||||
/* End PBXNativeTarget section */
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
/* Begin PBXProject section */
|
/* Begin PBXProject section */
|
||||||
|
|
@ -1879,6 +2120,8 @@
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
targets = (
|
targets = (
|
||||||
3E4359072FC48D26003470A5 /* QuickLocation */,
|
3E4359072FC48D26003470A5 /* QuickLocation */,
|
||||||
|
B626CA1007794E780B5B1CB3 /* DeviceActivityMonitorExtension */,
|
||||||
|
E4257D157F2E24904E9B6630 /* ShieldConfigurationExtension */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
|
|
@ -1896,6 +2139,21 @@
|
||||||
55B217953022DD6B00784774 /* 荆南波波黑-Bold.ttf in Resources */,
|
55B217953022DD6B00784774 /* 荆南波波黑-Bold.ttf in Resources */,
|
||||||
55B21D6E3023117900784774 /* 优设标题黑_猫啃网.ttf in Resources */,
|
55B21D6E3023117900784774 /* 优设标题黑_猫啃网.ttf in Resources */,
|
||||||
305A77FC2FCA8C7000227D26 /* Main.storyboard in Resources */,
|
305A77FC2FCA8C7000227D26 /* Main.storyboard in Resources */,
|
||||||
|
4392B1D9C125D873FCFCDDC9 /* app_catalog.json in Resources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
6E86F27A156DD5A139D94E1B /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
7FA03AF172854DD407AFED91 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|
@ -2283,11 +2541,57 @@
|
||||||
305A771B2FCA8C7000227D26 /* ReusableView.swift in Sources */,
|
305A771B2FCA8C7000227D26 /* ReusableView.swift in Sources */,
|
||||||
305A771C2FCA8C7000227D26 /* AppDelegate.swift in Sources */,
|
305A771C2FCA8C7000227D26 /* AppDelegate.swift in Sources */,
|
||||||
305A771D2FCA8C7000227D26 /* ViewController.swift in Sources */,
|
305A771D2FCA8C7000227D26 /* ViewController.swift in Sources */,
|
||||||
|
C15D41A1770F5B4A02E5FC59 /* AppRestrictShared.swift in Sources */,
|
||||||
|
830C70B1B53657BB020721DA /* AppRestrictManager.swift in Sources */,
|
||||||
|
A55D8DE8A1F679EBF7B562D1 /* AppCatalogStore.swift in Sources */,
|
||||||
|
A42DCB2A66E694B7CC50033B /* AppRestrictCell.swift in Sources */,
|
||||||
|
4AF6E5954D73E21F31F7A16B /* AppRestrictVC.swift in Sources */,
|
||||||
|
488FF21773B8E5021D8662B5 /* AppRestrictView.swift in Sources */,
|
||||||
|
A97A15CB71FDCA432974A768 /* AppRestrictShieldSettingsVC.swift in Sources */,
|
||||||
|
EA13C1E21EC94958AC9755A8 /* FamilyActivityPickerHost.swift in Sources */,
|
||||||
|
B52F13A261ED273A3A58183F /* SelectActivityVC.swift in Sources */,
|
||||||
|
C6A99DEE361AB31477F88475 /* LockDistractView.swift in Sources */,
|
||||||
|
E3D4129BDA33540BA4E1292E /* LockDistractVC.swift in Sources */,
|
||||||
|
E866CAAE7D4E74EB7AD608B9 /* ITunesSearchService.swift in Sources */,
|
||||||
|
DB67C2EB2521042829A3DBA5 /* PairGuideStepsView.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
933B6388A2F481EE1DF7A99C /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
80F749310651808BF00F95CF /* DeviceActivityMonitorExtension.swift in Sources */,
|
||||||
|
68F0D48EB76039050533197C /* AppRestrictShared.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
EF5DDEE45DD2FBD2EA2AAB8D /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
D765496DAEBAE6A8C98DB843 /* ShieldConfigurationExtension.swift in Sources */,
|
||||||
|
7B9D095A034C9FA5E69F55BA /* AppRestrictShared.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
/* End PBXSourcesBuildPhase section */
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXTargetDependency section */
|
||||||
|
0950A334CDF61EB8C1C94506 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
name = DeviceActivityMonitorExtension;
|
||||||
|
target = B626CA1007794E780B5B1CB3 /* DeviceActivityMonitorExtension */;
|
||||||
|
targetProxy = BCE404021C2F9BD8BCC26E94 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
DCCA06707CFDC3C0D07BD8B0 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
name = ShieldConfigurationExtension;
|
||||||
|
target = E4257D157F2E24904E9B6630 /* ShieldConfigurationExtension */;
|
||||||
|
targetProxy = 1B096A922D2FECE87D42D1D4 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
/* Begin PBXVariantGroup section */
|
/* Begin PBXVariantGroup section */
|
||||||
305A76822FCA8C7000227D26 /* LaunchScreen.storyboard */ = {
|
305A76822FCA8C7000227D26 /* LaunchScreen.storyboard */ = {
|
||||||
isa = PBXVariantGroup;
|
isa = PBXVariantGroup;
|
||||||
|
|
@ -2310,6 +2614,61 @@
|
||||||
/* End PBXVariantGroup section */
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
/* Begin XCBuildConfiguration section */
|
/* Begin XCBuildConfiguration section */
|
||||||
|
15B6023456261CCD0E3A673E /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = NO;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = DeviceActivityMonitorExtension/DeviceActivityMonitorExtension.entitlements;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = LRDLWZ2Y83;
|
||||||
|
GENERATE_INFOPLIST_FILE = NO;
|
||||||
|
INFOPLIST_FILE = DeviceActivityMonitorExtension/Info.plist;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = cn.zuomeng.jisuloca.DeviceActivityMonitor;
|
||||||
|
PRODUCT_NAME = DeviceActivityMonitorExtension;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_DEFAULT_ACTOR_ISOLATION = nonisolated;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = 1;
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
3A68FBB9A55555685B490D5C /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = NO;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = DeviceActivityMonitorExtension/DeviceActivityMonitorExtension.entitlements;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = LRDLWZ2Y83;
|
||||||
|
GENERATE_INFOPLIST_FILE = NO;
|
||||||
|
INFOPLIST_FILE = DeviceActivityMonitorExtension/Info.plist;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = cn.zuomeng.jisuloca.DeviceActivityMonitor;
|
||||||
|
PRODUCT_NAME = DeviceActivityMonitorExtension;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_DEFAULT_ACTOR_ISOLATION = nonisolated;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = 1;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
3E43591C2FC48D2B003470A5 /* Debug */ = {
|
3E43591C2FC48D2B003470A5 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = DA16D49AA46D4F6838340B55 /* Pods-QuickLocation.debug.xcconfig */;
|
baseConfigurationReference = DA16D49AA46D4F6838340B55 /* Pods-QuickLocation.debug.xcconfig */;
|
||||||
|
|
@ -2339,7 +2698,7 @@
|
||||||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||||
INFOPLIST_KEY_UIMainStoryboardFile = Main;
|
INFOPLIST_KEY_UIMainStoryboardFile = Main;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
|
@ -2393,7 +2752,7 @@
|
||||||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||||
INFOPLIST_KEY_UIMainStoryboardFile = Main;
|
INFOPLIST_KEY_UIMainStoryboardFile = Main;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
|
@ -2471,7 +2830,7 @@
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
|
|
@ -2529,7 +2888,7 @@
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
|
|
@ -2539,9 +2898,73 @@
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
A0AD067561B0A604B5DF5049 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = NO;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = ShieldConfigurationExtension/ShieldConfigurationExtension.entitlements;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = LRDLWZ2Y83;
|
||||||
|
GENERATE_INFOPLIST_FILE = NO;
|
||||||
|
INFOPLIST_FILE = ShieldConfigurationExtension/Info.plist;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = cn.zuomeng.jisuloca.ShieldConfiguration;
|
||||||
|
PRODUCT_NAME = ShieldConfigurationExtension;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_DEFAULT_ACTOR_ISOLATION = nonisolated;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = 1;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
B66BC4F86D908B8200A8DC8E /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = NO;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = ShieldConfigurationExtension/ShieldConfigurationExtension.entitlements;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = LRDLWZ2Y83;
|
||||||
|
GENERATE_INFOPLIST_FILE = NO;
|
||||||
|
INFOPLIST_FILE = ShieldConfigurationExtension/Info.plist;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = cn.zuomeng.jisuloca.ShieldConfiguration;
|
||||||
|
PRODUCT_NAME = ShieldConfigurationExtension;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_DEFAULT_ACTOR_ISOLATION = nonisolated;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = 1;
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
/* End XCBuildConfiguration section */
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
/* Begin XCConfigurationList section */
|
/* Begin XCConfigurationList section */
|
||||||
|
1A6BE372491582A933CD3ECE /* Build configuration list for PBXNativeTarget "DeviceActivityMonitorExtension" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
15B6023456261CCD0E3A673E /* Release */,
|
||||||
|
3A68FBB9A55555685B490D5C /* Debug */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
3E4359032FC48D26003470A5 /* Build configuration list for PBXProject "QuickLocation" */ = {
|
3E4359032FC48D26003470A5 /* Build configuration list for PBXProject "QuickLocation" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
|
|
@ -2560,6 +2983,15 @@
|
||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
|
BAA60EF75453DD3B5A4775AF /* Build configuration list for PBXNativeTarget "ShieldConfigurationExtension" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
B66BC4F86D908B8200A8DC8E /* Release */,
|
||||||
|
A0AD067561B0A604B5DF5049 /* Debug */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
};
|
};
|
||||||
rootObject = 3E4359002FC48D26003470A5 /* Project object */;
|
rootObject = 3E4359002FC48D26003470A5 /* Project object */;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"info" : { "author" : "xcode", "version" : 1 },
|
||||||
|
"properties" : {
|
||||||
|
"provides-namespace" : true
|
||||||
|
}
|
||||||
|
}
|
||||||
8
QuickLocation/Assets.xcassets/AppRestrict/pair_action_delete.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{ "idiom" : "universal", "scale" : "1x" },
|
||||||
|
{ "filename" : "pair_action_delete@2x.png", "idiom" : "universal", "scale" : "2x" },
|
||||||
|
{ "filename" : "pair_action_delete@3x.png", "idiom" : "universal", "scale" : "3x" }
|
||||||
|
],
|
||||||
|
"info" : { "author" : "xcode", "version" : 1 }
|
||||||
|
}
|
||||||
BIN
QuickLocation/Assets.xcassets/AppRestrict/pair_action_delete.imageset/pair_action_delete@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 867 B |
BIN
QuickLocation/Assets.xcassets/AppRestrict/pair_action_delete.imageset/pair_action_delete@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
8
QuickLocation/Assets.xcassets/AppRestrict/pair_action_link.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{ "idiom" : "universal", "scale" : "1x" },
|
||||||
|
{ "filename" : "pair_action_link@2x.png", "idiom" : "universal", "scale" : "2x" },
|
||||||
|
{ "filename" : "pair_action_link@3x.png", "idiom" : "universal", "scale" : "3x" }
|
||||||
|
],
|
||||||
|
"info" : { "author" : "xcode", "version" : 1 }
|
||||||
|
}
|
||||||
BIN
QuickLocation/Assets.xcassets/AppRestrict/pair_action_link.imageset/pair_action_link@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
QuickLocation/Assets.xcassets/AppRestrict/pair_action_link.imageset/pair_action_link@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
8
QuickLocation/Assets.xcassets/AppRestrict/pair_badge_check.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{ "idiom" : "universal", "scale" : "1x" },
|
||||||
|
{ "filename" : "pair_badge_check@2x.png", "idiom" : "universal", "scale" : "2x" },
|
||||||
|
{ "filename" : "pair_badge_check@3x.png", "idiom" : "universal", "scale" : "3x" }
|
||||||
|
],
|
||||||
|
"info" : { "author" : "xcode", "version" : 1 }
|
||||||
|
}
|
||||||
BIN
QuickLocation/Assets.xcassets/AppRestrict/pair_badge_check.imageset/pair_badge_check@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 493 B |
BIN
QuickLocation/Assets.xcassets/AppRestrict/pair_badge_check.imageset/pair_badge_check@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
8
QuickLocation/Assets.xcassets/AppRestrict/pair_link_active.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{ "idiom" : "universal", "scale" : "1x" },
|
||||||
|
{ "filename" : "pair_link_active@2x.png", "idiom" : "universal", "scale" : "2x" },
|
||||||
|
{ "filename" : "pair_link_active@3x.png", "idiom" : "universal", "scale" : "3x" }
|
||||||
|
],
|
||||||
|
"info" : { "author" : "xcode", "version" : 1 }
|
||||||
|
}
|
||||||
BIN
QuickLocation/Assets.xcassets/AppRestrict/pair_link_active.imageset/pair_link_active@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
QuickLocation/Assets.xcassets/AppRestrict/pair_link_active.imageset/pair_link_active@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
8
QuickLocation/Assets.xcassets/AppRestrict/pair_link_inactive.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{ "idiom" : "universal", "scale" : "1x" },
|
||||||
|
{ "filename" : "pair_link_inactive@2x.png", "idiom" : "universal", "scale" : "2x" },
|
||||||
|
{ "filename" : "pair_link_inactive@3x.png", "idiom" : "universal", "scale" : "3x" }
|
||||||
|
],
|
||||||
|
"info" : { "author" : "xcode", "version" : 1 }
|
||||||
|
}
|
||||||
BIN
QuickLocation/Assets.xcassets/AppRestrict/pair_link_inactive.imageset/pair_link_inactive@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
QuickLocation/Assets.xcassets/AppRestrict/pair_link_inactive.imageset/pair_link_inactive@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 678 B After Width: | Height: | Size: 830 B |
|
Before Width: | Height: | Size: 869 B After Width: | Height: | Size: 1.9 KiB |
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "join_hero_bg@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "join_hero_bg@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
QuickLocation/Assets.xcassets/Group/join_hero_bg.imageset/join_hero_bg@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
QuickLocation/Assets.xcassets/Group/join_hero_bg.imageset/join_hero_bg@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 11 KiB |
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "join_scan@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "join_scan@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"provides-namespace" : true
|
||||||
|
}
|
||||||
|
}
|
||||||
8
QuickLocation/Assets.xcassets/LockDistract/app_unknown.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{ "idiom" : "universal", "scale" : "1x" },
|
||||||
|
{ "filename" : "app_unknown@2x.png", "idiom" : "universal", "scale" : "2x" },
|
||||||
|
{ "filename" : "app_unknown@3x.png", "idiom" : "universal", "scale" : "3x" }
|
||||||
|
],
|
||||||
|
"info" : { "author" : "xcode", "version" : 1 }
|
||||||
|
}
|
||||||
BIN
QuickLocation/Assets.xcassets/LockDistract/app_unknown.imageset/app_unknown@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
QuickLocation/Assets.xcassets/LockDistract/app_unknown.imageset/app_unknown@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
8
QuickLocation/Assets.xcassets/LockDistract/arrow_right.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{ "idiom" : "universal", "scale" : "1x" },
|
||||||
|
{ "filename" : "arrow_right@2x.png", "idiom" : "universal", "scale" : "2x" },
|
||||||
|
{ "filename" : "arrow_right@3x.png", "idiom" : "universal", "scale" : "3x" }
|
||||||
|
],
|
||||||
|
"info" : { "author" : "xcode", "version" : 1 }
|
||||||
|
}
|
||||||
BIN
QuickLocation/Assets.xcassets/LockDistract/arrow_right.imageset/arrow_right@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 302 B |
BIN
QuickLocation/Assets.xcassets/LockDistract/arrow_right.imageset/arrow_right@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 465 B |
8
QuickLocation/Assets.xcassets/LockDistract/section_star.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{ "idiom" : "universal", "scale" : "1x" },
|
||||||
|
{ "filename" : "section_star@2x.png", "idiom" : "universal", "scale" : "2x" },
|
||||||
|
{ "filename" : "section_star@3x.png", "idiom" : "universal", "scale" : "3x" }
|
||||||
|
],
|
||||||
|
"info" : { "author" : "xcode", "version" : 1 }
|
||||||
|
}
|
||||||
BIN
QuickLocation/Assets.xcassets/LockDistract/section_star.imageset/section_star@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 433 B |
BIN
QuickLocation/Assets.xcassets/LockDistract/section_star.imageset/section_star@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 772 B |
8
QuickLocation/Assets.xcassets/LockDistract/wallpaper_1.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{ "idiom" : "universal", "scale" : "1x" },
|
||||||
|
{ "filename" : "wallpaper_1@2x.png", "idiom" : "universal", "scale" : "2x" },
|
||||||
|
{ "filename" : "wallpaper_1@3x.png", "idiom" : "universal", "scale" : "3x" }
|
||||||
|
],
|
||||||
|
"info" : { "author" : "xcode", "version" : 1 }
|
||||||
|
}
|
||||||
BIN
QuickLocation/Assets.xcassets/LockDistract/wallpaper_1.imageset/wallpaper_1@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
QuickLocation/Assets.xcassets/LockDistract/wallpaper_1.imageset/wallpaper_1@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 66 KiB |
8
QuickLocation/Assets.xcassets/LockDistract/wallpaper_2.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{ "idiom" : "universal", "scale" : "1x" },
|
||||||
|
{ "filename" : "wallpaper_2@2x.png", "idiom" : "universal", "scale" : "2x" },
|
||||||
|
{ "filename" : "wallpaper_2@3x.png", "idiom" : "universal", "scale" : "3x" }
|
||||||
|
],
|
||||||
|
"info" : { "author" : "xcode", "version" : 1 }
|
||||||
|
}
|
||||||
BIN
QuickLocation/Assets.xcassets/LockDistract/wallpaper_2.imageset/wallpaper_2@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
QuickLocation/Assets.xcassets/LockDistract/wallpaper_2.imageset/wallpaper_2@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 90 KiB |
8
QuickLocation/Assets.xcassets/LockDistract/wallpaper_3.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{ "idiom" : "universal", "scale" : "1x" },
|
||||||
|
{ "filename" : "wallpaper_3@2x.png", "idiom" : "universal", "scale" : "2x" },
|
||||||
|
{ "filename" : "wallpaper_3@3x.png", "idiom" : "universal", "scale" : "3x" }
|
||||||
|
],
|
||||||
|
"info" : { "author" : "xcode", "version" : 1 }
|
||||||
|
}
|
||||||
BIN
QuickLocation/Assets.xcassets/LockDistract/wallpaper_3.imageset/wallpaper_3@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
QuickLocation/Assets.xcassets/LockDistract/wallpaper_3.imageset/wallpaper_3@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 84 KiB |
|
|
@ -44,7 +44,7 @@ class BaseViewController: UIViewController {
|
||||||
// Do any additional setup after loading the view.
|
// Do any additional setup after loading the view.
|
||||||
fd_prefersNavigationBarHidden = isNavigationBarHidden
|
fd_prefersNavigationBarHidden = isNavigationBarHidden
|
||||||
|
|
||||||
view.backgroundColor = .white//ThemeManager.shared.color.backgroundColor
|
//view.backgroundColor = .white//ThemeManager.shared.color.backgroundColor
|
||||||
// setupNavigationBar()
|
// setupNavigationBar()
|
||||||
setupLeftItem()
|
setupLeftItem()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,10 @@ enum Route: String {
|
||||||
case memberInfo = "memberInfo"
|
case memberInfo = "memberInfo"
|
||||||
/// 注销账号
|
/// 注销账号
|
||||||
case cancelAccount = "cancelAccount"
|
case cancelAccount = "cancelAccount"
|
||||||
|
/// 限制 App 管理
|
||||||
|
case appRestrict = "appRestrict"
|
||||||
|
/// 锁住分心(一键锁机)
|
||||||
|
case lockDistract = "lockDistract"
|
||||||
/// 还在吗 / 打卡
|
/// 还在吗 / 打卡
|
||||||
case signIn = "signIn"
|
case signIn = "signIn"
|
||||||
/// SOS
|
/// SOS
|
||||||
|
|
@ -388,6 +392,18 @@ extension AppRouter: AppRouterProtocol {
|
||||||
CancelAccountVC()
|
CancelAccountVC()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - 限制 App 管理
|
||||||
|
AppRouter.register(Route.appRestrict) { _, _ in
|
||||||
|
AppRestrictFactory.make()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 锁住分心(一键锁机)
|
||||||
|
AppRouter.register(Route.lockDistract) { _, _ in
|
||||||
|
let vc = LockDistractVC()
|
||||||
|
vc.isNeedLogin = true
|
||||||
|
return vc
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - 还在吗
|
// MARK: - 还在吗
|
||||||
AppRouter.register(Route.signIn) { _, _ in
|
AppRouter.register(Route.signIn) { _, _ in
|
||||||
SignInVC(lastLocation: nil)
|
SignInVC(lastLocation: nil)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
//
|
||||||
|
// AppRestrictManager.swift
|
||||||
|
// QuickLocation
|
||||||
|
//
|
||||||
|
|
||||||
|
import DeviceActivity
|
||||||
|
import FamilyControls
|
||||||
|
import Foundation
|
||||||
|
import ManagedSettings
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@available(iOS 16.0, *)
|
||||||
|
final class AppRestrictManager {
|
||||||
|
static let shared = AppRestrictManager()
|
||||||
|
|
||||||
|
private let center = AuthorizationCenter.shared
|
||||||
|
private let activityCenter = DeviceActivityCenter()
|
||||||
|
private let activityName = DeviceActivityName(AppRestrictShared.activityName)
|
||||||
|
|
||||||
|
private init() {}
|
||||||
|
|
||||||
|
var authorizationStatus: AuthorizationStatus {
|
||||||
|
center.authorizationStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
var isAuthorized: Bool {
|
||||||
|
center.authorizationStatus == .approved
|
||||||
|
}
|
||||||
|
|
||||||
|
var selection: FamilyActivitySelection {
|
||||||
|
get { AppRestrictSharedStore.selection }
|
||||||
|
set {
|
||||||
|
AppRestrictSharedStore.selection = newValue
|
||||||
|
// Drop enabled tokens that are no longer in selection
|
||||||
|
let apps = newValue.applicationTokens
|
||||||
|
AppRestrictSharedStore.enabledTokens = AppRestrictSharedStore.enabledTokens.intersection(apps)
|
||||||
|
refreshMonitoringAndShield()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var applicationTokens: [ApplicationToken] {
|
||||||
|
Array(selection.applicationTokens)
|
||||||
|
}
|
||||||
|
|
||||||
|
var enabledTokens: Set<ApplicationToken> {
|
||||||
|
get { AppRestrictSharedStore.enabledTokens }
|
||||||
|
set {
|
||||||
|
AppRestrictSharedStore.enabledTokens = newValue
|
||||||
|
refreshMonitoringAndShield()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func requestAuthorization() async throws {
|
||||||
|
try await center.requestAuthorization(for: .individual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeSelection(_ incoming: FamilyActivitySelection) {
|
||||||
|
var current = selection
|
||||||
|
current.applicationTokens.formUnion(incoming.applicationTokens)
|
||||||
|
current.categoryTokens.formUnion(incoming.categoryTokens)
|
||||||
|
current.webDomainTokens.formUnion(incoming.webDomainTokens)
|
||||||
|
selection = current
|
||||||
|
}
|
||||||
|
|
||||||
|
func isEnabled(_ token: ApplicationToken) -> Bool {
|
||||||
|
enabledTokens.contains(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setEnabled(_ token: ApplicationToken, enabled: Bool) {
|
||||||
|
var set = enabledTokens
|
||||||
|
if enabled {
|
||||||
|
set.insert(token)
|
||||||
|
} else {
|
||||||
|
set.remove(token)
|
||||||
|
}
|
||||||
|
enabledTokens = set
|
||||||
|
}
|
||||||
|
|
||||||
|
func catalogId(for token: ApplicationToken) -> String? {
|
||||||
|
AppRestrictSharedStore.catalogId(for: token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func link(catalogId: String, token: ApplicationToken, displayName: String? = nil, iconURL: String? = nil) {
|
||||||
|
AppRestrictSharedStore.setLink(
|
||||||
|
catalogId: catalogId,
|
||||||
|
token: token,
|
||||||
|
displayName: displayName,
|
||||||
|
iconURL: iconURL
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func catalogItem(for token: ApplicationToken) -> AppCatalogItem? {
|
||||||
|
guard let record = AppRestrictSharedStore.linkRecord(for: token) else { return nil }
|
||||||
|
return AppCatalogStore.resolve(link: record)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unlink(_ token: ApplicationToken) {
|
||||||
|
AppRestrictSharedStore.removeLink(for: token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeApplication(_ token: ApplicationToken) {
|
||||||
|
unlink(token)
|
||||||
|
var set = enabledTokens
|
||||||
|
set.remove(token)
|
||||||
|
enabledTokens = set
|
||||||
|
|
||||||
|
var current = selection
|
||||||
|
current.applicationTokens.remove(token)
|
||||||
|
selection = current
|
||||||
|
}
|
||||||
|
|
||||||
|
var shieldConfig: AppRestrictShieldConfig {
|
||||||
|
get { AppRestrictSharedStore.shieldConfig }
|
||||||
|
set { AppRestrictSharedStore.shieldConfig = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
func saveCustomShieldImage(_ image: UIImage) -> Bool {
|
||||||
|
AppRestrictSharedStore.saveCustomImage(image)
|
||||||
|
}
|
||||||
|
|
||||||
|
func refreshMonitoringAndShield() {
|
||||||
|
let tokens = enabledTokens
|
||||||
|
AppRestrictSharedStore.applyShield(for: tokens)
|
||||||
|
activityCenter.stopMonitoring([activityName])
|
||||||
|
guard !tokens.isEmpty else { return }
|
||||||
|
// Near-daily schedule so Monitor can re-apply after reboot / schedule boundaries.
|
||||||
|
let schedule = DeviceActivitySchedule(
|
||||||
|
intervalStart: DateComponents(hour: 0, minute: 0),
|
||||||
|
intervalEnd: DateComponents(hour: 23, minute: 59),
|
||||||
|
repeats: true
|
||||||
|
)
|
||||||
|
do {
|
||||||
|
try activityCenter.startMonitoring(activityName, during: schedule)
|
||||||
|
} catch {
|
||||||
|
// Shield already applied above; monitoring is best-effort.
|
||||||
|
print("[AppRestrict] startMonitoring failed: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,5 +12,11 @@
|
||||||
</array>
|
</array>
|
||||||
<key>com.apple.developer.networking.wifi-info</key>
|
<key>com.apple.developer.networking.wifi-info</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>com.apple.developer.family-controls</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.application-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>group.cn.zuomeng.jisuloca</string>
|
||||||
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
//
|
||||||
|
// AppCatalogStore.swift
|
||||||
|
// QuickLocation
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
|
struct AppCatalogItem: Codable, Equatable {
|
||||||
|
let id: String
|
||||||
|
let name: String
|
||||||
|
let icon: String
|
||||||
|
let keywords: [String]
|
||||||
|
let iconURL: String?
|
||||||
|
|
||||||
|
init(id: String, name: String, icon: String, keywords: [String], iconURL: String? = nil) {
|
||||||
|
self.id = id
|
||||||
|
self.name = name
|
||||||
|
self.icon = icon
|
||||||
|
self.keywords = keywords
|
||||||
|
self.iconURL = iconURL
|
||||||
|
}
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
id = try container.decode(String.self, forKey: .id)
|
||||||
|
name = try container.decode(String.self, forKey: .name)
|
||||||
|
icon = try container.decodeIfPresent(String.self, forKey: .icon) ?? ""
|
||||||
|
keywords = try container.decodeIfPresent([String].self, forKey: .keywords) ?? []
|
||||||
|
iconURL = try container.decodeIfPresent(String.self, forKey: .iconURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
var isRemoteIcon: Bool {
|
||||||
|
guard let iconURL, !iconURL.isEmpty else { return false }
|
||||||
|
return icon.isEmpty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AppCatalogStore {
|
||||||
|
static let sharedItems: [AppCatalogItem] = {
|
||||||
|
guard let url = Bundle.main.url(forResource: "app_catalog", withExtension: "json"),
|
||||||
|
let data = try? Data(contentsOf: url),
|
||||||
|
let items = try? JSONDecoder().decode([AppCatalogItem].self, from: data) else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}()
|
||||||
|
|
||||||
|
static func item(id: String) -> AppCatalogItem? {
|
||||||
|
sharedItems.first { $0.id == id }
|
||||||
|
}
|
||||||
|
|
||||||
|
static func resolve(link: AppRestrictLinkRecord) -> AppCatalogItem? {
|
||||||
|
if let local = item(id: link.catalogId) {
|
||||||
|
return local
|
||||||
|
}
|
||||||
|
guard let name = link.displayName, !name.isEmpty else { return nil }
|
||||||
|
return AppCatalogItem(
|
||||||
|
id: link.catalogId,
|
||||||
|
name: name,
|
||||||
|
icon: "",
|
||||||
|
keywords: [],
|
||||||
|
iconURL: link.iconURL
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func search(_ query: String) -> [AppCatalogItem] {
|
||||||
|
let q = query.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
||||||
|
guard !q.isEmpty else { return sharedItems }
|
||||||
|
return sharedItems.filter { item in
|
||||||
|
item.name.lowercased().contains(q)
|
||||||
|
|| item.keywords.contains(where: { $0.lowercased().contains(q) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func image(for item: AppCatalogItem) -> UIImage? {
|
||||||
|
guard !item.icon.isEmpty else { return placeholder(for: item.name) }
|
||||||
|
return UIImage(named: item.icon) ?? placeholder(for: item.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func setIcon(for imageView: UIImageView, item: AppCatalogItem, cornerRadius: CGFloat = 10) {
|
||||||
|
imageView.layer.cornerRadius = cornerRadius
|
||||||
|
imageView.clipsToBounds = true
|
||||||
|
imageView.contentMode = .scaleAspectFill
|
||||||
|
if let urlString = item.iconURL, let url = URL(string: urlString) {
|
||||||
|
imageView.kf.setImage(with: url, placeholder: placeholder(for: item.name))
|
||||||
|
} else {
|
||||||
|
imageView.kf.cancelDownloadTask()
|
||||||
|
imageView.image = image(for: item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func placeholder(for name: String) -> UIImage? {
|
||||||
|
let size = CGSize(width: 44, height: 44)
|
||||||
|
let renderer = UIGraphicsImageRenderer(size: size)
|
||||||
|
return renderer.image { _ in
|
||||||
|
UIColor(hexStr: "#16B3FF").setFill()
|
||||||
|
UIBezierPath(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: 10).fill()
|
||||||
|
let letter = String(name.prefix(1)) as NSString
|
||||||
|
let attrs: [NSAttributedString.Key: Any] = [
|
||||||
|
.font: UIFont.systemFont(ofSize: 18, weight: .bold),
|
||||||
|
.foregroundColor: UIColor.white
|
||||||
|
]
|
||||||
|
let textSize = letter.size(withAttributes: attrs)
|
||||||
|
letter.draw(
|
||||||
|
at: CGPoint(x: (size.width - textSize.width) / 2, y: (size.height - textSize.height) / 2),
|
||||||
|
withAttributes: attrs
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,164 @@
|
||||||
|
//
|
||||||
|
// AppRestrictCell.swift
|
||||||
|
// QuickLocation
|
||||||
|
//
|
||||||
|
|
||||||
|
import FamilyControls
|
||||||
|
import UIKit
|
||||||
|
import ManagedSettings
|
||||||
|
|
||||||
|
@available(iOS 16.0, *)
|
||||||
|
final class AppRestrictCell: UITableViewCell {
|
||||||
|
static let reuseId = "AppRestrictCell"
|
||||||
|
|
||||||
|
private enum Metric {
|
||||||
|
static let appIcon: CGFloat = 40
|
||||||
|
static let linkIcon: CGFloat = 28
|
||||||
|
static let actionWidth: CGFloat = 48
|
||||||
|
static let actionHeight: CGFloat = 56
|
||||||
|
/// divider 右侧:8 + 48 + 8 + 48 + 12
|
||||||
|
static let actionAreaWidth: CGFloat = 124
|
||||||
|
}
|
||||||
|
|
||||||
|
var onPair: (() -> Void)?
|
||||||
|
var onDelete: (() -> Void)?
|
||||||
|
|
||||||
|
private let systemIconHost = UIView()
|
||||||
|
private let linkIconView = UIImageView()
|
||||||
|
private let catalogIconView = UIImageView()
|
||||||
|
private let placeholderView = UIView()
|
||||||
|
private let placeholderLab = UILabel()
|
||||||
|
|
||||||
|
private let divider = UIView()
|
||||||
|
private let pairBtn = UIButton(type: .custom)
|
||||||
|
private let deleteBtn = UIButton(type: .custom)
|
||||||
|
|
||||||
|
private var systemTokenHost: ApplicationTokenIconHostingView?
|
||||||
|
|
||||||
|
override init(style: CellStyle, reuseIdentifier: String?) {
|
||||||
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
|
selectionStyle = .none
|
||||||
|
backgroundColor = .clear
|
||||||
|
contentView.backgroundColor = .clear
|
||||||
|
setup()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||||
|
|
||||||
|
private func setup() {
|
||||||
|
contentView.addSubview(systemIconHost)
|
||||||
|
contentView.addSubview(linkIconView)
|
||||||
|
contentView.addSubview(catalogIconView)
|
||||||
|
contentView.addSubview(placeholderView)
|
||||||
|
contentView.addSubview(divider)
|
||||||
|
contentView.addSubview(pairBtn)
|
||||||
|
contentView.addSubview(deleteBtn)
|
||||||
|
|
||||||
|
systemIconHost.clipsToBounds = true
|
||||||
|
systemIconHost.layer.cornerRadius = 10
|
||||||
|
systemIconHost.layoutChain
|
||||||
|
.left(16)
|
||||||
|
.centerY()
|
||||||
|
.width(Metric.appIcon)
|
||||||
|
.height(Metric.appIcon)
|
||||||
|
|
||||||
|
linkIconView.contentMode = .scaleAspectFit
|
||||||
|
linkIconView.layoutChain
|
||||||
|
.leftToRightOfView(systemIconHost, offset: 12)
|
||||||
|
.centerY()
|
||||||
|
.width(Metric.linkIcon)
|
||||||
|
.height(Metric.linkIcon)
|
||||||
|
|
||||||
|
catalogIconView.contentMode = .scaleAspectFill
|
||||||
|
catalogIconView.layer.cornerRadius = 10
|
||||||
|
catalogIconView.clipsToBounds = true
|
||||||
|
catalogIconView.layoutChain
|
||||||
|
.leftToRightOfView(linkIconView, offset: 12)
|
||||||
|
.centerY()
|
||||||
|
.width(Metric.appIcon)
|
||||||
|
.height(Metric.appIcon)
|
||||||
|
|
||||||
|
placeholderView.backgroundColor = UIColor(hexStr: "#F3F4F6")
|
||||||
|
placeholderView.layer.cornerRadius = Metric.appIcon / 2
|
||||||
|
placeholderView.layer.borderWidth = 1
|
||||||
|
placeholderView.layer.borderColor = UIColor(hexStr: "#E5E7EB").cgColor
|
||||||
|
placeholderView.layoutChain
|
||||||
|
.leftToRightOfView(linkIconView, offset: 12)
|
||||||
|
.centerY()
|
||||||
|
.width(Metric.appIcon)
|
||||||
|
.height(Metric.appIcon)
|
||||||
|
placeholderView.addSubview(placeholderLab)
|
||||||
|
placeholderLab.text = "?"
|
||||||
|
placeholderLab.font = .systemFont(ofSize: 18, weight: .bold)
|
||||||
|
placeholderLab.textColor = UIColor(hexStr: "#9CA3AF")
|
||||||
|
placeholderLab.textAlignment = .center
|
||||||
|
placeholderLab.layoutChain.center()
|
||||||
|
|
||||||
|
divider.backgroundColor = UIColor(hexStr: "#F0F0F0")
|
||||||
|
divider.layoutChain
|
||||||
|
.right(Metric.actionAreaWidth)
|
||||||
|
.width(1)
|
||||||
|
.top(12)
|
||||||
|
.bottom(12)
|
||||||
|
|
||||||
|
configureActionButton(pairBtn, imageName: "AppRestrict/pair_action_link", title: "配对", color: UIColor(hexStr: "#00ADFE"))
|
||||||
|
configureActionButton(deleteBtn, imageName: "AppRestrict/pair_action_delete", title: "删除", color: UIColor(hexStr: "#FF2323"))
|
||||||
|
pairBtn.addTarget(self, action: #selector(tapPair), for: .touchUpInside)
|
||||||
|
deleteBtn.addTarget(self, action: #selector(tapDelete), for: .touchUpInside)
|
||||||
|
|
||||||
|
pairBtn.layoutChain
|
||||||
|
.leftToRightOfView(divider, offset: 8)
|
||||||
|
.centerY()
|
||||||
|
.width(Metric.actionWidth)
|
||||||
|
.height(Metric.actionHeight)
|
||||||
|
|
||||||
|
deleteBtn.layoutChain
|
||||||
|
.leftToRightOfView(pairBtn, offset: 8)
|
||||||
|
.centerY()
|
||||||
|
.width(Metric.actionWidth)
|
||||||
|
.height(Metric.actionHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
pairBtn.setImageEdge(.top, spacing: 4)
|
||||||
|
deleteBtn.setImageEdge(.top, spacing: 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func configureActionButton(_ button: UIButton, imageName: String, title: String, color: UIColor) {
|
||||||
|
button.setImage(UIImage(named: imageName)?.withRenderingMode(.alwaysOriginal), for: .normal)
|
||||||
|
button.setTitle(title, for: .normal)
|
||||||
|
button.setTitleColor(color, for: .normal)
|
||||||
|
button.titleLabel?.font = .systemFont(ofSize: 12, weight: .medium)
|
||||||
|
button.titleLabel?.textAlignment = .center
|
||||||
|
button.titleLabel?.lineBreakMode = .byClipping
|
||||||
|
}
|
||||||
|
|
||||||
|
func configure(token: ApplicationToken, catalog: AppCatalogItem?) {
|
||||||
|
systemTokenHost?.subviews.forEach { $0.removeFromSuperview() }
|
||||||
|
let host = ApplicationTokenIconHostingView(token: token)
|
||||||
|
systemIconHost.addSubview(host)
|
||||||
|
host.layoutChain.edges()
|
||||||
|
systemTokenHost = host
|
||||||
|
|
||||||
|
let isPaired = catalog != nil
|
||||||
|
linkIconView.image = UIImage(named: isPaired ? "AppRestrict/pair_link_active" : "AppRestrict/pair_link_inactive")?
|
||||||
|
.withRenderingMode(.alwaysOriginal)
|
||||||
|
|
||||||
|
catalogIconView.isHidden = !isPaired
|
||||||
|
placeholderView.isHidden = isPaired
|
||||||
|
if let catalog {
|
||||||
|
AppCatalogStore.setIcon(for: catalogIconView, item: catalog, cornerRadius: 10)
|
||||||
|
} else {
|
||||||
|
catalogIconView.image = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func tapPair() {
|
||||||
|
onPair?()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func tapDelete() {
|
||||||
|
onDelete?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,147 @@
|
||||||
|
//
|
||||||
|
// AppRestrictShieldSettingsVC.swift
|
||||||
|
// QuickLocation
|
||||||
|
//
|
||||||
|
|
||||||
|
import PhotosUI
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@available(iOS 16.0, *)
|
||||||
|
final class AppRestrictShieldSettingsVC: BaseViewController {
|
||||||
|
|
||||||
|
override var isNavigationBarHidden: Bool { false }
|
||||||
|
|
||||||
|
private let titleField = UITextField()
|
||||||
|
private let subtitleField = UITextField()
|
||||||
|
private let buttonField = UITextField()
|
||||||
|
private let preview = UIImageView()
|
||||||
|
private var config = AppRestrictManager.shared.shieldConfig
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
view.backgroundColor = UIColor(hexStr: "#FAFAFA")
|
||||||
|
title = "锁定界面设置"
|
||||||
|
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "保存", style: .done, target: self, action: #selector(save))
|
||||||
|
|
||||||
|
let stack = UIStackView()
|
||||||
|
stack.axis = .vertical
|
||||||
|
stack.spacing = 12
|
||||||
|
view.addSubview(stack)
|
||||||
|
stack.layoutChain.top(kNaviHeight + 20).left(16).right(16)
|
||||||
|
|
||||||
|
func makeField(_ placeholder: String, text: String) -> UITextField {
|
||||||
|
let f = UITextField()
|
||||||
|
f.placeholder = placeholder
|
||||||
|
f.text = text
|
||||||
|
f.borderStyle = .roundedRect
|
||||||
|
f.font = .systemFont(ofSize: 15)
|
||||||
|
f.backgroundColor = .white
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
titleField.text = config.title
|
||||||
|
subtitleField.text = config.subtitle
|
||||||
|
buttonField.text = config.primaryButtonLabel
|
||||||
|
[titleField, subtitleField, buttonField].forEach {
|
||||||
|
$0.borderStyle = .roundedRect
|
||||||
|
$0.font = .systemFont(ofSize: 15)
|
||||||
|
$0.backgroundColor = .white
|
||||||
|
stack.addArrangedSubview($0)
|
||||||
|
$0.layoutChain.height(44)
|
||||||
|
}
|
||||||
|
titleField.placeholder = "标题"
|
||||||
|
subtitleField.placeholder = "副标题"
|
||||||
|
buttonField.placeholder = "按钮文案"
|
||||||
|
|
||||||
|
let presetLab = UILabel()
|
||||||
|
presetLab.text = "预设图集"
|
||||||
|
presetLab.font = .systemFont(ofSize: 14, weight: .bold)
|
||||||
|
stack.addArrangedSubview(presetLab)
|
||||||
|
|
||||||
|
let presetRow = UIStackView()
|
||||||
|
presetRow.axis = .horizontal
|
||||||
|
presetRow.spacing = 10
|
||||||
|
presetRow.distribution = .fillEqually
|
||||||
|
[("默认", AppRestrictShieldConfig.ImageSource.presetDefault),
|
||||||
|
("专注", .presetFocus),
|
||||||
|
("平静", .presetCalm)].forEach { title, source in
|
||||||
|
let btn = UIButton(type: .system)
|
||||||
|
btn.setTitle(title, for: .normal)
|
||||||
|
btn.backgroundColor = .white
|
||||||
|
btn.layer.cornerRadius = 8
|
||||||
|
btn.tag = source.hashValue
|
||||||
|
btn.addAction(UIAction { [weak self] _ in
|
||||||
|
self?.config.imageSource = source
|
||||||
|
self?.refreshPreview()
|
||||||
|
}, for: .touchUpInside)
|
||||||
|
presetRow.addArrangedSubview(btn)
|
||||||
|
btn.layoutChain.height(40)
|
||||||
|
}
|
||||||
|
stack.addArrangedSubview(presetRow)
|
||||||
|
|
||||||
|
let albumBtn = UIButton(type: .system)
|
||||||
|
albumBtn.setTitle("从相册选择", for: .normal)
|
||||||
|
albumBtn.backgroundColor = UIColor(hexStr: "#16B3FF")
|
||||||
|
albumBtn.setTitleColor(.white, for: .normal)
|
||||||
|
albumBtn.layer.cornerRadius = 10
|
||||||
|
albumBtn.addTarget(self, action: #selector(pickAlbum), for: .touchUpInside)
|
||||||
|
stack.addArrangedSubview(albumBtn)
|
||||||
|
albumBtn.layoutChain.height(44)
|
||||||
|
|
||||||
|
preview.contentMode = .scaleAspectFit
|
||||||
|
preview.backgroundColor = .white
|
||||||
|
preview.layer.cornerRadius = 12
|
||||||
|
preview.clipsToBounds = true
|
||||||
|
stack.addArrangedSubview(preview)
|
||||||
|
preview.layoutChain.height(180)
|
||||||
|
|
||||||
|
refreshPreview()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func refreshPreview() {
|
||||||
|
preview.image = AppRestrictSharedStore.loadShieldImage()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func save() {
|
||||||
|
config.title = titleField.text?.trimmingCharacters(in: .whitespacesAndNewlines).nilIfEmpty ?? AppRestrictShieldConfig.default.title
|
||||||
|
config.subtitle = subtitleField.text?.trimmingCharacters(in: .whitespacesAndNewlines).nilIfEmpty ?? AppRestrictShieldConfig.default.subtitle
|
||||||
|
config.primaryButtonLabel = buttonField.text?.trimmingCharacters(in: .whitespacesAndNewlines).nilIfEmpty ?? AppRestrictShieldConfig.default.primaryButtonLabel
|
||||||
|
AppRestrictManager.shared.shieldConfig = config
|
||||||
|
DLToast.showSuccess(text: "已保存")
|
||||||
|
navigationController?.popViewController(animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func pickAlbum() {
|
||||||
|
var conf = PHPickerConfiguration(photoLibrary: .shared())
|
||||||
|
conf.filter = .images
|
||||||
|
conf.selectionLimit = 1
|
||||||
|
let picker = PHPickerViewController(configuration: conf)
|
||||||
|
picker.delegate = self
|
||||||
|
present(picker, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 16.0, *)
|
||||||
|
extension AppRestrictShieldSettingsVC: PHPickerViewControllerDelegate {
|
||||||
|
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
|
||||||
|
picker.dismiss(animated: true)
|
||||||
|
guard let provider = results.first?.itemProvider,
|
||||||
|
provider.canLoadObject(ofClass: UIImage.self) else { return }
|
||||||
|
provider.loadObject(ofClass: UIImage.self) { [weak self] object, _ in
|
||||||
|
guard let image = object as? UIImage else { return }
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
guard let self else { return }
|
||||||
|
if AppRestrictManager.shared.saveCustomShieldImage(image) {
|
||||||
|
self.config.imageSource = .album
|
||||||
|
self.refreshPreview()
|
||||||
|
} else {
|
||||||
|
DLToast.show(text: "图片保存失败")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension String {
|
||||||
|
var nilIfEmpty: String? { isEmpty ? nil : self }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
//
|
||||||
|
// AppRestrictVC.swift
|
||||||
|
// QuickLocation
|
||||||
|
//
|
||||||
|
|
||||||
|
import FamilyControls
|
||||||
|
import UIKit
|
||||||
|
import RxSwift
|
||||||
|
import RxCocoa
|
||||||
|
import ManagedSettings
|
||||||
|
|
||||||
|
@available(iOS 16.0, *)
|
||||||
|
final class AppRestrictVC: BaseViewController {
|
||||||
|
private var rootView: AppRestrictView!
|
||||||
|
private var tokens: [ApplicationToken] = []
|
||||||
|
private var linkingToken: ApplicationToken?
|
||||||
|
|
||||||
|
override func loadView() {
|
||||||
|
rootView = AppRestrictView(frame: UIScreen.main.bounds)
|
||||||
|
view = rootView
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
rootView.tableView.dataSource = self
|
||||||
|
rootView.tableView.delegate = self
|
||||||
|
rootView.tableView.register(AppRestrictCell.self, forCellReuseIdentifier: AppRestrictCell.reuseId)
|
||||||
|
bind()
|
||||||
|
reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func bind() {
|
||||||
|
rootView.navView.onBackTapped = { [weak self] in
|
||||||
|
self?.navigationController?.popViewController(animated: true)
|
||||||
|
}
|
||||||
|
rootView.addBtn.rx.tap.subscribe(onNext: { [weak self] in
|
||||||
|
self?.addApps()
|
||||||
|
}).disposed(by: disposeBag)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func reload() {
|
||||||
|
tokens = AppRestrictManager.shared.applicationTokens
|
||||||
|
rootView.setListVisible(!tokens.isEmpty, rowCount: tokens.count)
|
||||||
|
rootView.tableView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func addApps() {
|
||||||
|
Task { @MainActor in
|
||||||
|
do {
|
||||||
|
if !AppRestrictManager.shared.isAuthorized {
|
||||||
|
try await AppRestrictManager.shared.requestAuthorization()
|
||||||
|
}
|
||||||
|
guard AppRestrictManager.shared.isAuthorized else {
|
||||||
|
DLToast.show(text: "需要屏幕使用时间权限")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let presenter = FamilyActivityPickerPresenter(selection: AppRestrictManager.shared.selection)
|
||||||
|
presenter.onComplete = { [weak self] selection in
|
||||||
|
AppRestrictManager.shared.mergeSelection(selection)
|
||||||
|
self?.reload()
|
||||||
|
}
|
||||||
|
present(presenter, animated: false)
|
||||||
|
} catch {
|
||||||
|
DLToast.show(text: "授权失败,请在设置中开启屏幕使用时间权限")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func openLink(for token: ApplicationToken) {
|
||||||
|
linkingToken = token
|
||||||
|
let vc = SelectActivityVC()
|
||||||
|
vc.onSelect = { [weak self] item in
|
||||||
|
guard let self, let token = self.linkingToken else { return }
|
||||||
|
AppRestrictManager.shared.link(
|
||||||
|
catalogId: item.id,
|
||||||
|
token: token,
|
||||||
|
displayName: item.name,
|
||||||
|
iconURL: item.iconURL
|
||||||
|
)
|
||||||
|
self.linkingToken = nil
|
||||||
|
self.reload()
|
||||||
|
}
|
||||||
|
let nav = UINavigationController(rootViewController: vc)
|
||||||
|
present(nav, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func deleteApp(_ token: ApplicationToken) {
|
||||||
|
AppRestrictManager.shared.removeApplication(token)
|
||||||
|
reload()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 16.0, *)
|
||||||
|
extension AppRestrictVC: UITableViewDataSource, UITableViewDelegate {
|
||||||
|
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
|
tokens.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
|
let cell = tableView.dequeueReusableCell(withIdentifier: AppRestrictCell.reuseId, for: indexPath) as! AppRestrictCell
|
||||||
|
let token = tokens[indexPath.row]
|
||||||
|
let catalog = AppRestrictManager.shared.catalogItem(for: token)
|
||||||
|
cell.configure(token: token, catalog: catalog)
|
||||||
|
cell.onPair = { [weak self] in
|
||||||
|
self?.openLink(for: token)
|
||||||
|
}
|
||||||
|
cell.onDelete = { [weak self] in
|
||||||
|
self?.deleteApp(token)
|
||||||
|
}
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fallback when system is below iOS 16.
|
||||||
|
final class AppRestrictUnsupportedVC: BaseViewController {
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
view.backgroundColor = .white
|
||||||
|
let nav = BaseNavigationView(title: "应用配对库")
|
||||||
|
view.addSubview(nav)
|
||||||
|
nav.layoutChain.top().edgesHorzontal().height(kNaviHeight)
|
||||||
|
nav.onBackTapped = { [weak self] in
|
||||||
|
self?.navigationController?.popViewController(animated: true)
|
||||||
|
}
|
||||||
|
let lab = UILabel()
|
||||||
|
lab.text = "该功能需要 iOS 16 及以上系统"
|
||||||
|
lab.textAlignment = .center
|
||||||
|
lab.textColor = UIColor(hexStr: "#767676")
|
||||||
|
view.addSubview(lab)
|
||||||
|
lab.layoutChain.center()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AppRestrictFactory {
|
||||||
|
static func make() -> UIViewController {
|
||||||
|
if #available(iOS 16.0, *) {
|
||||||
|
return AppRestrictVC()
|
||||||
|
}
|
||||||
|
return AppRestrictUnsupportedVC()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,188 @@
|
||||||
|
//
|
||||||
|
// AppRestrictView.swift
|
||||||
|
// QuickLocation
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
final class AppRestrictView: UIView {
|
||||||
|
let navView = BaseNavigationView(title: "应用配对库")
|
||||||
|
let scrollView = UIScrollView()
|
||||||
|
let contentView = UIView()
|
||||||
|
let guideCard = UIView()
|
||||||
|
let exampleCard = UIView()
|
||||||
|
let listCard = UIView()
|
||||||
|
let tableView = UITableView(frame: .zero, style: .plain)
|
||||||
|
let addBtn = UIButton(type: .custom)
|
||||||
|
|
||||||
|
private var tableHeightConstraint: NSLayoutConstraint?
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
backgroundColor = UIColor(hexStr: "#FAFAFA")
|
||||||
|
setup()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||||
|
|
||||||
|
func setListVisible(_ visible: Bool, rowCount: Int, rowHeight: CGFloat = 80) {
|
||||||
|
listCard.isHidden = !visible
|
||||||
|
tableHeightConstraint?.constant = visible ? rowHeight * CGFloat(rowCount) : 0
|
||||||
|
layoutIfNeeded()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setup() {
|
||||||
|
addSubview(navView)
|
||||||
|
addSubview(scrollView)
|
||||||
|
addSubview(addBtn)
|
||||||
|
scrollView.addSubview(contentView)
|
||||||
|
|
||||||
|
contentView.addSubview(guideCard)
|
||||||
|
contentView.addSubview(exampleCard)
|
||||||
|
contentView.addSubview(listCard)
|
||||||
|
listCard.addSubview(tableView)
|
||||||
|
|
||||||
|
navView.layoutChain.top().edgesHorzontal().height(kNaviHeight)
|
||||||
|
|
||||||
|
addBtn.setTitle("新增配对应用", for: .normal)
|
||||||
|
addBtn.setTitleColor(.white, for: .normal)
|
||||||
|
addBtn.titleLabel?.font = FontManager.boboBold(18)
|
||||||
|
addBtn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
|
||||||
|
addBtn.cornerRadius = 20
|
||||||
|
addBtn.layoutChain
|
||||||
|
.left(16)
|
||||||
|
.right(16)
|
||||||
|
.bottom(kSafeBottomMargin + 16)
|
||||||
|
.height(56)
|
||||||
|
|
||||||
|
scrollView.layoutChain
|
||||||
|
.topToBottomOfView(navView)
|
||||||
|
.edgesHorzontal()
|
||||||
|
.bottomToTopOfView(addBtn, offset: -12)
|
||||||
|
|
||||||
|
contentView.layoutChain
|
||||||
|
.edges()
|
||||||
|
.widthToView(scrollView)
|
||||||
|
|
||||||
|
guideCard.layoutChain
|
||||||
|
.top(12)
|
||||||
|
.edgesHorzontal(16)
|
||||||
|
|
||||||
|
exampleCard.layoutChain
|
||||||
|
.topToBottomOfView(guideCard, offset: 12)
|
||||||
|
.edgesHorzontal(16)
|
||||||
|
|
||||||
|
listCard.layoutChain
|
||||||
|
.topToBottomOfView(exampleCard, offset: 12)
|
||||||
|
.edgesHorzontal(16)
|
||||||
|
.bottom(12)
|
||||||
|
|
||||||
|
tableView.backgroundColor = .clear
|
||||||
|
tableView.separatorStyle = .none
|
||||||
|
tableView.isScrollEnabled = false
|
||||||
|
tableView.rowHeight = 80
|
||||||
|
tableView.layoutChain.edges(all: 12)
|
||||||
|
tableHeightConstraint = tableView.heightAnchor.constraint(equalToConstant: 0)
|
||||||
|
tableHeightConstraint?.isActive = true
|
||||||
|
|
||||||
|
styleCard(guideCard)
|
||||||
|
styleCard(exampleCard)
|
||||||
|
styleCard(listCard)
|
||||||
|
setupGuideCard()
|
||||||
|
setupExampleCard()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func styleCard(_ card: UIView) {
|
||||||
|
card.backgroundColor = .white
|
||||||
|
card.layer.cornerRadius = 26
|
||||||
|
card.clipsToBounds = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupGuideCard() {
|
||||||
|
let titleLab = UILabel()
|
||||||
|
let attr = NSMutableAttributedString(
|
||||||
|
string: "如何配对?只需 ",
|
||||||
|
attributes: [
|
||||||
|
.font: UIFont.systemFont(ofSize: 14, weight: .bold),
|
||||||
|
.foregroundColor: UIColor(hexStr: "#293445")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
attr.append(NSAttributedString(
|
||||||
|
string: "3",
|
||||||
|
attributes: [
|
||||||
|
.font: FontManager.boboBold(16),
|
||||||
|
.foregroundColor: UIColor(hexStr: "#00ADFE")
|
||||||
|
]
|
||||||
|
))
|
||||||
|
attr.append(NSAttributedString(
|
||||||
|
string: " 步",
|
||||||
|
attributes: [
|
||||||
|
.font: UIFont.systemFont(ofSize: 14, weight: .bold),
|
||||||
|
.foregroundColor: UIColor(hexStr: "#293445")
|
||||||
|
]
|
||||||
|
))
|
||||||
|
titleLab.attributedText = attr
|
||||||
|
guideCard.addSubview(titleLab)
|
||||||
|
titleLab.layoutChain.top(20).left(20).right(20)
|
||||||
|
|
||||||
|
let stepsView = PairGuideStepsView()
|
||||||
|
guideCard.addSubview(stepsView)
|
||||||
|
stepsView.layoutChain
|
||||||
|
.topToBottomOfView(titleLab, offset: 20)
|
||||||
|
.edgesHorzontal(20)
|
||||||
|
.bottom(20)
|
||||||
|
.height(72)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupExampleCard() {
|
||||||
|
let badge = UIView()
|
||||||
|
badge.backgroundColor = UIColor(hexStr: "#F1FFED")
|
||||||
|
badge.layer.cornerRadius = 8
|
||||||
|
badge.clipsToBounds = true
|
||||||
|
exampleCard.addSubview(badge)
|
||||||
|
badge.layoutChain.top(10).left(10)
|
||||||
|
|
||||||
|
let checkIcon = UIImageView(
|
||||||
|
image: UIImage(named: "AppRestrict/pair_badge_check")?.withRenderingMode(.alwaysOriginal)
|
||||||
|
)
|
||||||
|
checkIcon.contentMode = .scaleAspectFit
|
||||||
|
let badgeLab = UILabel()
|
||||||
|
badgeLab.text = "正确示例"
|
||||||
|
badgeLab.font = .systemFont(ofSize: 12, weight: .medium)
|
||||||
|
badgeLab.textColor = UIColor(hexStr: "#00BB47")
|
||||||
|
|
||||||
|
badge.addSubview(checkIcon)
|
||||||
|
checkIcon.layoutChain.width(12).height(12).edgesVertical(7).left(4)
|
||||||
|
|
||||||
|
badge.addSubview(badgeLab)
|
||||||
|
badgeLab.layoutChain.leftToRightOfView(checkIcon, offset: 1).right(5).centerY()
|
||||||
|
|
||||||
|
let wechatItem = AppCatalogStore.item(id: "wechat")
|
||||||
|
let leftIcon = UIImageView()
|
||||||
|
let rightIcon = UIImageView()
|
||||||
|
let linkIcon = UIImageView(image: UIImage(named: "AppRestrict/pair_link_active")?.withRenderingMode(.alwaysOriginal))
|
||||||
|
[leftIcon, rightIcon].forEach {
|
||||||
|
$0.contentMode = .scaleAspectFill
|
||||||
|
$0.layer.cornerRadius = 10
|
||||||
|
$0.clipsToBounds = true
|
||||||
|
}
|
||||||
|
if let wechatItem {
|
||||||
|
AppCatalogStore.setIcon(for: leftIcon, item: wechatItem)
|
||||||
|
AppCatalogStore.setIcon(for: rightIcon, item: wechatItem)
|
||||||
|
}
|
||||||
|
linkIcon.contentMode = .scaleAspectFit
|
||||||
|
|
||||||
|
let row = UIStackView(arrangedSubviews: [leftIcon, linkIcon, rightIcon])
|
||||||
|
row.axis = .horizontal
|
||||||
|
row.alignment = .center
|
||||||
|
row.spacing = 12
|
||||||
|
exampleCard.addSubview(row)
|
||||||
|
row.layoutChain
|
||||||
|
.top(36)
|
||||||
|
.centerX()
|
||||||
|
.bottom(14)
|
||||||
|
leftIcon.layoutChain.width(50).height(50)
|
||||||
|
rightIcon.layoutChain.width(50).height(50)
|
||||||
|
linkIcon.layoutChain.width(35).height(25)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
//
|
||||||
|
// FamilyActivityPickerHost.swift
|
||||||
|
// QuickLocation
|
||||||
|
//
|
||||||
|
|
||||||
|
import FamilyControls
|
||||||
|
import SwiftUI
|
||||||
|
import UIKit
|
||||||
|
import ManagedSettings
|
||||||
|
|
||||||
|
@available(iOS 16.0, *)
|
||||||
|
private struct FamilyActivityPickerSheet: View {
|
||||||
|
@State private var selection: FamilyActivitySelection
|
||||||
|
@State private var isPresented = true
|
||||||
|
let onComplete: (FamilyActivitySelection) -> Void
|
||||||
|
|
||||||
|
init(initial: FamilyActivitySelection, onComplete: @escaping (FamilyActivitySelection) -> Void) {
|
||||||
|
_selection = State(initialValue: initial)
|
||||||
|
self.onComplete = onComplete
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Color.clear
|
||||||
|
.familyActivityPicker(isPresented: $isPresented, selection: $selection)
|
||||||
|
.onChange(of: isPresented) { presented in
|
||||||
|
if !presented {
|
||||||
|
onComplete(selection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 16.0, *)
|
||||||
|
final class FamilyActivityPickerPresenter: UIViewController {
|
||||||
|
private let initial: FamilyActivitySelection
|
||||||
|
var onComplete: ((FamilyActivitySelection) -> Void)?
|
||||||
|
|
||||||
|
init(selection: FamilyActivitySelection = FamilyActivitySelection()) {
|
||||||
|
self.initial = selection
|
||||||
|
super.init(nibName: nil, bundle: nil)
|
||||||
|
modalPresentationStyle = .overFullScreen
|
||||||
|
view.backgroundColor = .clear
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||||
|
|
||||||
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
|
super.viewDidAppear(animated)
|
||||||
|
guard children.isEmpty else { return }
|
||||||
|
let root = FamilyActivityPickerSheet(initial: initial) { [weak self] selection in
|
||||||
|
self?.onComplete?(selection)
|
||||||
|
self?.dismiss(animated: false)
|
||||||
|
}
|
||||||
|
let host = UIHostingController(rootView: root)
|
||||||
|
host.view.backgroundColor = .clear
|
||||||
|
addChild(host)
|
||||||
|
view.addSubview(host.view)
|
||||||
|
host.view.frame = .zero
|
||||||
|
host.didMove(toParent: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 16.0, *)
|
||||||
|
struct ApplicationTokenLabelView: View {
|
||||||
|
let token: ApplicationToken
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Label(token)
|
||||||
|
.labelStyle(.titleAndIcon)
|
||||||
|
.lineLimit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 16.0, *)
|
||||||
|
struct ApplicationTokenIconView: View {
|
||||||
|
let token: ApplicationToken
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Label(token)
|
||||||
|
.labelStyle(.iconOnly)
|
||||||
|
.scaleEffect(2.4)
|
||||||
|
.frame(width: 40, height: 40)
|
||||||
|
.clipped()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 16.0, *)
|
||||||
|
final class ApplicationTokenIconHostingView: UIView {
|
||||||
|
private let hosting: UIHostingController<ApplicationTokenIconView>
|
||||||
|
|
||||||
|
init(token: ApplicationToken) {
|
||||||
|
hosting = UIHostingController(rootView: ApplicationTokenIconView(token: token))
|
||||||
|
super.init(frame: .zero)
|
||||||
|
hosting.view.backgroundColor = .clear
|
||||||
|
addSubview(hosting.view)
|
||||||
|
hosting.view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
hosting.view.topAnchor.constraint(equalTo: topAnchor),
|
||||||
|
hosting.view.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||||
|
hosting.view.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||||
|
hosting.view.bottomAnchor.constraint(equalTo: bottomAnchor)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||||
|
|
||||||
|
func update(token: ApplicationToken) {
|
||||||
|
hosting.rootView = ApplicationTokenIconView(token: token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 16.0, *)
|
||||||
|
final class ApplicationTokenLabelHostingView: UIView {
|
||||||
|
private let hosting: UIHostingController<ApplicationTokenLabelView>
|
||||||
|
|
||||||
|
init(token: ApplicationToken) {
|
||||||
|
hosting = UIHostingController(rootView: ApplicationTokenLabelView(token: token))
|
||||||
|
super.init(frame: .zero)
|
||||||
|
hosting.view.backgroundColor = .clear
|
||||||
|
addSubview(hosting.view)
|
||||||
|
hosting.view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
hosting.view.topAnchor.constraint(equalTo: topAnchor),
|
||||||
|
hosting.view.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||||
|
hosting.view.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||||
|
hosting.view.bottomAnchor.constraint(equalTo: bottomAnchor)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||||
|
|
||||||
|
func update(token: ApplicationToken) {
|
||||||
|
hosting.rootView = ApplicationTokenLabelView(token: token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
//
|
||||||
|
// ITunesSearchService.swift
|
||||||
|
// QuickLocation
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum ITunesSearchError: Error {
|
||||||
|
case invalidURL
|
||||||
|
case invalidResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ITunesSearchService {
|
||||||
|
private struct SearchResponse: Decodable {
|
||||||
|
let results: [ITunesAppResult]
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct ITunesAppResult: Decodable {
|
||||||
|
let trackId: Int
|
||||||
|
let trackName: String
|
||||||
|
let artworkUrl100: String?
|
||||||
|
let artworkUrl512: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
static func search(term: String, limit: Int = 25) async throws -> [AppCatalogItem] {
|
||||||
|
let trimmed = term.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
guard !trimmed.isEmpty else { return [] }
|
||||||
|
|
||||||
|
var components = URLComponents(string: "https://itunes.apple.com/search")
|
||||||
|
components?.queryItems = [
|
||||||
|
URLQueryItem(name: "term", value: trimmed),
|
||||||
|
URLQueryItem(name: "entity", value: "software"),
|
||||||
|
URLQueryItem(name: "country", value: "cn"),
|
||||||
|
URLQueryItem(name: "limit", value: "\(limit)")
|
||||||
|
]
|
||||||
|
guard let url = components?.url else { throw ITunesSearchError.invalidURL }
|
||||||
|
|
||||||
|
let (data, response) = try await URLSession.shared.data(from: url)
|
||||||
|
guard let http = response as? HTTPURLResponse, (200...299).contains(http.statusCode) else {
|
||||||
|
throw ITunesSearchError.invalidResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
let decoded = try JSONDecoder().decode(SearchResponse.self, from: data)
|
||||||
|
return decoded.results.map { result in
|
||||||
|
let iconURL = result.artworkUrl512 ?? result.artworkUrl100
|
||||||
|
return AppCatalogItem(
|
||||||
|
id: "itunes:\(result.trackId)",
|
||||||
|
name: result.trackName,
|
||||||
|
icon: "",
|
||||||
|
keywords: [],
|
||||||
|
iconURL: iconURL
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func mergedResults(local: [AppCatalogItem], remote: [AppCatalogItem]) -> [AppCatalogItem] {
|
||||||
|
var seen = Set<String>()
|
||||||
|
var merged: [AppCatalogItem] = []
|
||||||
|
for item in local + remote {
|
||||||
|
guard !seen.contains(item.id) else { continue }
|
||||||
|
seen.insert(item.id)
|
||||||
|
merged.append(item)
|
||||||
|
}
|
||||||
|
return merged
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
//
|
||||||
|
// PairGuideStepsView.swift
|
||||||
|
// QuickLocation
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
final class PairGuideStepsView: UIView {
|
||||||
|
private struct Step {
|
||||||
|
let number: String
|
||||||
|
let title: String
|
||||||
|
let color: UIColor
|
||||||
|
}
|
||||||
|
|
||||||
|
private let steps: [Step] = [
|
||||||
|
Step(number: "1", title: "新增配对应用", color: UIColor(hexStr: "#8B5CF6")),
|
||||||
|
Step(number: "2", title: "选择相同应用", color: UIColor(hexStr: "#16B3FF")),
|
||||||
|
Step(number: "3", title: "完成配对", color: UIColor(hexStr: "#22C55E"))
|
||||||
|
]
|
||||||
|
|
||||||
|
private let circleSize: CGFloat = 28
|
||||||
|
private let dashLayer = CAShapeLayer()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
backgroundColor = .clear
|
||||||
|
dashLayer.strokeColor = UIColor(hexStr: "#D1D5DB").cgColor
|
||||||
|
dashLayer.lineWidth = 1
|
||||||
|
dashLayer.lineDashPattern = [3, 3]
|
||||||
|
dashLayer.fillColor = UIColor.clear.cgColor
|
||||||
|
layer.addSublayer(dashLayer)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||||
|
|
||||||
|
override var intrinsicContentSize: CGSize {
|
||||||
|
CGSize(width: UIView.noIntrinsicMetric, height: 72)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
subviews.forEach { $0.removeFromSuperview() }
|
||||||
|
|
||||||
|
let columnWidth = bounds.width / 3
|
||||||
|
let circleY = circleSize / 2 + 2
|
||||||
|
let centers = (0..<steps.count).map { columnWidth * (CGFloat($0) + 0.5) }
|
||||||
|
|
||||||
|
let path = UIBezierPath()
|
||||||
|
path.move(to: CGPoint(x: centers.first ?? 0, y: circleY))
|
||||||
|
path.addLine(to: CGPoint(x: centers.last ?? bounds.width, y: circleY))
|
||||||
|
dashLayer.path = path.cgPath
|
||||||
|
dashLayer.frame = bounds
|
||||||
|
|
||||||
|
for (index, step) in steps.enumerated() {
|
||||||
|
let centerX = centers[index]
|
||||||
|
|
||||||
|
let circle = UILabel(frame: CGRect(
|
||||||
|
x: centerX - circleSize / 2,
|
||||||
|
y: 0,
|
||||||
|
width: circleSize,
|
||||||
|
height: circleSize
|
||||||
|
))
|
||||||
|
circle.text = step.number
|
||||||
|
circle.font = FontManager.boboBold(14)
|
||||||
|
circle.textColor = .white
|
||||||
|
circle.textAlignment = .center
|
||||||
|
circle.backgroundColor = step.color
|
||||||
|
circle.layer.cornerRadius = circleSize / 2
|
||||||
|
circle.clipsToBounds = true
|
||||||
|
addSubview(circle)
|
||||||
|
|
||||||
|
let label = UILabel()
|
||||||
|
label.text = step.title
|
||||||
|
label.font = .systemFont(ofSize: 12, weight: .medium)
|
||||||
|
label.textColor = UIColor(hexStr: "#293445")
|
||||||
|
label.textAlignment = .center
|
||||||
|
label.frame = CGRect(
|
||||||
|
x: columnWidth * CGFloat(index) + 4,
|
||||||
|
y: circleSize + 10,
|
||||||
|
width: columnWidth - 8,
|
||||||
|
height: 34
|
||||||
|
)
|
||||||
|
addSubview(label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
//
|
||||||
|
// SelectActivityVC.swift
|
||||||
|
// QuickLocation
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import RxSwift
|
||||||
|
import RxCocoa
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
|
final class SelectActivityCell: UITableViewCell {
|
||||||
|
static let reuseId = "SelectActivityCell"
|
||||||
|
|
||||||
|
private let iconView = UIImageView()
|
||||||
|
|
||||||
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
|
iconView.contentMode = .scaleAspectFill
|
||||||
|
iconView.layer.cornerRadius = 10
|
||||||
|
iconView.clipsToBounds = true
|
||||||
|
contentView.addSubview(iconView)
|
||||||
|
iconView.layoutChain.left(16).centerY().width(40).height(40)
|
||||||
|
textLabel?.layoutChain.leftToRightOfView(iconView, offset: 12).centerY().right(16)
|
||||||
|
selectionStyle = .default
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||||
|
|
||||||
|
func configure(item: AppCatalogItem) {
|
||||||
|
textLabel?.text = item.name
|
||||||
|
textLabel?.font = .systemFont(ofSize: 16, weight: .medium)
|
||||||
|
textLabel?.textColor = UIColor(hexStr: "#293445")
|
||||||
|
AppCatalogStore.setIcon(for: iconView, item: item)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func prepareForReuse() {
|
||||||
|
super.prepareForReuse()
|
||||||
|
iconView.kf.cancelDownloadTask()
|
||||||
|
iconView.image = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class SelectActivityVC: BaseViewController {
|
||||||
|
var onSelect: ((AppCatalogItem) -> Void)?
|
||||||
|
|
||||||
|
private let searchField = UITextField()
|
||||||
|
private let tableView = UITableView(frame: .zero, style: .plain)
|
||||||
|
private let activityIndicator = UIActivityIndicatorView(style: .medium)
|
||||||
|
private var items: [AppCatalogItem] = AppCatalogStore.sharedItems
|
||||||
|
private var searchTask: Task<Void, Never>?
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
view.backgroundColor = .white
|
||||||
|
title = "选取应用"
|
||||||
|
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "取消", style: .plain, target: self, action: #selector(cancel))
|
||||||
|
|
||||||
|
searchField.placeholder = "搜索应用"
|
||||||
|
searchField.borderStyle = .roundedRect
|
||||||
|
searchField.font = .systemFont(ofSize: 15)
|
||||||
|
searchField.clearButtonMode = .whileEditing
|
||||||
|
searchField.returnKeyType = .search
|
||||||
|
searchField.addTarget(self, action: #selector(searchChanged), for: .editingChanged)
|
||||||
|
view.addSubview(searchField)
|
||||||
|
searchField.layoutChain.top(12).left(16).right(16).height(40)
|
||||||
|
|
||||||
|
activityIndicator.hidesWhenStopped = true
|
||||||
|
view.addSubview(activityIndicator)
|
||||||
|
activityIndicator.layoutChain.centerX().topToBottomOfView(searchField, offset: 8)
|
||||||
|
|
||||||
|
tableView.register(SelectActivityCell.self, forCellReuseIdentifier: SelectActivityCell.reuseId)
|
||||||
|
tableView.dataSource = self
|
||||||
|
tableView.delegate = self
|
||||||
|
tableView.rowHeight = 56
|
||||||
|
view.addSubview(tableView)
|
||||||
|
tableView.layoutChain.topToBottomOfView(searchField, offset: 12).edgesHorzontal().bottom()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func cancel() {
|
||||||
|
dismiss(animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func searchChanged() {
|
||||||
|
let query = searchField.text ?? ""
|
||||||
|
searchTask?.cancel()
|
||||||
|
|
||||||
|
let trimmed = query.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
if trimmed.isEmpty {
|
||||||
|
activityIndicator.stopAnimating()
|
||||||
|
items = AppCatalogStore.sharedItems
|
||||||
|
tableView.reloadData()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let localHits = AppCatalogStore.search(query)
|
||||||
|
items = localHits
|
||||||
|
tableView.reloadData()
|
||||||
|
activityIndicator.startAnimating()
|
||||||
|
|
||||||
|
searchTask = Task { [weak self] in
|
||||||
|
try? await Task.sleep(nanoseconds: 300_000_000)
|
||||||
|
guard let self, !Task.isCancelled else { return }
|
||||||
|
do {
|
||||||
|
let remote = try await ITunesSearchService.search(term: trimmed)
|
||||||
|
guard !Task.isCancelled else { return }
|
||||||
|
await MainActor.run {
|
||||||
|
self.activityIndicator.stopAnimating()
|
||||||
|
self.items = ITunesSearchService.mergedResults(local: localHits, remote: remote)
|
||||||
|
self.tableView.reloadData()
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
await MainActor.run {
|
||||||
|
self.activityIndicator.stopAnimating()
|
||||||
|
if self.items.isEmpty {
|
||||||
|
self.items = localHits
|
||||||
|
self.tableView.reloadData()
|
||||||
|
}
|
||||||
|
DLToast.show(text: "搜索失败,请检查网络")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SelectActivityVC: UITableViewDataSource, UITableViewDelegate {
|
||||||
|
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
|
items.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
|
let cell = tableView.dequeueReusableCell(withIdentifier: SelectActivityCell.reuseId, for: indexPath) as! SelectActivityCell
|
||||||
|
cell.configure(item: items[indexPath.row])
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
|
let item = items[indexPath.row]
|
||||||
|
onSelect?(item)
|
||||||
|
dismiss(animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "wechat",
|
||||||
|
"name": "微信",
|
||||||
|
"icon": "Login/wechat",
|
||||||
|
"keywords": ["微信", "wechat", "weixin"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "douyin",
|
||||||
|
"name": "抖音",
|
||||||
|
"icon": "AppRestrict/catalog_douyin",
|
||||||
|
"keywords": ["抖音", "douyin", "tiktok"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "qq",
|
||||||
|
"name": "QQ",
|
||||||
|
"icon": "AppRestrict/catalog_qq",
|
||||||
|
"keywords": ["qq"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "xiaohongshu",
|
||||||
|
"name": "小红书",
|
||||||
|
"icon": "AppRestrict/catalog_xiaohongshu",
|
||||||
|
"keywords": ["小红书", "red", "xhs"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "taobao",
|
||||||
|
"name": "淘宝",
|
||||||
|
"icon": "AppRestrict/catalog_taobao",
|
||||||
|
"keywords": ["淘宝", "taobao"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "netease_music",
|
||||||
|
"name": "网易云音乐",
|
||||||
|
"icon": "AppRestrict/catalog_netease",
|
||||||
|
"keywords": ["网易云", "music"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "kuaishou",
|
||||||
|
"name": "快手",
|
||||||
|
"icon": "AppRestrict/catalog_kuaishou",
|
||||||
|
"keywords": ["快手", "kuaishou"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "wangzhe",
|
||||||
|
"name": "王者荣耀",
|
||||||
|
"icon": "AppRestrict/catalog_wangzhe",
|
||||||
|
"keywords": ["王者", "荣耀"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -33,6 +33,8 @@ final class FeatureIntroVC: BaseViewController {
|
||||||
AppRouter.push(vc)
|
AppRouter.push(vc)
|
||||||
case .createBubble:
|
case .createBubble:
|
||||||
AppRouter.push(Route.createBubble)
|
AppRouter.push(Route.createBubble)
|
||||||
|
case .lockDistract:
|
||||||
|
AppRouter.push(Route.lockDistract)
|
||||||
case .searchLocation:
|
case .searchLocation:
|
||||||
AppRouter.push(Route.searchLocation)
|
AppRouter.push(Route.searchLocation)
|
||||||
case .sos:
|
case .sos:
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ struct FeatureIntroItem {
|
||||||
enum FeatureIntroAction {
|
enum FeatureIntroAction {
|
||||||
case signIn
|
case signIn
|
||||||
case createBubble
|
case createBubble
|
||||||
|
case lockDistract
|
||||||
case searchLocation
|
case searchLocation
|
||||||
case sos
|
case sos
|
||||||
case placeholder
|
case placeholder
|
||||||
|
|
@ -58,7 +59,7 @@ final class FeatureIntroView: UIView {
|
||||||
titleColor: UIColor(hexStr: "#2F8A5B"),
|
titleColor: UIColor(hexStr: "#2F8A5B"),
|
||||||
subtitleColor: UIColor(hexStr: "#6AAD88"),
|
subtitleColor: UIColor(hexStr: "#6AAD88"),
|
||||||
arrowTint: UIColor(hexStr: "#3EAE72"),
|
arrowTint: UIColor(hexStr: "#3EAE72"),
|
||||||
action: .placeholder
|
action: .lockDistract
|
||||||
),
|
),
|
||||||
FeatureIntroItem(
|
FeatureIntroItem(
|
||||||
title: "飞鸽传书",
|
title: "飞鸽传书",
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ class CreateGroupVC: BaseViewController {
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
view.backgroundColor = UIColor(hexStr: "#FAFAFA")
|
||||||
|
|
||||||
bindViewModel()
|
bindViewModel()
|
||||||
reactiveAction()
|
reactiveAction()
|
||||||
|
|
@ -31,18 +32,25 @@ class CreateGroupVC: BaseViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func reactiveAction() {
|
private func reactiveAction() {
|
||||||
rootView.groupIconInputView.rx.tapGesture.subscribe { _ in
|
rootView.iconCarousel.onSelectIndex = { [weak self] index in
|
||||||
let vc = GroupIconListVC(iconIndex: "1")
|
self?.viewModel.iconIndex = index
|
||||||
vc.onSelectIcon = { index in
|
}
|
||||||
self.viewModel.iconIndex = index
|
|
||||||
self.rootView.groupIconImgView.image = UIImage(named: "GroupIcon/\(index)")
|
|
||||||
}
|
|
||||||
self.navigationController?.pushViewController(vc, animated: true)
|
|
||||||
}.disposed(by: disposeBag)
|
|
||||||
|
|
||||||
rootView.submitBtn.rx.tap.subscribe(onNext: { _ in
|
rootView.submitBtn.rx.tap.subscribe(onNext: { [weak self] _ in
|
||||||
self.viewModel.requestCreateGroup()
|
self?.viewModel.requestCreateGroup()
|
||||||
}).disposed(by: disposeBag)
|
}).disposed(by: disposeBag)
|
||||||
|
|
||||||
|
rootView.onConfirmAddTag = { [weak self] text in
|
||||||
|
guard let self = self else { return }
|
||||||
|
switch self.viewModel.addCustomTag(text) {
|
||||||
|
case .success:
|
||||||
|
self.rootView.dismissAddTagSheet()
|
||||||
|
case .empty:
|
||||||
|
DLToast.show(text: "请输入标签")
|
||||||
|
case .duplicate:
|
||||||
|
DLToast.show(text: "标签已存在")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func bindViewModel() {
|
private func bindViewModel() {
|
||||||
|
|
@ -58,8 +66,29 @@ class CreateGroupVC: BaseViewController {
|
||||||
.bind(to: rootView.tagView.rx.items(dataSource: dataSource))
|
.bind(to: rootView.tagView.rx.items(dataSource: dataSource))
|
||||||
.disposed(by: disposeBag)
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
rootView.tagView.rx.modelSelected(String.self)
|
viewModel.output.sectionedItems
|
||||||
.subscribe(viewModel.cellAction.inputs)
|
.observe(on: MainScheduler.instance)
|
||||||
|
.subscribe(onNext: { [weak self] _ in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self?.rootView.updateTagViewHeight()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
|
rootView.tagView.rx.itemSelected
|
||||||
|
.subscribe(onNext: { [weak self] indexPath in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.rootView.tagView.deselectItem(at: indexPath, animated: false)
|
||||||
|
let tag = self.dataSource[indexPath]
|
||||||
|
if tag == CreateGroupViewModel.addTagToken {
|
||||||
|
self.rootView.showAddTagSheet()
|
||||||
|
} else {
|
||||||
|
self.viewModel.cellAction.execute(tag)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
|
rootView.tagView.rx.setDelegate(self)
|
||||||
.disposed(by: disposeBag)
|
.disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,8 +96,22 @@ class CreateGroupVC: BaseViewController {
|
||||||
private lazy var dataSource: RxCollectionViewSectionedReloadDataSource<GroupTagListSectionModel> = {
|
private lazy var dataSource: RxCollectionViewSectionedReloadDataSource<GroupTagListSectionModel> = {
|
||||||
RxCollectionViewSectionedReloadDataSource<GroupTagListSectionModel> { datasource, collectionView, indexPath, item in
|
RxCollectionViewSectionedReloadDataSource<GroupTagListSectionModel> { datasource, collectionView, indexPath, item in
|
||||||
let cell: TagCell = collectionView.dequeueReusableCell(for: indexPath)
|
let cell: TagCell = collectionView.dequeueReusableCell(for: indexPath)
|
||||||
cell.configure(item, isSelected: self.viewModel.isSelected(tag: item))
|
if item == CreateGroupViewModel.addTagToken {
|
||||||
|
cell.configureAsAdd()
|
||||||
|
} else {
|
||||||
|
cell.configure(item, isSelected: self.viewModel.isSelected(tag: item))
|
||||||
|
}
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension CreateGroupVC: UICollectionViewDelegateFlowLayout {
|
||||||
|
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
|
||||||
|
guard dataSource.sectionModels.indices.contains(indexPath.section),
|
||||||
|
dataSource.sectionModels[indexPath.section].items.indices.contains(indexPath.item) else {
|
||||||
|
return CGSize(width: 52, height: 28)
|
||||||
|
}
|
||||||
|
return TagCell.preferredSize(for: dataSource[indexPath])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,15 @@ import SwiftyUserDefaults
|
||||||
typealias GroupTagListSectionModel = SectionModel<String, String>
|
typealias GroupTagListSectionModel = SectionModel<String, String>
|
||||||
|
|
||||||
class CreateGroupViewModel {
|
class CreateGroupViewModel {
|
||||||
|
enum AddTagResult {
|
||||||
|
case success
|
||||||
|
case empty
|
||||||
|
case duplicate
|
||||||
|
}
|
||||||
|
|
||||||
|
static let addTagToken = "__add_tag__"
|
||||||
|
static let tagMaxLength = 10
|
||||||
|
|
||||||
struct Input {
|
struct Input {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -26,7 +35,7 @@ class CreateGroupViewModel {
|
||||||
|
|
||||||
private let sectionedItems = PublishSubject<[GroupTagListSectionModel]>()
|
private let sectionedItems = PublishSubject<[GroupTagListSectionModel]>()
|
||||||
|
|
||||||
private let tagList = ["私密", "游戏", "运动", "美食",
|
private var tagList = ["私密", "游戏", "运动", "美食",
|
||||||
"自驾", "聚会", "旅行", "学习"]
|
"自驾", "聚会", "旅行", "学习"]
|
||||||
|
|
||||||
var selectedTagList: [String] = [] {
|
var selectedTagList: [String] = [] {
|
||||||
|
|
@ -56,9 +65,21 @@ class CreateGroupViewModel {
|
||||||
return selectedTagList.first(where: { tag == $0 }) != nil
|
return selectedTagList.first(where: { tag == $0 }) != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addCustomTag(_ raw: String) -> AddTagResult {
|
||||||
|
let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
guard !trimmed.isEmpty else { return .empty }
|
||||||
|
let tag = String(trimmed.prefix(Self.tagMaxLength))
|
||||||
|
if tagList.contains(tag) {
|
||||||
|
return .duplicate
|
||||||
|
}
|
||||||
|
tagList.append(tag)
|
||||||
|
selectedTagList.append(tag)
|
||||||
|
return .success
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - 加载数据
|
// MARK: - 加载数据
|
||||||
func loadData() {
|
func loadData() {
|
||||||
sectionedItems.onNext(tagList.mapSection())
|
sectionedItems.onNext((tagList + [Self.addTagToken]).mapSection())
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Request
|
// MARK: - Request
|
||||||
|
|
|
||||||
|
|
@ -642,7 +642,7 @@ final class CircleGroupCell: UITableViewCell {
|
||||||
private let iconView: UIImageView = {
|
private let iconView: UIImageView = {
|
||||||
let iv = UIImageView()
|
let iv = UIImageView()
|
||||||
iv.contentMode = .scaleAspectFill
|
iv.contentMode = .scaleAspectFill
|
||||||
iv.cornerRadius = 20
|
iv.cornerRadius = 10
|
||||||
iv.clipsToBounds = true
|
iv.clipsToBounds = true
|
||||||
iv.backgroundColor = UIColor(hexStr: "#F0F0F0")
|
iv.backgroundColor = UIColor(hexStr: "#F0F0F0")
|
||||||
return iv
|
return iv
|
||||||
|
|
|
||||||
|
|
@ -17,20 +17,24 @@ class JoinGroupVC: BaseViewController {
|
||||||
rootView = JoinGroupView(frame: UIScreen.main.bounds)
|
rootView = JoinGroupView(frame: UIScreen.main.bounds)
|
||||||
view = rootView
|
view = rootView
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
view.backgroundColor = .white
|
||||||
|
|
||||||
// Do any additional setup after loading the view.
|
|
||||||
rootView.textField.rx.controlEvent(.editingDidEndOnExit).subscribe(onNext: {
|
rootView.textField.rx.controlEvent(.editingDidEndOnExit).subscribe(onNext: {
|
||||||
self.rootView.textField.resignFirstResponder()
|
self.rootView.textField.resignFirstResponder()
|
||||||
self.requestOperateGroup()
|
self.requestOperateGroup()
|
||||||
}).disposed(by: disposeBag)
|
}).disposed(by: disposeBag)
|
||||||
|
|
||||||
rootView.submitBtn.rx.tap.subscribe(onNext: { _ in
|
rootView.submitBtn.rx.tap.subscribe(onNext: { _ in
|
||||||
self.requestOperateGroup()
|
self.requestOperateGroup()
|
||||||
}).disposed(by: disposeBag)
|
}).disposed(by: disposeBag)
|
||||||
|
|
||||||
|
rootView.keyboardConfirmBtn.rx.tap.subscribe(onNext: { _ in
|
||||||
|
self.rootView.textField.resignFirstResponder()
|
||||||
|
}).disposed(by: disposeBag)
|
||||||
|
|
||||||
rootView.scanBtn.rx.tap.subscribe(onNext: { _ in
|
rootView.scanBtn.rx.tap.subscribe(onNext: { _ in
|
||||||
let vc = ScanVC { code in
|
let vc = ScanVC { code in
|
||||||
self.rootView.textField.text = code
|
self.rootView.textField.text = code
|
||||||
|
|
@ -39,12 +43,12 @@ class JoinGroupVC: BaseViewController {
|
||||||
AppRouter.push(vc)
|
AppRouter.push(vc)
|
||||||
}).disposed(by: disposeBag)
|
}).disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidAppear(_ animated: Bool) {
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
super.viewDidAppear(animated)
|
super.viewDidAppear(animated)
|
||||||
rootView.textField.becomeFirstResponder()
|
rootView.textField.becomeFirstResponder()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - API
|
// MARK: - API
|
||||||
private func requestOperateGroup() {
|
private func requestOperateGroup() {
|
||||||
guard rootView.inviteCode.count == 6 else {
|
guard rootView.inviteCode.count == 6 else {
|
||||||
|
|
|
||||||
|
|
@ -12,263 +12,278 @@ import RxCocoa
|
||||||
class JoinGroupView: UIView {
|
class JoinGroupView: UIView {
|
||||||
|
|
||||||
var disposeBag = DisposeBag()
|
var disposeBag = DisposeBag()
|
||||||
|
|
||||||
var numberBtns: [UIButton] = []
|
var numberBtns: [UIButton] = []
|
||||||
|
|
||||||
var inviteCode: String = "" {
|
var inviteCode: String = "" {
|
||||||
didSet {
|
didSet { updateCodeBoxes() }
|
||||||
for btn in numberBtns {
|
|
||||||
btn.setTitle("", for: .normal)
|
|
||||||
btn.isSelected = false
|
|
||||||
}
|
|
||||||
|
|
||||||
let characters = inviteCode.map { String($0) }
|
|
||||||
for (index, character) in characters.enumerated() {
|
|
||||||
numberBtns[index].setTitle(character, for: .normal)
|
|
||||||
if index < numberBtns.count-1 {
|
|
||||||
numberBtns[index].isSelected = false
|
|
||||||
numberBtns[index+1].isSelected = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func numAction(button: UIButton) {
|
@objc func numAction(button: UIButton) {
|
||||||
textField.becomeFirstResponder()
|
textField.becomeFirstResponder()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func updateCodeBoxes() {
|
||||||
|
for btn in numberBtns {
|
||||||
|
btn.setTitle("", for: .normal)
|
||||||
|
btn.isSelected = false
|
||||||
|
}
|
||||||
|
|
||||||
|
let characters = inviteCode.map { String($0) }
|
||||||
|
for (index, character) in characters.enumerated() {
|
||||||
|
numberBtns[index].setTitle(character, for: .normal)
|
||||||
|
numberBtns[index].isSelected = true
|
||||||
|
}
|
||||||
|
|
||||||
|
let cursorIndex = characters.count
|
||||||
|
if cursorIndex < numberBtns.count {
|
||||||
|
numberBtns[cursorIndex].isSelected = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func setupRx() {
|
private func setupRx() {
|
||||||
textField.rx.text
|
textField.rx.text
|
||||||
.orEmpty
|
.orEmpty
|
||||||
.subscribe(onNext: { text in
|
.subscribe(onNext: { text in
|
||||||
if text.count < 7 {
|
if text.count < 7 {
|
||||||
self.inviteCode = text
|
self.inviteCode = text
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
self.inviteCode = String(text.dropLast(2) + [text.last!])
|
self.inviteCode = String(text.dropLast(2) + [text.last!])
|
||||||
self.textField.text = self.inviteCode
|
self.textField.text = self.inviteCode
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.disposed(by: disposeBag)
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupUI() {
|
private func setupUI() {
|
||||||
addSubview(navBgView)
|
addSubview(navBgView)
|
||||||
|
addSubview(contentCard)
|
||||||
addSubview(navView)
|
addSubview(navView)
|
||||||
addSubview(titleLab)
|
|
||||||
addSubview(number1)
|
|
||||||
addSubview(number2)
|
|
||||||
addSubview(number3)
|
|
||||||
addSubview(lineView)
|
|
||||||
addSubview(number4)
|
|
||||||
addSubview(number5)
|
|
||||||
addSubview(number6)
|
|
||||||
addSubview(tipsLab)
|
|
||||||
addSubview(submitBtn)
|
|
||||||
addSubview(textField)
|
addSubview(textField)
|
||||||
|
addSubview(scanBtn)
|
||||||
|
|
||||||
|
contentCard.addSubview(cardTitleLab)
|
||||||
|
contentCard.addSubview(number1)
|
||||||
|
contentCard.addSubview(number2)
|
||||||
|
contentCard.addSubview(number3)
|
||||||
|
contentCard.addSubview(lineView)
|
||||||
|
contentCard.addSubview(number4)
|
||||||
|
contentCard.addSubview(number5)
|
||||||
|
contentCard.addSubview(number6)
|
||||||
|
contentCard.addSubview(submitBtn)
|
||||||
|
contentCard.addSubview(tipsLab)
|
||||||
|
|
||||||
navBgView.layoutChain
|
navBgView.layoutChain
|
||||||
.edges(excludingEdge: .bottom)
|
.top()
|
||||||
.heightToWidth(160/375)
|
.edgesHorzontal()
|
||||||
|
.heightToWidth(253 / 375)
|
||||||
|
|
||||||
navView.layoutChain
|
navView.layoutChain
|
||||||
.edges(excludingEdge: .bottom)
|
.top()
|
||||||
|
.edgesHorzontal()
|
||||||
.height(kNaviHeight)
|
.height(kNaviHeight)
|
||||||
|
|
||||||
titleLab.layoutChain
|
contentCard.layoutChain
|
||||||
.topToBottomOfView(navView, offset: 21)
|
.topToBottomOfView(navBgView, offset: -60)
|
||||||
|
.edgesHorzontal()
|
||||||
|
.bottom()
|
||||||
|
|
||||||
|
cardTitleLab.layoutChain
|
||||||
|
.top(17)
|
||||||
.centerX()
|
.centerX()
|
||||||
|
|
||||||
lineView.layoutChain
|
lineView.layoutChain
|
||||||
.topToBottomOfView(titleLab, offset: 58)
|
.topToBottomOfView(cardTitleLab, offset: 62)
|
||||||
.centerX()
|
.centerX()
|
||||||
.width(10)
|
.width(13)
|
||||||
.height(4)
|
.height(5)
|
||||||
|
|
||||||
number3.layoutChain
|
number3.layoutChain
|
||||||
.centerY(lineView)
|
.centerY(lineView)
|
||||||
.rightToLeftOfView(lineView, offset: -8)
|
.rightToLeftOfView(lineView, offset: -10)
|
||||||
.width(28)
|
.width(36)
|
||||||
.height(40)
|
.height(52)
|
||||||
|
|
||||||
number2.layoutChain
|
number2.layoutChain
|
||||||
.topToView(number3)
|
.topToView(number3)
|
||||||
.rightToLeftOfView(number3, offset: -8)
|
.rightToLeftOfView(number3, offset: -10)
|
||||||
.widthToView(number3)
|
.widthToView(number3)
|
||||||
.heightToView(number3)
|
.heightToView(number3)
|
||||||
|
|
||||||
number1.layoutChain
|
number1.layoutChain
|
||||||
.topToView(number3)
|
.topToView(number3)
|
||||||
.rightToLeftOfView(number2, offset: -8)
|
.rightToLeftOfView(number2, offset: -10)
|
||||||
.widthToView(number3)
|
.widthToView(number3)
|
||||||
.heightToView(number3)
|
.heightToView(number3)
|
||||||
|
|
||||||
number4.layoutChain
|
number4.layoutChain
|
||||||
.centerY(lineView)
|
.centerY(lineView)
|
||||||
.leftToRightOfView(lineView, offset: 8)
|
.leftToRightOfView(lineView, offset: 10)
|
||||||
.widthToView(number3)
|
.widthToView(number3)
|
||||||
.heightToView(number3)
|
.heightToView(number3)
|
||||||
|
|
||||||
number5.layoutChain
|
number5.layoutChain
|
||||||
.topToView(number3)
|
.topToView(number3)
|
||||||
.leftToRightOfView(number4, offset: 8)
|
.leftToRightOfView(number4, offset: 10)
|
||||||
.widthToView(number3)
|
.widthToView(number3)
|
||||||
.heightToView(number3)
|
.heightToView(number3)
|
||||||
|
|
||||||
number6.layoutChain
|
number6.layoutChain
|
||||||
.topToView(number3)
|
.topToView(number3)
|
||||||
.leftToRightOfView(number5, offset: 8)
|
.leftToRightOfView(number5, offset: 10)
|
||||||
.widthToView(number3)
|
.widthToView(number3)
|
||||||
.heightToView(number3)
|
.heightToView(number3)
|
||||||
|
|
||||||
tipsLab.layoutChain
|
|
||||||
.topToBottomOfView(lineView, offset: 38)
|
|
||||||
.centerX()
|
|
||||||
|
|
||||||
submitBtn.layoutChain
|
submitBtn.layoutChain
|
||||||
.bottom(kSafeBottomMargin + 36)
|
.topToBottomOfView(number3, offset: 40)
|
||||||
|
.edgesHorzontal(24)
|
||||||
|
.height(56)
|
||||||
|
|
||||||
|
tipsLab.layoutChain
|
||||||
|
.topToBottomOfView(submitBtn, offset: 16)
|
||||||
.centerX()
|
.centerX()
|
||||||
.edgesHorzontal(30)
|
.edgesHorzontal(24)
|
||||||
.height(50)
|
|
||||||
|
scanBtn.layoutChain
|
||||||
|
.top(kStatusBarHeight + 6)
|
||||||
|
.right(18)
|
||||||
|
.width(32).height(32)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func styleNavIconButton(_ button: UIButton) {
|
||||||
|
button.backgroundColor = .white
|
||||||
|
button.cornerRadius = 12
|
||||||
|
button.clipsToBounds = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private func makeCodeButton() -> UIButton {
|
||||||
|
let button = UIButton(type: .custom)
|
||||||
|
button.setTitleColor(.white, for: .selected)
|
||||||
|
button.setTitleColor(UIColor(hexStr: "#293445"), for: .normal)
|
||||||
|
button.titleLabel?.font = FontManager.youSheBiaoTiHei(30)
|
||||||
|
button.setBackgroundColor(UIColor(hexStr: "#16B3FF", alpha: 0.3), for: .normal)
|
||||||
|
button.setBackgroundColor(UIColor(hexStr: "#5CBBFF"), for: .selected)
|
||||||
|
button.cornerRadius = 12
|
||||||
|
button.addTarget(self, action: #selector(numAction), for: .touchUpInside)
|
||||||
|
return button
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy var navBgView: UIImageView = {
|
lazy var navBgView: UIImageView = {
|
||||||
let iv = UIImageView()
|
let iv = UIImageView()
|
||||||
iv.image = UIImage(named: "Common/navBar_bg_2")
|
iv.image = UIImage(named: "Group/join_hero_bg")
|
||||||
iv.contentMode = .scaleAspectFill
|
iv.contentMode = .scaleAspectFill
|
||||||
|
iv.clipsToBounds = true
|
||||||
return iv
|
return iv
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
lazy var contentCard: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = .white
|
||||||
|
view.layer.cornerRadius = 30
|
||||||
|
view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||||
|
view.clipsToBounds = true
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
lazy var navView: BaseNavigationView = {
|
lazy var navView: BaseNavigationView = {
|
||||||
let nav = BaseNavigationView(title: "加入圈子")
|
let nav = BaseNavigationView(title: " ")
|
||||||
nav.addRightButton(scanBtn)
|
|
||||||
return nav
|
return nav
|
||||||
}()
|
}()
|
||||||
|
|
||||||
lazy var scanBtn: UIButton = {
|
lazy var scanBtn: UIButton = {
|
||||||
let btn = UIButton(type: .custom)
|
let btn = UIButton(type: .custom)
|
||||||
btn.setImage(UIImage(named: "Group/scan"), for: .normal)
|
btn.setBackgroundImage(UIImage(named: "Group/join_scan"), for: .normal)
|
||||||
|
btn.extendEdgeInsets = UIEdgeInsets(top: 10, left: 20, bottom: 20, right: 18)
|
||||||
return btn
|
return btn
|
||||||
}()
|
}()
|
||||||
|
|
||||||
lazy var titleLab: UILabel = {
|
lazy var cardTitleLab: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.text = "输入邀请码"
|
label.text = "请输入邀请码"
|
||||||
label.font = .systemFont(ofSize: 24, weight: .medium)
|
label.font = .systemFont(ofSize: 16, weight: .bold)
|
||||||
label.textColor = ThemeManager.shared.color.titleAuxColor
|
label.textColor = UIColor(hexStr: "#353B4F")
|
||||||
label.textAlignment = .center
|
label.textAlignment = .center
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
lazy var tipsLab: UILabel = {
|
lazy var tipsLab: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.text = "向圈子创建者询问邀请码"
|
label.text = "输入好友分享的邀请码,添加好友专属圈子"
|
||||||
label.font = .systemFont(ofSize: 12, weight: .medium)
|
label.font = .systemFont(ofSize: 14, weight: .medium)
|
||||||
label.textColor = ThemeManager.shared.color.titleAuxColor
|
label.textColor = UIColor(hexStr: "#00ADFE")
|
||||||
|
label.textAlignment = .center
|
||||||
|
label.numberOfLines = 0
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
lazy var number1: UIButton = {
|
lazy var number1: UIButton = {
|
||||||
let button = UIButton (type: .custom)
|
let button = makeCodeButton()
|
||||||
button.setTitleColor(ThemeManager.shared.color.titleColor, for: .normal)
|
|
||||||
button.titleLabel?.font = .systemFont(ofSize: 14, weight: .medium)
|
|
||||||
button.setBackgroundColor(UIColor(hexStr: "#16B3FF").withAlphaComponent(0.3), for: .normal)
|
|
||||||
button.setBackgroundColor(UIColor(hexStr: "#5CBBFF"), for: .selected)
|
|
||||||
button.cornerRadius = 4
|
|
||||||
button.isSelected = true
|
button.isSelected = true
|
||||||
button.addTarget(self, action: #selector(numAction), for: .touchUpInside)
|
|
||||||
return button
|
return button
|
||||||
}()
|
}()
|
||||||
|
|
||||||
lazy var number2: UIButton = {
|
lazy var number2: UIButton = makeCodeButton()
|
||||||
let button = UIButton (type: .custom)
|
lazy var number3: UIButton = makeCodeButton()
|
||||||
button.setTitleColor(ThemeManager.shared.color.titleColor, for: .normal)
|
lazy var number4: UIButton = makeCodeButton()
|
||||||
button.titleLabel?.font = .systemFont(ofSize: 14, weight: .medium)
|
lazy var number5: UIButton = makeCodeButton()
|
||||||
button.setBackgroundColor(UIColor(hexStr: "#16B3FF").withAlphaComponent(0.3), for: .normal)
|
lazy var number6: UIButton = makeCodeButton()
|
||||||
button.setBackgroundColor(UIColor(hexStr: "#5CBBFF"), for: .selected)
|
|
||||||
button.addTarget(self, action: #selector(numAction), for: .touchUpInside)
|
|
||||||
button.cornerRadius = 4
|
|
||||||
return button
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var number3: UIButton = {
|
|
||||||
let button = UIButton (type: .custom)
|
|
||||||
button.setTitleColor(ThemeManager.shared.color.titleColor, for: .normal)
|
|
||||||
button.titleLabel?.font = .systemFont(ofSize: 14, weight: .medium)
|
|
||||||
button.setBackgroundColor(UIColor(hexStr: "#16B3FF").withAlphaComponent(0.3), for: .normal)
|
|
||||||
button.setBackgroundColor(UIColor(hexStr: "#5CBBFF"), for: .selected)
|
|
||||||
button.addTarget(self, action: #selector(numAction), for: .touchUpInside)
|
|
||||||
button.cornerRadius = 4
|
|
||||||
return button
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var number4: UIButton = {
|
|
||||||
let button = UIButton (type: .custom)
|
|
||||||
button.setTitleColor(ThemeManager.shared.color.titleColor, for: .normal)
|
|
||||||
button.titleLabel?.font = .systemFont(ofSize: 14, weight: .medium)
|
|
||||||
button.setBackgroundColor(UIColor(hexStr: "#16B3FF").withAlphaComponent(0.3), for: .normal)
|
|
||||||
button.setBackgroundColor(UIColor(hexStr: "#5CBBFF"), for: .selected)
|
|
||||||
button.addTarget(self, action: #selector(numAction), for: .touchUpInside)
|
|
||||||
button.cornerRadius = 4
|
|
||||||
return button
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var number5: UIButton = {
|
|
||||||
let button = UIButton (type: .custom)
|
|
||||||
button.setTitleColor(ThemeManager.shared.color.titleColor, for: .normal)
|
|
||||||
button.titleLabel?.font = .systemFont(ofSize: 14, weight: .medium)
|
|
||||||
button.setBackgroundColor(UIColor(hexStr: "#16B3FF").withAlphaComponent(0.3), for: .normal)
|
|
||||||
button.setBackgroundColor(UIColor(hexStr: "#5CBBFF"), for: .selected)
|
|
||||||
button.addTarget(self, action: #selector(numAction), for: .touchUpInside)
|
|
||||||
button.cornerRadius = 4
|
|
||||||
return button
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var number6: UIButton = {
|
|
||||||
let button = UIButton (type: .custom)
|
|
||||||
button.setTitleColor(ThemeManager.shared.color.titleColor, for: .normal)
|
|
||||||
button.titleLabel?.font = .systemFont(ofSize: 14, weight: .medium)
|
|
||||||
button.setBackgroundColor(UIColor(hexStr: "#16B3FF", alpha: 0.3), for: .normal)
|
|
||||||
button.setBackgroundColor(UIColor(hexStr: "#5CBBFF"), for: .selected)
|
|
||||||
button.addTarget(self, action: #selector(numAction), for: .touchUpInside)
|
|
||||||
button.cornerRadius = 4
|
|
||||||
return button
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var lineView: UIView = {
|
lazy var lineView: UIView = {
|
||||||
let view = UIView()
|
let view = UIView()
|
||||||
view.backgroundColor = UIColor(hexStr: "#16B3FF", alpha: 0.3)
|
view.backgroundColor = UIColor(hexStr: "#16B3FF")
|
||||||
|
view.cornerRadius = 0
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
||||||
lazy var submitBtn: UIButton = {
|
lazy var submitBtn: UIButton = {
|
||||||
let btn = UIButton(type: .custom)
|
let btn = UIButton(type: .custom)
|
||||||
btn.setTitle("加入", for: .normal)
|
btn.setTitle("加入", for: .normal)
|
||||||
btn.setTitleColor(UIColor(hexStr: "#0F2846"), for: .normal)
|
btn.setTitleColor(.white, for: .normal)
|
||||||
btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
|
btn.titleLabel?.font = FontManager.boboBold(18)
|
||||||
btn.setBackgroundImage(UIImage(named: "Common/gradient_bg"), for: .normal)
|
btn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
|
||||||
btn.cornerRadius = 25
|
btn.cornerRadius = 20
|
||||||
|
|
||||||
return btn
|
return btn
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
lazy var keyboardConfirmBtn: UIButton = {
|
||||||
|
let btn = UIButton(type: .custom)
|
||||||
|
btn.setTitle("确认", for: .normal)
|
||||||
|
btn.setTitleColor(.white, for: .normal)
|
||||||
|
btn.titleLabel?.font = FontManager.boboBold(18)
|
||||||
|
btn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
|
||||||
|
btn.cornerRadius = 20
|
||||||
|
return btn
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var keyboardConfirmBar: UIView = {
|
||||||
|
let bar = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 68))
|
||||||
|
bar.backgroundColor = .white
|
||||||
|
|
||||||
|
bar.addSubview(keyboardConfirmBtn)
|
||||||
|
keyboardConfirmBtn.layoutChain
|
||||||
|
.edgesHorzontal(16)
|
||||||
|
.top(8)
|
||||||
|
.height(52)
|
||||||
|
|
||||||
|
return bar
|
||||||
|
}()
|
||||||
|
|
||||||
lazy var textField: UITextField = {
|
lazy var textField: UITextField = {
|
||||||
let tf = UITextField()
|
let tf = UITextField()
|
||||||
tf.isHidden = true
|
tf.isHidden = true
|
||||||
tf.keyboardType = .asciiCapable
|
tf.keyboardType = .asciiCapable
|
||||||
tf.autocorrectionType = .no
|
tf.autocorrectionType = .no
|
||||||
tf.returnKeyType = .done
|
tf.returnKeyType = .done
|
||||||
|
// tf.inputAccessoryView = keyboardConfirmBar
|
||||||
return tf
|
return tf
|
||||||
}()
|
}()
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
backgroundColor = .white
|
backgroundColor = .white
|
||||||
setupUI()
|
setupUI()
|
||||||
setupRx()
|
setupRx()
|
||||||
|
|
||||||
numberBtns = [number1, number2, number3, number4, number5, number6]
|
numberBtns = [number1, number2, number3, number4, number5, number6]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -114,8 +114,8 @@ final class CreateBubbleSetupView: UIView {
|
||||||
confirmBtn.setTitleColor(.white, for: .normal)
|
confirmBtn.setTitleColor(.white, for: .normal)
|
||||||
confirmBtn.titleLabel?.font = FontManager.boboBold(18)
|
confirmBtn.titleLabel?.font = FontManager.boboBold(18)
|
||||||
confirmBtn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
|
confirmBtn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
|
||||||
confirmBtn.layer.cornerRadius = 16
|
confirmBtn.cornerRadius = 20
|
||||||
confirmBtn.layoutChain.left(20).right(20).bottom(34).height(56)
|
confirmBtn.layoutChain.left(30).right(30).bottom(34).height(56)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupPicker() {
|
private func setupPicker() {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
//
|
||||||
|
// LockDistractVC.swift
|
||||||
|
// QuickLocation
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import RxSwift
|
||||||
|
import RxCocoa
|
||||||
|
|
||||||
|
final class LockDistractVC: BaseViewController {
|
||||||
|
|
||||||
|
private var rootView: LockDistractView!
|
||||||
|
private var groupModel: GroupModel?
|
||||||
|
private var members: [GroupMemberModel] = []
|
||||||
|
private var selectedIndex = 0
|
||||||
|
|
||||||
|
override var isNavigationBarHidden: Bool { true }
|
||||||
|
|
||||||
|
override func loadView() {
|
||||||
|
rootView = LockDistractView(frame: UIScreen.main.bounds)
|
||||||
|
view = rootView
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
view.backgroundColor = UIColor(hexStr: "#FAFAFA")
|
||||||
|
bind()
|
||||||
|
loadGroupMembers()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func bind() {
|
||||||
|
rootView.navView.onBackTapped = { [weak self] in
|
||||||
|
self?.navigationController?.popViewController(animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
rootView.switchGroupBtn.rx.tap
|
||||||
|
.subscribe(onNext: { [weak self] in
|
||||||
|
self?.switchGroup()
|
||||||
|
})
|
||||||
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
|
rootView.memberPrevBtn.rx.tap
|
||||||
|
.subscribe(onNext: { [weak self] in
|
||||||
|
self?.stepMember(-1)
|
||||||
|
})
|
||||||
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
|
rootView.memberNextBtn.rx.tap
|
||||||
|
.subscribe(onNext: { [weak self] in
|
||||||
|
self?.stepMember(1)
|
||||||
|
})
|
||||||
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
|
// 未获取 / 锁定:本期不接业务
|
||||||
|
rootView.notFetchedView.rx.tapGesture
|
||||||
|
.subscribe(onNext: { _ in
|
||||||
|
/// TODO
|
||||||
|
}).disposed(by: disposeBag)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadGroupMembers() {
|
||||||
|
GroupService.groupInfo().subscribe(onNext: { [weak self] response in
|
||||||
|
guard let self, let model = response.model else { return }
|
||||||
|
self.groupModel = model
|
||||||
|
self.members = model.select_group_employee
|
||||||
|
self.selectedIndex = 0
|
||||||
|
self.refreshMember()
|
||||||
|
self.rootView.setTodayLockCount(0)
|
||||||
|
}).disposed(by: disposeBag)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func stepMember(_ delta: Int) {
|
||||||
|
guard !members.isEmpty else { return }
|
||||||
|
let count = members.count
|
||||||
|
selectedIndex = (selectedIndex + delta + count) % count
|
||||||
|
refreshMember()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func refreshMember() {
|
||||||
|
guard members.indices.contains(selectedIndex) else {
|
||||||
|
rootView.configureMember(name: " ", avatar: nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let m = members[selectedIndex]
|
||||||
|
rootView.configureMember(name: m.nick_name, avatar: m.userIcon)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func switchGroup() {
|
||||||
|
guard let groupModel else {
|
||||||
|
loadGroupMembers()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
GroupListPopView.show(groupModel: groupModel) { [weak self] groupKey in
|
||||||
|
guard let self, let key = groupKey else { return }
|
||||||
|
GroupService.operate(opType: "setdefault", requestData: ["group_key": key])
|
||||||
|
.subscribe(onNext: { [weak self] _ in
|
||||||
|
NotificationCenter.default.post(name: .RefreshGroupInfoNotification, object: nil)
|
||||||
|
self?.loadGroupMembers()
|
||||||
|
})
|
||||||
|
.disposed(by: self.disposeBag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,605 @@
|
||||||
|
//
|
||||||
|
// LockDistractView.swift
|
||||||
|
// QuickLocation
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import RxSwift
|
||||||
|
import RxCocoa
|
||||||
|
|
||||||
|
final class LockDistractView: UIView {
|
||||||
|
|
||||||
|
var disposeBag = DisposeBag()
|
||||||
|
|
||||||
|
let wallpaperNames = [
|
||||||
|
"LockDistract/wallpaper_1",
|
||||||
|
"LockDistract/wallpaper_2",
|
||||||
|
"LockDistract/wallpaper_3"
|
||||||
|
]
|
||||||
|
|
||||||
|
private(set) var selectedWallpaperIndex = 0
|
||||||
|
private let copyMaxLength = 20
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
backgroundColor = UIColor(hexStr: "#FAFAFA")
|
||||||
|
setupUI()
|
||||||
|
setupCopyLimit()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||||
|
|
||||||
|
// MARK: - Public
|
||||||
|
|
||||||
|
func configureMember(name: String, avatar: UIImage?) {
|
||||||
|
memberNameLab.text = name.isEmpty ? " " : name
|
||||||
|
memberAvatar.image = avatar ?? UIImage(named: "Common/default_avatar")
|
||||||
|
}
|
||||||
|
|
||||||
|
func setTodayLockCount(_ count: Int) {
|
||||||
|
todayCountLab.text = "\(count)"
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - UI
|
||||||
|
|
||||||
|
private func setupUI() {
|
||||||
|
addSubview(navBgView)
|
||||||
|
addSubview(navView)
|
||||||
|
navView.addRightButton(switchGroupBtn)
|
||||||
|
addSubview(scrollView)
|
||||||
|
scrollView.addSubview(contentView)
|
||||||
|
addSubview(lockBtn)
|
||||||
|
|
||||||
|
contentView.addSubview(titleLab)
|
||||||
|
contentView.addSubview(subtitleLab)
|
||||||
|
contentView.addSubview(heroPlaceholder)
|
||||||
|
contentView.addSubview(statsRow)
|
||||||
|
statsRow.addArrangedSubview(memberCard)
|
||||||
|
statsRow.addArrangedSubview(todayCard)
|
||||||
|
contentView.addSubview(appsSectionTitle)
|
||||||
|
contentView.addSubview(appsCard)
|
||||||
|
appsCard.addSubview(appsHeaderRow)
|
||||||
|
appsCard.addSubview(unknownAppIcon)
|
||||||
|
contentView.addSubview(styleSectionTitle)
|
||||||
|
contentView.addSubview(styleCard)
|
||||||
|
addSubview(tipRow)
|
||||||
|
|
||||||
|
navBgView.layoutChain
|
||||||
|
.edges(excludingEdge: .bottom)
|
||||||
|
.heightToWidth(160/375)
|
||||||
|
|
||||||
|
navView.layoutChain
|
||||||
|
.edges(excludingEdge: .bottom)
|
||||||
|
.height(kNaviHeight)
|
||||||
|
|
||||||
|
lockBtn.layoutChain
|
||||||
|
.bottom(kSafeBottomMargin + 0)
|
||||||
|
.edgesHorzontal(30)
|
||||||
|
.height(56)
|
||||||
|
|
||||||
|
scrollView.layoutChain
|
||||||
|
.topToBottomOfView(navView)
|
||||||
|
.edgesHorzontal()
|
||||||
|
.bottomToTopOfView(tipRow, offset: -12)
|
||||||
|
|
||||||
|
contentView.layoutChain
|
||||||
|
.edges()
|
||||||
|
.widthToView(scrollView)
|
||||||
|
|
||||||
|
titleLab.layoutChain
|
||||||
|
.top(8)
|
||||||
|
.left(20)
|
||||||
|
|
||||||
|
subtitleLab.layoutChain
|
||||||
|
.topToBottomOfView(titleLab, offset: 6)
|
||||||
|
.left(20)
|
||||||
|
.right(140)
|
||||||
|
|
||||||
|
heroPlaceholder.layoutChain
|
||||||
|
.top(0)
|
||||||
|
.right(16)
|
||||||
|
.width(120)
|
||||||
|
.height(100)
|
||||||
|
|
||||||
|
statsRow.layoutChain
|
||||||
|
.topToBottomOfView(subtitleLab, offset: 20)
|
||||||
|
.edgesHorzontal(16)
|
||||||
|
.height(120)
|
||||||
|
|
||||||
|
appsSectionTitle.layoutChain
|
||||||
|
.topToBottomOfView(statsRow, offset: 22)
|
||||||
|
.left(20)
|
||||||
|
|
||||||
|
appsCard.layoutChain
|
||||||
|
.topToBottomOfView(appsSectionTitle, offset: 12)
|
||||||
|
.edgesHorzontal(16)
|
||||||
|
|
||||||
|
appsHeaderRow.layoutChain
|
||||||
|
.top(16)
|
||||||
|
.edgesHorzontal(14)
|
||||||
|
|
||||||
|
unknownAppIcon.layoutChain
|
||||||
|
.topToBottomOfView(appsHeaderRow, offset: 14)
|
||||||
|
.left(14)
|
||||||
|
.width(56)
|
||||||
|
.height(56)
|
||||||
|
.bottom(16)
|
||||||
|
|
||||||
|
styleSectionTitle.layoutChain
|
||||||
|
.topToBottomOfView(appsCard, offset: 22)
|
||||||
|
.left(20)
|
||||||
|
|
||||||
|
styleCard.layoutChain
|
||||||
|
.topToBottomOfView(styleSectionTitle, offset: 12)
|
||||||
|
.edgesHorzontal(16)
|
||||||
|
.bottom(20)
|
||||||
|
|
||||||
|
tipRow.layoutChain
|
||||||
|
.centerX()
|
||||||
|
.bottomToTopOfView(lockBtn, offset: -6)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupCopyLimit() {
|
||||||
|
copyTF.rx.text.orEmpty
|
||||||
|
.subscribe(onNext: { [weak self] text in
|
||||||
|
guard let self else { return }
|
||||||
|
if text.count > self.copyMaxLength {
|
||||||
|
self.copyTF.text = String(text.prefix(self.copyMaxLength))
|
||||||
|
}
|
||||||
|
let count = self.copyTF.text?.count ?? 0
|
||||||
|
self.copyCountLab.text = "\(count)/\(self.copyMaxLength)"
|
||||||
|
})
|
||||||
|
.disposed(by: disposeBag)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Views
|
||||||
|
|
||||||
|
lazy var navBgView: UIImageView = {
|
||||||
|
let iv = UIImageView()
|
||||||
|
iv.image = UIImage(named: "Common/navBar_bg_2")
|
||||||
|
iv.contentMode = .scaleAspectFill
|
||||||
|
return iv
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var navView: BaseNavigationView = {
|
||||||
|
BaseNavigationView(title: "")
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var switchGroupBtn: UIButton = {
|
||||||
|
let btn = UIButton(type: .custom)
|
||||||
|
btn.backgroundColor = .white.withAlphaComponent(0.9)
|
||||||
|
btn.setTitle(" 切换圈子 ", for: .normal)
|
||||||
|
btn.titleLabel?.font = .systemFont(ofSize: 12, weight: .medium)
|
||||||
|
btn.setTitleColor(UIColor(hexStr: "#293445"), for: .normal)
|
||||||
|
btn.cornerRadius = 8
|
||||||
|
btn.extendEdgeInsets = UIEdgeInsets(top: 20, left: 12, bottom: 12, right: 4)
|
||||||
|
return btn
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var scrollView: UIScrollView = {
|
||||||
|
let sv = UIScrollView()
|
||||||
|
sv.showsVerticalScrollIndicator = false
|
||||||
|
sv.alwaysBounceVertical = true
|
||||||
|
sv.keyboardDismissMode = .onDrag
|
||||||
|
return sv
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var contentView = UIView()
|
||||||
|
|
||||||
|
lazy var titleLab: UILabel = {
|
||||||
|
let lab = UILabel()
|
||||||
|
lab.text = "锁住分心"
|
||||||
|
lab.font = FontManager.boboBold(30)
|
||||||
|
lab.textColor = UIColor(hexStr: "#293445")
|
||||||
|
return lab
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var subtitleLab: UILabel = {
|
||||||
|
let lab = UILabel()
|
||||||
|
lab.text = "减少干扰,只专注重要的事"
|
||||||
|
lab.font = .systemFont(ofSize: 13, weight: .medium)
|
||||||
|
lab.textColor = UIColor(hexStr: "#8A94A6")
|
||||||
|
lab.numberOfLines = 2
|
||||||
|
return lab
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var heroPlaceholder: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = .clear
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var statsRow: UIStackView = {
|
||||||
|
let stack = UIStackView()
|
||||||
|
stack.axis = .horizontal
|
||||||
|
stack.spacing = 12
|
||||||
|
stack.distribution = .fillEqually
|
||||||
|
return stack
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var memberCard: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = .white
|
||||||
|
view.cornerRadius = 16
|
||||||
|
|
||||||
|
view.addSubview(memberPrevBtn)
|
||||||
|
view.addSubview(memberAvatar)
|
||||||
|
view.addSubview(memberNextBtn)
|
||||||
|
view.addSubview(memberNameChip)
|
||||||
|
memberNameChip.addSubview(memberNameLab)
|
||||||
|
|
||||||
|
memberAvatar.layoutChain
|
||||||
|
.top(18)
|
||||||
|
.centerX()
|
||||||
|
.width(56)
|
||||||
|
.height(56)
|
||||||
|
|
||||||
|
memberPrevBtn.layoutChain
|
||||||
|
.centerY(memberAvatar)
|
||||||
|
.left(8)
|
||||||
|
.width(28)
|
||||||
|
.height(28)
|
||||||
|
|
||||||
|
memberNextBtn.layoutChain
|
||||||
|
.centerY(memberAvatar)
|
||||||
|
.right(8)
|
||||||
|
.width(28)
|
||||||
|
.height(28)
|
||||||
|
|
||||||
|
memberNameChip.layoutChain
|
||||||
|
.topToBottomOfView(memberAvatar, offset: 10)
|
||||||
|
.centerX()
|
||||||
|
.left(8, relation: .greaterThanOrEqual)
|
||||||
|
.right(8, relation: .greaterThanOrEqual)
|
||||||
|
.bottom(14)
|
||||||
|
|
||||||
|
memberNameLab.layoutChain
|
||||||
|
.top(6)
|
||||||
|
.bottom(6)
|
||||||
|
.left(5)
|
||||||
|
.right(5)
|
||||||
|
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var memberAvatar: UIImageView = {
|
||||||
|
let iv = UIImageView(image: UIImage(named: "Common/default_avatar"))
|
||||||
|
iv.contentMode = .scaleAspectFill
|
||||||
|
iv.clipsToBounds = true
|
||||||
|
iv.cornerRadius = 16
|
||||||
|
return iv
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var memberNameChip: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = UIColor(hexStr: "#F2F2F2")
|
||||||
|
view.cornerRadius = 8
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var memberNameLab: UILabel = {
|
||||||
|
let lab = UILabel()
|
||||||
|
lab.text = " "
|
||||||
|
lab.font = .systemFont(ofSize: 10, weight: .bold)
|
||||||
|
lab.textColor = UIColor(hexStr: "#293445")
|
||||||
|
lab.textAlignment = .center
|
||||||
|
lab.lineBreakMode = .byTruncatingTail
|
||||||
|
return lab
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var memberPrevBtn: UIButton = {
|
||||||
|
let btn = UIButton(type: .system)
|
||||||
|
btn.setImage(UIImage(systemName: "chevron.left"), for: .normal)
|
||||||
|
btn.tintColor = UIColor(hexStr: "#9CA3AF")
|
||||||
|
return btn
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var memberNextBtn: UIButton = {
|
||||||
|
let btn = UIButton(type: .system)
|
||||||
|
btn.setImage(UIImage(systemName: "chevron.right"), for: .normal)
|
||||||
|
btn.tintColor = UIColor(hexStr: "#9CA3AF")
|
||||||
|
return btn
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var todayCard: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = .white
|
||||||
|
view.cornerRadius = 16
|
||||||
|
|
||||||
|
let title = UILabel()
|
||||||
|
title.text = "今日锁定应用"
|
||||||
|
title.font = .systemFont(ofSize: 16, weight: .bold)
|
||||||
|
title.textColor = UIColor(hexStr: "#293445")
|
||||||
|
view.addSubview(title)
|
||||||
|
title.layoutChain.top(21).left(23)
|
||||||
|
|
||||||
|
view.addSubview(todayCountLab)
|
||||||
|
view.addSubview(todayUnitLab)
|
||||||
|
|
||||||
|
todayCountLab.layoutChain
|
||||||
|
.leftToView(title, offset: 4)
|
||||||
|
.bottom(10)
|
||||||
|
todayUnitLab.layoutChain
|
||||||
|
.leftToRightOfView(todayCountLab, offset: 7)
|
||||||
|
.bottomToView(todayCountLab, offset: -12)
|
||||||
|
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var todayCountLab: UILabel = {
|
||||||
|
let lab = UILabel()
|
||||||
|
lab.text = "0"
|
||||||
|
lab.font = .systemFont(ofSize: 50, weight: .heavy)
|
||||||
|
lab.textColor = UIColor(hexStr: "#00ADFE")
|
||||||
|
return lab
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var todayUnitLab: UILabel = {
|
||||||
|
let lab = UILabel()
|
||||||
|
lab.text = "个"
|
||||||
|
lab.font = .systemFont(ofSize: 16, weight: .bold)
|
||||||
|
lab.textColor = UIColor(hexStr: "#293445")
|
||||||
|
return lab
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var appsSectionTitle: UIView = {
|
||||||
|
makeSectionTitle("锁定应用")
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var appsCard: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = .white
|
||||||
|
view.cornerRadius = 26
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var appsHeaderRow: UIView = {
|
||||||
|
let row = UIView()
|
||||||
|
let left = UILabel()
|
||||||
|
left.text = "选择锁定TA的app"
|
||||||
|
left.font = .systemFont(ofSize: 14, weight: .medium)
|
||||||
|
left.textColor = UIColor(hexStr: "#293445")
|
||||||
|
row.addSubview(left)
|
||||||
|
left.layoutChain.left().centerY()
|
||||||
|
|
||||||
|
row.addSubview(notFetchedView)
|
||||||
|
notFetchedView.layoutChain.right().centerY().height(28)
|
||||||
|
row.layoutChain.height(28)
|
||||||
|
return row
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var notFetchedView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
|
||||||
|
let warn = UIView()
|
||||||
|
warn.backgroundColor = UIColor(hexStr: "#FA5151")
|
||||||
|
warn.cornerRadius = 5
|
||||||
|
let warnText = UILabel()
|
||||||
|
warnText.text = "!"
|
||||||
|
warnText.textColor = .white
|
||||||
|
warnText.font = .systemFont(ofSize: 8)
|
||||||
|
|
||||||
|
view.addSubview(warn)
|
||||||
|
warn.addSubview(warnText)
|
||||||
|
warnText.layoutChain.centerX().centerY()
|
||||||
|
|
||||||
|
let notFetchedText = UILabel()
|
||||||
|
notFetchedText.text = "未获取"
|
||||||
|
notFetchedText.textColor = UIColor(hexStr: "#293445")
|
||||||
|
notFetchedText.font = .systemFont(ofSize: 12, weight: .medium)
|
||||||
|
view.addSubview(notFetchedText)
|
||||||
|
|
||||||
|
let arrow = UIImageView(image: UIImage(named: "LockDistract/arrow_right"))
|
||||||
|
|
||||||
|
view.addSubview(arrow)
|
||||||
|
view.addSubview(notFetchedText)
|
||||||
|
|
||||||
|
arrow.layoutChain
|
||||||
|
.right(14)
|
||||||
|
.width(10)
|
||||||
|
.heightToWidth(1)
|
||||||
|
.centerY()
|
||||||
|
|
||||||
|
notFetchedText.layoutChain
|
||||||
|
.rightToLeftOfView(arrow)
|
||||||
|
.centerY()
|
||||||
|
|
||||||
|
warn.layoutChain
|
||||||
|
.rightToLeftOfView(notFetchedText, offset: -4)
|
||||||
|
.centerY()
|
||||||
|
.width(10).height(10)
|
||||||
|
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var unknownAppIcon: UIImageView = {
|
||||||
|
let iv = UIImageView(image: UIImage(named: "LockDistract/app_unknown"))
|
||||||
|
iv.contentMode = .scaleAspectFit
|
||||||
|
return iv
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var styleSectionTitle: UIView = {
|
||||||
|
makeSectionTitle("锁定界面样式")
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var styleCard: UIView = {
|
||||||
|
let card = UIView()
|
||||||
|
card.backgroundColor = .white
|
||||||
|
card.cornerRadius = 26
|
||||||
|
|
||||||
|
let wallLab = UILabel()
|
||||||
|
wallLab.text = "锁定壁纸图片"
|
||||||
|
wallLab.font = .systemFont(ofSize: 13, weight: .medium)
|
||||||
|
wallLab.textColor = UIColor(hexStr: "#8A94A6")
|
||||||
|
card.addSubview(wallLab)
|
||||||
|
wallLab.layoutChain.top(16).left(14)
|
||||||
|
|
||||||
|
card.addSubview(wallpaperCV)
|
||||||
|
wallpaperCV.layoutChain
|
||||||
|
.topToBottomOfView(wallLab, offset: 10)
|
||||||
|
.edgesHorzontal(14)
|
||||||
|
.height(74)
|
||||||
|
|
||||||
|
let copyLab = UILabel()
|
||||||
|
copyLab.text = "锁定文案"
|
||||||
|
copyLab.font = .systemFont(ofSize: 13, weight: .medium)
|
||||||
|
copyLab.textColor = UIColor(hexStr: "#8A94A6")
|
||||||
|
card.addSubview(copyLab)
|
||||||
|
copyLab.layoutChain.topToBottomOfView(wallpaperCV, offset: 16).left(14)
|
||||||
|
|
||||||
|
let input = UIView()
|
||||||
|
input.backgroundColor = UIColor(hexStr: "#F5F6F8")
|
||||||
|
input.cornerRadius = 12
|
||||||
|
card.addSubview(input)
|
||||||
|
input.layoutChain
|
||||||
|
.topToBottomOfView(copyLab, offset: 10)
|
||||||
|
.edgesHorzontal(14)
|
||||||
|
.height(44)
|
||||||
|
.bottom(16)
|
||||||
|
|
||||||
|
input.addSubview(copyTF)
|
||||||
|
input.addSubview(copyCountLab)
|
||||||
|
copyCountLab.layoutChain.centerY().right(12).width(35)
|
||||||
|
copyTF.layoutChain.edgesVertical().left(12).rightToLeftOfView(copyCountLab, offset: -8)
|
||||||
|
|
||||||
|
return card
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var wallpaperCV: UICollectionView = {
|
||||||
|
let layout = UICollectionViewFlowLayout()
|
||||||
|
layout.scrollDirection = .horizontal
|
||||||
|
layout.itemSize = CGSize(width: 50, height: 74)
|
||||||
|
layout.minimumLineSpacing = 12
|
||||||
|
layout.minimumInteritemSpacing = 0
|
||||||
|
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
|
||||||
|
cv.backgroundColor = .clear
|
||||||
|
cv.showsHorizontalScrollIndicator = false
|
||||||
|
cv.register(LockDistractWallpaperCell.self)
|
||||||
|
cv.dataSource = self
|
||||||
|
cv.delegate = self
|
||||||
|
return cv
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var copyTF: UITextField = {
|
||||||
|
let tf = UITextField()
|
||||||
|
tf.font = .systemFont(ofSize: 14, weight: .medium)
|
||||||
|
tf.textColor = UIColor(hexStr: "#293445")
|
||||||
|
tf.placeholderColor(placeholder: "随便说点什么吧~",
|
||||||
|
color: UIColor(hexStr: "#999999"),
|
||||||
|
font: .systemFont(ofSize: 14, weight: .medium))
|
||||||
|
return tf
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var copyCountLab: UILabel = {
|
||||||
|
let lab = UILabel()
|
||||||
|
lab.text = "0/20"
|
||||||
|
lab.font = .systemFont(ofSize: 14, weight: .medium)
|
||||||
|
lab.textColor = UIColor(hexStr: "#AAAAAA")
|
||||||
|
return lab
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var tipRow: UIView = {
|
||||||
|
let row = UIView()
|
||||||
|
let icon = UIImageView(image: UIImage(systemName: "info.circle.fill"))
|
||||||
|
icon.tintColor = UIColor(hexStr: "#16B3FF")
|
||||||
|
let lab = UILabel()
|
||||||
|
lab.text = "锁定功能只有在圈主的情况下才能使用哟~"
|
||||||
|
lab.font = .systemFont(ofSize: 12, weight: .regular)
|
||||||
|
lab.textColor = UIColor(hexStr: "#00ADFE")
|
||||||
|
lab.numberOfLines = 0
|
||||||
|
row.addSubview(icon)
|
||||||
|
row.addSubview(lab)
|
||||||
|
icon.layoutChain.left().top(1).width(14).height(14)
|
||||||
|
lab.layoutChain.leftToRightOfView(icon, offset: 6).right().top().bottom()
|
||||||
|
return row
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var lockBtn: UIButton = {
|
||||||
|
let btn = UIButton(type: .custom)
|
||||||
|
btn.setTitle("锁定", for: .normal)
|
||||||
|
btn.setTitleColor(.white, for: .normal)
|
||||||
|
btn.titleLabel?.font = FontManager.boboBold(18)
|
||||||
|
btn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
|
||||||
|
btn.cornerRadius = 20
|
||||||
|
return btn
|
||||||
|
}()
|
||||||
|
|
||||||
|
private func makeSectionTitle(_ text: String) -> UIView {
|
||||||
|
let row = UIView()
|
||||||
|
let star = UIImageView(image: UIImage(named: "LockDistract/section_star"))
|
||||||
|
// 切图目前带黑底,先用 SF Symbol 保证白底可读;资源补透明图后可改回 LockDistract/section_star
|
||||||
|
// let config = UIImage.SymbolConfiguration(pointSize: 12, weight: .bold)
|
||||||
|
// star.image = UIImage(systemName: "sparkle", withConfiguration: config)
|
||||||
|
// star.tintColor = UIColor(hexStr: "#16B3FF")
|
||||||
|
// star.contentMode = .scaleAspectFit
|
||||||
|
let lab = UILabel()
|
||||||
|
lab.text = text
|
||||||
|
lab.font = .systemFont(ofSize: 16, weight: .bold)
|
||||||
|
lab.textColor = UIColor(hexStr: "#1F2A44")
|
||||||
|
row.addSubview(star)
|
||||||
|
row.addSubview(lab)
|
||||||
|
star.layoutChain.left().centerY().width(14).height(14)
|
||||||
|
lab.layoutChain.leftToRightOfView(star, offset: 6).right().centerY()
|
||||||
|
row.layoutChain.height(22)
|
||||||
|
return row
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension LockDistractView: UICollectionViewDataSource, UICollectionViewDelegate {
|
||||||
|
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||||
|
wallpaperNames.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||||
|
let cell = collectionView.dequeueReusableCell(for: indexPath) as LockDistractWallpaperCell
|
||||||
|
cell.configure(imageName: wallpaperNames[indexPath.item],
|
||||||
|
isSelected: indexPath.item == selectedWallpaperIndex)
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||||
|
selectedWallpaperIndex = indexPath.item
|
||||||
|
collectionView.reloadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class LockDistractWallpaperCell: UICollectionViewCell {
|
||||||
|
private let frameView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = UIColor(hexStr: "#F6F6F6")
|
||||||
|
view.cornerRadius = 14
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
private let imageView: UIImageView = {
|
||||||
|
let iv = UIImageView()
|
||||||
|
iv.contentMode = .scaleAspectFill
|
||||||
|
iv.clipsToBounds = true
|
||||||
|
iv.cornerRadius = 10
|
||||||
|
return iv
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
contentView.addSubview(frameView)
|
||||||
|
frameView.addSubview(imageView)
|
||||||
|
frameView.layoutChain.edges()
|
||||||
|
imageView.layoutChain
|
||||||
|
.centerY()
|
||||||
|
.centerX()
|
||||||
|
.width(40)
|
||||||
|
.height(40)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||||
|
|
||||||
|
func configure(imageName: String, isSelected: Bool) {
|
||||||
|
imageView.image = UIImage(named: imageName)
|
||||||
|
frameView.backgroundColor = UIColor(hexStr: isSelected ? "#EAF8FF" : "#F6F6F6")
|
||||||
|
frameView.borderWidth = isSelected ? 1 : 0
|
||||||
|
frameView.borderColor = UIColor(hexStr: isSelected ? "#00ADFE" : "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,8 +11,13 @@ import RxCocoa
|
||||||
import RxDataSources
|
import RxDataSources
|
||||||
|
|
||||||
struct PrivacyPolicyItem {
|
struct PrivacyPolicyItem {
|
||||||
|
enum Destination {
|
||||||
|
case web(String)
|
||||||
|
case appRestrict
|
||||||
|
}
|
||||||
|
|
||||||
let name: String
|
let name: String
|
||||||
let url: String
|
let destination: Destination
|
||||||
}
|
}
|
||||||
|
|
||||||
class PrivacyPolicyVC: BaseViewController {
|
class PrivacyPolicyVC: BaseViewController {
|
||||||
|
|
@ -25,9 +30,10 @@ class PrivacyPolicyVC: BaseViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private let list: [PrivacyPolicyItem] = [
|
private let list: [PrivacyPolicyItem] = [
|
||||||
PrivacyPolicyItem(name: "用户协议", url: URLManager.shared.userAgreementUrl),
|
PrivacyPolicyItem(name: "用户协议", destination: .web(URLManager.shared.userAgreementUrl)),
|
||||||
PrivacyPolicyItem(name: "隐私政策", url: URLManager.shared.privacyPolicyUrl),
|
PrivacyPolicyItem(name: "隐私政策", destination: .web(URLManager.shared.privacyPolicyUrl)),
|
||||||
PrivacyPolicyItem(name: "儿童隐私政策", url: URLManager.shared.kidsPrivacyUrl)
|
PrivacyPolicyItem(name: "儿童隐私政策", destination: .web(URLManager.shared.kidsPrivacyUrl)),
|
||||||
|
PrivacyPolicyItem(name: "应用配对库", destination: .appRestrict)
|
||||||
]
|
]
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
|
|
@ -50,7 +56,12 @@ class PrivacyPolicyVC: BaseViewController {
|
||||||
// 点击跳转
|
// 点击跳转
|
||||||
rootView.tableView.rx.modelSelected(PrivacyPolicyItem.self)
|
rootView.tableView.rx.modelSelected(PrivacyPolicyItem.self)
|
||||||
.subscribe(onNext: { item in
|
.subscribe(onNext: { item in
|
||||||
AppRouter.push(Route.web, userInfo: ["url": item.url])
|
switch item.destination {
|
||||||
|
case .web(let url):
|
||||||
|
AppRouter.push(Route.web, userInfo: ["url": url])
|
||||||
|
case .appRestrict:
|
||||||
|
AppRouter.push(Route.appRestrict)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.disposed(by: disposeBag)
|
.disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>ShieldConfiguration</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>$(PRODUCT_NAME)</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>XPC!</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1</string>
|
||||||
|
<key>NSExtension</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSExtensionPointIdentifier</key>
|
||||||
|
<string>com.apple.ManagedSettingsUI.shield-configuration-service</string>
|
||||||
|
<key>NSExtensionPrincipalClass</key>
|
||||||
|
<string>$(PRODUCT_MODULE_NAME).ShieldConfigurationExtension</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.developer.family-controls</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.application-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>group.cn.zuomeng.jisuloca</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
import ManagedSettings
|
||||||
|
import ManagedSettingsUI
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
final class ShieldConfigurationExtension: ShieldConfigurationDataSource {
|
||||||
|
override func configuration(shielding application: Application) -> ShieldConfiguration {
|
||||||
|
makeConfiguration()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func configuration(shielding application: Application, in category: ActivityCategory) -> ShieldConfiguration {
|
||||||
|
makeConfiguration()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func configuration(shielding webDomain: WebDomain) -> ShieldConfiguration {
|
||||||
|
makeConfiguration()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func configuration(shielding webDomain: WebDomain, in category: ActivityCategory) -> ShieldConfiguration {
|
||||||
|
makeConfiguration()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func makeConfiguration() -> ShieldConfiguration {
|
||||||
|
let config = AppRestrictSharedStore.shieldConfig
|
||||||
|
let image = AppRestrictSharedStore.loadShieldImage()
|
||||||
|
let background = UIColor(red: 0.98, green: 0.98, blue: 0.98, alpha: 1)
|
||||||
|
let titleColor = UIColor(red: 0.16, green: 0.20, blue: 0.27, alpha: 1)
|
||||||
|
let bodyColor = UIColor(red: 0.46, green: 0.46, blue: 0.46, alpha: 1)
|
||||||
|
return ShieldConfiguration(
|
||||||
|
backgroundBlurStyle: .systemMaterial,
|
||||||
|
backgroundColor: background,
|
||||||
|
icon: image,
|
||||||
|
title: ShieldConfiguration.Label(text: config.title, color: titleColor),
|
||||||
|
subtitle: ShieldConfiguration.Label(text: config.subtitle, color: bodyColor),
|
||||||
|
primaryButtonLabel: ShieldConfiguration.Label(text: config.primaryButtonLabel, color: .white),
|
||||||
|
primaryButtonBackgroundColor: UIColor(red: 0.09, green: 0.70, blue: 1.0, alpha: 1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,152 @@
|
||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'xcodeproj'
|
||||||
|
|
||||||
|
ROOT = File.expand_path('..', __dir__)
|
||||||
|
PROJECT_PATH = File.join(ROOT, 'QuickLocation.xcodeproj')
|
||||||
|
project = Xcodeproj::Project.open(PROJECT_PATH)
|
||||||
|
|
||||||
|
main_target = project.targets.find { |t| t.name == 'QuickLocation' }
|
||||||
|
raise 'QuickLocation target not found' unless main_target
|
||||||
|
|
||||||
|
def ensure_group(project, path_components)
|
||||||
|
group = project.main_group
|
||||||
|
path_components.each do |name|
|
||||||
|
next_group = group.children.find { |c| c.respond_to?(:name) && (c.name == name || c.path == name) }
|
||||||
|
group = next_group || group.new_group(name)
|
||||||
|
end
|
||||||
|
group
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_file(project, target, group, relative_path, root:, resources: false)
|
||||||
|
abs = File.join(root, relative_path)
|
||||||
|
raise "Missing file: #{abs}" unless File.exist?(abs)
|
||||||
|
|
||||||
|
ref = group.files.find { |f| f.real_path.to_s == abs || f.path == File.basename(relative_path) }
|
||||||
|
ref ||= group.new_file(abs)
|
||||||
|
|
||||||
|
if resources
|
||||||
|
unless target.resources_build_phase.files_references.include?(ref)
|
||||||
|
target.add_resources([ref])
|
||||||
|
end
|
||||||
|
else
|
||||||
|
unless target.source_build_phase.files_references.include?(ref)
|
||||||
|
target.source_build_phase.add_file_reference(ref)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
ref
|
||||||
|
end
|
||||||
|
|
||||||
|
def configure_extension_target(target, bundle_id, entitlements, info_plist)
|
||||||
|
target.build_configurations.each do |config|
|
||||||
|
bs = config.build_settings
|
||||||
|
bs['PRODUCT_BUNDLE_IDENTIFIER'] = bundle_id
|
||||||
|
bs['PRODUCT_NAME'] = target.name
|
||||||
|
bs['PRODUCT_MODULE_NAME'] = target.name
|
||||||
|
bs['CODE_SIGN_ENTITLEMENTS'] = entitlements
|
||||||
|
bs['INFOPLIST_FILE'] = info_plist
|
||||||
|
bs['GENERATE_INFOPLIST_FILE'] = 'NO'
|
||||||
|
bs['IPHONEOS_DEPLOYMENT_TARGET'] = '16.0'
|
||||||
|
bs['TARGETED_DEVICE_FAMILY'] = '1'
|
||||||
|
bs['SKIP_INSTALL'] = 'YES'
|
||||||
|
bs['LD_RUNPATH_SEARCH_PATHS'] = '$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks'
|
||||||
|
bs['DEVELOPMENT_TEAM'] = 'LRDLWZ2Y83'
|
||||||
|
bs['SWIFT_DEFAULT_ACTOR_ISOLATION'] = 'nonisolated'
|
||||||
|
bs['SWIFT_VERSION'] = '5.0'
|
||||||
|
bs['CODE_SIGN_STYLE'] = 'Automatic'
|
||||||
|
bs['CURRENT_PROJECT_VERSION'] = '1'
|
||||||
|
bs['MARKETING_VERSION'] = '1.0'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_or_create_extension(project, name)
|
||||||
|
existing = project.targets.find { |t| t.name == name }
|
||||||
|
return existing if existing
|
||||||
|
|
||||||
|
project.new_target(:app_extension, name, :ios, '15.0')
|
||||||
|
end
|
||||||
|
|
||||||
|
def link_system_framework(target, name)
|
||||||
|
frameworks = project = target.project
|
||||||
|
frameworks_group = project.frameworks_group
|
||||||
|
path = "System/Library/Frameworks/#{name}.framework"
|
||||||
|
ref = frameworks_group.files.find { |f| f.path == "#{name}.framework" || f.name == "#{name}.framework" }
|
||||||
|
ref ||= frameworks_group.new_file(path)
|
||||||
|
ref.name = "#{name}.framework"
|
||||||
|
ref.source_tree = 'SDKROOT'
|
||||||
|
unless target.frameworks_build_phase.files_references.include?(ref)
|
||||||
|
target.frameworks_build_phase.add_file_reference(ref)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Shared
|
||||||
|
shared_group = ensure_group(project, ['AppRestrictShared'])
|
||||||
|
add_file(project, main_target, shared_group, 'AppRestrictShared/AppRestrictShared.swift', root: ROOT)
|
||||||
|
|
||||||
|
# Main app
|
||||||
|
mgr_group = ensure_group(project, ['QuickLocation', 'Manager', 'AppRestrict'])
|
||||||
|
add_file(project, main_target, mgr_group, 'QuickLocation/Manager/AppRestrict/AppRestrictManager.swift', root: ROOT)
|
||||||
|
|
||||||
|
section_group = ensure_group(project, ['QuickLocation', 'Section', 'AppRestrict'])
|
||||||
|
%w[
|
||||||
|
AppCatalogStore.swift
|
||||||
|
AppRestrictCell.swift
|
||||||
|
AppRestrictVC.swift
|
||||||
|
AppRestrictView.swift
|
||||||
|
AppRestrictShieldSettingsVC.swift
|
||||||
|
FamilyActivityPickerHost.swift
|
||||||
|
SelectActivityVC.swift
|
||||||
|
].each do |name|
|
||||||
|
add_file(project, main_target, section_group, "QuickLocation/Section/AppRestrict/#{name}", root: ROOT)
|
||||||
|
end
|
||||||
|
add_file(project, main_target, section_group, 'QuickLocation/Section/AppRestrict/app_catalog.json', root: ROOT, resources: true)
|
||||||
|
|
||||||
|
%w[FamilyControls ManagedSettings DeviceActivity ManagedSettingsUI].each do |fw|
|
||||||
|
link_system_framework(main_target, fw)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Monitor extension
|
||||||
|
monitor = find_or_create_extension(project, 'DeviceActivityMonitorExtension')
|
||||||
|
monitor_group = ensure_group(project, ['DeviceActivityMonitorExtension'])
|
||||||
|
add_file(project, monitor, monitor_group, 'DeviceActivityMonitorExtension/DeviceActivityMonitorExtension.swift', root: ROOT)
|
||||||
|
add_file(project, monitor, shared_group, 'AppRestrictShared/AppRestrictShared.swift', root: ROOT)
|
||||||
|
configure_extension_target(
|
||||||
|
monitor,
|
||||||
|
'cn.zuomeng.jisuloca.DeviceActivityMonitor',
|
||||||
|
'DeviceActivityMonitorExtension/DeviceActivityMonitorExtension.entitlements',
|
||||||
|
'DeviceActivityMonitorExtension/Info.plist'
|
||||||
|
)
|
||||||
|
%w[DeviceActivity FamilyControls ManagedSettings].each { |fw| link_system_framework(monitor, fw) }
|
||||||
|
|
||||||
|
# Shield extension
|
||||||
|
shield = find_or_create_extension(project, 'ShieldConfigurationExtension')
|
||||||
|
shield_group = ensure_group(project, ['ShieldConfigurationExtension'])
|
||||||
|
add_file(project, shield, shield_group, 'ShieldConfigurationExtension/ShieldConfigurationExtension.swift', root: ROOT)
|
||||||
|
add_file(project, shield, shared_group, 'AppRestrictShared/AppRestrictShared.swift', root: ROOT)
|
||||||
|
configure_extension_target(
|
||||||
|
shield,
|
||||||
|
'cn.zuomeng.jisuloca.ShieldConfiguration',
|
||||||
|
'ShieldConfigurationExtension/ShieldConfigurationExtension.entitlements',
|
||||||
|
'ShieldConfigurationExtension/Info.plist'
|
||||||
|
)
|
||||||
|
%w[ManagedSettings ManagedSettingsUI FamilyControls UIKit].each { |fw| link_system_framework(shield, fw) }
|
||||||
|
|
||||||
|
# Embed
|
||||||
|
embed_phase = main_target.copy_files_build_phases.find { |p| p.dst_subfolder_spec == '13' || p.name == 'Embed Foundation Extensions' }
|
||||||
|
unless embed_phase
|
||||||
|
embed_phase = main_target.new_copy_files_build_phase('Embed Foundation Extensions')
|
||||||
|
embed_phase.symbol_dst_subfolder_spec = :plug_ins
|
||||||
|
end
|
||||||
|
|
||||||
|
[monitor, shield].each do |ext|
|
||||||
|
main_target.add_dependency(ext) unless main_target.dependencies.any? { |d| d.target == ext }
|
||||||
|
product = ext.product_reference
|
||||||
|
unless embed_phase.files.any? { |f| f.file_ref == product }
|
||||||
|
bf = embed_phase.add_file_reference(product)
|
||||||
|
bf.settings = { 'ATTRIBUTES' => ['RemoveHeadersOnCopy'] }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
project.save
|
||||||
|
puts 'OK: App Restrict targets wired into QuickLocation.xcodeproj'
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'xcodeproj'
|
||||||
|
|
||||||
|
ROOT = File.expand_path('..', __dir__)
|
||||||
|
PROJECT_PATH = File.join(ROOT, 'QuickLocation.xcodeproj')
|
||||||
|
project = Xcodeproj::Project.open(PROJECT_PATH)
|
||||||
|
main_target = project.targets.find { |t| t.name == 'QuickLocation' }
|
||||||
|
raise 'QuickLocation target not found' unless main_target
|
||||||
|
|
||||||
|
section_group = project.main_group.find_subpath('QuickLocation/Section', true)
|
||||||
|
lock_group = section_group['LockDistract'] || section_group.new_group('LockDistract', 'LockDistract')
|
||||||
|
|
||||||
|
%w[LockDistractView.swift LockDistractVC.swift].each do |name|
|
||||||
|
abs = File.join(ROOT, 'QuickLocation/Section/LockDistract', name)
|
||||||
|
raise "Missing #{abs}" unless File.exist?(abs)
|
||||||
|
ref = lock_group.files.find { |f| f.path == name || f.real_path.to_s == abs }
|
||||||
|
ref ||= lock_group.new_file(abs)
|
||||||
|
unless main_target.source_build_phase.files_references.include?(ref)
|
||||||
|
main_target.source_build_phase.add_file_reference(ref)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
project.save
|
||||||
|
puts 'Added LockDistract sources to QuickLocation target'
|
||||||