This commit is contained in:
zeng committed 2025-10-28 11:11:21 +08:00
1 parent d454db55d4
commit 52d4104632
165 files changed
+26440 -1

No files matched your search

+2 -1
View File
@@ -38,7 +38,8 @@ playground.xcworkspace
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/
Pods/
Podfile.lock
#
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace
+24
View File
@@ -0,0 +1,24 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Version**
- OS:
- AppleParty:
+14
View File
@@ -0,0 +1,14 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Tell us how we can improve AppleParty**
**Is your feature request related to a problem? Please describe.**
**What would you like to see? How would you like it to work?**
+99
View File
@@ -0,0 +1,99 @@
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## User settings
xcuserdata/
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
## Obj-C/Swift specific
*.hmap
## App packaging
*.ipa
*.dSYM.zip
*.dSYM
## Playgrounds
timeline.xctimeline
playground.xcworkspace
# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
# Package.pins
# Package.resolved
# *.xcodeproj
#
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
# hence it is not needed unless you have added a package configuration file to your project
# .swiftpm
.build/
# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
Pods/
# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage/Build
# Accio dependency management
Dependencies/
.accio/
# fastlane
#
# It is recommended to not store the screenshots in the git repo.
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output
# Code Injection
#
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
iOSInjectionProject/
## Other
*.xcuserstate
## Obj-C/Swift specific
*.hmap
*.ipa
*.swp
.DS_Store
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?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>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:AppleParty.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?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>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,96 @@
{
"originHash" : "9d0d9b5cf3459d54c921a64b1bb7a803edf7e8255bc0e8227a135b3820aeee10",
"pins" : [
{
"identity" : "appstoreconnect-swift-sdk",
"kind" : "remoteSourceControl",
"location" : "https://github.com/AvdLee/appstoreconnect-swift-sdk.git",
"state" : {
"revision" : "78b2be2f68f30141fca2f7bce45ca7866535cf28",
"version" : "4.0.2"
}
},
{
"identity" : "bluecryptor",
"kind" : "remoteSourceControl",
"location" : "https://github.com/Kitura/BlueCryptor.git",
"state" : {
"revision" : "cec97c24b111351e70e448972a7d3fe68a756d6d",
"version" : "2.0.2"
}
},
{
"identity" : "bluesocket",
"kind" : "remoteSourceControl",
"location" : "https://github.com/Kitura/BlueSocket.git",
"state" : {
"revision" : "7b23a867008e0027bfd6f4d398d44720707bc8ca",
"version" : "2.0.4"
}
},
{
"identity" : "bluesslservice",
"kind" : "remoteSourceControl",
"location" : "https://github.com/Kitura/BlueSSLService.git",
"state" : {
"revision" : "b27a94d063962dfa1bba9f79814c4ef202cf33a4",
"version" : "2.0.2"
}
},
{
"identity" : "loggerapi",
"kind" : "remoteSourceControl",
"location" : "https://github.com/Kitura/LoggerAPI.git",
"state" : {
"revision" : "e82d34eab3f0b05391082b11ea07d3b70d2f65bb",
"version" : "1.9.200"
}
},
{
"identity" : "swift-asn1",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-asn1.git",
"state" : {
"revision" : "f70225981241859eb4aa1a18a75531d26637c8cc",
"version" : "1.4.0"
}
},
{
"identity" : "swift-crypto",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-crypto.git",
"state" : {
"revision" : "95ba0316a9b733e92bb6b071255ff46263bbe7dc",
"version" : "3.15.1"
}
},
{
"identity" : "swift-log",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-log.git",
"state" : {
"revision" : "6fe203dc33195667ce1759bf0182975e4653ba1c",
"version" : "1.4.4"
}
},
{
"identity" : "swift-smtp",
"kind" : "remoteSourceControl",
"location" : "https://github.com/Kitura/Swift-SMTP",
"state" : {
"revision" : "4b7666bb8cee33f0cb367786af17b9a2ebb63047",
"version" : "6.0.0"
}
},
{
"identity" : "urlqueryencoder",
"kind" : "remoteSourceControl",
"location" : "https://github.com/CreateAPI/URLQueryEncoder.git",
"state" : {
"revision" : "4ce950479707ea109f229d7230ec074a133b15d7",
"version" : "0.2.1"
}
}
],
"version" : 3
}
+52
View File
@@ -0,0 +1,52 @@
//
// AppDelegate.swift
// AppleParty
//
// Created by HTC on 2022/3/10.
// Copyright © 2022 37 Mobile Games. All rights reserved.
//
import Cocoa
import Sparkle
@main
class AppDelegate: NSObject, NSApplicationDelegate {
var mainWindow: NSWindow?
@IBOutlet weak var updaterController: SPUStandardUpdaterController!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// 后台检查更新
updaterController.updater.checkForUpdatesInBackground()
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
/// 当关闭最后一个窗口时,退出app
/// - Parameter sender:
/// - Returns: true-窗口程序两者都关闭,false-只关闭窗口
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
true
}
/// 应用窗口重新打开时
///
/// - Parameters:
/// - sender:
/// - flag:
/// - Returns:
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
return true
}
@IBAction func showHelp(_ sender: Any) {
let url = URL(string: kApplePartyWiKi)
NSWorkspace.shared.open(url!)
}
}
@@ -0,0 +1,76 @@
//
// APAppListAdapter.swift
// AppleParty
//
// Created by HTC on 2022/3/17.
// Copyright © 2022 37 Mobile Games. All rights reserved.
//
import Cocoa
class APAppListAdapter: NSObject {
public var purchseHandle: ((_ app: App) -> Void)?
public var screenshotHandle: ((_ app: App) -> Void)?
fileprivate static let numberOfSections = 1
fileprivate static let itemId = "APAppListCell"
fileprivate var items = [App]() {
didSet {
collectionView.reloadData()
}
}
private var collectionView: NSCollectionView
init(collectionView: NSCollectionView) {
self.collectionView = collectionView
super.init()
self.collectionView.dataSource = self
self.collectionView.delegate = self
self.collectionView.register(APAppListCell.self, forItemWithIdentifier: NSUserInterfaceItemIdentifier(rawValue: APAppListAdapter.itemId))
let itemWidth = CGFloat(350.0)
let itemHeight = CGFloat(150.0)
let itemSpacing = CGFloat(80.0)
let itemPadding = CGFloat(30.0)
let flowLayout = NSCollectionViewFlowLayout()
flowLayout.scrollDirection = .vertical
flowLayout.itemSize = NSMakeSize(itemWidth, itemHeight)
flowLayout.minimumInteritemSpacing = itemSpacing
flowLayout.minimumLineSpacing = itemSpacing
flowLayout.sectionInset = NSEdgeInsetsMake(itemPadding, itemPadding, itemPadding, itemPadding)
self.collectionView.collectionViewLayout = flowLayout
}
func set(items: [App]) {
self.items = items
}
}
extension APAppListAdapter: NSCollectionViewDataSource, NSCollectionViewDelegate {
func numberOfSectionsInCollectionView(collectionView: NSCollectionView) -> Int {
return APAppListAdapter.numberOfSections
}
func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
let item = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: APAppListAdapter.itemId), for: indexPath)
guard let collectionViewItem = item as? APAppListCell else { return item }
collectionViewItem.configure(app: items[indexPath.item])
collectionViewItem.purchseHandle = purchseHandle
collectionViewItem.screenshotHandle = screenshotHandle
return item
}
func collectionView(_ collectionView: NSCollectionView, didSelectItemsAt indexPaths: Set<IndexPath>) {
collectionView.deselectItems(at: indexPaths)
}
}
@@ -0,0 +1,44 @@
//
// APAppListCell.swift
// AppleParty
//
// Created by HTC on 2022/3/17.
// Copyright © 2022 37 Mobile Games. All rights reserved.
//
import Cocoa
class APAppListCell: NSCollectionViewItem {
public var purchseHandle: ((_ app: App) -> Void)?
public var screenshotHandle: ((_ app: App) -> Void)?
@IBOutlet weak var imgView: NSImageView!
@IBOutlet weak var nameView: NSTextField!
private var app: App?
override func viewDidLoad() {
super.viewDidLoad()
nameView.maximumNumberOfLines = 2
imgView.wantsLayer = true
imgView.layer?.cornerRadius = 22
imgView.layer?.masksToBounds = true
}
@IBAction func clickedPurchseItem(_ sender: Any) {
purchseHandle?(app!)
}
@IBAction func clickedScreenshotItem(_ sender: Any) {
screenshotHandle?(app!)
}
func configure(app: App) {
self.app = app
nameView.stringValue = app.appName
imgView?.showWebImage(app.iconUrl)
}
}
@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="APAppListCell" customModule="AppleParty" customModuleProvider="target">
<connections>
<outlet property="imgView" destination="ua3-NY-53n" id="kAD-Bq-3qV"/>
<outlet property="nameView" destination="66e-57-Sbq" id="3Bf-jl-Gem"/>
<outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView id="Hz6-mo-xeY">
<rect key="frame" x="0.0" y="0.0" width="404" height="182"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="ua3-NY-53n">
<rect key="frame" x="10" y="20" width="142" height="142"/>
<constraints>
<constraint firstAttribute="width" secondItem="ua3-NY-53n" secondAttribute="height" id="HYa-6B-qMQ"/>
</constraints>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="PlaceholderIcon" id="WgU-9p-su6"/>
</imageView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="66e-57-Sbq">
<rect key="frame" x="170" y="141" width="216" height="21"/>
<textFieldCell key="cell" lineBreakMode="charWrapping" truncatesLastVisibleLine="YES" allowsUndo="NO" title="App" id="0Xh-fB-Cb2">
<font key="font" metaFont="systemBold" size="18"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="aV2-cM-e0o">
<rect key="frame" x="165" y="64" width="132" height="32"/>
<constraints>
<constraint firstAttribute="height" constant="20" id="ZLF-9M-xkM"/>
</constraints>
<buttonCell key="cell" type="push" title="上传商店图/视频" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="bPm-Fb-nKp">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" size="13" name="STSongti-SC-Regular"/>
</buttonCell>
<connections>
<action selector="clickedScreenshotItem:" target="-2" id="8Nl-TD-kpN"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="osx-a5-YMW">
<rect key="frame" x="165" y="99" width="114" height="32"/>
<constraints>
<constraint firstAttribute="height" constant="20" id="7Jd-yY-gR9"/>
</constraints>
<buttonCell key="cell" type="push" title="上传内购项目" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="w9k-1j-6eh">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="clickedPurchseItem:" target="-2" id="Rze-yt-gng"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="ua3-NY-53n" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" constant="20" id="51T-Sw-w41"/>
<constraint firstItem="aV2-cM-e0o" firstAttribute="leading" secondItem="66e-57-Sbq" secondAttribute="leading" id="VlU-Rb-hoR"/>
<constraint firstAttribute="trailing" secondItem="66e-57-Sbq" secondAttribute="trailing" constant="20" id="dic-eb-ugT"/>
<constraint firstItem="66e-57-Sbq" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" constant="20" symbolic="YES" id="gaf-lX-5U5"/>
<constraint firstItem="ua3-NY-53n" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="10" id="j8M-Z4-uEW"/>
<constraint firstItem="osx-a5-YMW" firstAttribute="top" secondItem="66e-57-Sbq" secondAttribute="bottom" constant="15" id="kpc-Ag-sOu"/>
<constraint firstItem="66e-57-Sbq" firstAttribute="leading" secondItem="ua3-NY-53n" secondAttribute="trailing" constant="20" id="tvr-pZ-tsU"/>
<constraint firstItem="osx-a5-YMW" firstAttribute="leading" secondItem="66e-57-Sbq" secondAttribute="leading" id="uFZ-bD-xHJ"/>
<constraint firstItem="ua3-NY-53n" firstAttribute="centerY" secondItem="Hz6-mo-xeY" secondAttribute="centerY" id="uo4-mG-tIg"/>
<constraint firstAttribute="bottom" secondItem="ua3-NY-53n" secondAttribute="bottom" constant="20" id="wJA-Fd-dWN"/>
<constraint firstItem="aV2-cM-e0o" firstAttribute="top" secondItem="osx-a5-YMW" secondAttribute="bottom" constant="15" id="zua-6z-GtP"/>
</constraints>
<point key="canvasLocation" x="116" y="129"/>
</customView>
</objects>
<resources>
<image name="PlaceholderIcon" width="141" height="141"/>
</resources>
</document>
@@ -0,0 +1,83 @@
//
// APAppListModel.swift
// AppleParty
//
// Created by HTC on 2022/3/17.
// Copyright © 2022 37 Mobile Games. All rights reserved.
//
import Foundation
// MARK: - 游戏列表
struct AppList {
var games: [App]
init(body: [String: Any]) {
games = [App]()
let included = dictionaryArray(body["included"])
let apps = dictionaryArray(body["data"])
for software in apps {
var game = App()
let attributes = dictionary(software["attributes"])
game.appId = string(from: software["id"])
game.appName = string(from: attributes["name"])
game.platforms = string(from: attributes["distributionType"])
game.bundleId = string(from: attributes["bundleId"])
game.sku = string(from: attributes["sku"])
game.primaryLocale = string(from: attributes["primaryLocale"])
// icon 处理
let appVersion = dictionaryArray( dictionary( dictionary(software["relationships"])["appStoreVersions"])["data"]).first
if let version = appVersion {
let vid = string(from: version["id"])
for info in included {
let iid = string(from: info["id"])
if vid == iid, vid.count > 0 {
let info_att = dictionary(info["attributes"])
let storeIcon = dictionary(info_att["storeIcon"])
let templateUrl = string(from: storeIcon["templateUrl"])
if templateUrl.count > 0 {
game.iconUrl = templateUrl.replacingOccurrences(of: "{w}x{h}bb.{f}", with: "500x500bb.png")
}
break
}
}
}
games.append(game)
}
games = games.sorted(by: { (g1, g2) -> Bool in
g1.appName < g2.appName
})
}
}
struct App {
var appId: String = ""
var appName: String = ""
var platforms: String = ""
var iconUrl: String = ""
var bundleId: String = ""
var sku: String = ""
var primaryLocale: String = ""
}
struct AppInfo {
var name: String = ""
var bundleId: String = ""
var bundleIdReferenceName: String = ""
var distributionType: String = ""
var educationDiscountType: String = ""
var sku: String = ""
var primaryLocale: String = ""
init(body: [String: Any]) {
let data = dictionary(body["data"])
let attributes = dictionary(data["attributes"])
name = string(from: attributes["name"])
bundleId = string(from: attributes["bundleId"])
bundleIdReferenceName = string(from: attributes["bundleIdReferenceName"])
distributionType = string(from: attributes["distributionType"])
educationDiscountType = string(from: attributes["educationDiscountType"])
sku = string(from: attributes["sku"])
primaryLocale = string(from: attributes["primaryLocale"])
}
}
@@ -0,0 +1,58 @@
//
// APAppListVC.swift
// AppleParty
//
// Created by HTC on 2022/3/17.
// Copyright © 2022 37 Mobile Games. All rights reserved.
//
import Cocoa
class APAppListVC: NSViewController {
fileprivate var adapter: APAppListAdapter?
override func viewDidLoad() {
super.viewDidLoad()
configureCollectionView()
fetchAppList()
}
/// 配置显示的功能列表
func configureCollectionView() {
let colview = APCollectionView()
colview.configure(superView: view)
adapter = APAppListAdapter(collectionView: colview.collectionView)
adapter?.purchseHandle = { [weak self] app in
let sb = NSStoryboard(name: "APInAppPurchseVC", bundle: nil)
let wc = sb.instantiateController(withIdentifier: "APInAppPurchseVC") as! NSWindowController
let vc = wc.contentViewController as! APInAppPurchseVC
vc.currentApp = app
wc.showWindow(self)
}
adapter?.screenshotHandle = { [weak self] app in
let sb = NSStoryboard(name: "ScreenShotUpload", bundle: nil)
let wc = sb.instantiateController(withIdentifier: "ScreenShotUploadVC") as! NSWindowController
let vc = wc.contentViewController as! ScreenShotUploadVC
vc.currentApp = app
wc.showWindow(self)
}
}
}
// MARK: - 网络请求
extension APAppListVC {
func fetchAppList() {
APClient.appList(status: .filter(nil)).request(showLoading: true, inView: self.view) { [weak self] result, response, error in
guard let err = error else {
let gamelist = AppList(body: result)
self?.adapter?.set(items: gamelist.games)
return
}
APHUD.hide(message: err.localizedDescription)
}
}
}
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="f9w-9S-vuR">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Window Controller-->
<scene sceneID="W73-xg-4SH">
<objects>
<windowController storyboardIdentifier="AppList" id="f9w-9S-vuR" sceneMemberID="viewController">
<window key="window" title="My Apps" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="mcp-fI-0bQ">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<rect key="contentRect" x="270" y="342" width="600" height="450"/>
<rect key="screenRect" x="0.0" y="0.0" width="1800" height="1125"/>
<value key="minSize" type="size" width="900" height="560"/>
<view key="contentView" id="ukO-uZ-d7I">
<rect key="frame" x="0.0" y="0.0" width="600" height="450"/>
<autoresizingMask key="autoresizingMask"/>
</view>
<connections>
<outlet property="delegate" destination="f9w-9S-vuR" id="AQB-f7-cDr"/>
</connections>
</window>
<connections>
<segue destination="xCt-xq-Ud0" kind="relationship" relationship="window.shadowedContentViewController" id="xMD-q7-vQF"/>
</connections>
</windowController>
<customObject id="LJV-vg-xy1" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-188" y="160"/>
</scene>
<!--App ListVC-->
<scene sceneID="RSl-ja-SD9">
<objects>
<viewController id="xCt-xq-Ud0" customClass="APAppListVC" customModule="AppleParty" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="JzO-os-HSp">
<rect key="frame" x="0.0" y="0.0" width="600" height="450"/>
<autoresizingMask key="autoresizingMask"/>
</view>
</viewController>
<customObject id="nCT-dm-ck1" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="515" y="151"/>
</scene>
</scenes>
</document>
@@ -0,0 +1,158 @@
//
// APInAppPurchseCell.swift
// AppleParty
//
// Created by HTC on 2022/3/28.
// Copyright © 2022 37 Mobile Games. All rights reserved.
//
import Cocoa
class APInAppPurchseCell: NSTableCellView {
}
class ImageViewCell: NSTableCellView {
@IBOutlet weak var imgSel: NSImageView!
override func awakeFromNib() {
super.awakeFromNib()
}
}
class UploadCell: NSTableCellView {
var row: Int = 0
@IBOutlet weak var imgSel: NSImageView!
@IBOutlet weak var dragView: DragView!
@IBOutlet weak var dragBox: NSView!
typealias CallBackFunc = (_ path: String, _ row: Int) -> Void
var callBackFunc: CallBackFunc?
override func awakeFromNib() {
super.awakeFromNib()
dragView.delegate = self
}
}
extension UploadCell: DragViewDelegate {
func dragView(_ path: String?) {
if let path = path {
debugPrint(path)
imgSel.image = NSImage(contentsOfFile: path)
if let callBackFunc = callBackFunc {
callBackFunc(path, row)
}
} else {
imgSel.image = nil
}
}
}
enum ColumnIdetifier: String {
case id
case productID
case productName
case priceLevel
case appleid
case price
case type
case state
// list
case productPds
case level
case status
case screenshot
case language
case upload
case picname
var columnValue: NSUserInterfaceItemIdentifier {
return NSUserInterfaceItemIdentifier(rawValue: self.rawValue+"Column")
}
var cellValue: NSUserInterfaceItemIdentifier {
return NSUserInterfaceItemIdentifier(rawValue: self.rawValue+"Cell")
}
}
extension NSUserInterfaceItemIdentifier {
func stringValue() -> String {
switch self {
case ColumnIdetifier.id.columnValue:
return ColumnIdetifier.id.rawValue
case ColumnIdetifier.productID.columnValue:
return ColumnIdetifier.productID.rawValue
case ColumnIdetifier.productName.columnValue:
return ColumnIdetifier.productName.rawValue
case ColumnIdetifier.price.columnValue:
return ColumnIdetifier.price.rawValue
case ColumnIdetifier.type.columnValue:
return ColumnIdetifier.type.rawValue
case ColumnIdetifier.state.columnValue:
return ColumnIdetifier.state.rawValue
case ColumnIdetifier.productPds.columnValue:
return ColumnIdetifier.productPds.rawValue
case ColumnIdetifier.level.columnValue:
return ColumnIdetifier.level.rawValue
case ColumnIdetifier.status.columnValue:
return ColumnIdetifier.status.rawValue
case ColumnIdetifier.appleid.columnValue:
return ColumnIdetifier.appleid.rawValue
case ColumnIdetifier.priceLevel.columnValue:
return ColumnIdetifier.priceLevel.rawValue
case ColumnIdetifier.screenshot.columnValue:
return ColumnIdetifier.screenshot.rawValue
case ColumnIdetifier.picname.columnValue:
return ColumnIdetifier.picname.rawValue
case ColumnIdetifier.upload.columnValue:
return ColumnIdetifier.upload.rawValue
case ColumnIdetifier.language.columnValue:
return ColumnIdetifier.language.rawValue
default:
return "none"
}
}
func enumValue() -> NSUserInterfaceItemIdentifier {
switch self {
case ColumnIdetifier.id.columnValue:
return ColumnIdetifier.id.cellValue
case ColumnIdetifier.productID.columnValue:
return ColumnIdetifier.productID.cellValue
case ColumnIdetifier.productName.columnValue:
return ColumnIdetifier.productName.cellValue
case ColumnIdetifier.price.columnValue:
return ColumnIdetifier.price.cellValue
case ColumnIdetifier.type.columnValue:
return ColumnIdetifier.type.cellValue
case ColumnIdetifier.state.columnValue:
return ColumnIdetifier.state.cellValue
case ColumnIdetifier.productPds.columnValue:
return ColumnIdetifier.productPds.cellValue
case ColumnIdetifier.level.columnValue:
return ColumnIdetifier.level.cellValue
case ColumnIdetifier.status.columnValue:
return ColumnIdetifier.status.cellValue
case ColumnIdetifier.appleid.columnValue:
return ColumnIdetifier.appleid.cellValue
case ColumnIdetifier.priceLevel.columnValue:
return ColumnIdetifier.priceLevel.cellValue
case ColumnIdetifier.screenshot.columnValue:
return ColumnIdetifier.screenshot.cellValue
case ColumnIdetifier.picname.columnValue:
return ColumnIdetifier.picname.cellValue
case ColumnIdetifier.upload.columnValue:
return ColumnIdetifier.upload.cellValue
case ColumnIdetifier.language.columnValue:
return ColumnIdetifier.language.cellValue
default:
return NSUserInterfaceItemIdentifier(rawValue: "none")
}
}
}
@@ -0,0 +1,475 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="23727" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="23727"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Window Controller-->
<scene sceneID="yaf-bM-ro6">
<objects>
<windowController storyboardIdentifier="APInAppPurchseVC" id="GBA-jI-3If" sceneMemberID="viewController">
<window key="window" title="内购买项目管理" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="nwi-c6-OjX">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<rect key="contentRect" x="425" y="462" width="600" height="450"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
<value key="minSize" type="size" width="600" height="450"/>
<view key="contentView" id="eYM-5O-yjP">
<rect key="frame" x="0.0" y="0.0" width="600" height="450"/>
<autoresizingMask key="autoresizingMask"/>
</view>
<connections>
<outlet property="delegate" destination="GBA-jI-3If" id="5Vc-2R-wtG"/>
</connections>
</window>
<connections>
<segue destination="WHe-rZ-F7O" kind="relationship" relationship="window.shadowedContentViewController" id="KEB-Al-cX7"/>
</connections>
</windowController>
<customObject id="fxq-MF-Z4G" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-193" y="236"/>
</scene>
<!--In App PurchseVC-->
<scene sceneID="tfY-7B-aIS">
<objects>
<viewController id="WHe-rZ-F7O" customClass="APInAppPurchseVC" customModule="AppleParty" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="9ub-G9-EoX">
<rect key="frame" x="0.0" y="0.0" width="790" height="225"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<scrollView autohidesScrollers="YES" horizontalLineScroll="52" horizontalPageScroll="10" verticalLineScroll="52" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="V54-fz-e6e">
<rect key="frame" x="0.0" y="0.0" width="790" height="164"/>
<clipView key="contentView" ambiguous="YES" id="i9I-WP-piD">
<rect key="frame" x="1" y="1" width="788" height="162"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<outlineView verticalHuggingPriority="750" ambiguous="YES" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="50" rowSizeStyle="automatic" headerView="Vci-E4-hZF" viewBased="YES" indentationPerLevel="16" outlineTableColumn="kmX-0Y-Prt" id="Yd4-ve-MqM">
<rect key="frame" x="0.0" y="0.0" width="788" height="134"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<tableViewGridLines key="gridStyleMask" vertical="YES" horizontal="YES"/>
<color key="gridColor" red="0.91372549020000005" green="0.91372549020000005" blue="0.91372549020000005" alpha="1" colorSpace="calibratedRGB"/>
<tableColumns>
<tableColumn identifier="idColumn" width="60" minWidth="60" maxWidth="70" id="kmX-0Y-Prt">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="ID">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="GpP-s7-Ouk">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView identifier="idCell" id="lp2-R8-h3a">
<rect key="frame" x="1" y="1" width="65" height="50"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="6Wx-lc-fpC">
<rect key="frame" x="6" y="17" width="16" height="16"/>
<constraints>
<constraint firstAttribute="height" constant="16" id="aNd-6b-TqP"/>
<constraint firstAttribute="width" constant="16" id="mLS-PQ-s6k"/>
</constraints>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="unselect" id="woP-Wq-aBW"/>
</imageView>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="aNG-P7-E3Y">
<rect key="frame" x="23" y="17" width="96" height="16"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="hjI-ir-7iT">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="aNG-P7-E3Y" firstAttribute="centerY" secondItem="6Wx-lc-fpC" secondAttribute="centerY" id="CMf-lo-HE5"/>
<constraint firstItem="6Wx-lc-fpC" firstAttribute="centerY" secondItem="lp2-R8-h3a" secondAttribute="centerY" id="RE7-tC-iFF"/>
<constraint firstItem="6Wx-lc-fpC" firstAttribute="leading" secondItem="lp2-R8-h3a" secondAttribute="leading" constant="6" id="VKE-G2-7hp"/>
<constraint firstItem="aNG-P7-E3Y" firstAttribute="leading" secondItem="6Wx-lc-fpC" secondAttribute="trailing" constant="3" id="yxl-Wx-leE"/>
</constraints>
<connections>
<outlet property="textField" destination="aNG-P7-E3Y" id="eZN-eA-lug"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn identifier="productIDColumn" width="149.5" minWidth="40" maxWidth="1000" id="dWg-Q6-LNF">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Product ID">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="coU-2a-usX">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView identifier="productIDCell" id="HMV-5V-AY4">
<rect key="frame" x="69" y="1" width="150" height="50"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6sM-uW-9cW">
<rect key="frame" x="0.0" y="17" width="149" height="16"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="5d1-mj-cla">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<connections>
<outlet property="textField" destination="6sM-uW-9cW" id="jSl-Zp-v33"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn identifier="productNameColumn" width="101.5" minWidth="40" maxWidth="1000" id="cu0-Fg-efY">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="品项名称">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="DUN-WZ-MnU">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView identifier="productNameCell" id="GlU-7h-ss9">
<rect key="frame" x="221.5" y="1" width="101" height="50"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9VL-cf-yHZ">
<rect key="frame" x="-1" y="17" width="101" height="16"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="Q3a-OZ-g6W">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<connections>
<outlet property="textField" destination="9VL-cf-yHZ" id="2Lk-ZX-d07"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn identifier="priceLevelColumn" width="54.5" minWidth="40" maxWidth="100" id="QvX-dg-tZC">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="销售范围">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<comboBoxCell key="dataCell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" alignment="center" buttonBordered="NO" completes="NO" numberOfVisibleItems="5" id="KhK-VP-9gv">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<objectValues>
<string>6</string>
<string>12</string>
<string>18</string>
</objectValues>
</comboBoxCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView identifier="priceLevelCell" id="Ixq-9W-hrr">
<rect key="frame" x="326" y="1" width="55" height="50"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="nh9-yz-azS">
<rect key="frame" x="-1" y="17" width="55" height="16"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="CfZ-Ol-bRD">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<connections>
<outlet property="textField" destination="nh9-yz-azS" id="HB2-QL-ySt"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn identifier="priceColumn" width="78" minWidth="60" maxWidth="100" id="vDg-bl-irs">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="价格机制">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<comboBoxCell key="dataCell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" alignment="center" buttonBordered="NO" completes="NO" numberOfVisibleItems="5" id="Qh1-4g-d40">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<objectValues>
<string>6</string>
<string>12</string>
<string>18</string>
</objectValues>
</comboBoxCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView identifier="priceCell" id="fHK-u3-suB">
<rect key="frame" x="383.5" y="1" width="78" height="50"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="JSr-Bq-gso">
<rect key="frame" x="-1" y="17" width="78" height="16"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="XMd-gw-vxG">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<connections>
<outlet property="textField" destination="JSr-Bq-gso" id="SkZ-iA-u7Q"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn identifier="appleidColumn" width="120" minWidth="40" maxWidth="130" id="FFJ-KU-Lsi">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Apple ID">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<comboBoxCell key="dataCell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" alignment="center" buttonBordered="NO" completes="NO" numberOfVisibleItems="5" id="89a-ev-30f">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<objectValues>
<string>6</string>
<string>12</string>
<string>18</string>
</objectValues>
</comboBoxCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView identifier="appleidCell" id="2r2-YN-gxc">
<rect key="frame" x="464.5" y="1" width="120" height="50"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Zr2-bI-CNv">
<rect key="frame" x="-1" y="17" width="120" height="16"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="rHF-4a-zo7">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<connections>
<outlet property="textField" destination="Zr2-bI-CNv" id="R0Z-ey-UBH"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn identifier="typeColumn" width="99.5" minWidth="40" maxWidth="100" id="BAk-nA-FHU">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="品项类型">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="ePF-D6-oMG">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView identifier="typeCell" id="Lym-V5-iX8">
<rect key="frame" x="587.5" y="1" width="99" height="50"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KmJ-Eh-Ikj">
<rect key="frame" x="-1" y="17" width="99" height="16"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="TQy-ks-kac">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<connections>
<outlet property="textField" destination="KmJ-Eh-Ikj" id="U29-Bd-kYT"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn identifier="stateColumn" width="58.5" minWidth="40" maxWidth="110" id="zNJ-vs-M9B">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="状态">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="Ny4-hA-TaW">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView identifier="stateCell" id="JqD-IF-M9E">
<rect key="frame" x="690" y="1" width="63" height="50"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bt8-9h-N0G">
<rect key="frame" x="0.0" y="17" width="62" height="16"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="hXq-cj-HG6">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<connections>
<outlet property="textField" destination="bt8-9h-N0G" id="ZlI-AB-lkJ"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
</tableColumns>
</outlineView>
</subviews>
</clipView>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="150" id="ysI-Go-iNV"/>
</constraints>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="oPx-Do-j7O">
<rect key="frame" x="1" y="35" width="1113" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="yaM-yh-Nqt">
<rect key="frame" x="804" y="29" width="15" height="42"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<tableHeaderView key="headerView" wantsLayer="YES" id="Vci-E4-hZF">
<rect key="frame" x="0.0" y="0.0" width="788" height="28"/>
<autoresizingMask key="autoresizingMask"/>
</tableHeaderView>
</scrollView>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="RWS-I4-Hj5">
<rect key="frame" x="18" y="189" width="72" height="16"/>
<constraints>
<constraint firstAttribute="height" constant="16" id="b1e-La-oHG"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" title="AppName" id="ocn-p7-7tY">
<font key="font" metaFont="systemBold" size="14"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="g1s-qC-zZv">
<rect key="frame" x="248" y="172" width="94" height="48"/>
<buttonCell key="cell" type="push" title="刷新" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="PBX-Aw-4gO">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<constraints>
<constraint firstAttribute="width" constant="80" id="KLz-QE-MgQ"/>
<constraint firstAttribute="height" constant="36" id="VRS-Wp-jqM"/>
</constraints>
<connections>
<action selector="reloadIAPs:" target="WHe-rZ-F7O" id="NKR-Dj-n4M"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="g4b-K9-FLu">
<rect key="frame" x="338" y="172" width="114" height="48"/>
<buttonCell key="cell" type="push" title="导入表格" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="vsX-vy-Zvo">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<constraints>
<constraint firstAttribute="width" constant="100" id="duV-PA-5xP"/>
<constraint firstAttribute="height" constant="36" id="wU1-by-aaT"/>
</constraints>
<connections>
<action selector="importExcel:" target="WHe-rZ-F7O" id="NyY-A2-rYY"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="DoY-Xj-sox">
<rect key="frame" x="448" y="172" width="114" height="48"/>
<buttonCell key="cell" type="push" title="导出表格" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Qpw-5P-Y95">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<constraints>
<constraint firstAttribute="height" constant="36" id="S2N-Zb-bUT"/>
<constraint firstAttribute="width" constant="100" id="SQi-d8-LOD"/>
</constraints>
<connections>
<action selector="outputExcel:" target="WHe-rZ-F7O" id="07F-Jd-GLD"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pDs-nU-YDZ">
<rect key="frame" x="668" y="172" width="114" height="48"/>
<buttonCell key="cell" type="push" title="下载表格示例" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="cwE-He-2kf">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<constraints>
<constraint firstAttribute="height" constant="36" id="6T9-y8-fcw"/>
<constraint firstAttribute="width" constant="100" id="cHT-mK-iEg"/>
</constraints>
<connections>
<action selector="downloadExcel:" target="WHe-rZ-F7O" id="Hdd-v5-BPn"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fCY-Bo-Sre">
<rect key="frame" x="558" y="172" width="114" height="48"/>
<buttonCell key="cell" type="push" title="导出品项ID" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="cwm-HN-x0f">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<constraints>
<constraint firstAttribute="width" constant="100" id="LMH-kg-AeM"/>
<constraint firstAttribute="height" constant="36" id="RFy-br-N3a"/>
</constraints>
<connections>
<action selector="outputProductID:" target="WHe-rZ-F7O" id="JyB-hQ-Tn0"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="V54-fz-e6e" firstAttribute="top" secondItem="g4b-K9-FLu" secondAttribute="bottom" constant="15" id="3V5-A4-VMG"/>
<constraint firstItem="fCY-Bo-Sre" firstAttribute="centerY" secondItem="g1s-qC-zZv" secondAttribute="centerY" id="3wr-s7-mlV"/>
<constraint firstItem="fCY-Bo-Sre" firstAttribute="leading" secondItem="DoY-Xj-sox" secondAttribute="trailing" constant="10" id="657-v3-Nxa"/>
<constraint firstItem="RWS-I4-Hj5" firstAttribute="leading" secondItem="9ub-G9-EoX" secondAttribute="leading" constant="20" symbolic="YES" id="6NV-i6-UqO"/>
<constraint firstItem="RWS-I4-Hj5" firstAttribute="centerY" secondItem="g1s-qC-zZv" secondAttribute="centerY" id="8pU-c6-L46"/>
<constraint firstItem="DoY-Xj-sox" firstAttribute="centerY" secondItem="g1s-qC-zZv" secondAttribute="centerY" id="9f0-vD-ZSY"/>
<constraint firstAttribute="bottom" secondItem="V54-fz-e6e" secondAttribute="bottom" id="BQe-7k-3BG"/>
<constraint firstItem="g4b-K9-FLu" firstAttribute="top" secondItem="9ub-G9-EoX" secondAttribute="top" constant="10" id="Loj-uR-Xw3"/>
<constraint firstItem="g4b-K9-FLu" firstAttribute="leading" secondItem="g1s-qC-zZv" secondAttribute="trailing" constant="10" id="QdO-2i-xQc"/>
<constraint firstItem="V54-fz-e6e" firstAttribute="leading" secondItem="9ub-G9-EoX" secondAttribute="leading" id="RJg-65-WD6"/>
<constraint firstItem="g1s-qC-zZv" firstAttribute="top" secondItem="9ub-G9-EoX" secondAttribute="top" constant="10" id="TmA-QA-knT"/>
<constraint firstItem="pDs-nU-YDZ" firstAttribute="leading" secondItem="fCY-Bo-Sre" secondAttribute="trailing" constant="10" id="al9-zg-mfC"/>
<constraint firstAttribute="trailing" secondItem="pDs-nU-YDZ" secondAttribute="trailing" constant="15" id="ems-r3-Hzs"/>
<constraint firstItem="pDs-nU-YDZ" firstAttribute="centerY" secondItem="g1s-qC-zZv" secondAttribute="centerY" id="f8v-tI-hgZ"/>
<constraint firstAttribute="trailing" secondItem="V54-fz-e6e" secondAttribute="trailing" id="gDP-cl-PDs"/>
<constraint firstItem="DoY-Xj-sox" firstAttribute="leading" secondItem="g4b-K9-FLu" secondAttribute="trailing" constant="10" id="oVv-xx-EqB"/>
</constraints>
</view>
<connections>
<outlet property="appNameView" destination="RWS-I4-Hj5" id="B2s-1y-ClK"/>
<outlet property="outlineView" destination="Yd4-ve-MqM" id="gzd-Pc-XWm"/>
<outlet property="outputIAPListBtn" destination="DoY-Xj-sox" id="Age-qN-Qlj"/>
</connections>
</viewController>
<customObject id="LWD-js-20w" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="651" y="228.5"/>
</scene>
</scenes>
<resources>
<image name="unselect" width="16" height="16"/>
</resources>
</document>
@@ -0,0 +1,302 @@
//
// APInAppPurchseVC.swift
// AppleParty
//
// Created by HTC on 2022/3/28.
// Copyright © 2022 37 Mobile Games. All rights reserved.
//
import Cocoa
class APInAppPurchseVC: NSViewController {
public var currentApp: App? {
didSet {
fetchIAPs()
appNameView.stringValue = currentApp?.appName ?? ""
}
}
var iapList: [IAPList.IAP] = []
private var countryCode = ""
private var checkPrice = [String: String]()
@IBOutlet weak var outputIAPListBtn: NSButton!
@IBOutlet weak var appNameView: NSTextField!
@IBOutlet weak var outlineView: NSOutlineView!
override func viewDidLoad() {
super.viewDidLoad()
self.outlineView.delegate = self
self.outlineView.dataSource = self
self.outlineView.columnAutoresizingStyle = .uniformColumnAutoresizingStyle
self.outlineView.selectionHighlightStyle = .none
self.outlineView.allowsMultipleSelection = true
self.outlineView.sizeToFit()
}
@IBAction func reloadIAPs(_ sender: Any) {
fetchIAPs()
}
@IBAction func importExcel(_ sender: Any) {
let openPanel = NSOpenPanel()
openPanel.canChooseFiles = true
openPanel.canChooseDirectories = true
openPanel.allowsMultipleSelection = false
openPanel.beginSheetModal(for: self.view.window!) { [self] (modalResponse: NSApplication.ModalResponse) in
if modalResponse == .OK, let filePath = openPanel.url {
handelExcel(filePath)
}
}
}
@IBAction func outputExcel(_ sender: Any) {
guard iapList.count > 0 else {
NSAlert.show("当前商品为空~")
return
}
// 创建格式
var iaps = "productId, 商品名称, 价格等级, 价格(\(countryCode)), AppleID, 商品类型, 状态, 送审图片\n"
let separator = "\",\""
iaps += iapList.map { item -> String in
return "\"" + item.vendorId + separator + item.referenceName + separator + item.priceTier + separator + (checkPrice[item.priceTier] ?? "-") + separator + item.adamId
+ separator + item.addOnType.CNValue() + separator + item.iTunesConnectStatus.statusValue.0 + separator + item.reviewScreenshot + "\""
}.joined(separator: "\n")
// 保存文件
let dateFormatter : DateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyyMMdd_HHmm_"
let currentDate = dateFormatter.string(from: Date())
let mySave = NSSavePanel()
mySave.allowedFileTypes = ["csv"]
mySave.nameFieldStringValue = "内购列表" + currentDate + currentApp!.appName.replacingOccurrences(of: " ", with: "-")
mySave.begin { (result) -> Void in
if result == .OK {
let filePath = mySave.url
do {
// 含有中文,excel在打开CSV文件时默认用ASNI打开,无BOM头的unicode文件会出现乱码
var data = Data([0xEF, 0xBB, 0xBF])
data.append(contentsOf: iaps.data(using: .utf8) ?? Data())
try data.write(to: filePath!)
} catch {
NSAlert.show("导出失败:\(error.localizedDescription)")
}
}
}
}
@IBAction func outputProductID(_ sender: Any) {
let mainStoryboard = NSStoryboard(name: "InAppPurchseView", bundle: Bundle(for: self.classForCoder))
let outputVC = mainStoryboard.instantiateController(withIdentifier: "OutputExcelVCID") as? OutputExcelVC
outputVC?.iapList = iapList
presentAsSheet(outputVC!)
}
@IBAction func downloadExcel(_ sender: Any) {
XMLManager.copySimpleExel()
}
}
// MARK: - 网络请求
extension APInAppPurchseVC {
// 请求商品列表
func fetchIAPs() {
APClient.iaps(appid: currentApp!.appId).request(showLoading: true, inView: self.view) { [weak self] result, response, error in
guard let err = error else {
guard let app = self?.currentApp else { return } //请求过程关闭页面可能导致为空
let iapL = IAPList(body:result, app: app)
self?.iapList = iapL.iapList
self?.outlineView.reloadData()
self?.updateRowInfo()
return
}
APHUD.hide(message: err.localizedDescription, view: self?.view ?? currentView())
}
}
func updateRowInfo() {
// TODO: 接口请求失败
return;
guard self.iapList.count > 0 else {
return
}
outputIAPListBtn.isEnabled = false
let group = DispatchGroup()
for i in 0..<self.iapList.count {
group.enter()
let iapid = iapList[i].adamId
// 商品详细
APClient.inAppPurchaseDetail(iapid: iapid).request { [weak self] result, response, error in
group.leave()
guard let err = error else {
if var newModel = self?.iapList[i] {
newModel.updateDetail(body: result)
self?.iapList[i] = newModel
self?.outlineView.reloadData()
group.enter()
// 商品价格
APClient.inAppPurchasePrices(iapid: iapid).request { [weak self] result, response, error in
group.leave()
guard let err = error else {
if var newModel = self?.iapList[i] {
newModel.updatePrices(body: result)
self?.iapList[i] = newModel
self?.outlineView.reloadData()
}
return
}
APHUD.hide(message: err.localizedDescription, view: self?.view ?? currentView())
}
}
return
}
APHUD.hide(message: err.localizedDescription, view: self?.view ?? currentView())
}
}
group.notify(queue: .main) {
self.outputIAPListBtn.isEnabled = true
self.outlineView.reloadData()
}
}
}
// MARK: - 内部方法
extension APInAppPurchseVC {
func handelExcel(_ excelFilePath: URL) {
let iaps = IAPExcelParser.parser(excelFilePath)
// 检查内购品项是否后台已经创建过
if iapList.isNotEmpty {
let pids = iaps.map { $0.productId }
let iaps = iapList.map { $0.vendorId }
let warns = pids.filter { iaps.contains($0) }
if warns.isNotEmpty {
NSAlert.show("‼️警告:已经存在相同商品id的品项!请检查:\(warns)。\n⚠️提示:如果继续上传,将会覆盖已有品项的信息!")
}
}
// 检查商品同名,苹果后台不允许存在同名
let iapNames = iaps.map { $0.name }
let iapUniNames = Array(Set(iapNames))
if iapNames.isNotEmpty, iapNames.count != iapUniNames.count {
NSAlert.show("‼️警告:存在相同参考名字的商品!请检查~\n⚠️提示:如果继续上传,同名的商品只会有一个能创建成功!")
}
let sb = NSStoryboard(name: "InAppPurchseView", bundle: Bundle(for: self.classForCoder))
let wc = sb.instantiateController(withIdentifier: "InputExcelList") as? NSWindowController
let vc = wc?.contentViewController as? APUploadIAPListVC
vc?.currentApp = currentApp
vc?.iaps = iaps
wc?.showWindow(self)
}
func setModels(models: [IAPList.IAP]) {
iapList = models
outlineView.reloadData()
outlineView.sizeToFit()
updateRowInfo()
}
}
// MARK: - NSOutlineViewDelegate
extension APInAppPurchseVC: NSOutlineViewDelegate, NSOutlineViewDataSource {
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
if let item = item as? IAPList.IAP {
switch tableColumn?.identifier.enumValue() {
case ColumnIdetifier.id.cellValue:
let cell = outlineView.makeView(withIdentifier: ColumnIdetifier.id.cellValue, owner: self) as? NSTableCellView
cell?.textField?.stringValue = String(item.curid)
return cell
case ColumnIdetifier.productID.cellValue:
let cell = outlineView.makeView(withIdentifier: ColumnIdetifier.productID.cellValue, owner: self) as? NSTableCellView
cell?.textField?.stringValue = item.vendorId
return cell
case ColumnIdetifier.productName.cellValue:
let cell = outlineView.makeView(withIdentifier: ColumnIdetifier.productName.cellValue, owner: self) as? NSTableCellView
cell?.textField?.stringValue = item.referenceName
return cell
case ColumnIdetifier.price.cellValue:
let cell = outlineView.makeView(withIdentifier: ColumnIdetifier.price.cellValue, owner: self) as? NSTableCellView
cell?.textField?.stringValue = checkPrice[item.priceTier] ?? "-"
return cell
case ColumnIdetifier.priceLevel.cellValue:
let cell = outlineView.makeView(withIdentifier: ColumnIdetifier.priceLevel.cellValue, owner: self) as? NSTableCellView
cell?.textField?.stringValue = item.priceTier
return cell
case ColumnIdetifier.appleid.cellValue:
let cell = outlineView.makeView(withIdentifier: ColumnIdetifier.appleid.cellValue, owner: self) as? NSTableCellView
cell?.textField?.stringValue = item.adamId
return cell
case ColumnIdetifier.type.cellValue:
let cell = outlineView.makeView(withIdentifier: ColumnIdetifier.type.cellValue, owner: self) as? NSTableCellView
cell?.textField?.stringValue = item.addOnType.CNValue()
return cell
case ColumnIdetifier.state.cellValue:
let cell = outlineView.makeView(withIdentifier: ColumnIdetifier.state.cellValue, owner: self) as? NSTableCellView
cell?.textField?.stringValue = item.iTunesConnectStatus.statusValue.0
cell?.textField?.textColor = item.iTunesConnectStatus.statusValue.1
return cell
default:
return nil
}
}else if let item = item as? String {
switch tableColumn?.identifier.enumValue() {
case ColumnIdetifier.productID.cellValue:
let imgView = NSImageView()
imgView.showWebImage(item)
return imgView
default:
return nil
}
}else {
return nil
}
}
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
if item is IAPList.IAP {
return 1
}else {
return iapList.count
}
}
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
if item is IAPList.IAP {
return true
}else {
return false
}
}
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
if let item = item as? IAPList.IAP {
return item.reviewScreenshot as Any
}
return iapList[index]
}
func outlineView(_ outlineView: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat {
if item is IAPList.IAP {
return 35
}else {
return 100
}
}
}
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner"/>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
</objects>
</document>
@@ -0,0 +1,806 @@
//
// InputTableListVC.swift
// AppleParty
//
// Created by 易承 on 2020/12/15.
//
import Cocoa
class APUploadIAPListVC: NSViewController {
@IBOutlet weak var tableView: NSTableView!
@IBOutlet weak var enterBtn: NSButton!
@IBOutlet weak var preserveCurrentPriceBtn: NSButton!
@IBOutlet weak var showApiRateLimitLogsBtn: NSButton!
public var currentApp: App? {
didSet {
setupUI()
}
}
public var iaps = [IAPProduct]() {
didSet {
self.tableView.reloadData()
}
}
private var screenshotPaths = [String: String]()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.columnAutoresizingStyle = .uniformColumnAutoresizingStyle
self.tableView.selectionHighlightStyle = .none
self.tableView.sizeToFit()
}
func setupUI() {
self.view.window?.title = "批量内购买项目上传 - " + (currentApp?.appName ?? "")
self.tableView.reloadData()
}
func showUploadView() {
// 不能同时 present 出来
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
let mainStoryboard = NSStoryboard(name: "InAppPurchseView", bundle: Bundle(for: self.classForCoder))
let upVC = mainStoryboard.instantiateController(withIdentifier: "IAPUploadVCID") as? IAPUploadImageVC
let screenshot = self.iaps.filter({ $0.reviewScreenshot.count > 0 }).map({ $0.reviewScreenshot })
// 去重后的图片名
let uniquedshot = screenshot.enumerated().filter { (index, value) -> Bool in
return screenshot.firstIndex(of: value) == index
}.map { $0.element }
upVC?.picnames = uniquedshot
upVC?.callBackFunc = { paths in
self.screenshotPaths = paths
self.tableView.reloadData()
}
self.presentAsSheet(upVC!)
}
}
@IBAction func clickedUploadShotBtn(_ sender: Any) {
showUploadView()
}
@IBAction func clickedSPasswordBtn(_ sender: Any) {
let vc = APASCKeysSettingVC()
presentAsSheet(vc)
}
@IBAction func createIAP(_ sender: Any) {
let list = self.iaps
guard list.count > 0 else {
APHUD.hide(message: "当前 App 无上传的内购商品!", delayTime: 1)
return
}
guard let appid = currentApp?.appId else {
APHUD.hide(message: "当前 App 的 appleid 为空!", delayTime: 1)
return
}
enterBtn.isEnabled = false
APHUD.show(message: "上传中", view: self.view)
let uploadIAPs: ((AppStoreConnectKey) -> Void) = { [weak self] ascKey in
// 上传数据
self?.updateInAppPurchse(iaps: list, appId: appid, ascKey: ascKey)
}
guard let ascKey = InfoCenter.shared.currentASCKey else {
let vc = APASCKeysSettingVC()
vc.updateCompletion = { password in
if let ascKey = password {
uploadIAPs(ascKey)
}
}
presentAsSheet(vc)
return
}
uploadIAPs(ascKey)
}
}
// MARK: - 网络请求
extension APUploadIAPListVC {
func updateInAppPurchse(iaps: [IAPProduct], appId: String, ascKey: AppStoreConnectKey) {
let showApiRateLimit = showApiRateLimitLogsBtn.state.rawValue == 1
let ascAPI = APASCAPI.init(issuerID: ascKey.issuerID,
privateKeyID: ascKey.privateKeyID,
privateKey: ascKey.privateKey,
showApiRateLimit: showApiRateLimit)
ascAPI.addMessage("密钥信息:\(ascKey.issuerID), \(ascKey.privateKeyID), \(ascKey.privateKey)")
Task {
// 1、获取当前账号下 app,判断是否包含当前 提交商品的 app
guard let apps = await ascAPI.apps() else {
self.enterBtn.isEnabled = true
APHUD.hide()
APHUD.hide(message: "当前请求异常,请检查密钥是否正确~", delayTime: 2)
return
}
let app = apps.filter { $0.id == appId }
guard app.count > 0 else {
self.enterBtn.isEnabled = true
APHUD.hide()
APHUD.hide(message: "当前的密钥没有查到App: \(appId),请检查~", delayTime: 2)
return
}
// 2. 同步显示进度日志
let sb = NSStoryboard(name: "APDebugVC", bundle: Bundle(for: self.classForCoder))
let newWC = sb.instantiateController(withIdentifier: "APDebugWC") as? NSWindowController
newWC?.window?.title = "内购批量上传日志"
let logVC = newWC?.contentViewController as? APDebugVC
logVC?.debugLog = "开始上传"
newWC?.showWindow(self)
ascAPI.updateMsg = { messages in
logVC?.debugLog = messages.joined(separator: "\n")
}
ascAPI.addMessage("开始处理内购商品,获取现有商品中...")
// 3、获取所有的内购商品,如果存在的商品就直接修改,如果不存在就创建
let oldIAPs = await ascAPI.fetchInAppPurchasesList(appId: appId)
// 4、遍历所有要上传的商品
for product in iaps {
// 订阅类型与非订单类型不一样的处理逻辑
if product.inAppPurchaseType == .AUTO_RENEWABLE {
await createRenewSubscription(appId: appId, product: product, ascAPI: ascAPI)
} else {
await createInAppPurchase(appId: appId, product: product, oldIAPs: oldIAPs, ascAPI: ascAPI)
}
}
self.enterBtn.isEnabled = true
APHUD.hide()
ascAPI.addMessage("完成全部内购商品,可稍后在苹果后台查看!✅✅✅")
}
}
// MARK: - 上传内购类型商品
/// 创建内购商品
func createInAppPurchase(appId: String, product: IAPProduct, oldIAPs: [ASCInAppPurchaseV2], ascAPI: APASCAPI) async {
ascAPI.addMessage("开始上传内购商品:\(product.productId),\(product.name) ")
// 检查是否已经存在此商品,如果存在就修改信息,如果不存在就创建
let iaps = oldIAPs.filter({ $0.attributes?.productID == product.productId })
if let iap = iaps.first {
ascAPI.addMessage("内购已经存在:\(product.productId) ,开始更新信息中...")
// 0. 审核备注如果原来有值,而新字段无值,则使用原值
var product = product
if let note = iap.attributes?.reviewNote, product.reviewNote.isEmpty {
product.reviewNote = note
}
// 1.修改原商品信息
guard let iap = await ascAPI.updateInAppPurchases(iapId: iap.id, product: product) else {
// 修改失败
ascAPI.addMessage("内购已经存在:\(product.productId) ,更新信息失败!❌ ")
return
}
// 2. 商品价格档位
await updateIAPPricePoint(iapId: iap.id, product: product, ascAPI: ascAPI)
// 3. 商品本地化语言
ascAPI.addMessage("开始更新内购本地化版本:\(product.productId)")
let localizations = await ascAPI.fetchInAppPurchasesLocalizations(iapId: iap.id)
for localization in product.localizations {
// 如果已经存在本地化语言,则更新
if let locale = localizations.filter({ $0.attributes?.locale == localization.locale }).first {
// 更新
ascAPI.addMessage("内购已存在本地化版本:\(localization.locale),开始更新信息中...")
if (await ascAPI.updateInAppPurchasesLocalization(iapLocaleId: locale.id, localization: localization)) != nil {
// 本地化语言更新成功
ascAPI.addMessage("内购本地化版本:\(localization.locale) ,更新语言成功!✅ ")
} else {
// 本地化语言更新失败
ascAPI.addMessage("内购本地化版本:\(localization.locale) ,更新语言失败!❌ ")
}
} else {
// 创建
await createIAPLocalization(iapId: iap.id, localization: localization, product: product, ascAPI: ascAPI)
}
}
// 4. 商品截图
await createIAPScreenshot(iapId: iap.id, product: product, ascAPI: ascAPI)
// 5. 销售国家或地区
await updateIAPAvailableTerritories(iapId: iap.id, product: product, ascAPI: ascAPI)
} else {
// 1. 创建新的商品
guard let iap = await ascAPI.createInAppPurchases(appId: appId, product: product) else {
// 创建失败
ascAPI.addMessage("内购商品:\(product.productId) ,创建失败!❌ ")
return
}
// 2. 商品价格档位
await updateIAPPricePoint(iapId: iap.id, product: product, ascAPI: ascAPI)
// 3. 商品本地化语言
for localization in product.localizations {
await createIAPLocalization(iapId: iap.id, localization: localization, product: product, ascAPI: ascAPI)
}
// 4. 商品截图
await createIAPScreenshot(iapId: iap.id, product: product, ascAPI: ascAPI)
// 5. 销售国家或地区
await updateIAPAvailableTerritories(iapId: iap.id, product: product, ascAPI: ascAPI)
}
ascAPI.addMessage("内购商品:\(product.productId),\(product.name) ,上传完成!\n")
}
/// 创建内购商品价格档位
func updateIAPPricePoint(iapId: String, product: IAPProduct, ascAPI: APASCAPI) async {
guard let schedule = product.priceSchedules else {
ascAPI.addMessage("无价格计划表:\(product.productId) ,请确认!❌ ")
return
}
let baseTerritory = schedule.baseTerritory
let baseCustomerPrice = schedule.baseCustomerPrice.normalizePrice()
ascAPI.addMessage("开始更新价格计划表:\(product.productId),\(baseTerritory),\(baseCustomerPrice) \n")
let points = await ascAPI.fetchPricePoints(iapId: iapId, territory: [baseTerritory])
if let point = points.filter({ $0.attributes?.customerPrice!.normalizePrice() == baseCustomerPrice }).first {
var manualPrices: [Any] = []
var included: [Any] = []
ascAPI.addMessage("开始构建基准国家和自定价格:")
// base Territory
manualPrices.append(["id": "${\(baseTerritory)-\(included.count)}", "type": "inAppPurchasePrices"])
included.append(ascAPI.fetchInAppPurchasePriceSchedule(scheduleId: baseTerritory, pricePointId: point.id, iapId: iapId, index: included.count))
// customerPrice
for pricePoint in schedule.manualPrices {
let territory = pricePoint.territory
let customerPrice = pricePoint.customerPrice.normalizePrice()
let points = await ascAPI.fetchPricePoints(iapId: iapId, territory: [territory])
if let point = points.filter({ $0.attributes?.customerPrice!.normalizePrice() == customerPrice }).first {
manualPrices.append(["id": "${\(territory)-\(included.count)}", "type": "inAppPurchasePrices"])
included.append(ascAPI.fetchInAppPurchasePriceSchedule(scheduleId: territory, pricePointId: point.id, iapId: iapId, index: included.count))
} else {
ascAPI.addMessage("自定价格的内购价格点:\(territory),\(customerPrice) ,未找到此档位!❌ ")
}
}
ascAPI.saveLogs(log: "内购的基准国家和自定价格:\(manualPrices),\(included)")
if (await ascAPI.updateInAppPurchasePricePoint(iapId: iapId, baseTerritoryId: baseTerritory, manualPrices: manualPrices, included: included)) != nil {
// 价格档位配置成功
ascAPI.addMessage("内购价格点:\(baseTerritory),\(baseCustomerPrice) ,更新价格成功!✅ ")
} else {
// 价格档位配置失败
ascAPI.addMessage("内购价格点:\(baseTerritory),\(baseCustomerPrice) ,更新价格失败!❌ ")
}
} else {
// 找不到价格档位
ascAPI.addMessage("基准国家的内购价格点:\(baseTerritory),\(baseCustomerPrice) ,未找到此档位!❌ ")
}
}
/// 创建内购商品本地化信息
func createIAPLocalization(iapId: String, localization: IAPLocalization, product: IAPProduct, ascAPI: APASCAPI) async {
ascAPI.addMessage("开始更新本地化版本:\(product.productId),\(localization.locale)")
if (await ascAPI.createInAppPurchasesLocalization(iapId: iapId, localization: localization)) != nil {
// 本地化语言配置成功
ascAPI.addMessage("内购本地化版本:\(localization.locale) ,更新语言成功!✅ ")
} else {
// 本地化语言配置失败
ascAPI.addMessage("内购本地化版本:\(localization.locale) ,更新语言失败!❌ ")
}
}
/// 更新内购商品的送审截图
func createIAPScreenshot(iapId: String, product: IAPProduct, ascAPI: APASCAPI) async {
ascAPI.addMessage("开始更新内购商品的送审截图:\(product.productId),\(product.reviewScreenshot)")
let imgName = product.reviewScreenshot
guard let imgPath = screenshotPaths[imgName] else {
ascAPI.addMessage("内购商品:\(product.productId) 无送审截图或未上传截图~")
return
}
let imaUrl = URL.init(fileURLWithPath: imgPath)
guard let fileMD5 = URL.init(fileURLWithPath: imgPath).fileMD5() else {
ascAPI.addMessage("内购商品截图文件错误:\(imgPath) ,无法生成 md5 值~")
return
}
let oldShot = await ascAPI.fetchInAppPurchasesScreenshot(iapId: iapId)
// 存在需要删除,避免文件名不一样或者过期文件
if let ost = oldShot {
ascAPI.addMessage("删除旧的送审截图:\(ost.attributes?.fileName ?? "")")
let status = await ascAPI.deleteInAppPurchasesScreenshot(iapShotId: ost.id)
if status != 204 {
ascAPI.addMessage("内购商品截图创建失败:\(imgName) ,无法删除旧截图~")
}
}
ascAPI.addMessage("创建新的送审截图:\(product.reviewScreenshot)")
// 创建截图
let imaSize = imaUrl.fileSizeInt()
guard let shot = await ascAPI.createInAppPurchasesScreenshot(iapId: iapId, fileName: imgName, fileSize: imaSize) else {
// 创建失败
ascAPI.addMessage("内购商品:\(product.productId) ,创建送审截图失败!❌ ")
return
}
// 根据苹果接口返回的上传接口上传
guard let method = shot.attributes?.uploadOperations?.first?.method,
let url = shot.attributes?.uploadOperations?.first?.url,
let requestHeaders = shot.attributes?.uploadOperations?.first?.requestHeaders,
let baseURL = URL(string: url) else {
ascAPI.addMessage("内购商品:\(product.productId) ,创建送审截图失败!苹果参数异常~ ❌ ")
return
}
var request = URLRequest(url: baseURL)
request.httpMethod = method
for header in requestHeaders {
request.headers[header.name ?? ""] = header.value ?? ""
}
ascAPI.addMessage("上传新的送审截图:\(product.reviewScreenshot)")
// 上传图片
guard let response = try? await URLSession.shared.upload(for: request, fromFile: imaUrl) else {
ascAPI.addMessage("内购商品:\(product.productId) ,创建送审截图失败!上传图片异常~ ❌ ")
return
}
guard let responseCode = (response.1 as? HTTPURLResponse)?.statusCode, responseCode == 200 else {
ascAPI.addMessage("内购商品:\(product.productId) ,创建送审截图失败!上传图片异常 \(response.1.description)~ ❌ ")
return
}
ascAPI.addMessage("提交新的送审截图:\(product.reviewScreenshot)")
// 确认图片
if ((await ascAPI.updateInAppPurchasesScreenshot(iapShotId: shot.id, fileMD5: fileMD5)) != nil) {
ascAPI.addMessage("内购商品:\(product.productId) ,送审截图上传成功!✅ ")
} else {
ascAPI.addMessage("内购商品:\(product.productId) ,送审截图可能上传失败! ")
}
}
/// 销售国家或地区
func updateIAPAvailableTerritories(iapId: String, product: IAPProduct, ascAPI: APASCAPI) async {
let inAll = product.territories.availableInAllTerritories
let inNew = product.territories.availableInNewTerritories
let summary = territoryInfo(product: product)
let newTerritory = inNew ? "将来新国家(地区)时自动提供!" : "将来新国家(地区)时不自动提供!"
ascAPI.addMessage("开始更新内购商品的销售国家/地区:\(summary)")
guard !inAll else {
var allTerritories: [[String: String]] = []
if let territories = await ascAPI.territories() {
territories.forEach { territory in
allTerritories.append([
"type": "territories",
"id": territory.id
])
}
// 更新全部国家地区
if (await ascAPI.updateInAppPurchasesAvailabilityTerritories(iapId: iapId, availableTerritories: allTerritories, availableInNewTerritories: inNew)) != nil {
ascAPI.addMessage("选择:所有国家(地区)销售,\(newTerritory),更新成功!✅ ")
} else {
ascAPI.addMessage("选择:所有国家(地区)销售,\(newTerritory),更新失败!❌ ")
}
} else {
ascAPI.addMessage("选择:所有国家(地区)销售,\(newTerritory),无法设置!获取国家标识码失败!❌ ")
}
return
}
/// 选择销售的国家或地区
var territories: [[String: String]] = []
product.territories.territories?.forEach({ territory in
territories.append([
"type": "territories",
"id": territory.id
])
})
let customerTerritory = product.territories.territories?.map({ $0.id }).joined(separator: ",") ?? "无"
if (await ascAPI.updateInAppPurchasesAvailabilityTerritories(iapId: iapId, availableTerritories: territories, availableInNewTerritories: inNew)) != nil {
ascAPI.addMessage("内购商品的销售国家/地区:\(customerTerritory) ,更新成功!✅ ")
} else {
ascAPI.addMessage("内购商品的销售国家/地区:\(customerTerritory) ,更新失败!❌ ")
}
}
// MARK: - 上传订阅商品
/// 订阅商品创建或更新
func createRenewSubscription(appId: String, product: IAPProduct, ascAPI: APASCAPI) async {
let groupName = product.groupName
var currentSubGroup: ASCSubscriptionGroup?
// 1、是否有订阅组,没有时要先创建
var subGroups = await ascAPI.fetchSubscriptionGroups(appId: appId)
// if subGroups.isEmpty {
// }
for subGroup in subGroups {
if subGroup.attributes?.referenceName == groupName {
currentSubGroup = subGroup
}
}
// 创建订阅组
if currentSubGroup == nil, let group = await ascAPI.createSubscriptionGroups(appId: appId, groupName: groupName) {
currentSubGroup = group
subGroups.append(group)
for localization in product.localizations {
let _ = await ascAPI.createSubscriptionGroupLocalizations(iapGroupId: group.id, name: localization.name, locale: localization.locale, customAppName: nil)
}
}
//订阅组设置国际化
if let group = currentSubGroup {
for localization in product.localizations {
let _ = await ascAPI.createSubscriptionGroupLocalizations(iapGroupId: group.id, name: localization.name, locale: localization.locale, customAppName: nil)
}
}
// 2、有订阅组,获取所有订阅组的所有订阅商品
var subscriptions = [ASCSubscription]()
for subGroup in subGroups {
let subs = await ascAPI.fetchSubscriptionGroupSubscriptions(iapGroupId: subGroup.id)
subscriptions.append(contentsOf: subs)
}
// 3、查看是否存在订阅商品,不存在就创建,存在就更新
let subs = subscriptions.filter({ $0.attributes?.productID == product.productId })
if let sub = subs.first {
ascAPI.addMessage("订阅商品已经存在:\(product.productId) ,开始更新信息中...")
// 0. 审核备注如果原来有值,而新字段无值,则使用原值
var product = product
if let note = sub.attributes?.reviewNote, product.reviewNote.isEmpty {
product.reviewNote = note
}
// 1.修改原商品信息
guard let iap = await ascAPI.updateSubscription(iapId: sub.id, product: product) else {
// 修改失败
ascAPI.addMessage("订阅商品已经存在:\(product.productId) ,更新信息失败!❌ ")
return
}
// 2. 商品本地化语言
ascAPI.addMessage("开始更新订阅商品本地化版本:\(product.productId)")
let localizations = await ascAPI.fetchSubscriptionLocalizations(iapId: iap.id)
for localization in product.localizations {
// 如果已经存在本地化语言,则更新
if let locale = localizations.filter({ $0.attributes?.locale == localization.locale }).first {
// 更新
ascAPI.addMessage("订阅商品已存在本地化版本:\(localization.locale),开始更新信息中...")
if (await ascAPI.updateSubscriptionLocalization(iapLocaleId: locale.id, localization: localization)) != nil {
// 本地化语言更新成功
ascAPI.addMessage("订阅商品本地化版本:\(localization.locale) ,更新语言成功!✅ ")
} else {
// 本地化语言更新失败
ascAPI.addMessage("订阅商品本地化版本:\(localization.locale) ,更新语言失败!❌ ")
}
} else {
// 创建
await createSubscriptionLocalization(iapId: iap.id, localization: localization, product: product, ascAPI: ascAPI)
}
}
// 3. 商品价格档位
await updateSubscriptionPricePoint(iapId: iap.id, product: product, ascAPI: ascAPI)
// 4. 商品截图
await createSubscriptionScreenshot(iapId: iap.id, product: product, ascAPI: ascAPI)
// 5. 销售国家或地区
await updateSubscriptionAvailableTerritories(iapId: iap.id, product: product, ascAPI: ascAPI)
} else {
// 1. 创建新的商品
guard let iapGroupId = currentSubGroup?.id, let iap = await ascAPI.createSubscription(iapGroupId: iapGroupId, product: product) else {
// 创建失败
ascAPI.addMessage("订阅商品:\(product.productId) ,创建失败!❌ ")
return
}
// 2. 商品本地化语言
for localization in product.localizations {
await createSubscriptionLocalization(iapId: iap.id, localization: localization, product: product, ascAPI: ascAPI)
}
// 3. 商品价格档位
await updateSubscriptionPricePoint(iapId: iap.id, product: product, ascAPI: ascAPI)
// 4. 商品截图
await createSubscriptionScreenshot(iapId: iap.id, product: product, ascAPI: ascAPI)
// 5. 销售国家或地区
await updateSubscriptionAvailableTerritories(iapId: iap.id, product: product, ascAPI: ascAPI)
}
ascAPI.addMessage("订阅商品:\(product.productId),\(product.name) ,上传完成!\n")
}
/// 更新订阅商品的价格档位
func updateSubscriptionPricePoint(iapId: String, product: IAPProduct, ascAPI: APASCAPI) async {
guard let schedule = product.priceSchedules else {
ascAPI.addMessage("无价格计划表:\(product.productId) ,请确认!❌ ")
return
}
let baseTerritory = schedule.baseTerritory
let baseCustomerPrice = schedule.baseCustomerPrice.normalizePrice()
ascAPI.addMessage("开始更新订阅商品价格点,基准国家:\(product.productId),\(baseTerritory),\(baseCustomerPrice) \n")
let isPreservePrice = preserveCurrentPriceBtn.state.rawValue == 1
ascAPI.addMessage("保留自动续期订阅者现有定价:\(isPreservePrice ? "是" : "否")")
let points = await ascAPI.fetchSubscriptionPricePoints(iapId: iapId, territory: [baseTerritory])
if let point = points.filter({ $0.attributes?.customerPrice!.normalizePrice() == baseCustomerPrice }).first {
ascAPI.addMessage("开始更新自定价格:")
// 自定价格的国家或地区, 基准国家也算是自定价格
var customerPriceSchedules = schedule.manualPrices
customerPriceSchedules.append(IAPPricePoint(territory: baseTerritory, customerPrice: baseCustomerPrice))
let manualPricesTerritory: [String] = customerPriceSchedules.map({ $0.territory })
// 设置自定价格
for pricePoint in customerPriceSchedules {
let territory = pricePoint.territory
let customerPrice = pricePoint.customerPrice.normalizePrice()
let points = await ascAPI.fetchSubscriptionPricePoints(iapId: iapId, territory: [territory])
if let point = points.filter({ $0.attributes?.customerPrice!.normalizePrice() == customerPrice }).first {
if (await ascAPI.updateSubscriptionPricePoint(iapId: iapId, pricePointId: point.id, preserveCurrentPrice: isPreservePrice)) != nil {
ascAPI.addMessage("自定价格的订阅商品的价格点:\(territory),\(customerPrice) ,更新价格成功!✅ ")
} else {
ascAPI.addMessage("自定价格的订阅商品的价格点:\(territory),\(customerPrice) ,更新价格失败!❌ ")
}
} else {
ascAPI.addMessage("自定价格的订阅商品价格点:\(territory),\(customerPrice) ,未找到此档位!❌ ")
}
}
ascAPI.addMessage("开始更新全球均衡价格:")
// 剩余的所有的国家地区的订阅价格点,然后一个一个设置。API不支持全部国家一次配置
let allPoints = await ascAPI.fetchSubscriptionPricePointsEqualizations(pointId: point.id, territory: nil)
for apoint in allPoints {
let territory = apoint.relationships?.territory?.data?.id ?? ""
// 自定价格的国家跳过
if manualPricesTerritory.contains(territory) {
continue
}
let customerPrice = apoint.attributes?.customerPrice ?? ""
if (await ascAPI.updateSubscriptionPricePoint(iapId: iapId, pricePointId: apoint.id, preserveCurrentPrice: isPreservePrice)) != nil {
// 价格档位配置成功
ascAPI.addMessage("全球均衡价格的订阅商品的价格点:\(territory),\(customerPrice) ,更新价格成功!✅ ")
} else {
// 价格档位配置失败
ascAPI.addMessage("全球均衡价格的订阅商品的价格点:\(territory),\(customerPrice) ,更新价格失败!❌ ")
}
}
} else {
// 找不到价格档位
ascAPI.addMessage("基准国家的订阅商品价格点:\(baseTerritory),\(baseCustomerPrice) ,未找到此档位!❌ ")
}
}
/// 更新订阅商品的本地化信息
func createSubscriptionLocalization(iapId: String, localization: IAPLocalization, product: IAPProduct, ascAPI: APASCAPI) async {
ascAPI.addMessage("开始更新订阅商品本地化版本:\(product.productId),\(localization.locale)")
if (await ascAPI.createSubscriptionLocalization(iapId: iapId, localization: localization)) != nil {
// 本地化语言配置成功
ascAPI.addMessage("订阅商品本地化版本:\(localization.locale) ,更新语言成功!✅ ")
} else {
// 本地化语言配置失败
ascAPI.addMessage("订阅商品本地化版本:\(localization.locale) ,更新语言失败!❌ ")
}
}
/// 更新订阅商品的送审截图
func createSubscriptionScreenshot(iapId: String, product: IAPProduct, ascAPI: APASCAPI) async {
ascAPI.addMessage("开始更新订阅商品的送审截图:\(product.productId),\(product.reviewScreenshot)")
let imgName = product.reviewScreenshot
guard let imgPath = screenshotPaths[imgName] else {
ascAPI.addMessage("订阅商品:\(product.productId) 无送审截图或未上传截图~")
return
}
let imaUrl = URL.init(fileURLWithPath: imgPath)
guard let fileMD5 = URL.init(fileURLWithPath: imgPath).fileMD5() else {
ascAPI.addMessage("订阅商品截图文件错误:\(imgPath) ,无法生成 md5 值~")
return
}
let oldShot = await ascAPI.fetchSubscriptionScreenshot(iapId: iapId)
// 存在需要删除,避免文件名不一样或者过期文件
if let ost = oldShot {
ascAPI.addMessage("删除旧的送审截图:\(ost.attributes?.fileName ?? "")")
let status = await ascAPI.deleteSubscriptionScreenshot(iapShotId: ost.id)
if status != 204 {
ascAPI.addMessage("订阅商品截图创建失败:\(imgName) ,无法删除旧截图~")
}
}
ascAPI.addMessage("创建新的送审截图:\(product.reviewScreenshot)")
// 创建截图
let imaSize = imaUrl.fileSizeInt()
guard let shot = await ascAPI.createSubscriptionScreenshot(iapId: iapId, fileName: imgName, fileSize: imaSize) else {
// 创建失败
ascAPI.addMessage("订阅商品:\(product.productId) ,创建送审截图失败!❌ ")
return
}
// 根据苹果接口返回的上传接口上传
guard let method = shot.attributes?.uploadOperations?.first?.method,
let url = shot.attributes?.uploadOperations?.first?.url,
let requestHeaders = shot.attributes?.uploadOperations?.first?.requestHeaders,
let baseURL = URL(string: url) else {
ascAPI.addMessage("订阅商品:\(product.productId) ,创建送审截图失败!苹果参数异常~ ❌ ")
return
}
var request = URLRequest(url: baseURL)
request.httpMethod = method
for header in requestHeaders {
request.headers[header.name ?? ""] = header.value ?? ""
}
ascAPI.addMessage("上传新的送审截图:\(product.reviewScreenshot)")
// 上传图片
guard let response = try? await URLSession.shared.upload(for: request, fromFile: imaUrl) else {
ascAPI.addMessage("订阅商品:\(product.productId) ,创建送审截图失败!上传图片异常~ ❌ ")
return
}
guard let responseCode = (response.1 as? HTTPURLResponse)?.statusCode, responseCode == 200 else {
ascAPI.addMessage("订阅商品:\(product.productId) ,创建送审截图失败!上传图片异常 \(response.1.description)~ ❌ ")
return
}
ascAPI.addMessage("提交新的送审截图:\(product.reviewScreenshot)")
// 确认图片
if ((await ascAPI.updateSubscriptionScreenshot(iapShotId: shot.id, fileMD5: fileMD5)) != nil) {
ascAPI.addMessage("订阅商品:\(product.productId) ,送审截图上传成功!✅ ")
} else {
ascAPI.addMessage("订阅商品:\(product.productId) ,送审截图可能上传失败! ")
}
}
/// 销售国家或地区
func updateSubscriptionAvailableTerritories(iapId: String, product: IAPProduct, ascAPI: APASCAPI) async {
let inAll = product.territories.availableInAllTerritories
let inNew = product.territories.availableInNewTerritories
let summary = territoryInfo(product: product)
let newTerritory = inNew ? "将来新国家(地区)时自动提供!" : "将来新国家(地区)时不自动提供!"
ascAPI.addMessage("开始更新订阅商品的销售国家/地区:\(summary)")
guard !inAll else {
var allTerritories: [[String: String]] = []
if let territories = await ascAPI.territories() {
territories.forEach { territory in
allTerritories.append([
"type": "territories",
"id": territory.id
])
}
// 更新全部国家地区
if (await ascAPI.updateSubscriptionAvailabilityTerritories(iapId: iapId, availableTerritories: allTerritories, availableInNewTerritories: inNew)) != nil {
ascAPI.addMessage("选择:所有国家(地区)销售,\(newTerritory),更新成功!✅ ")
} else {
ascAPI.addMessage("选择:所有国家(地区)销售,\(newTerritory),更新失败!❌ ")
}
} else {
ascAPI.addMessage("选择:所有国家(地区)销售,\(newTerritory),无法设置!获取国家标识码失败!❌ ")
}
return
}
/// 选择销售的国家或地区
var territories: [[String: String]] = []
product.territories.territories?.forEach({ territory in
territories.append([
"type": "territories",
"id": territory.id
])
})
let customerTerritory = product.territories.territories?.map({ $0.id }).joined(separator: ",") ?? "无"
if (await ascAPI.updateSubscriptionAvailabilityTerritories(iapId: iapId, availableTerritories: territories, availableInNewTerritories: inNew)) != nil {
ascAPI.addMessage("订阅商品的销售国家/地区:\(customerTerritory) ,更新成功!✅ ")
} else {
ascAPI.addMessage("订阅商品的销售国家/地区:\(customerTerritory) ,更新失败!❌ ")
}
}
}
// MARK: - Privacy Method
extension APUploadIAPListVC {
func territoryInfo(product: IAPProduct) -> String {
let inAll = product.territories.availableInAllTerritories
let inNew = product.territories.availableInNewTerritories
let customerTerritory = product.territories.territories?.map({ $0.id }).joined(separator: ",") ?? ""
let off = !inAll && !inNew && (product.territories.territories?.isEmpty ?? true)
let territory = off ? "下架" : (customerTerritory.isEmpty ? (inAll ? "全部" : "当前下架") : customerTerritory)
let stringValue = "在所有国家/地区销售:'\(inAll ? "是" : "否")'\n将来新国家/地区自动提供:'\(inNew ? "是" : "否")'\n指定国家/地区销售:\(territory)"
return stringValue
}
}
// MARK: - NSTableViewDelegate
extension APUploadIAPListVC: NSTableViewDelegate, NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
return iaps.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let iap = iaps[row]
switch tableColumn?.identifier.enumValue() {
case ColumnIdetifier.id.cellValue:
let cell = tableView.makeView(withIdentifier: ColumnIdetifier.id.cellValue, owner: self) as? NSTableCellView
cell?.textField?.stringValue = String(row+1)
return cell
case ColumnIdetifier.productID.cellValue:
let cell = tableView.makeView(withIdentifier: ColumnIdetifier.productID.cellValue, owner: self) as? NSTableCellView
cell?.textField?.stringValue = iap.productId
return cell
case ColumnIdetifier.productName.cellValue:
let cell = tableView.makeView(withIdentifier: ColumnIdetifier.productName.cellValue, owner: self) as? NSTableCellView
cell?.textField?.stringValue = iap.name
return cell
case ColumnIdetifier.price.cellValue:
let cell = tableView.makeView(withIdentifier: ColumnIdetifier.price.cellValue, owner: self) as? NSTableCellView
cell?.textField?.stringValue = territoryInfo(product: iap)
return cell
case ColumnIdetifier.level.cellValue:
let cell = tableView.makeView(withIdentifier: ColumnIdetifier.level.cellValue, owner: self) as? NSTableCellView
let territory = iap.priceSchedules?.baseTerritory ?? "-"
let price = iap.priceSchedules?.baseCustomerPrice ?? "-"
let customerPrice = iap.priceSchedules?.manualPrices.map({ pp in
"{'国家:'\(pp.territory)', '自定价格':'\(pp.customerPrice)'}\n"
}).joined() ?? "-"
cell?.textField?.stringValue = "基准国家:'\(territory)'\n基准价格:'\(price)'\n\(customerPrice)"
return cell
case ColumnIdetifier.productPds.cellValue:
let cell = tableView.makeView(withIdentifier: ColumnIdetifier.productPds.cellValue, owner: self) as? NSTableCellView
cell?.textField?.stringValue = iap.reviewNote
return cell
case ColumnIdetifier.state.cellValue:
let cell = tableView.makeView(withIdentifier: ColumnIdetifier.state.cellValue, owner: self) as? NSTableCellView
cell?.textField?.stringValue = iap.inAppPurchaseType.CNValue()
return cell
case ColumnIdetifier.screenshot.cellValue:
let cell = tableView.makeView(withIdentifier: ColumnIdetifier.screenshot.cellValue, owner: self) as? ImageViewCell
let file_name = iap.reviewScreenshot
let imgPath = screenshotPaths[file_name] ?? ""
cell?.imgSel.image = NSImage(contentsOfFile: imgPath)
return cell
case ColumnIdetifier.language.cellValue:
let cell = tableView.makeView(withIdentifier: ColumnIdetifier.language.cellValue, owner: self) as? NSTableCellView
cell?.textField?.stringValue = iap.localizations.map({ lz in
"{'locale:'\(lz.locale)', 'title':'\(lz.name)', 'desc':'\(lz.description)'}\n"
}).joined()
return cell
default:
return nil
}
}
func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
let prices = iaps[row].priceSchedules?.manualPrices.count ?? 0
let count = max(prices + 2, 3)
return count > 10 ? CGFloat(20 * count) : CGFloat(25 * count)
}
}
@@ -0,0 +1,64 @@
//
// DragView.swift
// AppleParty
//
// Created by 易承 on 2020/12/16.
//
import Cocoa
protocol DragViewDelegate {
func dragView(_ path: String?)
}
class DragView: NSView {
var delegate: DragViewDelegate?
private var fileTypeIsOk = false
let NSFilenamesPboardType = NSPasteboard.PasteboardType("NSFilenamesPboardType")
let fileTypes = ["jpg", "jpeg", "png"]
var droppedFilePath: String?
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
// Declare and register an array of accepted types
registerForDraggedTypes([NSPasteboard.PasteboardType(kUTTypeFileURL as String),
NSPasteboard.PasteboardType(kUTTypeItem as String)])
}
override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
fileTypeIsOk = checkExtension(drag: sender)
return []
}
override func draggingUpdated(_ sender: NSDraggingInfo) -> NSDragOperation {
return fileTypeIsOk ? .link : []
}
override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
if let board = sender.draggingPasteboard.propertyList(forType: NSFilenamesPboardType) as? NSArray, let imagePath = board[0] as? String {
// THIS IS WERE YOU GET THE PATH FOR THE DROPPED FILE
droppedFilePath = imagePath
if fileTypeIsOk {
delegate?.dragView(droppedFilePath)
}
return true
}
return false
}
fileprivate func checkExtension(drag: NSDraggingInfo) -> Bool {
if let board = drag.draggingPasteboard.propertyList(forType: NSFilenamesPboardType) as? NSArray, let path = board[0] as? String {
let url = NSURL(fileURLWithPath: path)
if let fileExtension = url.pathExtension?.lowercased() {
return fileTypes.contains(fileExtension)
}
}
return false
}
}
@@ -0,0 +1,105 @@
//
// UploadVC.swift
// AppleParty
//
// Created by 易承 on 2021/6/3.
//
import AppKit
import Foundation
class IAPUploadImageVC: NSViewController {
@IBOutlet weak var tableView: NSTableView!
@IBOutlet weak var cancelBtm: NSButton!
@IBOutlet weak var submitBtm: NSButton!
@IBOutlet weak var tipLb: NSTextField!
var picnames = [String]()
var resultPaths = [String: String]()
typealias CallBackFunc = (_ paths: [String: String]) -> Void
var callBackFunc: CallBackFunc?
fileprivate lazy var fileTypes: [String] = {
return ["jpg", "jpeg", "png"]
}()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tipLb.stringValue = "需要上传\(picnames.count)张图片"
}
@IBAction func clickedBatchUploadBtn(_ sender: Any) {
let openPanel = NSOpenPanel()
openPanel.canChooseFiles = true
openPanel.canChooseDirectories = false
openPanel.allowsMultipleSelection = true
openPanel.allowedFileTypes = fileTypes
openPanel.beginSheetModal(for: self.view.window!) { (modalResponse: NSApplication.ModalResponse) in
if modalResponse == .OK {
openPanel.urls.forEach { url in
let picname = url.lastPathComponent
debugPrint(picname)
if self.picnames.contains(picname) {
debugPrint("contains")
self.resultPaths[picname] = url.path
}
}
self.tableView.reloadData()
}
}
}
@IBAction func cancel(_ sender: Any) {
dismiss(self)
}
@IBAction func submit(_ sender: Any) {
guard picnames.count == resultPaths.keys.count else {
APHUD.hide(message: "必须图片数量不正确!", view: self.view)
return
}
dismiss(self)
if let callBackFunc = callBackFunc {
callBackFunc(resultPaths)
}
}
}
extension IAPUploadImageVC: NSTableViewDelegate, NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
return picnames.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
switch tableColumn?.identifier.enumValue() {
case ColumnIdetifier.picname.cellValue:
let cell = tableView.makeView(withIdentifier: ColumnIdetifier.picname.cellValue, owner: self) as? NSTableCellView
cell?.textField?.stringValue = picnames[row]
return cell
case ColumnIdetifier.upload.cellValue:
let cell = tableView.makeView(withIdentifier: ColumnIdetifier.upload.cellValue, owner: self) as? UploadCell
cell?.row = row
cell?.dragView(resultPaths[picnames[row]])
cell?.callBackFunc = { path,crow in
self.resultPaths[self.picnames[crow]] = path
}
return cell
default:
return nil
}
}
func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
100
}
func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
return false
}
}
@@ -0,0 +1,993 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="23727" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="23727"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Window Controller-->
<scene sceneID="6Md-ET-vaH">
<objects>
<windowController storyboardIdentifier="InputExcelList" id="x3B-Ma-He7" sceneMemberID="viewController">
<window key="window" title="批量内购买项目上传" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="baa-hG-CGu">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<rect key="contentRect" x="425" y="432" width="600" height="400"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
<value key="minSize" type="size" width="600" height="380"/>
<view key="contentView" id="iWX-hs-skU">
<rect key="frame" x="0.0" y="0.0" width="600" height="400"/>
<autoresizingMask key="autoresizingMask"/>
</view>
<connections>
<outlet property="delegate" destination="x3B-Ma-He7" id="e65-q9-nQL"/>
</connections>
</window>
<connections>
<segue destination="HcW-sO-g2Q" kind="relationship" relationship="window.shadowedContentViewController" id="HBc-rj-vrU"/>
</connections>
</windowController>
<customObject id="3MN-ty-i6n" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-950" y="1930"/>
</scene>
<!--批量内购买项目上传-->
<scene sceneID="biu-hN-rXF">
<objects>
<viewController title="批量内购买项目上传" id="HcW-sO-g2Q" customClass="APUploadIAPListVC" customModule="AppleParty" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" misplaced="YES" id="K0H-Tj-XIL">
<rect key="frame" x="0.0" y="0.0" width="816" height="363"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<scrollView autohidesScrollers="YES" horizontalLineScroll="40" horizontalPageScroll="10" verticalLineScroll="40" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="2iO-iU-0Fy">
<rect key="frame" x="0.0" y="100" width="796" height="263"/>
<clipView key="contentView" ambiguous="YES" id="TPK-e2-6v9">
<rect key="frame" x="1" y="1" width="794" height="261"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" ambiguous="YES" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnSelection="YES" multipleSelection="NO" autosaveColumns="NO" rowHeight="40" headerView="5s8-Sc-F8G" viewBased="YES" id="FfT-VW-kGY">
<rect key="frame" x="0.0" y="0.0" width="794" height="236"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<tableViewGridLines key="gridStyleMask" vertical="YES" horizontal="YES"/>
<color key="gridColor" red="0.93333333330000001" green="0.93333333330000001" blue="0.93333333330000001" alpha="1" colorSpace="calibratedRGB"/>
<tableColumns>
<tableColumn identifier="idColumn" width="30" minWidth="30" maxWidth="40" id="vMK-OX-shk">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="id">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="EXT-G7-gaH">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView identifier="idCell" id="ANH-fd-8Ev">
<rect key="frame" x="0.0" y="0.0" width="36" height="40"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="QOn-S3-Fa5">
<rect key="frame" x="0.0" y="12" width="36" height="16"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="1" id="LsW-Zq-hB0">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<connections>
<outlet property="textField" destination="QOn-S3-Fa5" id="adV-BV-fd5"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn identifier="screenshotColumn" width="75" minWidth="40" maxWidth="120" id="B7q-XM-l6y">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="截图">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="OtQ-JF-R0L">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView identifier="screenshotCell" id="aFa-Mz-jnj" customClass="ImageViewCell" customModule="AppleParty" customModuleProvider="target">
<rect key="frame" x="36" y="0.0" width="75" height="40"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="hys-p0-19y">
<rect key="frame" x="0.0" y="0.0" width="75" height="40"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="C9E-lZ-VJ9"/>
</imageView>
</subviews>
<constraints>
<constraint firstItem="hys-p0-19y" firstAttribute="leading" secondItem="aFa-Mz-jnj" secondAttribute="leading" id="5aT-8N-sLh"/>
<constraint firstItem="hys-p0-19y" firstAttribute="top" secondItem="aFa-Mz-jnj" secondAttribute="top" id="PcE-OE-rpA"/>
<constraint firstAttribute="trailing" secondItem="hys-p0-19y" secondAttribute="trailing" id="QaH-hj-2yB"/>
<constraint firstAttribute="bottom" secondItem="hys-p0-19y" secondAttribute="bottom" id="SDW-Qd-ZWN"/>
</constraints>
<connections>
<outlet property="imgSel" destination="hys-p0-19y" id="FJd-2g-vzv"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn identifier="productIDColumn" width="118" minWidth="40" maxWidth="150" id="AaY-pT-reC">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Product ID">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="urO-vI-Sw4">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView identifier="productIDCell" id="3o0-NI-ewo">
<rect key="frame" x="111" y="0.0" width="118" height="40"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="c8C-RK-lNM">
<rect key="frame" x="0.0" y="12" width="118" height="16"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="OzZ-QS-Xrv">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<connections>
<outlet property="textField" destination="c8C-RK-lNM" id="yrw-fd-VbS"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn identifier="stateColumn" width="60" minWidth="40" maxWidth="90" id="K81-yv-X5l">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="类型">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="zey-VL-KI9">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView identifier="stateCell" id="hKB-hg-TrY">
<rect key="frame" x="229" y="0.0" width="60" height="40"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="D1n-4P-b0Q">
<rect key="frame" x="0.0" y="12" width="60" height="16"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="1" id="sq1-BL-Rcf">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<connections>
<outlet property="textField" destination="D1n-4P-b0Q" id="uHy-rg-Ef7"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn identifier="productNameColumn" width="102" minWidth="40" maxWidth="150" id="9em-uk-bzW">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="参考名字">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="mik-0H-hQ1">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView identifier="productNameCell" id="Wp7-Z8-fyG">
<rect key="frame" x="289" y="0.0" width="102" height="40"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="V05-T9-An4">
<rect key="frame" x="0.0" y="12" width="102" height="16"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="Fyk-ea-jSK">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<connections>
<outlet property="textField" destination="V05-T9-An4" id="eHR-Tz-Q5j"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn identifier="priceColumn" width="112" minWidth="40" maxWidth="400" id="dHE-xa-6WE">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="销售范围">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="9cd-hH-o3v">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView identifier="priceCell" id="M23-59-mE6">
<rect key="frame" x="391" y="0.0" width="112" height="40"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LdV-Er-PG6">
<rect key="frame" x="0.0" y="12" width="112" height="16"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<textFieldCell key="cell" lineBreakMode="charWrapping" sendsActionOnEndEditing="YES" title="1" id="k5H-G7-RvE">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<allowedInputSourceLocales>
<string>NSAllRomanInputSourcesLocaleIdentifier</string>
</allowedInputSourceLocales>
</textFieldCell>
</textField>
</subviews>
<connections>
<outlet property="textField" destination="LdV-Er-PG6" id="hKk-WY-If1"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn identifier="levelColumn" width="102" minWidth="40" maxWidth="400" id="RAv-kj-cld">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="价格机制">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="2so-VG-q75">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView identifier="levelCell" id="Pz4-4W-nO5">
<rect key="frame" x="503" y="0.0" width="102" height="40"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="e8p-aX-EOQ">
<rect key="frame" x="0.0" y="12" width="102" height="16"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<textFieldCell key="cell" lineBreakMode="charWrapping" sendsActionOnEndEditing="YES" title="1" id="txV-L7-8pc">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<allowedInputSourceLocales>
<string>NSAllRomanInputSourcesLocaleIdentifier</string>
</allowedInputSourceLocales>
</textFieldCell>
</textField>
</subviews>
<connections>
<outlet property="textField" destination="e8p-aX-EOQ" id="cFO-zR-V7E"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn identifier="languageColumn" width="100" minWidth="40" maxWidth="550" id="cPw-xm-W9c">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="本地化语言">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="S22-0C-i35">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView identifier="languageCell" id="qG2-Ag-GoS">
<rect key="frame" x="605" y="0.0" width="100" height="40"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="4pw-S3-VJ2">
<rect key="frame" x="0.0" y="3" width="100" height="34"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<textFieldCell key="cell" lineBreakMode="charWrapping" sendsActionOnEndEditing="YES" title="1" id="cgN-Ef-H9K">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<allowedInputSourceLocales>
<string>NSAllRomanInputSourcesLocaleIdentifier</string>
</allowedInputSourceLocales>
</textFieldCell>
</textField>
</subviews>
<connections>
<outlet property="textField" destination="4pw-S3-VJ2" id="yZO-no-fs9"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn identifier="productPdsColumn" width="38" minWidth="30" maxWidth="150" id="Lic-uH-erC">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="审核备注(可选)">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="6Ts-zi-iQ8">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView identifier="productPdsCell" id="tSv-x2-iSQ">
<rect key="frame" x="705" y="0.0" width="44" height="40"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="CcF-6d-Y0Y">
<rect key="frame" x="0.0" y="12" width="44" height="16"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" lineBreakMode="charWrapping" sendsActionOnEndEditing="YES" title="1" id="RdH-e2-lmI">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<connections>
<outlet property="textField" destination="CcF-6d-Y0Y" id="epa-re-I4A"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
</tableColumns>
<connections>
<outlet property="dataSource" destination="HcW-sO-g2Q" id="TAQ-cE-1Hb"/>
<outlet property="delegate" destination="HcW-sO-g2Q" id="cV7-Es-JKZ"/>
</connections>
</tableView>
</subviews>
</clipView>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="250" id="88B-Ob-G5p"/>
</constraints>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="KNl-rP-gjC">
<rect key="frame" x="1" y="246" width="794" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="z1V-UM-aHa">
<rect key="frame" x="224" y="17" width="15" height="102"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<tableHeaderView key="headerView" identifier="languageColumn" wantsLayer="YES" id="5s8-Sc-F8G">
<rect key="frame" x="0.0" y="0.0" width="794" height="25"/>
<autoresizingMask key="autoresizingMask"/>
</tableHeaderView>
</scrollView>
<box boxType="custom" cornerRadius="4" translatesAutoresizingMaskIntoConstraints="NO" id="DFI-Sf-hCb">
<rect key="frame" x="0.0" y="0.0" width="796" height="100"/>
<view key="contentView" id="6rW-KE-ixB">
<rect key="frame" x="1" y="1" width="794" height="98"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="yl7-CE-urn">
<rect key="frame" x="340" y="25" width="114" height="47"/>
<buttonCell key="cell" type="push" title="提交" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="ysI-I3-a0g">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
DQ
</string>
</buttonCell>
<color key="contentTintColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="height" constant="35" id="L7q-rE-qUa"/>
<constraint firstAttribute="width" constant="100" id="luq-WV-0zV"/>
</constraints>
<connections>
<action selector="createIAP:" target="HcW-sO-g2Q" id="mZJ-aY-eOM"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Wiy-jc-2Pw">
<rect key="frame" x="700" y="13" width="86" height="32"/>
<buttonCell key="cell" type="push" title="设置密钥" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="AHB-ou-qRW">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<constraints>
<constraint firstAttribute="height" constant="20" id="Sbb-sq-gsR"/>
<constraint firstAttribute="height" constant="20" id="gLt-9i-Kpk"/>
</constraints>
<connections>
<action selector="clickedSPasswordBtn:" target="HcW-sO-g2Q" id="4WZ-cz-seF"/>
</connections>
</button>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gwi-GP-uQf">
<rect key="frame" x="184" y="8" width="426" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="注:请检查数据与表格是否一致,提交成功后,商品id和类型不能再更改!" id="04V-rH-2el">
<font key="font" metaFont="system"/>
<color key="textColor" name="systemOrangeColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="sv7-52-2Cw">
<rect key="frame" x="700" y="45" width="86" height="32"/>
<buttonCell key="cell" type="push" title="上传截图" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="n9B-2Z-TXF">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<constraints>
<constraint firstAttribute="height" constant="20" id="Os2-cR-YSb"/>
</constraints>
<connections>
<action selector="clickedUploadShotBtn:" target="HcW-sO-g2Q" id="pfM-0y-Fwy"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="mWR-IS-Cxn">
<rect key="frame" x="18" y="56" width="194" height="18"/>
<buttonCell key="cell" type="check" title="保留自动续期订阅者现有定价" bezelStyle="regularSquare" imagePosition="left" inset="2" id="rcb-F8-Wjv">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<constraints>
<constraint firstAttribute="height" constant="16" id="jCn-pM-TqY"/>
</constraints>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="O5k-L7-xuw">
<rect key="frame" x="18" y="30" width="200" height="18"/>
<buttonCell key="cell" type="check" title="显示 ASC API 请求速率的阈值" bezelStyle="regularSquare" imagePosition="left" inset="2" id="7jz-Eq-MTQ">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<constraints>
<constraint firstAttribute="height" constant="16" id="UTk-E1-F8x"/>
</constraints>
</button>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="gwi-GP-uQf" secondAttribute="bottom" constant="8" id="446-fY-M9B"/>
<constraint firstAttribute="bottom" secondItem="Wiy-jc-2Pw" secondAttribute="bottom" constant="20" symbolic="YES" id="7js-1K-u7U"/>
<constraint firstItem="O5k-L7-xuw" firstAttribute="top" secondItem="mWR-IS-Cxn" secondAttribute="bottom" constant="10" id="8dK-sn-ZXM"/>
<constraint firstItem="yl7-CE-urn" firstAttribute="centerY" secondItem="6rW-KE-ixB" secondAttribute="centerY" id="ITd-YX-Trm"/>
<constraint firstAttribute="trailing" secondItem="sv7-52-2Cw" secondAttribute="trailing" constant="15" id="QUP-CD-c2a"/>
<constraint firstItem="Wiy-jc-2Pw" firstAttribute="top" secondItem="sv7-52-2Cw" secondAttribute="bottom" constant="12" id="Rp0-lh-D4G"/>
<constraint firstItem="Wiy-jc-2Pw" firstAttribute="trailing" secondItem="sv7-52-2Cw" secondAttribute="trailing" id="a9w-yY-EWd"/>
<constraint firstItem="yl7-CE-urn" firstAttribute="centerX" secondItem="6rW-KE-ixB" secondAttribute="centerX" id="efU-0I-QhV"/>
<constraint firstItem="mWR-IS-Cxn" firstAttribute="leading" secondItem="6rW-KE-ixB" secondAttribute="leading" constant="20" symbolic="YES" id="hjT-ey-2ZV"/>
<constraint firstItem="O5k-L7-xuw" firstAttribute="leading" secondItem="6rW-KE-ixB" secondAttribute="leading" constant="20" symbolic="YES" id="htV-cS-Gxk"/>
<constraint firstItem="gwi-GP-uQf" firstAttribute="centerX" secondItem="6rW-KE-ixB" secondAttribute="centerX" id="o1O-9Y-VOc"/>
<constraint firstItem="mWR-IS-Cxn" firstAttribute="top" secondItem="6rW-KE-ixB" secondAttribute="top" constant="25" id="yx3-wU-dQT"/>
</constraints>
</view>
<constraints>
<constraint firstAttribute="height" constant="100" id="Nfj-Ag-iuW"/>
</constraints>
<color key="borderColor" name="quaternaryLabelColor" catalog="System" colorSpace="catalog"/>
</box>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="2iO-iU-0Fy" secondAttribute="trailing" id="0jj-T0-SZ6"/>
<constraint firstItem="2iO-iU-0Fy" firstAttribute="top" secondItem="K0H-Tj-XIL" secondAttribute="top" id="Dh4-Ma-QQZ"/>
<constraint firstItem="2iO-iU-0Fy" firstAttribute="leading" secondItem="K0H-Tj-XIL" secondAttribute="leading" id="GhX-65-sYx"/>
<constraint firstItem="DFI-Sf-hCb" firstAttribute="top" secondItem="2iO-iU-0Fy" secondAttribute="bottom" id="U03-9h-hLB"/>
<constraint firstAttribute="bottom" secondItem="DFI-Sf-hCb" secondAttribute="bottom" id="X11-LN-u3M"/>
<constraint firstAttribute="trailing" secondItem="DFI-Sf-hCb" secondAttribute="trailing" id="YC4-6m-bim"/>
<constraint firstItem="DFI-Sf-hCb" firstAttribute="leading" secondItem="K0H-Tj-XIL" secondAttribute="leading" id="ieA-uv-pUf"/>
</constraints>
</view>
<connections>
<outlet property="enterBtn" destination="yl7-CE-urn" id="fxo-dC-Eo4"/>
<outlet property="preserveCurrentPriceBtn" destination="mWR-IS-Cxn" id="kC5-n4-ddM"/>
<outlet property="showApiRateLimitLogsBtn" destination="O5k-L7-xuw" id="NkA-Kq-jRr"/>
<outlet property="tableView" destination="FfT-VW-kGY" id="eo0-Hf-anq"/>
</connections>
</viewController>
<customObject id="oPL-uS-kEl" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-175" y="1918.5"/>
</scene>
<!--Upload ImageVC-->
<scene sceneID="l5Q-5j-Ieg">
<objects>
<viewController storyboardIdentifier="IAPUploadVCID" id="ZGW-X2-eY0" customClass="IAPUploadImageVC" customModule="AppleParty" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="XlO-n7-hEA">
<rect key="frame" x="0.0" y="0.0" width="449" height="241"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<box boxType="custom" cornerRadius="4" title="Box" translatesAutoresizingMaskIntoConstraints="NO" id="4UC-ET-dPZ">
<rect key="frame" x="0.0" y="0.0" width="449" height="241"/>
<view key="contentView" id="bXG-f2-WGM">
<rect key="frame" x="1" y="1" width="447" height="239"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rfb-ot-DNY">
<rect key="frame" x="13" y="193" width="105" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="需要传入x张图片" id="oMa-Eg-4Q4">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" red="0.90980392160000001" green="0.4431372549" blue="0.28235294119999998" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="kcS-un-E1x">
<rect key="frame" x="353" y="192" width="86" height="32"/>
<buttonCell key="cell" type="push" title="批量上传" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="W6k-qu-hFp">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<constraints>
<constraint firstAttribute="height" constant="20" id="Nq6-G6-yns"/>
</constraints>
<connections>
<action selector="clickedBatchUploadBtn:" target="ZGW-X2-eY0" id="s3T-H7-xH7"/>
</connections>
</button>
<scrollView autohidesScrollers="YES" horizontalLineScroll="102" horizontalPageScroll="10" verticalLineScroll="102" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="92G-BP-MIt">
<rect key="frame" x="15" y="80" width="417" height="103"/>
<clipView key="contentView" ambiguous="YES" id="KqA-NU-fUc">
<rect key="frame" x="1" y="1" width="415" height="101"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" ambiguous="YES" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" alternatingRowBackgroundColors="YES" columnSelection="YES" multipleSelection="NO" autosaveColumns="NO" rowHeight="100" headerView="9ym-TL-xUJ" viewBased="YES" id="Pkx-YF-hF7">
<rect key="frame" x="0.0" y="0.0" width="415" height="76"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<tableViewGridLines key="gridStyleMask" vertical="YES" horizontal="YES"/>
<color key="gridColor" red="0.9137254901960784" green="0.9137254901960784" blue="0.9137254901960784" alpha="1" colorSpace="calibratedRGB"/>
<tableColumns>
<tableColumn identifier="picnameColumn" width="151" minWidth="40" maxWidth="1000" id="vzw-Wm-X2f">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="图片名称">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="nPA-Qe-y92">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView identifier="picnameCell" id="nHX-Ef-hlo">
<rect key="frame" x="1" y="1" width="156" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="hmR-mk-1ol">
<rect key="frame" x="-2" y="-1" width="160" height="20"/>
<constraints>
<constraint firstAttribute="height" constant="20" id="Seh-ch-rSz"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" alignment="center" title="Table View Cell" id="hoj-H0-NhX">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="hmR-mk-1ol" firstAttribute="leading" secondItem="nHX-Ef-hlo" secondAttribute="leading" id="BBN-E4-pLd"/>
<constraint firstItem="hmR-mk-1ol" firstAttribute="centerX" secondItem="nHX-Ef-hlo" secondAttribute="centerX" id="Otu-Lw-wvf"/>
<constraint firstItem="hmR-mk-1ol" firstAttribute="centerY" secondItem="nHX-Ef-hlo" secondAttribute="centerY" id="fK5-ug-JU3"/>
</constraints>
<connections>
<outlet property="textField" destination="hmR-mk-1ol" id="Ufw-70-c7l"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn identifier="uploadColumn" width="220" minWidth="40" maxWidth="1000" id="E8R-ck-u3Z">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="上传图片">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="b6z-Gv-phs">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView identifier="uploadCell" id="kSW-b0-iBl" customClass="UploadCell" customModule="AppleParty" customModuleProvider="target">
<rect key="frame" x="160" y="1" width="224" height="100"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="fOf-F6-66M">
<rect key="frame" x="0.0" y="0.0" width="224" height="100"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="YKV-PN-c9B"/>
</imageView>
<view id="Ml3-Ia-Ewi">
<rect key="frame" x="86" y="24" width="121" height="52"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<box boxType="custom" cornerRadius="4" title="Box" translatesAutoresizingMaskIntoConstraints="NO" id="y7w-3d-1iH">
<rect key="frame" x="0.0" y="0.0" width="121" height="52"/>
<view key="contentView" id="dTZ-YH-cOp">
<rect key="frame" x="1" y="1" width="119" height="50"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</view>
<color key="borderColor" red="0.97254901959999995" green="0.97254901959999995" blue="0.97254901959999995" alpha="1" colorSpace="calibratedRGB"/>
<color key="fillColor" name="selectedTextBackgroundColor" catalog="System" colorSpace="catalog"/>
</box>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="8nS-Sr-Tez">
<rect key="frame" x="45" y="16.5" width="32.5" height="40"/>
<constraints>
<constraint firstAttribute="width" constant="32" id="8ms-3v-6Vf"/>
<constraint firstAttribute="height" constant="32" id="oxZ-DL-wE0"/>
</constraints>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="NSAddTemplate" id="qFS-6U-ZiY"/>
</imageView>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8Zb-BZ-4UZ">
<rect key="frame" x="20" y="4" width="82" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="拖拽图片上传" id="0LG-sI-2vV">
<font key="font" metaFont="system"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="zSP-IZ-bba" customClass="DragView" customModule="AppleParty" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="121" height="52"/>
</customView>
</subviews>
<constraints>
<constraint firstItem="y7w-3d-1iH" firstAttribute="top" secondItem="Ml3-Ia-Ewi" secondAttribute="top" id="6r8-S0-IOe"/>
<constraint firstItem="8Zb-BZ-4UZ" firstAttribute="top" secondItem="8nS-Sr-Tez" secondAttribute="bottom" id="A3W-gz-1Gn"/>
<constraint firstAttribute="bottom" secondItem="y7w-3d-1iH" secondAttribute="bottom" id="Jpg-KB-D6M"/>
<constraint firstAttribute="trailing" secondItem="zSP-IZ-bba" secondAttribute="trailing" id="MyV-Rc-dU6"/>
<constraint firstAttribute="trailing" secondItem="y7w-3d-1iH" secondAttribute="trailing" id="N7y-lw-ncn"/>
<constraint firstItem="8nS-Sr-Tez" firstAttribute="centerX" secondItem="Ml3-Ia-Ewi" secondAttribute="centerX" id="Uce-u0-e1b"/>
<constraint firstItem="8Zb-BZ-4UZ" firstAttribute="centerX" secondItem="8nS-Sr-Tez" secondAttribute="centerX" id="hEt-M5-oOZ"/>
<constraint firstItem="y7w-3d-1iH" firstAttribute="leading" secondItem="Ml3-Ia-Ewi" secondAttribute="leading" id="uZv-kV-R1v"/>
<constraint firstAttribute="bottom" secondItem="zSP-IZ-bba" secondAttribute="bottom" id="vDY-zD-VN0"/>
<constraint firstItem="zSP-IZ-bba" firstAttribute="leading" secondItem="Ml3-Ia-Ewi" secondAttribute="leading" id="wHv-9r-cPP"/>
<constraint firstItem="zSP-IZ-bba" firstAttribute="top" secondItem="Ml3-Ia-Ewi" secondAttribute="top" id="xDb-7m-ju4"/>
<constraint firstItem="8nS-Sr-Tez" firstAttribute="top" secondItem="Ml3-Ia-Ewi" secondAttribute="top" id="zaV-gB-RH9"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstItem="fOf-F6-66M" firstAttribute="top" secondItem="kSW-b0-iBl" secondAttribute="top" id="7Sz-wg-BVW"/>
<constraint firstAttribute="trailing" secondItem="fOf-F6-66M" secondAttribute="trailing" id="IZf-Mm-zGq"/>
<constraint firstItem="fOf-F6-66M" firstAttribute="leading" secondItem="kSW-b0-iBl" secondAttribute="leading" id="h7c-Qw-WYp"/>
<constraint firstAttribute="bottom" secondItem="fOf-F6-66M" secondAttribute="bottom" id="yaU-zc-Om0"/>
</constraints>
<connections>
<outlet property="dragBox" destination="Ml3-Ia-Ewi" id="QV5-l0-sSy"/>
<outlet property="dragView" destination="zSP-IZ-bba" id="Lmb-dX-AQf"/>
<outlet property="imgSel" destination="fOf-F6-66M" id="cNh-TV-TVQ"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
</tableColumns>
</tableView>
</subviews>
</clipView>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="100" id="ycy-R4-dvl"/>
</constraints>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="uRq-Iv-4qH">
<rect key="frame" x="1" y="383" width="415" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="Icy-PU-IhP">
<rect key="frame" x="224" y="17" width="15" height="102"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<tableHeaderView key="headerView" wantsLayer="YES" id="9ym-TL-xUJ">
<rect key="frame" x="0.0" y="0.0" width="415" height="25"/>
<autoresizingMask key="autoresizingMask"/>
</tableHeaderView>
</scrollView>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="QQe-WG-p4R">
<rect key="frame" x="227" y="13" width="94" height="52"/>
<buttonCell key="cell" type="push" title="提交" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="4u8-R4-iJl">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
DQ
</string>
</buttonCell>
<color key="contentTintColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="width" constant="80" id="5lo-X9-l3L"/>
<constraint firstAttribute="height" constant="40" id="t3W-Aj-MWR"/>
</constraints>
<connections>
<action selector="submit:" target="ZGW-X2-eY0" id="XBS-Cc-kwA"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="4yB-HB-Sko">
<rect key="frame" x="132" y="13" width="94" height="52"/>
<buttonCell key="cell" type="push" title="取消" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="pTB-tq-Xcl">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
Gw
</string>
</buttonCell>
<color key="contentTintColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="width" constant="80" id="Vxa-b9-goA"/>
<constraint firstAttribute="height" constant="40" id="uMw-Ja-95b"/>
</constraints>
<connections>
<action selector="cancel:" target="ZGW-X2-eY0" id="giL-cF-Fnw"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="QQe-WG-p4R" firstAttribute="leading" secondItem="4yB-HB-Sko" secondAttribute="trailing" constant="15" id="0by-Cs-rAn"/>
<constraint firstItem="kcS-un-E1x" firstAttribute="top" secondItem="bXG-f2-WGM" secondAttribute="top" constant="20" symbolic="YES" id="AJN-8G-5DG"/>
<constraint firstAttribute="bottom" secondItem="92G-BP-MIt" secondAttribute="bottom" constant="80" id="Fg8-sp-FRb"/>
<constraint firstItem="4yB-HB-Sko" firstAttribute="centerY" secondItem="QQe-WG-p4R" secondAttribute="centerY" id="JID-Ip-h5y"/>
<constraint firstItem="92G-BP-MIt" firstAttribute="top" secondItem="rfb-ot-DNY" secondAttribute="bottom" constant="10" id="N6h-wM-NNs"/>
<constraint firstItem="QQe-WG-p4R" firstAttribute="centerX" secondItem="bXG-f2-WGM" secondAttribute="centerX" constant="50" id="TD1-kT-Y8m"/>
<constraint firstItem="rfb-ot-DNY" firstAttribute="leading" secondItem="92G-BP-MIt" secondAttribute="leading" id="fZ3-jp-zmA"/>
<constraint firstAttribute="trailing" secondItem="92G-BP-MIt" secondAttribute="trailing" constant="15" id="hN7-OK-yLE"/>
<constraint firstItem="rfb-ot-DNY" firstAttribute="top" secondItem="bXG-f2-WGM" secondAttribute="top" constant="30" id="hWX-u6-nVQ"/>
<constraint firstAttribute="trailing" secondItem="kcS-un-E1x" secondAttribute="trailing" constant="15" id="nPY-XB-593"/>
<constraint firstItem="92G-BP-MIt" firstAttribute="leading" secondItem="bXG-f2-WGM" secondAttribute="leading" constant="15" id="ohu-Ls-Ekd"/>
<constraint firstItem="QQe-WG-p4R" firstAttribute="top" secondItem="92G-BP-MIt" secondAttribute="bottom" constant="20" id="zVx-Ed-CpA"/>
</constraints>
</view>
<color key="borderColor" name="quaternaryLabelColor" catalog="System" colorSpace="catalog"/>
</box>
</subviews>
<constraints>
<constraint firstItem="4UC-ET-dPZ" firstAttribute="leading" secondItem="XlO-n7-hEA" secondAttribute="leading" id="7fN-p4-G0s"/>
<constraint firstItem="4UC-ET-dPZ" firstAttribute="top" secondItem="XlO-n7-hEA" secondAttribute="top" id="RtL-aw-X7P"/>
<constraint firstAttribute="bottom" secondItem="4UC-ET-dPZ" secondAttribute="bottom" id="pkF-XL-mVD"/>
<constraint firstAttribute="trailing" secondItem="4UC-ET-dPZ" secondAttribute="trailing" id="uMi-vb-NaW"/>
</constraints>
</view>
<connections>
<outlet property="cancelBtm" destination="4yB-HB-Sko" id="cPP-RC-on7"/>
<outlet property="submitBtm" destination="QQe-WG-p4R" id="fhA-8S-Xdr"/>
<outlet property="tableView" destination="Pkx-YF-hF7" id="PfU-Tz-mHg"/>
<outlet property="tipLb" destination="rfb-ot-DNY" id="K0Q-YO-BoS"/>
</connections>
</viewController>
<customObject id="s7R-ka-Qj0" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-148" y="2436"/>
</scene>
<!--导出excel-->
<scene sceneID="v0g-L8-OB0">
<objects>
<viewController title="导出excel" storyboardIdentifier="OutputExcelVCID" id="MF2-R2-2hO" customClass="OutputExcelVC" customModule="AppleParty" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="4gc-AE-KbR">
<rect key="frame" x="0.0" y="0.0" width="500" height="600"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<box boxType="custom" cornerRadius="4" translatesAutoresizingMaskIntoConstraints="NO" id="C5N-3t-BHc">
<rect key="frame" x="0.0" y="0.0" width="500" height="600"/>
<view key="contentView" id="vgI-Wv-mhT">
<rect key="frame" x="1" y="1" width="498" height="598"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="FdO-Rj-ruG">
<rect key="frame" x="28" y="553" width="40" height="15"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="商品id" id="QYB-Zw-eHV">
<font key="font" metaFont="cellTitle"/>
<color key="textColor" name="AccentColor"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<scrollView borderType="line" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" translatesAutoresizingMaskIntoConstraints="NO" id="UeB-cG-eE4">
<rect key="frame" x="30" y="150" width="200" height="393"/>
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="IQM-Fb-jKv">
<rect key="frame" x="1" y="1" width="198" height="391"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textView importsGraphics="NO" richText="NO" verticallyResizable="YES" spellingCorrection="YES" smartInsertDelete="YES" id="ceg-vF-fQf">
<rect key="frame" x="0.0" y="0.0" width="198" height="391"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<size key="minSize" width="198" height="391"/>
<size key="maxSize" width="248" height="10000000"/>
<color key="insertionPointColor" name="textColor" catalog="System" colorSpace="catalog"/>
</textView>
</subviews>
</clipView>
<constraints>
<constraint firstAttribute="width" constant="200" id="FOJ-YZ-qvi"/>
</constraints>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="rFr-Dg-h0l">
<rect key="frame" x="-100" y="-100" width="240" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="EPk-ve-LJd">
<rect key="frame" x="183" y="1" width="16" height="391"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="s72-vm-I76">
<rect key="frame" x="237" y="552" width="86" height="15"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="对应的AppleID" id="avW-ms-Mra">
<font key="font" metaFont="cellTitle"/>
<color key="textColor" name="AccentColor"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<scrollView hidden="YES" borderType="line" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" translatesAutoresizingMaskIntoConstraints="NO" id="YhA-gX-xjC">
<rect key="frame" x="239" y="150" width="229" height="392"/>
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="mqr-je-Omp">
<rect key="frame" x="1" y="1" width="227" height="390"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textView importsGraphics="NO" richText="NO" verticallyResizable="YES" spellingCorrection="YES" smartInsertDelete="YES" id="Z0K-AM-ybS">
<rect key="frame" x="0.0" y="0.0" width="227" height="390"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<size key="minSize" width="227" height="390"/>
<size key="maxSize" width="248" height="10000000"/>
<color key="insertionPointColor" name="textColor" catalog="System" colorSpace="catalog"/>
</textView>
</subviews>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="Jg2-AF-jRX">
<rect key="frame" x="-100" y="-100" width="240" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="xVd-kl-KcI">
<rect key="frame" x="212" y="1" width="16" height="390"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="HH6-Lm-CXI">
<rect key="frame" x="380" y="23" width="94" height="52"/>
<buttonCell key="cell" type="push" title="提交" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="wti-uT-BJP">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
DQ
</string>
</buttonCell>
<color key="contentTintColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="width" constant="80" id="7Pu-0Y-cZl"/>
<constraint firstAttribute="height" constant="40" id="Tl3-IB-jTG"/>
</constraints>
<connections>
<action selector="commit:" target="MF2-R2-2hO" id="zUs-9M-dat"/>
</connections>
</button>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="voo-el-aSb">
<rect key="frame" x="195" y="131" width="36" height="15"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="0/100" id="Rvu-t4-mqS">
<font key="font" metaFont="cellTitle"/>
<color key="textColor" red="0.80000000000000004" green="0.80000000000000004" blue="0.80000000000000004" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fJh-Pa-GRs">
<rect key="frame" x="433" y="131" width="36" height="15"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="0/100" id="xGg-Co-KSE">
<font key="font" metaFont="cellTitle"/>
<color key="textColor" red="0.80000000000000004" green="0.80000000000000004" blue="0.80000000000000004" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gHe-JD-W0l">
<rect key="frame" x="285" y="23" width="94" height="52"/>
<buttonCell key="cell" type="push" title="关闭" bezelStyle="rounded" alignment="center" borderStyle="border" inset="2" id="mdF-cu-Xk3">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
Gw
</string>
</buttonCell>
<constraints>
<constraint firstAttribute="width" constant="80" id="DGd-OO-fun"/>
<constraint firstAttribute="height" constant="40" id="aeW-Ej-VAh"/>
</constraints>
<connections>
<action selector="close:" target="MF2-R2-2hO" id="4dP-Wn-RS1"/>
</connections>
</button>
<scrollView borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" hasVerticalScroller="NO" horizontalScrollElasticity="none" verticalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="mqs-KO-hcm">
<rect key="frame" x="31" y="81" width="436" height="40"/>
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="Na5-JS-qH5">
<rect key="frame" x="0.0" y="0.0" width="436" height="40"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textView editable="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" spellingCorrection="YES" smartInsertDelete="YES" id="ccx-Sz-Ndo">
<rect key="frame" x="0.0" y="0.0" width="436" height="40"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="textColor" name="systemRedColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<size key="minSize" width="436" height="40"/>
<size key="maxSize" width="438" height="10000000"/>
<attributedString key="textStorage">
<fragment content="操作说明:将excel表格中商品id列的数据粘贴到输入框,点击提交后自动生成对应的AppleID。(商品不存在时为空行)">
<attributes>
<color key="NSColor" name="systemRedColor" catalog="System" colorSpace="catalog"/>
<font key="NSFont" size="12" name="PingFangSC-Regular"/>
<paragraphStyle key="NSParagraphStyle" alignment="natural" lineBreakMode="wordWrapping" baseWritingDirection="natural" tighteningFactorForTruncation="0.0"/>
</attributes>
</fragment>
</attributedString>
<color key="insertionPointColor" name="textColor" catalog="System" colorSpace="catalog"/>
</textView>
</subviews>
</clipView>
<constraints>
<constraint firstAttribute="height" constant="40" id="3kc-P8-Mm2"/>
</constraints>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="clV-B4-N7M">
<rect key="frame" x="-100" y="-100" width="240" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="tQq-xq-oSv">
<rect key="frame" x="-100" y="-100" width="15" height="40"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
</subviews>
<constraints>
<constraint firstItem="YhA-gX-xjC" firstAttribute="top" secondItem="s72-vm-I76" secondAttribute="bottom" constant="10" id="7Jq-tR-fOe"/>
<constraint firstItem="UeB-cG-eE4" firstAttribute="leading" secondItem="FdO-Rj-ruG" secondAttribute="leading" id="AjD-0r-0jb"/>
<constraint firstItem="YhA-gX-xjC" firstAttribute="leading" secondItem="ceg-vF-fQf" secondAttribute="trailing" constant="10" id="H8P-yH-SL9"/>
<constraint firstItem="UeB-cG-eE4" firstAttribute="top" secondItem="FdO-Rj-ruG" secondAttribute="bottom" constant="10" id="KCv-Ah-SLf"/>
<constraint firstItem="mqs-KO-hcm" firstAttribute="leading" secondItem="ceg-vF-fQf" secondAttribute="leading" id="NHT-nW-xzZ"/>
<constraint firstItem="gHe-JD-W0l" firstAttribute="centerY" secondItem="HH6-Lm-CXI" secondAttribute="centerY" id="RMB-mz-vBd"/>
<constraint firstItem="YhA-gX-xjC" firstAttribute="leading" secondItem="s72-vm-I76" secondAttribute="leading" id="Sgp-QJ-OXe"/>
<constraint firstItem="HH6-Lm-CXI" firstAttribute="top" secondItem="mqs-KO-hcm" secondAttribute="bottom" constant="11" id="Up0-1N-Qrl"/>
<constraint firstAttribute="trailing" secondItem="YhA-gX-xjC" secondAttribute="trailing" constant="30" id="ViH-MG-fzQ"/>
<constraint firstItem="voo-el-aSb" firstAttribute="trailing" secondItem="ceg-vF-fQf" secondAttribute="trailing" id="WWa-Sf-kRh"/>
<constraint firstItem="fJh-Pa-GRs" firstAttribute="trailing" secondItem="xVd-kl-KcI" secondAttribute="trailing" id="Wak-yX-deV"/>
<constraint firstItem="voo-el-aSb" firstAttribute="top" secondItem="EPk-ve-LJd" secondAttribute="bottom" constant="5" id="XPn-up-SMl"/>
<constraint firstAttribute="bottom" secondItem="HH6-Lm-CXI" secondAttribute="bottom" constant="30" id="aen-UO-NPm"/>
<constraint firstItem="FdO-Rj-ruG" firstAttribute="leading" secondItem="vgI-Wv-mhT" secondAttribute="leading" constant="30" id="cH1-sV-aTc"/>
<constraint firstItem="mqs-KO-hcm" firstAttribute="trailing" secondItem="Z0K-AM-ybS" secondAttribute="trailing" id="dkZ-4E-thk"/>
<constraint firstItem="FdO-Rj-ruG" firstAttribute="top" secondItem="vgI-Wv-mhT" secondAttribute="top" constant="30" id="epk-Ii-nZC"/>
<constraint firstItem="HH6-Lm-CXI" firstAttribute="leading" secondItem="gHe-JD-W0l" secondAttribute="trailing" constant="15" id="fZ1-bH-ib2"/>
<constraint firstAttribute="bottom" secondItem="YhA-gX-xjC" secondAttribute="bottom" constant="150" id="q9r-vD-dwI"/>
<constraint firstItem="HH6-Lm-CXI" firstAttribute="trailing" secondItem="fJh-Pa-GRs" secondAttribute="trailing" id="wlF-3S-KCy"/>
<constraint firstItem="YhA-gX-xjC" firstAttribute="top" secondItem="ceg-vF-fQf" secondAttribute="top" id="ymP-Xb-sKJ"/>
<constraint firstItem="fJh-Pa-GRs" firstAttribute="top" secondItem="xVd-kl-KcI" secondAttribute="bottom" constant="5" id="ywL-9o-fuz"/>
<constraint firstAttribute="bottom" secondItem="UeB-cG-eE4" secondAttribute="bottom" constant="150" id="zen-rj-VXk"/>
</constraints>
</view>
<color key="borderColor" name="quaternaryLabelColor" catalog="System" colorSpace="catalog"/>
</box>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="C5N-3t-BHc" secondAttribute="trailing" id="4eZ-yr-AoV"/>
<constraint firstItem="C5N-3t-BHc" firstAttribute="top" secondItem="4gc-AE-KbR" secondAttribute="top" id="7U4-CP-ET9"/>
<constraint firstItem="C5N-3t-BHc" firstAttribute="leading" secondItem="4gc-AE-KbR" secondAttribute="leading" id="bPx-xX-ROK"/>
<constraint firstAttribute="bottom" secondItem="C5N-3t-BHc" secondAttribute="bottom" id="cFe-Zn-SIN"/>
</constraints>
</view>
<connections>
<outlet property="inputCount" destination="voo-el-aSb" id="u2W-KU-hpu"/>
<outlet property="inputText" destination="ceg-vF-fQf" id="Xg7-8E-VkG"/>
<outlet property="outputCount" destination="fJh-Pa-GRs" id="E51-EA-iaR"/>
<outlet property="outputText" destination="Z0K-AM-ybS" id="uJz-Fy-lhw"/>
<outlet property="outputView" destination="YhA-gX-xjC" id="wKl-if-GTQ"/>
</connections>
</viewController>
<customObject id="JEX-pt-NDP" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-887" y="2554"/>
</scene>
</scenes>
<resources>
<image name="NSAddTemplate" width="18" height="17"/>
<namedColor name="AccentColor">
<color red="0.0" green="0.46000000000000002" blue="0.89000000000000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
</resources>
</document>
@@ -0,0 +1,421 @@
//
// IAPExcelParser.swift
// AppleParty
//
// Created by HTC on 2022/11/10.
// Copyright © 2022 37 Mobile Games. All rights reserved.
//
import Foundation
import CoreXLSX
/// 上传的商品类型
enum IAPType: String {
case CONSUMABLE = "CONSUMABLE"
case NON_CONSUMABLE = "NON_CONSUMABLE"
case NON_RENEWING_SUBSCRIPTION = "NON_RENEWING_SUBSCRIPTION"
case AUTO_RENEWABLE = "auto-renewable"
case UNKNOW = "unknown"
static func type(name: String) -> IAPType {
switch name {
case "消耗型":
return .CONSUMABLE
case "非消耗型":
return .NON_CONSUMABLE
case "非续期订阅":
return .NON_RENEWING_SUBSCRIPTION
case "自动续期订阅":
return .AUTO_RENEWABLE
default:
return .UNKNOW
}
}
func CNValue() -> String {
switch self {
case .CONSUMABLE:
return "消耗型"
case .NON_CONSUMABLE:
return "非消耗型"
case .NON_RENEWING_SUBSCRIPTION:
return "非续期订阅"
case .AUTO_RENEWABLE:
return "自动续期订阅"
default:
return "未知"
}
}
}
// 本地化名称和描述
struct IAPLocalization {
var name: String = ""
var description: String = ""
var locale: String = ""
}
// 订阅类型的子字段
struct IAPSubscriptions {
var groupLevel: Int = 1
var subscriptionPeriod: String = "ONE_MONTH" //ONE_WEEK, ONE_MONTH, TWO_MONTHS, THREE_MONTHS, SIX_MONTHS, ONE_YEAR
}
/// 内购统一模型
struct IAPProduct {
var name: String = ""
var groupName: String = ""
var productId: String = ""
var reviewNote: String = ""
var reviewScreenshot: String = ""
var familySharable: Bool = false
var inAppPurchaseType: IAPType = .UNKNOW //# CONSUMABLE、NON_CONSUMABLE、NON_RENEWING_SUBSCRIPTION
var localizations: [IAPLocalization] = []
// 订阅类型的特有
var subscriptions: IAPSubscriptions?
// 价格计划表
var priceSchedules: IAPPriceSchedules?
// 销售的国家或地区
var territories: IAPTerritories = IAPTerritories()
}
/// 价格计划表
struct IAPPriceSchedules {
var productId: String = ""
var baseTerritory: String = ""
var baseCustomerPrice: String = ""
var manualPrices: [IAPPricePoint] = []
}
/// 价格点
struct IAPPricePoint {
var territory: String
var customerPrice: String
}
/// 销售的国家或地区
struct IAPTerritories {
var productId: String = ""
/// 所有国家或地区销售(包括将来新国家或地区)
var availableInAllTerritories: Bool = true
/// 将来新国家/地区自动提供销售
var availableInNewTerritories: Bool = true
var territories: [IAPTerritory]?
}
struct IAPTerritory {
var id: String
}
struct IAPExcelParser {
static func parser(_ filePath: URL) -> [IAPProduct] {
guard let file = XLSXFile(filepath: filePath.path) else {
fatalError("XLSX file at \(filePath.path) is corrupted or does not exist")
}
// 先读取价格计划表
let priceSchedules = parserPricePoints(file)
// 销售的国家或地区
let territories = parserTerritories(file)
var result: [IAPProduct] = []
for wbk in try! file.parseWorkbooks() {
for (name, path) in try! file.parseWorksheetPathsAndNames(workbook: wbk) {
if let worksheetName = name {
print("This worksheet has a name: \(worksheetName)")
}
if name != "AppleParty" {
continue
}
guard let worksheet = try? file.parseWorksheet(at: path),
let sharedStrings = try! file.parseSharedStrings() else {
print("This worksheet/sharedStrings is null")
return result
}
var index = 0
var columnTitles = [String]()
var columnIndexs = [String]()
for row in worksheet.data?.rows ?? [] {
var columnValues = [String: String]()
index += 1
for cell in row.cells {
let key = cell.reference.column.value
var columnStrings = cell.stringValue(sharedStrings) ?? ""
// 富文本读取
var richStr = ""
let richColumnCString = cell.richStringValue(sharedStrings)
for richChar in richColumnCString {
richStr += string(from: richChar.text)
}
columnStrings += richStr
// 第一行作为标识行,用于多语言标识,默认信任此字段
if index == 1 {
columnTitles.append(columnStrings)
columnIndexs.append(key)
} else {
columnValues[key] = columnStrings
}
}
if index == 1 {
print(columnTitles)
print(columnIndexs)
continue
}
// Product ID 参考名字 内购买类型 审核截图(可选) 审核备注(可选) zh-Hans zh-Hans ja ja ko ko
var iap = IAPProduct()
iap.groupName = columnValues["A"] ?? ""
iap.productId = columnValues["B"] ?? ""
iap.name = columnValues["C"] ?? ""
let productType = columnValues["D"] ?? ""
iap.inAppPurchaseType = IAPType.type(name: productType)
// 订阅类型默认的字段
if iap.inAppPurchaseType == .AUTO_RENEWABLE {
let period = columnValues["E"] ?? ""
iap.subscriptions = IAPSubscriptions()
iap.subscriptions?.subscriptionPeriod = period
}
iap.reviewScreenshot = columnValues["F"] ?? ""
iap.reviewNote = columnValues["G"] ?? ""
// 非法的行
if iap.productId.isEmpty, iap.name.isEmpty {
continue
}
// 【产品 ID】 可以由字母、数字、下划线(_)和句点(.)构成。 2 ~ 100 个字符)
if iap.productId.count < 2 || iap.productId.count > 100 {
NSAlert.show("Product ID 长度为:2~100 字符!")
}
// 参考名字
if iap.name.count < 2 || iap.name.count > 64 {
NSAlert.show("\(iap.productId):“参考名字”长度超过 2~64 字符!")
}
// 价格计划表
let schedules = priceSchedules.filter({ $0.productId == iap.productId })
if let schedule = schedules.first {
iap.priceSchedules = schedule
}
//销售国家和地区
let territorys = territories.filter({ $0.productId == iap.productId })
if let territory = territorys.first {
iap.territories = territory
}
// 商品本地化名称和描述
var localizations: [IAPLocalization] = []
// 本地化的标识,从下标7开始,奇数遍历,成对出现的
let columeMax = columnIndexs.count
let columeEndIndex = columnIndexs.count - 1
for idx in stride(from: 7, to: columeEndIndex, by: 2){
if idx + 1 <= columeMax {
let locale = columnTitles[idx]
let key1 = columnIndexs[idx]
let key2 = columnIndexs[idx+1]
let name = columnValues[key1] ?? ""
let description = columnValues[key2] ?? ""
if !name.isEmpty && !description.isEmpty {
var localization = IAPLocalization()
localization.locale = locale
localization.name = name
localization.description = description
localizations.append(localization)
}
}
}
iap.localizations = localizations
result.append(iap)
}
}
}
return result
}
/// 公共方法
fileprivate static func handleRowContents(_ row: Row, _ sharedStrings: SharedStrings, _ index: Int, _ columnIndexs: inout [String], _ columnValues: inout [String : String]) {
for cell in row.cells {
let key = cell.reference.column.value
var columnStrings = cell.stringValue(sharedStrings) ?? ""
// 富文本读取
var richStr = ""
let richColumnCString = cell.richStringValue(sharedStrings)
for richChar in richColumnCString {
richStr += string(from: richChar.text)
}
columnStrings += richStr
// 第一行作为标识行,用于多语言标识,默认信任此字段
if index == 1 {
columnIndexs.append(key)
} else {
columnValues[key] = columnStrings
}
}
}
/// 读取价格计划表
static func parserPricePoints(_ file: XLSXFile) -> [IAPPriceSchedules] {
var result: [IAPPriceSchedules] = []
for wbk in try! file.parseWorkbooks() {
for (name, path) in try! file.parseWorksheetPathsAndNames(workbook: wbk) {
if let worksheetName = name {
print("This worksheet has a name: \(worksheetName)")
}
if name != "PricePoints" {
continue
}
guard let worksheet = try? file.parseWorksheet(at: path),
let sharedStrings = try! file.parseSharedStrings() else {
print("This worksheet/sharedStrings is null")
return result
}
var index = 0
var columnIndexs = [String]()
for row in worksheet.data?.rows ?? [] {
var columnValues = [String: String]()
index += 1
handleRowContents(row, sharedStrings, index, &columnIndexs, &columnValues)
// 第一行是标题行,忽视
if index == 1 {
print(columnIndexs)
continue
}
// Product ID 基准国家(代码) 基准国价格 自定价格国家1 自定价格1 自定价格国家2 自定价格2
let productId = columnValues["A"] ?? ""
let baseTerritory = columnValues["B"] ?? ""
let baseCustomerPrice = (columnValues["C"] ?? "").twoDecimalPrice()
// 非法的行
if productId.isEmpty, baseTerritory.isEmpty, baseCustomerPrice.isEmpty {
continue
}
// 【产品 ID】 可以由字母、数字、下划线(_)和句点(.)构成。 2 ~ 100 个字符)
if productId.count < 2 || productId.count > 100 {
NSAlert.show("PricePoints Product ID 长度为:2~100 字符!")
}
// 价格计划表
var schedule = IAPPriceSchedules(productId: productId, baseTerritory: baseTerritory, baseCustomerPrice: baseCustomerPrice)
// 自定价格的国家和价格
var manualPrices: [IAPPricePoint] = []
// 自定价格,从下标3开始,奇数遍历,成对出现的
let columeMax = columnValues.count
let columeEndIndex = columnValues.count - 1
for idx in stride(from: 3, to: columeEndIndex, by: 2) {
if idx + 1 <= columeMax, columnIndexs.count > idx+1 {
let key1 = columnIndexs[idx]
let key2 = columnIndexs[idx+1]
let name = columnValues[key1] ?? ""
let price = (columnValues[key2] ?? "").twoDecimalPrice()
if !name.isEmpty && !price.isEmpty {
let pricePoint = IAPPricePoint(territory: name, customerPrice: price)
manualPrices.append(pricePoint)
}
}
}
schedule.manualPrices = manualPrices
result.append(schedule)
}
}
}
return result
}
/// 读取销售的国家或地区
static func parserTerritories(_ file: XLSXFile) -> [IAPTerritories] {
var result: [IAPTerritories] = []
for wbk in try! file.parseWorkbooks() {
for (name, path) in try! file.parseWorksheetPathsAndNames(workbook: wbk) {
if let worksheetName = name {
print("This worksheet has a name: \(worksheetName)")
}
if name != "Territories" {
continue
}
guard let worksheet = try? file.parseWorksheet(at: path),
let sharedStrings = try! file.parseSharedStrings() else {
print("This worksheet/sharedStrings is null")
return result
}
var index = 0
var columnIndexs = [String]()
for row in worksheet.data?.rows ?? [] {
var columnValues = [String: String]()
index += 1
handleRowContents(row, sharedStrings, index, &columnIndexs, &columnValues)
// 第一行是标题行,忽视
if index == 1 {
print(columnIndexs)
continue
}
// Product ID 在所有国家/地区销售(1是,0否) 将来新国家/地区自动提供(1是,0否) 销售1 销售2 ...
let productId = columnValues["A"] ?? ""
let availableInAllTerritories = columnValues["B"] ?? ""
let availableInNewTerritories = columnValues["C"] ?? ""
let isInAll = availableInAllTerritories == "1" ? true : false
let isInNew = availableInNewTerritories == "1" ? true : false
// 非法的行
if productId.isEmpty {
continue
}
// 【产品 ID】 可以由字母、数字、下划线(_)和句点(.)构成。 2 ~ 100 个字符)
if productId.count < 2 || productId.count > 100 {
NSAlert.show("Territories Product ID 长度为:2~100 字符!")
}
// 销售的国家或地区
var territory = IAPTerritories(productId: productId, availableInAllTerritories: isInAll, availableInNewTerritories: isInNew)
// 如果在所有国家或地区销售,则不在读取自定销售国家或地区
if isInAll {
territory.availableInNewTerritories = true
result.append(territory)
continue
}
// 自定销售的国家或地区
var territories: [IAPTerritory] = []
// 从下标3开始
let columeMax = columnValues.count
for idx in stride(from: 3, to: columeMax, by: 1) {
let key = columnIndexs[idx]
let name = columnValues[key] ?? ""
if !name.isEmpty {
let pricePoint = IAPTerritory(id: name)
territories.append(pricePoint)
}
}
territory.territories = territories
result.append(territory)
}
}
}
return result
}
}
@@ -0,0 +1,341 @@
//
// ITCResponseModel.swift
// AppleParty
//
// Created by 易承 on 2021/5/13.
//
// HTTP请求返回的数据解析类
import Foundation
import Cocoa
/* 消耗型项目consumable
非消耗型non-consumable
自动续期订阅auto-renewable
非续期订阅subscription
*/
// 上传的商品类型
enum InAppPurchaseType: String {
case CONSUMABLE = "consumable"
case NON_CONSUMABLE = "non-consumable"
case AUTO_RENEWABLE = "auto-renewable"
case FREE_SUBSCRIPTION = "free-subscription"
case SUBSCRIPTION = "subscription"
case UNKNOW = "unknown"
func CNValue() -> String {
switch self {
case .CONSUMABLE:
return "消耗型"
case .NON_CONSUMABLE:
return "非消耗型"
case .SUBSCRIPTION:
return "非续订型"
case .AUTO_RENEWABLE:
return "自动续订型"
case .FREE_SUBSCRIPTION:
return "免费订阅型"
default:
return "未知"
}
}
}
/*
"ITC.addons.type.consumable": "消耗型项目",
"ITC.addons.type.freeSubscription": "免费订阅",
"ITC.addons.type.nonConsumable": "非消耗型项目",
"ITC.addons.type.recurring": "自动续期订阅",
"ITC.addons.type.subscription": "非续期订阅",
*/
// 苹果后台商品类型
enum ITCAddOnType: String {
case consumable = "ITC.addons.type.consumable"
case subscription = "ITC.addons.type.subscription"
case free_subscription = "ITC.addons.type.freeSubscription"
case non_consumable = "ITC.addons.type.nonConsumable"
case auto_renewable = "ITC.addons.type.recurring"
case unknown
func CNValue() -> String {
switch self {
case .consumable:
return "消耗型项目"
case .non_consumable:
return "非消耗型项目"
case .subscription:
return "非续期订阅"
case .auto_renewable:
return "自动续期订阅"
case .free_subscription:
return "免费订阅"
default:
return "未知"
}
}
}
/*
"approved": "已批准",
"created": "已创建",
"deleted": "已删除",
"deletePending": "正在删除",
"developerActionNeeded": "需要开发人员操作",
"developerRemovedFromSale": "被开发人员下架",
"developerSignedOff": "开发人员签名",
"inReview": "正在审核",
"missingMetadata": "元数据丢失",
"pendingBinaryApproval": "正在审核",
"pendingDeveloperRelease": "等待开发人员发布",
"pendingScreenshot": "正在等待屏幕快照",
"prepareForSubmission": "准备提交",
"processingContentUpload": "正在处理",
"readyForSale": "已批准",
"readyToSubmit": "准备提交",
"rejected": "被拒绝",
"removedFromSale": "被下架",
"replaced": "被替换",
"waitingForContentUpload": "正在等待上传",
"waitingForReview": "正在等待审核",
*/
// iap状态
enum InAppPurchaseState: String {
case readyForSale
case missingMetadata
case developerActionNeeded
case developerRemovedFromSale
case readyToSubmit
case prepareForSubmission
case waitingForReview
case waitingForContentUpload
case inReview
case pendingBinaryApproval
case pendingDeveloperRelease
case rejected
case removedFromSale
case approved
case created
case deleted
case deletePending
case developerSignedOff
case pendingScreenshot
case processingContentUpload
case replaced
case unknown
var statusValue: (String, NSColor) {
switch self {
case .readyForSale:
return ("可供销售", NSColor(calibratedRed: 0.23, green: 0.64, blue: 0.40, alpha: 1.00))
case .missingMetadata:
return ("元数据丢失", NSColor(calibratedRed: 0.97, green: 0.50, blue: 0.19, alpha: 1.00))
case .developerActionNeeded:
return ("需要开发人员操作", NSColor(calibratedRed: 0.95, green: 0.00, blue: 0.13, alpha: 1.00))
case .developerRemovedFromSale:
return ("被开发人员下架", NSColor(calibratedRed: 0.95, green: 0.00, blue: 0.13, alpha: 1.00))
case .readyToSubmit, .prepareForSubmission:
return ("准备提交", NSColor(calibratedRed: 0.23, green: 0.64, blue: 0.40, alpha: 1.00))
case .waitingForReview:
return ("正在等待审核", NSColor(calibratedRed: 0.900, green:0.658, blue:0.625, alpha:1.000))
case .waitingForContentUpload:
return ("正在等待上传", NSColor(calibratedRed: 0.23, green: 0.64, blue: 0.40, alpha: 1.00))
case .inReview, .pendingBinaryApproval:
return ("正在审核", NSColor(calibratedRed: 0.999, green:0.775, blue:0.031, alpha:1.000))
case .pendingDeveloperRelease:
return ("等待开发人员发布", NSColor(calibratedRed: 0.23, green: 0.64, blue: 0.40, alpha: 1.00))
case .rejected:
return ("被拒绝", NSColor(calibratedRed:0.548, green:0.145, blue:0.781, alpha:1.000))
case .removedFromSale:
return ("被下架", NSColor(calibratedRed:0.906, green:0.148, blue:0.155, alpha:1.000))
default:
return (self.rawValue, NSColor.secondaryLabelColor)
}
}
}
// MARK: - 内购列表-新
struct Product {
var type: String = ""
var id: String = ""
var referenceName: String = ""
var productId: String = ""
var inAppPurchaseType: InAppPurchaseType = .UNKNOW
var state: InAppPurchaseState = .unknown
}
struct ProductList {
var products: [Product]
init(body: [String: Any]) {
products = [Product]()
let data = dictionaryArray(body["data"])
for temp in data {
var product = Product()
product.type = string(from: temp["type"])
product.id = string(from: temp["id"])
let attributes = dictionary(temp["attributes"])
product.referenceName = string(from: attributes["referenceName"])
product.productId = string(from: attributes["productId"])
product.inAppPurchaseType = InAppPurchaseType(rawValue: string(from: attributes["inAppPurchaseType"])) ?? .UNKNOW
product.state = InAppPurchaseState(rawValue: string(from: attributes["state"])) ?? .unknown
products.append(product)
}
}
}
// MARK: - 内购列表-旧
struct IAPList {
struct IAP {
struct Version {
var screenshotUrl: String = ""
var itunesConnectStatus: String = ""
var issuesCount: Int = 0
var canSubmit: Bool = false
init(dict: [String: Any]) {
screenshotUrl = string(from: dict["screenshotUrl"])
itunesConnectStatus = string(from: dict["itunesConnectStatus"])
issuesCount = int(from: dict["issuesCount"]) ?? 0
canSubmit = bool(from: dict["canSubmit"])
}
}
var familyReferenceName: String = ""
var durationDays: Int = 0
var numberOfCodes: Int = 0
var maximumNumberOfCodes: Int = 0
var appMaximumNumberOfCodes: Int = 0
var isEditable: Bool = false
var isRequired: Bool = false
var canDeleteAddOn: Bool = false
var errorKeys: String = ""
var itcsubmitNextVersion: Bool = false
var isEmptyValue: Bool = false
var adamId: String = "" // appleid
var referenceName: String = "" // 商品名称
var vendorId: String = "" // 商品id
var addOnType: ITCAddOnType = .unknown // 商品类型
var versions: [Version] = []
var purpleSoftwareAdamIds: [String] = []
var lastModifiedDate: String = ""
var isNewsSubscription: Bool = false
var iTunesConnectStatus: InAppPurchaseState = .unknown
// detail
var status: String = "" // 状态
var familySharable: Bool = false // 家庭共享
var availableInAllTerritories: Bool = true // 可供销售
var reviewNote: String = "" // 截图
var reviewScreenshot: String = "" // 截图
var localizations: [IAPLocalization] = [] // 本地化描述
mutating func updateDetail(body: [String: Any]) {
let data = dictionary(body["data"])
status = string(from: dictionary(dictionary(data)["attributes"])["state"])
reviewNote = string(from: dictionary(dictionary(data)["attributes"])["reviewNote"])
familySharable = bool(from: dictionary(dictionary(data)["attributes"])["familySharable"])
let included = dictionaryArray(body["included"])
for include in included {
guard let type = include["type"] as? String else {
return
}
if type == "inAppPurchaseLocalizations" {
let attr = dictionary(include["attributes"])
let name = string(from: attr["name"])
let locale = string(from: attr["locale"])
let description = string(from: attr["description"])
localizations.append(IAPLocalization(name: name, description: description, locale: locale))
}
if type == "inAppPurchaseAppStoreReviewScreenshots" {
let attr = dictionary(include["attributes"])
let asset = dictionary(attr["imageAsset"])
let width = string(from: asset["width"])
let height = string(from: asset["height"])
let templateUrl = string(from: asset["templateUrl"])
// {w}x{h}bb.{f}
let reviewScreenshot = templateUrl
.replacingOccurrences(of: "{w}", with: width)
.replacingOccurrences(of: "{h}", with: height)
.replacingOccurrences(of: "{f}", with: "png")
self.reviewScreenshot = reviewScreenshot
}
}
}
// price
var priceTier: String = "" // 价格等级
mutating func updatePrices(body: [String: Any]) {
let included = dictionaryArray(body["included"])
for include in included {
let attr = dictionary(include["attributes"])
self.priceTier = string(from: attr["priceTier"])
}
}
/// 本地属性
var isSelected = false // 是否标记为批量选中
var curid = 0 // 数组中的序号
var app: App
init(app: App) {
self.app = app
}
}
var iapList: [IAP]
var app: App
init(body: [String: Any], app: App) {
iapList = [IAP]()
let data = dictionaryArray(body["data"])
for i in 0..<data.count {
let temp = data[i]
var iap = IAP(app: app)
iap.curid = i+1
iap.familyReferenceName = string(from: temp["familyReferenceName"])
iap.durationDays = int(from: temp["durationDays"]) ?? 0
iap.numberOfCodes = int(from: temp["numberOfCodes"]) ?? 0
iap.maximumNumberOfCodes = int(from: temp["maximumNumberOfCodes"]) ?? 0
iap.appMaximumNumberOfCodes = int(from: temp["appMaximumNumberOfCodes"]) ?? 0
iap.isEditable = bool(from: temp["isEditable"])
iap.isRequired = bool(from: temp["isRequired"])
iap.canDeleteAddOn = bool(from: temp["canDeleteAddOn"])
iap.errorKeys = string(from: temp["errorKeys"])
iap.itcsubmitNextVersion = bool(from: temp["itcsubmitNextVersion"])
iap.isEmptyValue = bool(from: temp["isEmptyValue"])
iap.adamId = string(from: temp["adamId"])
iap.referenceName = string(from: temp["referenceName"])
iap.vendorId = string(from: temp["vendorId"])
iap.addOnType = ITCAddOnType(rawValue: string(from: temp["addOnType"])) ?? .unknown
let verArr = dictionaryArray(temp["versions"])
for ver in verArr {
let newVer = IAP.Version(dict: ver)
iap.versions.append(newVer)
}
iap.purpleSoftwareAdamIds = stringArray(temp["purpleSoftwareAdamIds"])
iap.lastModifiedDate = string(from: temp["lastModifiedDate"])
iap.isNewsSubscription = bool(from: temp["isNewsSubscription"])
iap.iTunesConnectStatus = InAppPurchaseState(rawValue: string(from: temp["iTunesConnectStatus"])) ?? .unknown
iapList.append(iap)
}
// 简单排序
iapList = iapList.sorted(by: { (iap1, iap2) -> Bool in
let nonDigits = CharacterSet.decimalDigits.inverted
let numStr1 = iap1.vendorId.trimmingCharacters(in: nonDigits)
let numStr2 = iap2.vendorId.trimmingCharacters(in: nonDigits)
return int(from: numStr1) ?? 0 < int(from: numStr2) ?? 0
})
self.app = app
}
}
@@ -0,0 +1,364 @@
//
// XMLModel.swift
// AppleParty
//
// Created by 易承 on 2021/5/26.
//
import Foundation
import SWXMLHash
struct In_App_Purchase {
var product_id = "" // 商品id
var reference_name = "" // 商品名称
var type: InAppPurchaseType = .UNKNOW // 商品类型
var wholesale_price_tier = 0 // 价格等级
var title = "" // 本地化title
var description = "" // 本地化描述
var file_name = "" // 截图文件名
var size = "" // 截图大小
var checksum = "" // 截图md5
var review_notes = "" // 商品描述
var lang = "zh-Hans" // 本地化语言
var inputPrice = "" // 表格的价格
}
struct Screen_Shot {
var file_name = "" // 文件名
var size = "" // 大小
var checksum = "" // md5
var position = "0" // 位置
var preview_time = "00:00:05:00" //设置默认视频时间节点
}
struct XMLModel {
var provider = UserCenter.shared.developerTeamId // 公司名称
var team_id = UserCenter.shared.developerTeamId // 开发者teamid
var vendor_id = "" // sku 套装id
// 内购品项
var iaps: [In_App_Purchase] = []
// 商店截图
var app_locale = "zh-Hans" // 本地化语言
var app_platform = "ios" // ios 或 osx
var app_version = "" //当前版本
var app_title = "" //商店应用名
var shots: [String: [Screen_Shot]] = [:]
var videos: [String: [Screen_Shot]] = [:]
// 上传ipa文件
var apple_id = "" // apple id
var archive_type = "bundle" //上传文件类型
var ipa_name = "ipa.ipa" //默认包名
var ipa_size = ""
var ipa_md5 = ""
// 需要复制的文件 [fileName: fileURL]
var filePaths: [String: String] = [:]
func createIAP(directoryPath: String) {
// 根标签
let root = GDataXMLNode.element(withName: "package")
// package属性
let version = GDataXMLNode.attribute(withName: "version", stringValue: "software5.11") as? GDataXMLNode
let xmlns = GDataXMLNode.attribute(withName: "xmlns", stringValue: "http://apple.com/itunes/importer") as? GDataXMLNode
root?.addAttribute(version)
root?.addAttribute(xmlns)
// provider\team_id
let pro = GDataXMLNode.element(withName: "provider", stringValue: provider)
let tid = GDataXMLNode.element(withName: "team_id", stringValue: team_id)
root?.addChild(pro)
root?.addChild(tid)
// software
// vendor_id
let software = GDataXMLNode.element(withName: "software")
let vid = GDataXMLNode.element(withName: "vendor_id", stringValue: vendor_id)
software?.addChild(vid)
// software_metadata/in_app_purchases
let software_metadata = GDataXMLNode.element(withName: "software_metadata")
let in_app_purchases = GDataXMLNode.element(withName: "in_app_purchases")
// in_app_purchase array
for iap in iaps {
let in_app_purchase = GDataXMLNode.element(withName: "in_app_purchase")
// product_id/reference_name/type
let product_id = GDataXMLNode.element(withName: "product_id", stringValue: iap.product_id)
let reference_name = GDataXMLNode.element(withName: "reference_name", stringValue: iap.reference_name)
let type = GDataXMLNode.element(withName: "type", stringValue: iap.type.rawValue)
// products
let products = GDataXMLNode.element(withName: "products")
let product = GDataXMLNode.element(withName: "product")
let cleared_for_sale = GDataXMLNode.element(withName: "cleared_for_sale", stringValue: "true")
let wholesale_price_tier = GDataXMLNode.element(withName: "wholesale_price_tier", stringValue: String(iap.wholesale_price_tier))
product?.addChild(cleared_for_sale)
product?.addChild(wholesale_price_tier)
products?.addChild(product)
// locales
let locales = GDataXMLNode.element(withName: "locales")
let locale = GDataXMLNode.element(withName: "locale")
let name = GDataXMLNode.attribute(withName: "name", stringValue: iap.lang) as? GDataXMLNode
locale?.addAttribute(name)
let title = GDataXMLNode.element(withName: "title", stringValue: iap.title)
let description = GDataXMLNode.element(withName: "description", stringValue: iap.description)
locale?.addChild(title)
locale?.addChild(description)
locales?.addChild(locale)
// review_screenshot
let review_screenshot = GDataXMLNode.element(withName: "review_screenshot")
let file_name = GDataXMLNode.element(withName: "file_name", stringValue: iap.file_name)
let size = GDataXMLNode.element(withName: "size", stringValue: iap.size)
let checksum = GDataXMLNode.element(withName: "checksum", stringValue: iap.checksum)
let checksum_type = GDataXMLNode.attribute(withName: "type", stringValue: "md5") as? GDataXMLNode
checksum?.addAttribute(checksum_type)
review_screenshot?.addChild(file_name)
review_screenshot?.addChild(size)
review_screenshot?.addChild(checksum)
// review_notes
let review_notes = GDataXMLNode.element(withName: "review_notes", stringValue: iap.review_notes)
// 合并到in_app_purchase
in_app_purchase?.addChild(product_id)
in_app_purchase?.addChild(reference_name)
in_app_purchase?.addChild(type)
in_app_purchase?.addChild(products)
in_app_purchase?.addChild(locales)
in_app_purchase?.addChild(review_screenshot)
in_app_purchase?.addChild(review_notes)
in_app_purchases?.addChild(in_app_purchase)
}
software_metadata?.addChild(in_app_purchases)
software?.addChild(software_metadata)
root?.addChild(software)
// 生成xml文件
let xmlDoc = GDataXMLDocument(rootElement: root)
let data = xmlDoc?.xmlData()
// let xmlString = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
// 保存文件
// 创建文件夹
if !FileManager.default.fileExists(atPath: directoryPath) {
do {
try FileManager.default.createDirectory(atPath: directoryPath, withIntermediateDirectories: true, attributes: nil)
} catch {
print(error.localizedDescription);
}
}
// 创建文件
let filePath = directoryPath + "/metadata.xml"
debugPrint(filePath)
if !FileManager.default.fileExists(atPath: filePath) {
FileManager.default.createFile(atPath: filePath, contents: data, attributes: nil)
}else {
try? FileManager.default.removeItem(atPath: filePath)
FileManager.default.createFile(atPath: filePath, contents: data, attributes: nil)
}
for name in filePaths.keys {
let path = filePaths[name] ?? ""
try? FileManager.default.copyItem(atPath: path, toPath: directoryPath + "/" + name)
}
}
func createShots(directoryPath: String) {
// 根标签
let root = GDataXMLNode.element(withName: "package")
// package属性
let version = GDataXMLNode.attribute(withName: "version", stringValue: "software5.11") as? GDataXMLNode
let xmlns = GDataXMLNode.attribute(withName: "xmlns", stringValue: "http://apple.com/itunes/importer") as? GDataXMLNode
root?.addAttribute(version)
root?.addAttribute(xmlns)
// provider\team_id
let pro = GDataXMLNode.element(withName: "provider", stringValue: provider)
let tid = GDataXMLNode.element(withName: "team_id", stringValue: team_id)
root?.addChild(pro)
root?.addChild(tid)
// software
// vendor_id
let software = GDataXMLNode.element(withName: "software")
let vid = GDataXMLNode.element(withName: "vendor_id", stringValue: vendor_id)
software?.addChild(vid)
// software_metadata/in_app_purchases
let software_metadata = GDataXMLNode.element(withName: "software_metadata")
let app_platform = GDataXMLNode.attribute(withName: "app_platform", stringValue: app_platform) as? GDataXMLNode
software_metadata?.addAttribute(app_platform)
// versions
let s_versions = GDataXMLNode.element(withName: "versions")
let s_version = GDataXMLNode.element(withName: "version")
let app_version = GDataXMLNode.attribute(withName: "string", stringValue: app_version) as? GDataXMLNode
s_version?.addAttribute(app_version)
// locales
let locales = GDataXMLNode.element(withName: "locales")
let locale = GDataXMLNode.element(withName: "locale")
let locale_name = GDataXMLNode.attribute(withName: "name", stringValue: app_locale) as? GDataXMLNode
locale?.addAttribute(locale_name)
let locale_title = GDataXMLNode.element(withName: "title", stringValue: app_title)
locale?.addChild(locale_title)
// app_previews
if videos.filter({ $0.value.count > 0 }).count > 0 {
let app_previews = GDataXMLNode.element(withName: "app_previews")
videos.forEach { (key: String, value: [Screen_Shot]) in
// app_preview
value.forEach { video in
let app_preview = GDataXMLNode.element(withName: "app_preview")
// display_target
let display_target = GDataXMLNode.attribute(withName: "display_target", stringValue: key) as? GDataXMLNode
app_preview?.addAttribute(display_target)
let position = GDataXMLNode.attribute(withName: "position", stringValue: video.position) as? GDataXMLNode
app_preview?.addAttribute(position)
// data_file
let data_file = GDataXMLNode.element(withName: "data_file")
let file_role = GDataXMLNode.attribute(withName: "role", stringValue: "source") as? GDataXMLNode
data_file?.addAttribute(file_role)
let file_size = GDataXMLNode.element(withName: "size", stringValue: video.size)
data_file?.addChild(file_size)
let file_name = GDataXMLNode.element(withName: "file_name", stringValue: video.file_name)
data_file?.addChild(file_name)
let checksum = GDataXMLNode.element(withName: "checksum", stringValue: video.checksum)
data_file?.addChild(checksum)
// data_file
let preview_image_time = GDataXMLNode.element(withName: "preview_image_time", stringValue: video.preview_time)
let preview_format = GDataXMLNode.attribute(withName: "format", stringValue: "30/1:1/nonDrop") as? GDataXMLNode
preview_image_time?.addAttribute(preview_format)
// 最后添加
app_preview?.addChild(data_file)
app_preview?.addChild(preview_image_time)
app_previews?.addChild(app_preview)
}
}
locale?.addChild(app_previews)
}
if shots.filter({ $0.value.count > 0 }).count > 0 {
let app_screenshots = GDataXMLNode.element(withName: "software_screenshots")
shots.forEach { (key: String, value: [Screen_Shot]) in
// app_preview
value.forEach { video in
let app_screenshot = GDataXMLNode.element(withName: "software_screenshot")
// display_target
let display_target = GDataXMLNode.attribute(withName: "display_target", stringValue: key) as? GDataXMLNode
app_screenshot?.addAttribute(display_target)
let position = GDataXMLNode.attribute(withName: "position", stringValue: video.position) as? GDataXMLNode
app_screenshot?.addAttribute(position)
// data_file
let file_size = GDataXMLNode.element(withName: "size", stringValue: video.size)
app_screenshot?.addChild(file_size)
let file_name = GDataXMLNode.element(withName: "file_name", stringValue: video.file_name)
app_screenshot?.addChild(file_name)
let checksum = GDataXMLNode.element(withName: "checksum", stringValue: video.checksum)
app_screenshot?.addChild(checksum)
let checksum_type = GDataXMLNode.attribute(withName: "type", stringValue: "md5") as? GDataXMLNode
checksum?.addAttribute(checksum_type)
// 最后添加
app_screenshots?.addChild(app_screenshot)
}
}
locale?.addChild(app_screenshots)
}
locales?.addChild(locale)
s_version?.addChild(locales)
s_versions?.addChild(s_version)
software_metadata?.addChild(s_versions)
software?.addChild(software_metadata)
root?.addChild(software)
// 生成xml文件
let xmlDoc = GDataXMLDocument(rootElement: root)
let data = xmlDoc?.xmlData()
//let xmlString = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
// 创建文件夹
if !FileManager.default.fileExists(atPath: directoryPath) {
do {
try FileManager.default.createDirectory(atPath: directoryPath, withIntermediateDirectories: true, attributes: nil)
} catch {
print(error.localizedDescription);
}
}
// 创建文件
let filePath = directoryPath + "/metadata.xml"
debugPrint(filePath)
if !FileManager.default.fileExists(atPath: filePath) {
FileManager.default.createFile(atPath: filePath, contents: data, attributes: nil)
}else {
try? FileManager.default.removeItem(atPath: filePath)
FileManager.default.createFile(atPath: filePath, contents: data, attributes: nil)
}
// 文件复制
for name in filePaths.keys {
let path = filePaths[name] ?? ""
try? FileManager.default.copyItem(atPath: path, toPath: directoryPath + "/" + name)
}
}
func createIpaFile(directoryPath: String) {
// 根标签
let root = GDataXMLNode.element(withName: "package")
// package属性
let version = GDataXMLNode.attribute(withName: "version", stringValue: "software5.11") as? GDataXMLNode
let xmlns = GDataXMLNode.attribute(withName: "xmlns", stringValue: "http://apple.com/itunes/importer") as? GDataXMLNode
root?.addAttribute(version)
root?.addAttribute(xmlns)
// software_assets
let software_assets = GDataXMLNode.element(withName: "software_assets")
let apple_id = GDataXMLNode.attribute(withName: "apple_id", stringValue: apple_id) as? GDataXMLNode
let app_platform = GDataXMLNode.attribute(withName: "app_platform", stringValue: app_platform) as? GDataXMLNode
software_assets?.addAttribute(apple_id)
software_assets?.addAttribute(app_platform)
// asset
let asset = GDataXMLNode.element(withName: "asset")
let asset_type = GDataXMLNode.attribute(withName: "type", stringValue: archive_type) as? GDataXMLNode
asset?.addAttribute(asset_type)
//data_file
let data_file = GDataXMLNode.element(withName: "data_file")
let size = GDataXMLNode.element(withName: "size", stringValue: ipa_size)
let file_name = GDataXMLNode.element(withName: "file_name", stringValue: ipa_name)
let checksum = GDataXMLNode.element(withName: "checksum", stringValue: ipa_md5)
let checksum_type = GDataXMLNode.attribute(withName: "type", stringValue: "md5") as? GDataXMLNode
checksum?.addAttribute(checksum_type)
// 逆序添加
data_file?.addChild(size)
data_file?.addChild(file_name)
data_file?.addChild(checksum)
asset?.addChild(data_file)
software_assets?.addChild(asset)
root?.addChild(software_assets)
// 生成xml文件
let xmlDoc = GDataXMLDocument(rootElement: root)
let data = xmlDoc?.xmlData()
let xmlString = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
print(xmlString as Any)
// 创建文件夹
if !FileManager.default.fileExists(atPath: directoryPath) {
do {
try FileManager.default.createDirectory(atPath: directoryPath, withIntermediateDirectories: true, attributes: nil)
} catch {
print(error.localizedDescription);
}
}
// 创建文件
let filePath = directoryPath + "/metadata.xml"
debugPrint(filePath)
if !FileManager.default.fileExists(atPath: filePath) {
FileManager.default.createFile(atPath: filePath, contents: data, attributes: nil)
}else {
try? FileManager.default.removeItem(atPath: filePath)
FileManager.default.createFile(atPath: filePath, contents: data, attributes: nil)
}
// 文件复制
for name in filePaths.keys {
let path = filePaths[name] ?? ""
try? FileManager.default.copyItem(atPath: path, toPath: directoryPath + "/" + name)
}
}
}
@@ -0,0 +1,67 @@
//
// OutputExcelVC.swift
// AppleParty
//
// Created by 易承 on 2020/12/23.
//
import Cocoa
class OutputExcelVC: NSViewController, NSTextViewDelegate {
@IBOutlet var inputText: NSTextView!
@IBOutlet weak var outputView: NSScrollView!
@IBOutlet var outputText: NSTextView!
@IBOutlet weak var inputCount: NSTextField!
@IBOutlet weak var outputCount: NSTextField!
var inputs = [String]()
var outputs = [String]()
var iapList: [IAPList.IAP] = []
override func viewDidLoad() {
super.viewDidLoad()
inputText.delegate = self
}
@IBAction func close(_ sender: Any) {
dismiss(self)
}
@IBAction func commit(_ sender: Any) {
outputs.removeAll()
for i in 0..<inputs.count {
for iap in iapList {
if inputs[i] == iap.vendorId {
outputs.append(iap.adamId)
break
}
}
// 没有找到对应的商品id
if outputs.count != i+1 {
outputs.append("")
}
}
outputView.isHidden = false
outputText.string = outputs.joined(separator: "\n")
}
func textViewDidChangeSelection(_ notification: Notification) {
inputs = inputText.string.components(separatedBy: CharacterSet(["\r", "\n"]))
inputs = inputs.filter { $0.count > 0 }
inputCount.stringValue = String(inputs.count)+"/100"
}
func checkCount() -> Bool {
guard inputs.count == outputs.count else {
inputCount.textColor = NSColor.red
outputCount.textColor = NSColor.red
return false
}
inputCount.textColor = NSColor.lightGray
outputCount.textColor = NSColor.lightGray
return true
}
}
@@ -0,0 +1,18 @@
//
// ScreenShotHelpPopoverVC.swift
// AppleParty
//
// Created by HTC on 2022/2/28.
// Copyright © 2022 37 Mobile Games. All rights reserved.
//
import Cocoa
class ScreenShotHelpPopoverVC: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,526 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Window Controller-->
<scene sceneID="ere-Be-a90">
<objects>
<windowController storyboardIdentifier="ScreenShotUploadVC" id="3W3-GI-a0f" sceneMemberID="viewController">
<window key="window" title="App 预览和屏幕快照" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="BM8-gz-zq1">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<rect key="contentRect" x="425" y="462" width="651" height="582"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
<value key="minSize" type="size" width="651" height="582"/>
<view key="contentView" id="uHV-KU-1f4">
<rect key="frame" x="0.0" y="0.0" width="651" height="582"/>
<autoresizingMask key="autoresizingMask"/>
</view>
<connections>
<outlet property="delegate" destination="3W3-GI-a0f" id="dJN-a3-pmC"/>
</connections>
</window>
<connections>
<segue destination="0Dc-hX-r4A" kind="relationship" relationship="window.shadowedContentViewController" id="gH6-kg-BmL"/>
</connections>
</windowController>
<customObject id="Iam-wp-ls0" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-206" y="-129"/>
</scene>
<!--Screen Shot UploadVC-->
<scene sceneID="sHf-r7-vGK">
<objects>
<viewController id="0Dc-hX-r4A" customClass="ScreenShotUploadVC" customModule="AppleParty" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" identifier="fileName" misplaced="YES" id="85F-uq-hwS">
<rect key="frame" x="0.0" y="0.0" width="878" height="549"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Mxb-rl-K9i">
<rect key="frame" x="18" y="470" width="294" height="27"/>
<constraints>
<constraint firstAttribute="height" constant="27" id="Lv9-ug-a94"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" title="App 预览和屏幕快照批量上传" id="uj6-Ck-fci">
<font key="font" textStyle="title1" name=".SFNS-Regular"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="3MQ-6H-3p4">
<rect key="frame" x="28" y="439" width="72" height="16"/>
<constraints>
<constraint firstAttribute="height" constant="16" id="MSZ-z2-cOa"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" title="App Name:" id="IWG-xW-Lzs">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZlO-v7-4q0">
<rect key="frame" x="28" y="411" width="62" height="16"/>
<constraints>
<constraint firstAttribute="height" constant="16" id="Bss-QQ-gWA"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" title="App SKU:" id="Esn-Cx-dqY">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="20s-B0-gsg">
<rect key="frame" x="28" y="382" width="57" height="17"/>
<constraints>
<constraint firstAttribute="height" constant="17" id="ekU-ib-I7r"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" title="Team ID:" id="28f-6H-Z8f">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="UtW-mq-cag">
<rect key="frame" x="28" y="343" width="82" height="27"/>
<constraints>
<constraint firstAttribute="height" constant="27" id="3eX-Ed-aLC"/>
<constraint firstAttribute="width" constant="78" id="8Sb-z0-LeY"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" title="本地化语言:" id="i7o-yB-2hn">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="KuX-1c-GZT">
<rect key="frame" x="694" y="366" width="142" height="48"/>
<constraints>
<constraint firstAttribute="width" constant="128" id="Rmb-yW-0Aa"/>
<constraint firstAttribute="height" constant="36" id="eTA-Jd-hvB"/>
</constraints>
<buttonCell key="cell" type="push" title="提交上传" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="hrB-5Z-Y0F">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
DQ
</string>
</buttonCell>
<connections>
<action selector="clickedUploadButton:" target="0Dc-hX-r4A" id="u4G-Sv-SxO"/>
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Jvd-lw-qhr" userLabel="Line Label">
<rect key="frame" x="20" y="327" width="829" height="1"/>
<constraints>
<constraint firstAttribute="height" constant="0.5" id="aJu-0a-X2Z"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" borderStyle="bezel" drawsBackground="YES" id="Xa3-zH-NIv">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="tertiaryLabelColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="spK-09-Iv9">
<rect key="frame" x="28" y="251" width="167" height="19"/>
<constraints>
<constraint firstAttribute="height" constant="19" id="tEG-yM-eor"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" title="iPhone 5.5 英寸显示屏" id="wxW-1V-RfC">
<font key="font" metaFont="system" size="16"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dep-lv-PPg">
<rect key="frame" x="206" y="251" width="206" height="16"/>
<constraints>
<constraint firstAttribute="height" constant="16" id="a0I-Ni-9u6"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" title="0/3 个 App 预览 | 0/10 张屏幕快照" id="5fc-UX-tt6">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" red="0.90980392160000001" green="0.4431372549" blue="0.28235294119999998" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<scrollView misplaced="YES" autohidesScrollers="YES" horizontalLineScroll="46" horizontalPageScroll="10" verticalLineScroll="46" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="aVs-J6-lEK">
<rect key="frame" x="26" y="15" width="817" height="228"/>
<clipView key="contentView" id="p4Q-t6-wzE">
<rect key="frame" x="1" y="1" width="815" height="226"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" multipleSelection="NO" autosaveColumns="NO" rowHeight="46" rowSizeStyle="automatic" headerView="Ku2-Vf-JWY" viewBased="YES" id="p1m-uP-OeG">
<rect key="frame" x="0.0" y="0.0" width="815" height="198"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="17" height="0.0"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<tableViewGridLines key="gridStyleMask" horizontal="YES"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns>
<tableColumn identifier="review" width="103" minWidth="10" maxWidth="3.4028234663852886e+38" id="fHy-Ho-wZH">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="预览">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="ha0-0M-Ffe">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="KdO-NE-A5Z">
<rect key="frame" x="8" y="0.0" width="101" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="NAG-NQ-ohy">
<rect key="frame" x="3" y="5" width="95" height="16"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="Aph-Kz-rKF">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="NAG-NQ-ohy" firstAttribute="leading" secondItem="KdO-NE-A5Z" secondAttribute="leading" constant="5" id="Qzd-pY-YNu"/>
<constraint firstAttribute="trailing" secondItem="NAG-NQ-ohy" secondAttribute="trailing" constant="5" id="Z7l-cP-soS"/>
<constraint firstAttribute="bottom" secondItem="NAG-NQ-ohy" secondAttribute="bottom" constant="5" id="mqp-Cr-oTB"/>
</constraints>
<connections>
<outlet property="textField" destination="NAG-NQ-ohy" id="JDc-27-na2"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn identifier="fileName" width="107" minWidth="10" maxWidth="3.4028234663852886e+38" id="XoH-de-TQc">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="文件名">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="PAy-jN-amB">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="8kw-Oh-5eV">
<rect key="frame" x="126" y="0.0" width="107" height="46"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="ZpI-yX-cwI">
<rect key="frame" x="8" y="15" width="91" height="16"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="Nuc-wp-Y8q">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="ZpI-yX-cwI" secondAttribute="bottom" constant="15" id="9CM-5H-EDv"/>
<constraint firstItem="ZpI-yX-cwI" firstAttribute="top" secondItem="8kw-Oh-5eV" secondAttribute="top" constant="15" id="XJl-uJ-BTy"/>
<constraint firstItem="ZpI-yX-cwI" firstAttribute="leading" secondItem="8kw-Oh-5eV" secondAttribute="leading" constant="10" id="og2-26-a3E"/>
<constraint firstAttribute="trailing" secondItem="ZpI-yX-cwI" secondAttribute="trailing" constant="10" id="zHv-Br-7Ec"/>
</constraints>
<connections>
<outlet property="textField" destination="ZpI-yX-cwI" id="y0B-aw-4ha"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn identifier="setting" width="401" minWidth="40" maxWidth="1000" id="3KK-up-tOz">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="设置">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="yaD-XC-Wsc">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="cyo-FF-nHa" customClass="ScreenShotUploadCell" customModule="AppleParty" customModuleProvider="target">
<rect key="frame" x="250" y="0.0" width="401" height="77"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="E01-10-bG2">
<rect key="frame" x="8" y="47" width="97" height="20"/>
<constraints>
<constraint firstAttribute="width" constant="93" id="SSF-7C-Gu5"/>
<constraint firstAttribute="height" constant="20" id="ejh-gc-2QL"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" title="显示排序位置:" id="cfR-We-ve3">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="kZA-xD-IWD">
<rect key="frame" x="8" y="12" width="97" height="20"/>
<constraints>
<constraint firstAttribute="height" constant="20" id="jsB-v9-1VC"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" title="默认海报帧:" id="IYp-rd-BXS">
<font key="font" size="13" name="STSongti-SC-Regular"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" tag="1" translatesAutoresizingMaskIntoConstraints="NO" id="uxr-F9-mJi">
<rect key="frame" x="108" y="46" width="273" height="21"/>
<constraints>
<constraint firstAttribute="height" constant="21" id="5pH-Pj-fbY"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="显示顺序,从1开始排序" drawsBackground="YES" id="L6y-Sf-TMN">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" tag="2" translatesAutoresizingMaskIntoConstraints="NO" id="YcP-ee-RZT">
<rect key="frame" x="108" y="6" width="273" height="21"/>
<constraints>
<constraint firstAttribute="height" constant="21" id="gdz-We-LIn"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" title="00:00" placeholderString="时间格式:SS:mm 秒:毫秒 数值为:0~29间。例如:16:07 、05:29,最高为 30:00。不能高于视频最长时间。" drawsBackground="YES" id="dzm-Es-S5j">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="kZA-xD-IWD" firstAttribute="leading" secondItem="cyo-FF-nHa" secondAttribute="leading" constant="10" id="3B9-4Z-nZw"/>
<constraint firstItem="uxr-F9-mJi" firstAttribute="top" secondItem="E01-10-bG2" secondAttribute="top" id="7f7-M1-GGN"/>
<constraint firstItem="kZA-xD-IWD" firstAttribute="width" secondItem="E01-10-bG2" secondAttribute="width" id="DQC-79-ehS"/>
<constraint firstAttribute="trailing" secondItem="YcP-ee-RZT" secondAttribute="trailing" constant="20" id="EYn-dY-rP9"/>
<constraint firstAttribute="trailing" secondItem="uxr-F9-mJi" secondAttribute="trailing" constant="20" id="Jn6-44-sqT"/>
<constraint firstItem="YcP-ee-RZT" firstAttribute="leading" secondItem="kZA-xD-IWD" secondAttribute="trailing" constant="5" id="JtC-OE-Kwq"/>
<constraint firstItem="E01-10-bG2" firstAttribute="leading" secondItem="cyo-FF-nHa" secondAttribute="leading" constant="10" id="Q03-Qy-7Q0"/>
<constraint firstItem="YcP-ee-RZT" firstAttribute="top" secondItem="kZA-xD-IWD" secondAttribute="top" constant="5" id="ZYC-Gh-ASH"/>
<constraint firstItem="kZA-xD-IWD" firstAttribute="top" secondItem="E01-10-bG2" secondAttribute="bottom" constant="15" id="cFz-HX-QOp"/>
<constraint firstItem="E01-10-bG2" firstAttribute="top" secondItem="cyo-FF-nHa" secondAttribute="top" constant="10" id="fqy-H3-EiP"/>
<constraint firstItem="uxr-F9-mJi" firstAttribute="leading" secondItem="E01-10-bG2" secondAttribute="trailing" constant="5" id="kGR-Bo-qFu"/>
</constraints>
<connections>
<outlet property="cellTopConstraint" destination="fqy-H3-EiP" id="emc-ci-ZXC"/>
<outlet property="sortField" destination="uxr-F9-mJi" id="VMZ-ff-1bN"/>
<outlet property="videoField" destination="YcP-ee-RZT" id="gMs-cb-IYj"/>
<outlet property="videoTitleField" destination="kZA-xD-IWD" id="I0e-oS-eCl"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn identifier="operation" width="107" minWidth="10" maxWidth="3.4028234663852886e+38" id="cMb-Af-EgP">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="操作">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="Jwz-5G-aFH">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="hhC-jQ-V0O" customClass="ScreenShotDeleteCell" customModule="AppleParty" customModuleProvider="target">
<rect key="frame" x="668" y="0.0" width="104" height="46"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="JVG-Tc-6tD">
<rect key="frame" x="8" y="15" width="88" height="16"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" id="bHJ-JZ-aNx">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Bwd-Sl-ANk">
<rect key="frame" x="22" y="13" width="60" height="19"/>
<constraints>
<constraint firstAttribute="width" constant="60" id="HYq-OQ-l58"/>
<constraint firstAttribute="height" constant="18" id="s97-ed-fpd"/>
</constraints>
<buttonCell key="cell" type="roundRect" title="删除" bezelStyle="roundedRect" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="GjW-md-LGa">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="cellTitle"/>
</buttonCell>
<connections>
<action selector="clickedDeleteBtn:" target="hhC-jQ-V0O" id="LT8-Wz-RW8"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="Bwd-Sl-ANk" firstAttribute="centerX" secondItem="JVG-Tc-6tD" secondAttribute="centerX" id="2HE-Jv-Gsk"/>
<constraint firstItem="JVG-Tc-6tD" firstAttribute="leading" secondItem="hhC-jQ-V0O" secondAttribute="leading" constant="10" id="2Yv-Im-yn9"/>
<constraint firstAttribute="bottom" secondItem="JVG-Tc-6tD" secondAttribute="bottom" constant="15" id="Dwk-rZ-aMz"/>
<constraint firstItem="JVG-Tc-6tD" firstAttribute="top" secondItem="hhC-jQ-V0O" secondAttribute="top" constant="15" id="ITt-0w-o5i"/>
<constraint firstItem="Bwd-Sl-ANk" firstAttribute="centerY" secondItem="JVG-Tc-6tD" secondAttribute="centerY" id="M8u-c0-abZ"/>
<constraint firstAttribute="trailing" secondItem="JVG-Tc-6tD" secondAttribute="trailing" constant="10" id="WTf-Uj-Jbw"/>
</constraints>
<connections>
<outlet property="deleteBtn" destination="Bwd-Sl-ANk" id="F67-Zi-wkL"/>
<outlet property="textField" destination="JVG-Tc-6tD" id="rsf-6P-99d"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
</tableColumns>
</tableView>
</subviews>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="Hkn-Y6-aEG">
<rect key="frame" x="1" y="166" width="737" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="NZV-o5-AMa">
<rect key="frame" x="224" y="17" width="15" height="102"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<tableHeaderView key="headerView" wantsLayer="YES" id="Ku2-Vf-JWY">
<rect key="frame" x="0.0" y="0.0" width="815" height="28"/>
<autoresizingMask key="autoresizingMask"/>
</tableHeaderView>
</scrollView>
<button horizontalHuggingPriority="750" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nCH-Zn-5hw">
<rect key="frame" x="816" y="470" width="25" height="25"/>
<constraints>
<constraint firstAttribute="height" constant="20" id="F5P-Yf-BiP"/>
<constraint firstAttribute="width" constant="20" id="YYQ-Ev-d7y"/>
</constraints>
<buttonCell key="cell" type="help" bezelStyle="helpButton" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="jfD-lj-NmL">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="clickedHelp:" target="0Dc-hX-r4A" id="tfT-3q-gD0"/>
</connections>
</button>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ehn-6X-54w">
<rect key="frame" x="115" y="341" width="97" height="30"/>
<constraints>
<constraint firstAttribute="width" constant="90" id="Fno-Uj-HPM"/>
<constraint firstAttribute="height" constant="25" id="a5m-i1-Yfm"/>
</constraints>
<popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" arrowPosition="arrowAtCenter" id="g9M-Bc-Q1m">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="zbe-7T-qiI"/>
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="at4-iG-YQR">
<rect key="frame" x="30" y="227" width="801" height="14"/>
<constraints>
<constraint firstAttribute="height" constant="14" id="cqp-a6-Vxq"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" title="图片:1242 x 2208、2208 x 1242,视频:1080 x 1920、1920 x 1080" id="DWg-8k-ghA">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="tertiaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Nej-FK-Dzi">
<rect key="frame" x="222" y="340" width="109" height="32"/>
<constraints>
<constraint firstAttribute="height" constant="20" id="Gzp-sC-fV6"/>
<constraint firstAttribute="width" constant="95" id="eev-KL-t4w"/>
</constraints>
<buttonCell key="cell" type="push" title="刷新App信息" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="CAs-4h-gHg">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="reloadAppData:" target="0Dc-hX-r4A" id="SCO-to-s2U"/>
</connections>
</button>
<button verticalHuggingPriority="750" tag="100" translatesAutoresizingMaskIntoConstraints="NO" id="PIn-kO-4pl">
<rect key="frame" x="338" y="340" width="114" height="32"/>
<constraints>
<constraint firstAttribute="height" constant="20" id="IrN-eM-S0v"/>
<constraint firstAttribute="width" constant="100" id="hIt-id-j80"/>
</constraints>
<buttonCell key="cell" type="push" title="素材一键导入" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Ziv-5L-rRw">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="uploadFiles:" target="0Dc-hX-r4A" id="Zfc-Qu-PD1"/>
</connections>
</button>
<segmentedControl verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hHc-Te-K7g">
<rect key="frame" x="30" y="284" width="801" height="24"/>
<segmentedCell key="cell" borderStyle="border" alignment="left" style="rounded" trackingMode="selectOne" id="mBw-8Z-Jk1">
<font key="font" metaFont="system"/>
<segments>
<segment label="iPhone 5.5 英寸显示屏" width="197" selected="YES"/>
<segment label="iPhone 6.5 英寸显示屏" width="196" tag="1"/>
<segment label="iPad Pro 12.9 英寸显示屏" width="195" tag="2"/>
</segments>
</segmentedCell>
<connections>
<action selector="changeSegmentedControl:" target="0Dc-hX-r4A" id="Djb-aJ-oLH"/>
</connections>
</segmentedControl>
</subviews>
<constraints>
<constraint firstItem="spK-09-Iv9" firstAttribute="top" secondItem="hHc-Te-K7g" secondAttribute="bottom" constant="15" id="4cX-f4-l1P"/>
<constraint firstItem="ZlO-v7-4q0" firstAttribute="top" secondItem="3MQ-6H-3p4" secondAttribute="bottom" constant="12" id="5Ps-J2-pOj"/>
<constraint firstItem="aVs-J6-lEK" firstAttribute="leading" secondItem="85F-uq-hwS" secondAttribute="leading" constant="26" id="AFt-L6-42t"/>
<constraint firstItem="at4-iG-YQR" firstAttribute="leading" secondItem="85F-uq-hwS" secondAttribute="leading" constant="32" id="BOm-qs-DjO"/>
<constraint firstItem="20s-B0-gsg" firstAttribute="leading" secondItem="85F-uq-hwS" secondAttribute="leading" constant="30" id="Btu-xj-Wg4"/>
<constraint firstItem="ZlO-v7-4q0" firstAttribute="leading" secondItem="85F-uq-hwS" secondAttribute="leading" constant="30" id="C9X-av-LFu"/>
<constraint firstItem="hHc-Te-K7g" firstAttribute="leading" secondItem="85F-uq-hwS" secondAttribute="leading" constant="32" id="CuY-Lx-weE"/>
<constraint firstAttribute="trailing" secondItem="Jvd-lw-qhr" secondAttribute="trailing" constant="20" symbolic="YES" id="EyO-Yp-0aB"/>
<constraint firstItem="ehn-6X-54w" firstAttribute="top" secondItem="UtW-mq-cag" secondAttribute="top" id="Ez2-k9-idp"/>
<constraint firstItem="hHc-Te-K7g" firstAttribute="top" secondItem="Jvd-lw-qhr" secondAttribute="bottom" constant="20" id="FUG-WX-bgc"/>
<constraint firstAttribute="bottom" secondItem="aVs-J6-lEK" secondAttribute="bottom" constant="15" id="FbI-zp-JSu"/>
<constraint firstAttribute="trailing" secondItem="hHc-Te-K7g" secondAttribute="trailing" constant="40" id="Fxj-LZ-Q4z"/>
<constraint firstItem="dep-lv-PPg" firstAttribute="leading" secondItem="spK-09-Iv9" secondAttribute="trailing" constant="15" id="NtE-hZ-KKZ"/>
<constraint firstItem="Nej-FK-Dzi" firstAttribute="leading" secondItem="ehn-6X-54w" secondAttribute="trailing" constant="21" id="P8h-PO-Y8M"/>
<constraint firstItem="3MQ-6H-3p4" firstAttribute="top" secondItem="Mxb-rl-K9i" secondAttribute="bottom" constant="15" id="PRO-Jm-Ssn"/>
<constraint firstItem="20s-B0-gsg" firstAttribute="top" secondItem="ZlO-v7-4q0" secondAttribute="bottom" constant="12" id="PsX-tY-Q46"/>
<constraint firstItem="Mxb-rl-K9i" firstAttribute="top" secondItem="85F-uq-hwS" secondAttribute="top" constant="20" symbolic="YES" id="Rkh-Lj-Ck2"/>
<constraint firstAttribute="trailing" secondItem="KuX-1c-GZT" secondAttribute="trailing" constant="40" id="T5P-Xc-38I"/>
<constraint firstAttribute="trailing" secondItem="aVs-J6-lEK" secondAttribute="trailing" constant="26" id="UAF-fF-Sik"/>
<constraint firstItem="at4-iG-YQR" firstAttribute="top" secondItem="spK-09-Iv9" secondAttribute="bottom" constant="10" id="VAg-k8-pvN"/>
<constraint firstItem="PIn-kO-4pl" firstAttribute="centerY" secondItem="Nej-FK-Dzi" secondAttribute="centerY" id="Vsj-Qj-KvF"/>
<constraint firstItem="aVs-J6-lEK" firstAttribute="top" secondItem="at4-iG-YQR" secondAttribute="bottom" constant="12" id="b8f-1e-2KH"/>
<constraint firstItem="Mxb-rl-K9i" firstAttribute="leading" secondItem="85F-uq-hwS" secondAttribute="leading" constant="20" symbolic="YES" id="cmx-sk-H0h"/>
<constraint firstItem="Nej-FK-Dzi" firstAttribute="centerY" secondItem="ehn-6X-54w" secondAttribute="centerY" id="cye-es-vQJ"/>
<constraint firstItem="dep-lv-PPg" firstAttribute="bottom" secondItem="spK-09-Iv9" secondAttribute="bottom" id="fHM-de-aYs"/>
<constraint firstItem="Jvd-lw-qhr" firstAttribute="top" secondItem="UtW-mq-cag" secondAttribute="bottom" constant="15" id="iEn-ye-6b5"/>
<constraint firstItem="3MQ-6H-3p4" firstAttribute="leading" secondItem="85F-uq-hwS" secondAttribute="leading" constant="30" id="igN-zI-akK"/>
<constraint firstItem="UtW-mq-cag" firstAttribute="top" secondItem="20s-B0-gsg" secondAttribute="bottom" constant="12" id="l7B-Bm-WY9"/>
<constraint firstItem="UtW-mq-cag" firstAttribute="leading" secondItem="85F-uq-hwS" secondAttribute="leading" constant="30" id="pW0-Ng-65T"/>
<constraint firstItem="PIn-kO-4pl" firstAttribute="leading" secondItem="Nej-FK-Dzi" secondAttribute="trailing" constant="21" id="qIZ-Gq-oEG"/>
<constraint firstAttribute="trailing" secondItem="nCH-Zn-5hw" secondAttribute="trailing" constant="30" id="rtJ-UI-jgY"/>
<constraint firstAttribute="trailing" secondItem="at4-iG-YQR" secondAttribute="trailing" constant="40" id="s3i-uE-Ywh"/>
<constraint firstItem="nCH-Zn-5hw" firstAttribute="top" secondItem="85F-uq-hwS" secondAttribute="top" constant="23" id="tLD-px-sAZ"/>
<constraint firstItem="spK-09-Iv9" firstAttribute="leading" secondItem="85F-uq-hwS" secondAttribute="leading" constant="30" id="wLU-yw-vve"/>
<constraint firstItem="ehn-6X-54w" firstAttribute="leading" secondItem="UtW-mq-cag" secondAttribute="trailing" constant="10" id="wkp-Bu-SXk"/>
<constraint firstItem="Jvd-lw-qhr" firstAttribute="leading" secondItem="85F-uq-hwS" secondAttribute="leading" constant="20" symbolic="YES" id="ydi-DL-qcW"/>
<constraint firstItem="KuX-1c-GZT" firstAttribute="bottom" secondItem="UtW-mq-cag" secondAttribute="bottom" constant="-30" id="zjy-Sp-N01"/>
</constraints>
</view>
<connections>
<outlet property="appName" destination="3MQ-6H-3p4" id="Wbh-6s-F6V"/>
<outlet property="appSKU" destination="ZlO-v7-4q0" id="Jzc-ud-uAe"/>
<outlet property="appleID" destination="20s-B0-gsg" id="f9M-XH-Bu0"/>
<outlet property="localButton" destination="ehn-6X-54w" id="rmS-y1-F1h"/>
<outlet property="tableView" destination="p1m-uP-OeG" id="xmk-vt-0eI"/>
<outlet property="tips_count" destination="dep-lv-PPg" id="mR6-By-bUY"/>
<outlet property="tips_desc" destination="at4-iG-YQR" id="C2L-ly-Vhd"/>
<outlet property="tips_title" destination="spK-09-Iv9" id="h4X-0x-ewy"/>
</connections>
</viewController>
<customObject id="Ih9-u3-myc" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="714" y="-140.5"/>
</scene>
</scenes>
</document>
@@ -0,0 +1,74 @@
//
// ScreenShotUploadCell.swift
// AppleParty
//
// Created by HTC on 2022/2/25.
// Copyright © 2022 37 Mobile Games. All rights reserved.
//
import Cocoa
import Foundation
class ScreenShotDeleteCell: NSTableCellView {
typealias CallFunc = (_ row: Int) -> Void
var deleteCell: CallFunc?
var row: Int = 0
@IBOutlet weak var deleteBtn: NSButton!
@IBAction func clickedDeleteBtn(_ sender: NSButton) {
if let callBack = deleteCell {
callBack(row)
}
}
}
class ScreenShotUploadCell: NSTableCellView {
typealias CallBackHandler = (_ value: String, _ row: Int) -> Void
var changeSortIndex: CallBackHandler?
var changeVideoFrame: CallBackHandler?
var row: Int = 0
@IBOutlet weak var sortField: NSTextField!
@IBOutlet weak var videoField: NSTextField!
@IBOutlet weak var videoTitleField: NSTextField!
@IBOutlet weak var cellTopConstraint: NSLayoutConstraint!
override func awakeFromNib() {
super.awakeFromNib()
sortField.delegate = self
videoField.delegate = self
}
func updateData(sort: String, frame: String) {
sortField.stringValue = sort
videoField.stringValue = frame
}
func showVideoView(_ show: Bool) {
videoField.isHidden = !show
videoTitleField.isHidden = !show
cellTopConstraint.constant = show ? 10.0 : 20.0
}
}
extension ScreenShotUploadCell: NSTextFieldDelegate {
/// 内容改变
func controlTextDidChange(_ obj: Notification) {
let textField = obj.object as! NSTextField
let value = textField.stringValue
if textField.tag == sortField.tag, let callBack = changeSortIndex {
callBack(value, row)
}
if textField.tag == videoField.tag, let callBack = changeVideoFrame {
callBack(value, row)
}
}
}
@@ -0,0 +1,515 @@
//
// ScreenShotUploadVC.swift
// AppleParty
//
// Created by HTC on 2022/2/25.
// Copyright © 2022 37 Mobile Games. All rights reserved.
//
import Cocoa
import AVFoundation
enum ScreenShotType: Int {
case iOS5_5 = 0 //iPhone 5.5 英寸显示屏
case iOS6_5 = 1 //iPhone 6.5 英寸显示屏
case iPad_Pro = 2 //iPad Pro 12.9 英寸显示屏
}
class ScreenShotUploadVC: NSViewController {
public var currentApp: App? {
didSet {
fetchAppInfo()
fetchAppVersionData()
}
}
public var appInfo: AppInfo?
@IBOutlet weak var appName: NSTextField!
@IBOutlet weak var appSKU: NSTextField!
@IBOutlet weak var appleID: NSTextField!
@IBOutlet weak var localButton: NSPopUpButton!
@IBOutlet weak var tableView: NSTableView!
@IBOutlet weak var tips_title: NSTextField!
@IBOutlet weak var tips_count: NSTextField!
@IBOutlet weak var tips_desc: NSTextField!
private var selectTag = 0
private var filesData = [[[String:String]]](repeating: [[String:String]](), count: 3)
private let imageTypes = ["jpg", "jpeg", "png", "JPG", "JPEG", "PNG"]
private let videoTypes = ["mov", "m4v", "mp4", "MOV", "M4V", "MP4"]
private let locales = ["zh-Hans", "zh-Hant", "ko", "ja"]
private let imageSizes = ["0": ["1242x2208", "2208x1242"],
"1": ["1242x2688", "2688x1242"],
"2": ["2048x2732", "2732x2048"]]
private let videoSizes = ["0": ["1080x1920", "1920x1080"],
"1": ["886x1920", "1920x886"],
"2": ["1200x1600", "1600x1200"]]
private var app_name = ""
private var app_version = ""
private var uploadModel = XMLModel()
override func viewDidLoad() {
super.viewDidLoad()
setupView()
}
func setupView() {
tableView.delegate = self
tableView.dataSource = self
tableView.selectionHighlightStyle = .none
localButton.removeAllItems()
for locale in locales {
localButton.addItem(withTitle: locale)
}
localButton.selectItem(at: 0)
let teamId = UserCenter.shared.developerTeamId
if teamId.isEmpty {
fetchAccountTeamInfo()
} else {
appleID.stringValue = "Team ID: " + UserCenter.shared.developerTeamId
}
}
@IBAction func clickedUploadButton(_ sender: NSButton) {
guard filesData.filter({ $0.count > 0 }).count > 0 else {
APHUD.hide(message: "图片或视频不能为空!", view: self.view, delayTime: 1)
return
}
guard app_name.count > 0, app_version.count > 0 else {
APHUD.hide(message: "应用名和版本获取失败!请刷新重试~", view: self.view, delayTime: 1)
return
}
guard let sp = UserCenter.shared.currentSPassword else {
let vc = APSPasswordSettingVC()
vc.updateCompletion = { [weak self] spassword in
if let sp = spassword {
self?.uploadData(sp)
}
}
presentAsSheet(vc)
return
}
uploadData(sp)
}
@IBAction func reloadAppData(_ sender: Any) {
fetchAppVersionData()
}
@IBAction func clickedHelp(_ sender: NSButton) {
let vc = ScreenShotHelpPopoverVC()
self.present(vc, asPopoverRelativeTo: sender.frame, of: self.view, preferredEdge: .maxX, behavior:.transient)
}
@IBAction func changeSegmentedControl(_ sender: NSSegmentedControl) {
selectTag = sender.selectedTag()
reloadTableView(ScreenShotType.init(rawValue: selectTag) ?? .iOS5_5)
}
@IBAction func uploadFiles(_ sender: NSButton) {
let openPanel = NSOpenPanel()
openPanel.canChooseFiles = true
openPanel.canChooseDirectories = true
openPanel.allowsMultipleSelection = true
openPanel.allowedFileTypes = imageTypes + videoTypes
openPanel.beginSheetModal(for: self.view.window!) { [self] (modalResponse) in
if modalResponse == .OK {
handleAutoImages(openPanel.urls)
}
}
}
}
// MARK: - 网络请求
extension ScreenShotUploadVC {
func fetchAccountTeamInfo() {
// 获取开发者 Team id 信息
APClient.ascProvider.request { [weak self] result, response, error in
guard let err = error else {
self?.appleID.stringValue = "Team ID: " + UserCenter.shared.developerTeamId
return
}
APHUD.hide(message: err.localizedDescription, delayTime: 2)
}
}
func fetchAppInfo(_ replay: Int = 3) {
guard let appid = currentApp?.appId else {
APHUD.hide(message: "当前 App 的 appleid 为空!", delayTime: 1)
return
}
APClient.appInfo(appid: appid).request(showLoading: true) { [weak self] result, response, error in
if let err = error {
if replay > 0 {
self?.fetchAppInfo(replay-1)
} else {
NSAlert.show(err.localizedDescription)
}
return
}
let info = AppInfo(body: result)
self?.appInfo = info
self?.appSKU.stringValue = "App SKU: \(info.sku)"
}
}
func fetchAppVersionData(_ replay: Int = 3) {
guard let appid = currentApp?.appId else {
APHUD.hide(message: "当前 App 的 appleid 为空!", delayTime: 1)
return
}
APClient.appVersion(appid: appid).request { [weak self] data, response, error in
if let err = error {
if replay > 0 {
self?.fetchAppVersionData(replay-1)
} else {
NSAlert.show(err.localizedDescription)
}
return
}
let data = data["data"] as? [String: Any]
var dict = [String: Any]()
if let data = data, let platforms = data["platforms"] as? [[String: Any]] {
platforms.forEach { pf in
let platformString = pf["platformString"] as! String
dict[platformString] = pf
}
}
guard let ios = dict["ios"] as? [String: Any], let inFlightVersion = ios["inFlightVersion"] as? [String: Any] else {
NSAlert.show("当前 App 无待送审的版本,请检查确认!")
return
}
self?.app_version = inFlightVersion["version"] as! String
if let data = data, let titles = data["localizedMetadata"] as? [[String: Any]] {
if titles.count > 0 {
// 这里只读取第一个
self?.app_name = titles[0]["name"] as! String
}
}
DispatchQueue.main.async { [self] in
self?.appName.stringValue = "App Name: \(self!.app_name) (\(self!.app_version))"
}
}
}
}
// MARK: - NSTableViewDelegate
extension ScreenShotUploadVC: NSTableViewDelegate, NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
return filesData[selectTag].count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
var data = filesData[selectTag][row]
let isImg = data["type"]! == "1"
switch tableColumn!.identifier.rawValue {
case "review":
let imgView = NSImageView()
if let imageRef = NSImage(byReferencingFile: data["url"]!) {
imgView.image = imageRef
}
if !isImg {
let url = URL(fileURLWithPath: data["url"]!)
let videoImg = previewImageForLocalVideo(url)
imgView.image = videoImg
}
return imgView
case "fileName":
let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "fileName"), owner: self) as? NSTableCellView
cell?.textField?.stringValue = "\(isImg ? "图片" : "视频"):\n\(data["name"]!)"
return cell
case "setting":
let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "setting"), owner: self) as? ScreenShotUploadCell
cell?.row = row
cell?.showVideoView(!isImg)
cell?.updateData(sort: data["index"] ?? "", frame: data["frame"] ?? "00:00")
cell?.changeSortIndex = { index, crow in
data["index"] = index
self.filesData[self.selectTag][row] = data
}
cell?.changeVideoFrame = { frame, crow in
data["frame"] = frame
self.filesData[self.selectTag][row] = data
}
return cell
case "operation":
let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "operation"), owner: self) as? ScreenShotDeleteCell
cell?.row = row
cell?.deleteCell = { row in
self.filesData[self.selectTag].remove(at: row)
self.tableView.reloadData()
self.reloadTableView(ScreenShotType(rawValue: self.selectTag) ?? .iOS5_5)
}
return cell
default:
return nil
}
}
func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
80
}
func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
// macOS 11 以下,如果不能点击,textfield 也不能点击
return true
}
}
// MARK: - Upload
extension ScreenShotUploadVC {
func uploadData(_ sp: SPassword) {
APHUD.show(message: "上传中", view: self.view)
let localIndex = localButton.indexOfSelectedItem
DispatchQueue.global(qos: .userInitiated).async { [self] in
uploadModel = XMLModel()
guard let info = appInfo else {
fetchAppInfo()
APHUD.hide(message: "加载数据异常,请刷新后重试~")
return
}
uploadModel.vendor_id = info.sku
// 获取创建 itms 文件的路径
let filePath = XMLManager.getShotsPath(currentApp!.appId)
// 先删除旧的文档
XMLManager.deleteITMS(filePath)
// 数据转模型
handleDataToModel(localIndex)
uploadModel.createShots(directoryPath: filePath)
let result = XMLManager.uploadITMS(account: sp.account, pwd: sp.password, filePath: filePath)
DispatchQueue.main.async {
APHUD.hide()
self.closeSelfAndCallBack(result)
}
}
}
func handleDataToModel(_ localIndex: Int) {
let locale = locales[localIndex]
uploadModel.app_locale = locale
uploadModel.app_title = app_name
uploadModel.app_version = app_version
let iOS5_5 = filesData[0]
let iOS6_5 = filesData[1]
let iPad = filesData[2]
//iPhone 5.5 英寸
let (img5_5, video5_5, set5_5) = getScreenShotModel(iOS5_5)
uploadModel.shots["iOS-5.5-in"] = img5_5
uploadModel.videos["iOS-5.5-in"] = video5_5
//iPhone 6.5 英寸
let (img6_5, video6_5, set6_5) = getScreenShotModel(iOS6_5)
uploadModel.shots["iOS-6.5-in"] = img6_5
uploadModel.videos["iOS-6.5-in"] = video6_5
let (imgiPad, videoiPad, setiPad) = getScreenShotModel(iPad)
//iPad Pro(第2代) 12.9 英寸
uploadModel.shots["iOS-iPad-Pro"] = imgiPad
uploadModel.videos["iOS-iPad-Pro"] = videoiPad
//iPad Pro(第3代) 12.9 英寸
uploadModel.shots["iOS-iPad-Pro-2018"] = imgiPad
uploadModel.videos["iOS-iPad-Pro-2018"] = videoiPad
// 图片和视频资源
let fileURLs = set5_5.union(set6_5).union(setiPad)
var dict = [String: String]()
fileURLs.forEach { value in
value.forEach { dict[$0] = $1 }
}
uploadModel.filePaths = dict
}
func getScreenShotModel(_ shots: [[String: String]]) -> ([Screen_Shot], [Screen_Shot], Set<[String:String]>) {
var imageList = [Screen_Shot]()
var videoList = [Screen_Shot]()
var urlList: Set<[String:String]> = []
shots.forEach { data in
let isImg = data["type"]! == "1"
let url = data["url"] ?? ""
let kind = data["kind"] ?? ""
let name = kind + (data["name"] ?? "") //kind+name:避免不同尺寸使用同一个图片名字,导致替换的问题
let index = data["index"] ?? ""
let frame = "00:00:" + (data["frame"] ?? "00:00")
let size = URL.init(fileURLWithPath: url).fileSize()
let md5 = URL.init(fileURLWithPath: url).fileMD5()
let item = Screen_Shot(file_name: name, size: size, checksum: md5 ?? "", position: index, preview_time: frame)
urlList.insert([name: url])
isImg ? imageList.append(item) : videoList.append(item)
}
return (imageList, videoList, urlList)
}
func closeSelfAndCallBack(_ result: (Int32, String?)) {
if result.0 == 0 {
NSAlert.show("上传成功!稍后可在苹果后台查看~")
// 删除旧的文档,避免占用空间过大
let filePath = XMLManager.getShotsPath(currentApp!.appId)
XMLManager.deleteITMS(filePath)
} else {
let sb = NSStoryboard(name: "APDebugVC", bundle: Bundle(for: self.classForCoder))
let newWC = sb.instantiateController(withIdentifier: "APDebugWC") as? NSWindowController
let logVC = newWC?.contentViewController as? APDebugVC
newWC?.window?.title = "上传错误日志"
logVC?.debugLog = result.1 ?? ""
newWC?.showWindow(self)
}
}
}
// MARK: - 内部方法
extension ScreenShotUploadVC {
func handleAutoImages(_ files: [URL]) {
files.forEach { url in
// 如果是文件夹,则递归
if url.hasDirectoryPath {
let urls = subFilesInDirectory(url: url)
handleAutoImages(urls)
return
}
var size = "0x0"
var sizes = [String: [String]]()
var type = "1"
let fileType = url.pathExtension
if imageTypes.contains(fileType) {
let image = NSImage(contentsOf: url)
guard let rep = image?.representations.first as? NSBitmapImageRep else {
return
}
size = "\(rep.pixelsWide)x\(rep.pixelsHigh)"
sizes = imageSizes
type = "1"
} else if videoTypes.contains(fileType) {
guard let track = AVURLAsset(url: url).tracks(withMediaType: AVMediaType.video).first else {
return
}
let rep = track.naturalSize.applying(track.preferredTransform)
size = "\(Int(rep.width))x\(Int(rep.height))"
sizes = videoSizes
type = "2"
}
sizes.forEach { (index: String, value: [String]) in
if value.contains(size) {
// 处理数据
var dict = [String: String]()
dict["url"] = url.path
dict["name"] = url.lastPathComponent
dict["type"] = type
dict["kind"] = index
// 更新数据
var value = filesData[Int(index)!]
value.append(dict)
filesData[Int(index)!] = value
}
}
}
// 刷新列表
reloadTableView(ScreenShotType.init(rawValue: selectTag) ?? .iOS5_5)
}
func subFilesInDirectory(url: URL) -> [URL] {
var urls = [URL]()
// 迭代器,包含子目录
let files = FileManager.default.enumerator(atPath: url.path)
while let file = files?.nextObject() {
if let file = file as? String {
let nextFile = url.appendingPathComponent(file)
if !nextFile.hasDirectoryPath {
urls.append(nextFile)
}
}
}
return urls
}
func reloadTableView(_ index: ScreenShotType) {
let list = filesData[index.rawValue]
let images = list.filter({ $0["type"] == "1" })
tips_count.stringValue = "\(list.count - images.count)/3 个 App 预览 | \(images.count)/10 张屏幕快照"
switch index {
case .iOS5_5:
tips_title.stringValue = "iPhone 5.5 英寸显示屏"
tips_desc.stringValue = "图片:1242 x 2208、2208 x 1242,视频:1080 x 1920、1920 x 1080"
case .iOS6_5:
tips_title.stringValue = "iPhone 6.5 英寸显示屏"
tips_desc.stringValue = "图片:1242 x 2688、2688 x 1242,视频:886 x 1920、1920 x 886"
case .iPad_Pro:
tips_title.stringValue = "iPad Pro 12.9 英寸显示屏"
tips_desc.stringValue = "图片:2048 x 2732、2732 x 2048,视频:1200 x 1600、1600 x 1200"
}
tableView.reloadData()
}
func previewImageForLocalVideo(_ url: URL) -> NSImage? {
let asset = AVAsset(url: url)
let imageGenerator = AVAssetImageGenerator(asset: asset)
imageGenerator.appliesPreferredTrackTransform = true
var time = asset.duration
//If possible - take not the first frame (it could be completely black or white on camara's videos)
time.value = min(time.value, 2)
do {
let imageRef = try imageGenerator.copyCGImage(at: time, actualTime: nil)
let track = asset.tracks(withMediaType: AVMediaType.video).first
let rep = track?.naturalSize.applying(track?.preferredTransform ?? CGAffineTransform())
return NSImage(cgImage: imageRef, size: NSSize(width: rep?.width ?? 120, height: rep?.height ?? 60))
}
catch let error as NSError
{
print("Image generation failed with error \(error)")
return nil
}
}
}
@@ -0,0 +1,3 @@
/* Class = "NSWindow"; title = "My Apps"; ObjectID = "mcp-fI-0bQ"; */
"mcp-fI-0bQ.title" = "我的 App";
@@ -0,0 +1,49 @@
//
// SettingVC.swift
// AppleParty
//
// Created by HTC on 2022/3/25.
// Copyright © 2021 37 Mobile Games. All rights reserved.
//
import Cocoa
class APSettingVC: NSViewController {
var isLoginViewShow: Bool {
get { return false }
set {
sPasswordBtn.isHidden = newValue
clearCacheBtn.isHidden = !newValue
}
}
@IBOutlet weak var trusDeviceBtn: NSButton!
@IBOutlet weak var sPasswordBtn: NSButton!
@IBOutlet weak var clearCacheBtn: NSButton!
@IBAction func clickedTrusDeviceBtn(_ sender: NSButton) {
InfoCenter.shared.trusDevice = sender.state == .on ? true : false
}
@IBAction func clickedSPasswordBtn(_ sender: Any) {
let vc = APSPasswordSettingVC()
presentAsSheet(vc)
}
@IBAction func clickedClearCacheBtn(_ sender: NSButton) {
// 清掉缓存
HTTPCookieStorage.shared.cookies?.forEach(HTTPCookieStorage.shared.deleteCookie)
InfoCenter.shared.cookies = []
APHUD.hide(message: "清掉缓存成功", view: self.view)
}
override func viewDidLoad() {
super.viewDidLoad()
title = "App设置"
trusDeviceBtn.state = InfoCenter.shared.trusDevice ? .on : .off
}
}
@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21507" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="APSettingVC">
<connections>
<outlet property="clearCacheBtn" destination="CIQ-yY-qAH" id="Mk4-R7-k6m"/>
<outlet property="sPasswordBtn" destination="OeI-Rx-XtW" id="qOB-78-ZuO"/>
<outlet property="trusDeviceBtn" destination="nK7-5f-UIm" id="bjH-fk-npo"/>
<outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView id="Hz6-mo-xeY">
<rect key="frame" x="0.0" y="0.0" width="479" height="376"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BKC-66-mUu">
<rect key="frame" x="23" y="333" width="98" height="28"/>
<constraints>
<constraint firstAttribute="height" constant="28" id="vQz-oQ-tcK"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" title="设置选项" id="kek-10-Cgd">
<font key="font" metaFont="system" size="23"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nK7-5f-UIm">
<rect key="frame" x="23" y="287" width="106" height="22"/>
<constraints>
<constraint firstAttribute="height" constant="20" id="hLE-Pd-isf"/>
</constraints>
<buttonCell key="cell" type="check" title="信任当前设备" bezelStyle="regularSquare" imagePosition="left" lineBreakMode="truncatingTail" state="on" inset="2" id="xCh-9D-HiS">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="clickedTrusDeviceBtn:" target="-2" id="n1j-ha-ysp"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="OeI-Rx-XtW">
<rect key="frame" x="147" y="281" width="114" height="32"/>
<constraints>
<constraint firstAttribute="height" constant="20" id="QVA-uj-MiU"/>
</constraints>
<buttonCell key="cell" type="push" title="设置专用密码" bezelStyle="rounded" alignment="center" lineBreakMode="truncatingTail" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="8of-Bf-BU2">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" usesAppearanceFont="YES"/>
</buttonCell>
<connections>
<action selector="clickedSPasswordBtn:" target="-2" id="et4-Z4-s9t"/>
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="TXz-pa-Bnd" userLabel="Line Label">
<rect key="frame" x="15" y="252" width="449" height="1"/>
<constraints>
<constraint firstAttribute="height" constant="1" id="Hb5-8R-DdZ"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" borderStyle="bezel" drawsBackground="YES" id="FWN-Oq-vFj">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="tertiaryLabelColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button hidden="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="CIQ-yY-qAH">
<rect key="frame" x="272" y="281" width="114" height="32"/>
<constraints>
<constraint firstAttribute="height" constant="20" id="J5b-3X-pyU"/>
</constraints>
<buttonCell key="cell" type="push" title="清除缓存数据" bezelStyle="rounded" alignment="center" lineBreakMode="truncatingTail" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="ylL-yw-axd">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="clickedClearCacheBtn:" target="-2" id="Sy2-qH-JPk"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="OeI-Rx-XtW" firstAttribute="centerY" secondItem="nK7-5f-UIm" secondAttribute="centerY" id="1fH-HN-CXg"/>
<constraint firstAttribute="trailing" secondItem="TXz-pa-Bnd" secondAttribute="trailing" constant="15" id="7d6-CI-IjY"/>
<constraint firstItem="BKC-66-mUu" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="25" id="9hM-UB-Bbc"/>
<constraint firstItem="CIQ-yY-qAH" firstAttribute="leading" secondItem="nK7-5f-UIm" secondAttribute="trailing" priority="550" constant="25" id="FAu-ZM-maQ"/>
<constraint firstItem="TXz-pa-Bnd" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="15" id="GHp-tK-8s3"/>
<constraint firstItem="CIQ-yY-qAH" firstAttribute="centerY" secondItem="nK7-5f-UIm" secondAttribute="centerY" id="Koe-bl-rog"/>
<constraint firstItem="TXz-pa-Bnd" firstAttribute="top" secondItem="nK7-5f-UIm" secondAttribute="bottom" constant="35" id="Nue-2l-Tec"/>
<constraint firstItem="BKC-66-mUu" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" constant="15" id="bcl-Ls-nqp"/>
<constraint firstItem="nK7-5f-UIm" firstAttribute="top" secondItem="BKC-66-mUu" secondAttribute="bottom" constant="25" id="l1u-Xr-4yX"/>
<constraint firstItem="nK7-5f-UIm" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="25" id="n3v-kC-eX9"/>
<constraint firstItem="OeI-Rx-XtW" firstAttribute="leading" secondItem="nK7-5f-UIm" secondAttribute="trailing" constant="25" id="qUw-wS-OCL"/>
<constraint firstItem="CIQ-yY-qAH" firstAttribute="leading" secondItem="OeI-Rx-XtW" secondAttribute="trailing" priority="750" constant="25" id="wnS-ya-Ulb"/>
</constraints>
<point key="canvasLocation" x="48.5" y="194"/>
</customView>
</objects>
</document>
@@ -0,0 +1,7 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "GDataXMLNode.h"
#import "QrcodeUtil.h"
#import "MBProgressHUD.h"
@@ -0,0 +1,5 @@
<?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/>
</plist>
@@ -0,0 +1,61 @@
//
// EmailSettingVC.swift
// AppleParty
//
// Created by HTC on 2022/3/29.
// Copyright © 2022 37 Mobile Games. All rights reserved.
//
import Cocoa
struct EamilConfigs {
var name: String = ""
var addr: String = ""
var pwd: String = ""
var smtp: String = ""
}
var eamilConfigs: EamilConfigs? {
get {
let value = (try? APUtil.keychain.getString("APEmailSetting_Key")) ?? ""
let arr = value.components(separatedBy: "|")
guard arr.count == 4 else {
return nil
}
return EamilConfigs(name: arr[0], addr: arr[1], pwd: arr[2], smtp: arr[3])
}
set {
guard let newValue = newValue else { return }
let value = "\(newValue.name)|\(newValue.addr)|\(newValue.pwd)|\(newValue.smtp)"
try? APUtil.keychain.set(value, key: "APEmailSetting_Key")
}
}
class EmailSettingVC: NSViewController {
public var closeHandle: (() -> Void)?
@IBOutlet weak var emailNameView: NSTextField!
@IBOutlet weak var emailAddrView: NSTextField!
@IBOutlet weak var emailPwdView: NSTextField!
@IBOutlet weak var emailSMTPView: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
}
@IBAction func clickedCancelBtn(_ sender: Any) {
closeHandle?()
}
@IBAction func clickedSubmitBtn(_ sender: Any) {
eamilConfigs = EamilConfigs(name: emailNameView.stringValue, addr: emailAddrView.stringValue, pwd: emailPwdView.stringValue, smtp: emailSMTPView.stringValue)
closeHandle?()
}
}
@@ -0,0 +1,131 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21507" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="EmailSettingVC" customModule="AppleParty" customModuleProvider="target">
<connections>
<outlet property="emailAddrView" destination="xC8-OP-5OB" id="iA5-lT-uGT"/>
<outlet property="emailNameView" destination="eDA-H4-wtu" id="ggR-LW-T6u"/>
<outlet property="emailPwdView" destination="VVK-4v-kKh" id="gkL-zS-b4P"/>
<outlet property="emailSMTPView" destination="Fnq-BH-an4" id="c0U-Xb-4Ak"/>
<outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView id="Hz6-mo-xeY">
<rect key="frame" x="0.0" y="0.0" width="319" height="328"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="UGV-LT-zwz">
<rect key="frame" x="8" y="283" width="303" height="25"/>
<constraints>
<constraint firstAttribute="height" constant="25" id="MTZ-og-I8m"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="设置邮箱账户" id="Txf-cs-igw">
<font key="font" metaFont="systemBold" size="17"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fOt-av-hwc">
<rect key="frame" x="206" y="18" width="80" height="34"/>
<constraints>
<constraint firstAttribute="height" constant="22" id="8Vn-vC-kMW"/>
<constraint firstAttribute="width" constant="66" id="II3-ZH-6L6"/>
</constraints>
<buttonCell key="cell" type="push" title="设置" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="P5t-QJ-zKH">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="clickedSubmitBtn:" target="-2" id="5bV-hs-YGt"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LT7-p4-ggn">
<rect key="frame" x="33" y="18" width="80" height="32"/>
<constraints>
<constraint firstAttribute="width" constant="66" id="Le4-RM-yje"/>
</constraints>
<buttonCell key="cell" type="push" title="取消" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="zzW-EO-bUm">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" size="13" name="STSongti-SC-Regular"/>
</buttonCell>
<connections>
<action selector="clickedCancelBtn:" target="-2" id="Fdm-gw-h3c"/>
</connections>
</button>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="eDA-H4-wtu">
<rect key="frame" x="40" y="219" width="239" height="34"/>
<constraints>
<constraint firstAttribute="height" constant="34" id="Pha-T8-v9U"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="发送人名字" bezelStyle="round" id="u8o-KC-IxS">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="xC8-OP-5OB">
<rect key="frame" x="40" y="173" width="239" height="34"/>
<constraints>
<constraint firstAttribute="height" constant="34" id="80Y-rx-GcW"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="发送邮箱地址" bezelStyle="round" id="g7M-ao-gse">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="VVK-4v-kKh">
<rect key="frame" x="40" y="127" width="239" height="34"/>
<constraints>
<constraint firstAttribute="height" constant="34" id="kT1-yB-MMR"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="发送邮箱密码" bezelStyle="round" id="Doe-al-eff">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Fnq-BH-an4">
<rect key="frame" x="40" y="81" width="239" height="34"/>
<constraints>
<constraint firstAttribute="height" constant="34" id="zSX-jX-gJu"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="邮箱服务地址(如 smtp.xx.com)" bezelStyle="round" id="otT-Sn-TWW">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="VVK-4v-kKh" firstAttribute="top" secondItem="xC8-OP-5OB" secondAttribute="bottom" constant="12" id="5gY-FH-ljk"/>
<constraint firstItem="xC8-OP-5OB" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="40" id="Ax7-4Z-zIa"/>
<constraint firstAttribute="trailing" secondItem="eDA-H4-wtu" secondAttribute="trailing" constant="40" id="BaG-jk-2M6"/>
<constraint firstItem="Fnq-BH-an4" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="40" id="FCR-Fd-als"/>
<constraint firstItem="Fnq-BH-an4" firstAttribute="top" secondItem="VVK-4v-kKh" secondAttribute="bottom" constant="12" id="Lsf-Gg-Hi5"/>
<constraint firstItem="LT7-p4-ggn" firstAttribute="leading" secondItem="Fnq-BH-an4" secondAttribute="leading" id="NqC-bU-xow"/>
<constraint firstAttribute="bottom" secondItem="LT7-p4-ggn" secondAttribute="bottom" constant="25" id="Pgl-yp-65W"/>
<constraint firstItem="fOt-av-hwc" firstAttribute="trailing" secondItem="Fnq-BH-an4" secondAttribute="trailing" id="TNu-gl-01i"/>
<constraint firstItem="VVK-4v-kKh" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="40" id="TeH-8x-GIb"/>
<constraint firstItem="eDA-H4-wtu" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="40" id="UtA-LQ-pGW"/>
<constraint firstItem="UGV-LT-zwz" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" constant="20" symbolic="YES" id="XAE-zG-uBj"/>
<constraint firstItem="xC8-OP-5OB" firstAttribute="top" secondItem="eDA-H4-wtu" secondAttribute="bottom" constant="12" id="fG3-IY-ptx"/>
<constraint firstAttribute="trailing" secondItem="Fnq-BH-an4" secondAttribute="trailing" constant="40" id="oJO-fs-eS0"/>
<constraint firstAttribute="trailing" secondItem="VVK-4v-kKh" secondAttribute="trailing" constant="40" id="olk-5z-Atb"/>
<constraint firstAttribute="bottom" secondItem="fOt-av-hwc" secondAttribute="bottom" constant="25" id="qUL-ii-wn5"/>
<constraint firstAttribute="trailing" secondItem="UGV-LT-zwz" secondAttribute="trailing" constant="10" id="rew-BG-Egu"/>
<constraint firstAttribute="trailing" secondItem="xC8-OP-5OB" secondAttribute="trailing" constant="40" id="z5o-8S-Uvi"/>
<constraint firstItem="eDA-H4-wtu" firstAttribute="top" secondItem="UGV-LT-zwz" secondAttribute="bottom" constant="30" id="zDm-hN-QrO"/>
<constraint firstItem="UGV-LT-zwz" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="10" id="zVD-UO-S2w"/>
</constraints>
<point key="canvasLocation" x="58.5" y="182"/>
</customView>
</objects>
</document>
@@ -0,0 +1,251 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Window Controller-->
<scene sceneID="6Wf-BN-a9j">
<objects>
<windowController storyboardIdentifier="EmailTool" id="T4u-uh-EbA" sceneMemberID="viewController">
<window key="window" title="邮件助手" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="qJz-u6-Hg1">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<rect key="contentRect" x="359" y="388" width="600" height="450"/>
<rect key="screenRect" x="0.0" y="0.0" width="2240" height="1235"/>
<view key="contentView" id="mmv-OD-ZTq">
<rect key="frame" x="0.0" y="0.0" width="600" height="450"/>
<autoresizingMask key="autoresizingMask"/>
</view>
<connections>
<outlet property="delegate" destination="T4u-uh-EbA" id="dxr-DV-lYU"/>
</connections>
</window>
<connections>
<segue destination="hI8-xu-11y" kind="relationship" relationship="window.shadowedContentViewController" id="XrI-xM-H9W"/>
</connections>
</windowController>
<customObject id="a65-QT-Nbx" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-249" y="220"/>
</scene>
<!--邮件助手-->
<scene sceneID="NVw-xI-A8X">
<objects>
<viewController title="邮件助手" id="hI8-xu-11y" customClass="EmailToolVC" customModule="AppleParty" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="fZy-4A-sDv">
<rect key="frame" x="0.0" y="0.0" width="600" height="557"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="RWF-3X-mT6">
<rect key="frame" x="20" y="505" width="377" height="32"/>
<constraints>
<constraint firstAttribute="height" constant="32" id="XIR-wi-l2X"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="收件人邮箱地址" bezelStyle="round" id="j19-N4-QQn">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="czV-Gg-3Ri">
<rect key="frame" x="20" y="453" width="467" height="32"/>
<constraints>
<constraint firstAttribute="height" constant="32" id="Osc-bj-ZTE"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="邮件标题" bezelStyle="round" id="GwA-zk-eAs">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="aBl-I5-nCZ">
<rect key="frame" x="495" y="447" width="92" height="42"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="1zZ-G3-6Qa"/>
<constraint firstAttribute="width" constant="78" id="s1Y-ba-LVb"/>
</constraints>
<buttonCell key="cell" type="push" title="发送" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="msG-2W-3ir">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="sendEmailButton:" target="hI8-xu-11y" id="6D3-P8-7tz"/>
</connections>
</button>
<scrollView borderType="line" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bZ7-lQ-t1B">
<rect key="frame" x="20" y="300" width="560" height="125"/>
<clipView key="contentView" drawsBackground="NO" id="EkG-rr-J9X">
<rect key="frame" x="1" y="1" width="558" height="123"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textView importsGraphics="NO" richText="NO" verticallyResizable="YES" allowsUndo="YES" spellingCorrection="YES" smartInsertDelete="YES" id="4ah-H5-AbK">
<rect key="frame" x="0.0" y="0.0" width="558" height="123"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<size key="minSize" width="558" height="123"/>
<size key="maxSize" width="758" height="10000000"/>
<color key="insertionPointColor" name="textColor" catalog="System" colorSpace="catalog"/>
</textView>
</subviews>
</clipView>
<constraints>
<constraint firstAttribute="height" constant="125" id="DGW-ts-Y3w"/>
</constraints>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="w5S-wA-NZW">
<rect key="frame" x="-100" y="-100" width="240" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="I17-we-bph">
<rect key="frame" x="543" y="1" width="16" height="123"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
<scrollView hidden="YES" borderType="line" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Dd7-vC-Tdz">
<rect key="frame" x="20" y="30" width="560" height="220"/>
<clipView key="contentView" drawsBackground="NO" id="nJr-qZ-vEo">
<rect key="frame" x="1" y="1" width="558" height="218"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textView editable="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" spellingCorrection="YES" smartInsertDelete="YES" id="egz-HU-sRx">
<rect key="frame" x="0.0" y="0.0" width="558" height="218"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<size key="minSize" width="558" height="218"/>
<size key="maxSize" width="758" height="10000000"/>
<color key="insertionPointColor" name="textColor" catalog="System" colorSpace="catalog"/>
</textView>
</subviews>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="C94-21-myA">
<rect key="frame" x="-100" y="-100" width="240" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="qOY-uP-fav">
<rect key="frame" x="543" y="1" width="16" height="218"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hPQ-8D-VUS">
<rect key="frame" x="18" y="268" width="87" height="18"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="85" id="CGO-Yn-TVv"/>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="16" id="Kbs-f6-qvx"/>
<constraint firstAttribute="height" constant="16" id="MYL-95-hpC"/>
</constraints>
<buttonCell key="cell" type="check" title="多个附件" bezelStyle="regularSquare" imagePosition="left" inset="2" id="cMY-nK-eFc">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="ChangeMultipleFiles:" target="hI8-xu-11y" id="SS4-P3-ac7"/>
</connections>
</button>
<button hidden="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="YDc-3K-JgG">
<rect key="frame" x="113" y="255" width="114" height="42"/>
<constraints>
<constraint firstAttribute="width" constant="100" id="cHP-vM-kak"/>
<constraint firstAttribute="height" constant="30" id="iaX-PB-BFX"/>
</constraints>
<buttonCell key="cell" type="push" title="批量选择附件" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="b4z-Tm-zdY">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="selectFiles:" target="hI8-xu-11y" id="cJV-aH-jrm"/>
</connections>
</button>
<button hidden="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fkW-LQ-FK3">
<rect key="frame" x="473" y="255" width="114" height="42"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="Dc4-0C-chh"/>
<constraint firstAttribute="width" constant="100" id="UkJ-G9-xpX"/>
</constraints>
<buttonCell key="cell" type="push" title="清空所有附件" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Eol-UM-Fq4">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" size="13" name="Times-Roman"/>
</buttonCell>
<connections>
<action selector="clearnAllFiles:" target="hI8-xu-11y" id="aQ3-JN-lkH"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nEa-8J-VHH">
<rect key="frame" x="510" y="511" width="78" height="19"/>
<constraints>
<constraint firstAttribute="height" constant="18" id="SNh-ap-QDY"/>
<constraint firstAttribute="width" constant="78" id="akd-Tt-PbM"/>
</constraints>
<buttonCell key="cell" type="roundRect" title="邮箱设置" bezelStyle="roundedRect" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="5t6-n1-Yfi">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="cellTitle"/>
</buttonCell>
<connections>
<action selector="clickedEmailSettingButton:" target="hI8-xu-11y" id="KkT-rB-A5p"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="wKD-68-g5N">
<rect key="frame" x="410" y="512" width="80" height="18"/>
<constraints>
<constraint firstAttribute="height" constant="16" id="Am0-qN-l34"/>
<constraint firstAttribute="width" constant="78" id="C2k-Ct-lk8"/>
</constraints>
<buttonCell key="cell" type="check" title="记住邮箱" bezelStyle="regularSquare" imagePosition="left" inset="2" id="XZa-0z-9Wx">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="rememberEmail:" target="hI8-xu-11y" id="Yk7-qe-oOx"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="fkW-LQ-FK3" firstAttribute="centerY" secondItem="YDc-3K-JgG" secondAttribute="centerY" id="3vf-mh-NPN"/>
<constraint firstAttribute="trailing" secondItem="aBl-I5-nCZ" secondAttribute="trailing" constant="20" symbolic="YES" id="D6v-Dw-Hce"/>
<constraint firstItem="czV-Gg-3Ri" firstAttribute="top" secondItem="RWF-3X-mT6" secondAttribute="bottom" constant="20" id="EUY-Fa-byW"/>
<constraint firstItem="hPQ-8D-VUS" firstAttribute="leading" secondItem="fZy-4A-sDv" secondAttribute="leading" constant="20" symbolic="YES" id="Gff-XY-Gge"/>
<constraint firstItem="bZ7-lQ-t1B" firstAttribute="top" secondItem="czV-Gg-3Ri" secondAttribute="bottom" constant="28" id="GhT-KF-C9V"/>
<constraint firstItem="aBl-I5-nCZ" firstAttribute="leading" secondItem="czV-Gg-3Ri" secondAttribute="trailing" constant="15" id="Ie7-SA-119"/>
<constraint firstItem="Dd7-vC-Tdz" firstAttribute="leading" secondItem="fZy-4A-sDv" secondAttribute="leading" constant="20" symbolic="YES" id="V6i-qP-2gR"/>
<constraint firstAttribute="bottom" secondItem="Dd7-vC-Tdz" secondAttribute="bottom" constant="30" id="Wb4-eP-RbL"/>
<constraint firstItem="YDc-3K-JgG" firstAttribute="centerY" secondItem="hPQ-8D-VUS" secondAttribute="centerY" id="XCM-Ec-Gh8"/>
<constraint firstItem="bZ7-lQ-t1B" firstAttribute="leading" secondItem="fZy-4A-sDv" secondAttribute="leading" constant="20" symbolic="YES" id="Z0A-we-uv2"/>
<constraint firstItem="nEa-8J-VHH" firstAttribute="centerY" secondItem="RWF-3X-mT6" secondAttribute="centerY" id="c43-YH-VpN"/>
<constraint firstItem="nEa-8J-VHH" firstAttribute="leading" secondItem="wKD-68-g5N" secondAttribute="trailing" constant="20" id="dAP-vZ-QuW"/>
<constraint firstItem="aBl-I5-nCZ" firstAttribute="centerY" secondItem="czV-Gg-3Ri" secondAttribute="centerY" id="eOU-ym-ifE"/>
<constraint firstItem="RWF-3X-mT6" firstAttribute="top" secondItem="fZy-4A-sDv" secondAttribute="top" constant="20" symbolic="YES" id="gTw-GK-wmY"/>
<constraint firstItem="wKD-68-g5N" firstAttribute="leading" secondItem="RWF-3X-mT6" secondAttribute="trailing" constant="15" id="iq6-W1-gyS"/>
<constraint firstItem="czV-Gg-3Ri" firstAttribute="leading" secondItem="fZy-4A-sDv" secondAttribute="leading" constant="20" symbolic="YES" id="lRZ-eR-gSE"/>
<constraint firstAttribute="trailing" secondItem="fkW-LQ-FK3" secondAttribute="trailing" constant="20" symbolic="YES" id="lTW-1f-YpI"/>
<constraint firstAttribute="trailing" secondItem="bZ7-lQ-t1B" secondAttribute="trailing" constant="20" symbolic="YES" id="ldL-eS-Nkc"/>
<constraint firstItem="hPQ-8D-VUS" firstAttribute="top" secondItem="bZ7-lQ-t1B" secondAttribute="bottom" constant="15" id="lhC-BK-8Tf"/>
<constraint firstItem="YDc-3K-JgG" firstAttribute="leading" secondItem="hPQ-8D-VUS" secondAttribute="trailing" constant="15" id="nVw-TE-aXd"/>
<constraint firstItem="wKD-68-g5N" firstAttribute="centerY" secondItem="RWF-3X-mT6" secondAttribute="centerY" id="uBR-yz-DpK"/>
<constraint firstItem="RWF-3X-mT6" firstAttribute="leading" secondItem="fZy-4A-sDv" secondAttribute="leading" constant="20" symbolic="YES" id="vI6-lv-Ec5"/>
<constraint firstAttribute="trailing" secondItem="Dd7-vC-Tdz" secondAttribute="trailing" constant="20" symbolic="YES" id="wyE-Sc-s5e"/>
<constraint firstAttribute="trailing" secondItem="nEa-8J-VHH" secondAttribute="trailing" constant="12" id="xMj-dk-PSK"/>
<constraint firstItem="Dd7-vC-Tdz" firstAttribute="top" secondItem="YDc-3K-JgG" secondAttribute="bottom" constant="12" id="xRd-FW-dKv"/>
</constraints>
</view>
<connections>
<outlet property="clearnAllFilesButton" destination="fkW-LQ-FK3" id="xYy-yP-YKJ"/>
<outlet property="emailContentTextView" destination="4ah-H5-AbK" id="Khn-UX-lUg"/>
<outlet property="emailRecipientTF" destination="RWF-3X-mT6" id="yKO-Jx-mde"/>
<outlet property="emailSendButton" destination="aBl-I5-nCZ" id="Vw5-h1-kJe"/>
<outlet property="emailTitleTF" destination="czV-Gg-3Ri" id="nXa-9E-AF0"/>
<outlet property="emialContentView" destination="bZ7-lQ-t1B" id="xQU-qv-3Af"/>
<outlet property="multipleFilesButton" destination="hPQ-8D-VUS" id="mCJ-3B-Fve"/>
<outlet property="rememberEmailButton" destination="wKD-68-g5N" id="Gwn-wI-TB3"/>
<outlet property="selectFilesButton" destination="YDc-3K-JgG" id="HJv-eO-apA"/>
<outlet property="selectFilesView" destination="Dd7-vC-Tdz" id="BXM-HP-Y0A"/>
<outlet property="selectilesTextView" destination="egz-HU-sRx" id="7kr-5W-ahZ"/>
</connections>
</viewController>
<customObject id="NPu-9T-cfy" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="455" y="211.5"/>
</scene>
</scenes>
</document>
@@ -0,0 +1,247 @@
//
// EmailToolVC.swift
// AppleParty
//
// Created by iHTC on 20211025.
// Copyright © 2021 37 Mobile Games. All rights reserved.
//
import Cocoa
class EmailToolVC: NSViewController {
var emailTitle: String? {
didSet {
if let text = emailTitle {
emailTitleTF.stringValue = text
}
}
}
var emailContent: String? {
didSet {
if let text = emailContent {
emailContentTextView.string = text
}
}
}
var attachmentFileUrl: URL? {
didSet {
if let url = attachmentFileUrl {
fileURLs?.append(url)
fileDropZoneView.setFile(url)
}
}
}
@IBOutlet weak var emailRecipientTF: NSTextField!
@IBOutlet weak var rememberEmailButton: NSButton!
@IBOutlet weak var emailTitleTF: NSTextField!
@IBOutlet weak var emailSendButton: NSButton!
@IBOutlet weak var emailContentTextView: NSTextView!
@IBOutlet weak var emialContentView: NSScrollView!
@IBOutlet weak var multipleFilesButton: NSButton!
@IBOutlet weak var selectFilesButton: NSButton!
@IBOutlet weak var clearnAllFilesButton: NSButton!
@IBOutlet weak var selectFilesView: NSScrollView!
@IBOutlet weak var selectilesTextView: NSTextView!
private var fileDropZoneView = DropZoneView(fileTypes: [], text: "点击或拖拽文件到这里")
private var fileURLs: [URL]?
// 邮件地址
private var emailsString: String {
get { string(from: UserDefaults.standard.object(forKey: "EmailToolVC_RememberEmailString")) }
set { UserDefaults.standard.setValue(newValue, forKey: "EmailToolVC_RememberEmailString") }
}
private lazy var settingPopover: NSPopover = {
let settingPopover = NSPopover()
let vc = EmailSettingVC()
vc.closeHandle = {
settingPopover.close()
}
settingPopover.contentViewController = vc
return settingPopover
}()
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
func setupUI() {
if emailsString.count > 0 {
emailRecipientTF.stringValue = emailsString
rememberEmailButton.state = .on
} else {
rememberEmailButton.state = .off
}
fileDropZoneView.translatesAutoresizingMaskIntoConstraints = false
fileDropZoneView.delegate = self
view.addSubview(fileDropZoneView)
fileDropZoneView.snp.makeConstraints { (make) in
make.top.equalTo(multipleFilesButton.snp.bottom).offset(15)
make.left.equalToSuperview().offset(20)
make.right.equalToSuperview().offset(-20)
make.bottom.equalToSuperview().offset(-30)
}
}
func validEmail() -> [String]? {
let recipient = emailRecipientTF.stringValue
guard recipient.count > 0 else {
APHUD.hide(message: "收件人邮箱不能为空!", view: self.view, delayTime: 2)
return nil
}
let allEmails = recipient.components(separatedBy: [";", ";", ",", ","]).filter({!$0.isEmpty})
let emails = allEmails.filter({ isEmailValid($0) })
if emails.isEmpty {
APHUD.hide(message: "收件人邮箱格式不正确!", view: self.view, delayTime: 2)
return nil
}
return emails
}
@IBAction func clickedEmailSettingButton(_ sender: NSButton) {
if settingPopover.isShown {
settingPopover.performClose(self)
}else {
settingPopover.show(relativeTo: sender.bounds, of: sender, preferredEdge: NSRectEdge.minY)
}
}
@IBAction func sendEmailButton(_ sender: NSButton) {
guard let config = eamilConfigs else {
APHUD.hide(message: "请先设置邮箱服务器信息!", view: self.view, delayTime: 2)
return
}
// 收件人
guard let emails = validEmail() else {
return
}
sender.isEnabled = false
APHUD.show(message: "发送中...", view: self.view)
var title = emailTitleTF.stringValue
if title.isEmpty {
title = "邮件助手"
}
let contents = "<p style='white-space:pre-wrap;'>\(emailContentTextView.textStorage?.string ?? "")</p>".replacingOccurrences(of: "\n", with: "<br>")
var files = [String]()
fileURLs?.forEach({ url in
files.append(url.path)
})
// 发送邮件
EmailUtils.autoSendAtts(subject: "AppleParty — \(title)", recipients: emails, htmlContent: contents, attachmentFiles: files, config: config) { error in
DispatchQueue.main.async {
sender.isEnabled = true
APHUD.hide()
debugPrint(error as Any)
var msg = "邮箱发送成功~"
if let err = error {
msg = "邮箱发送失败:\(String(describing: err))"
}
NSAlert.show(msg)
}
}
}
@IBAction func rememberEmail(_ sender: Any) {
// 收件人
guard validEmail() != nil else {
return
}
emailsString = emailRecipientTF.stringValue
}
@IBAction func ChangeMultipleFiles(_ sender: NSButton) {
clearnAllFiles(clearnAllFilesButton)
if sender.state == .on {
fileDropZoneView.isHidden = true
selectFilesButton.isHidden = false
clearnAllFilesButton.isHidden = false
selectFilesView.isHidden = false
} else {
fileDropZoneView.reset()
fileDropZoneView.isHidden = false
selectFilesButton.isHidden = true
clearnAllFilesButton.isHidden = true
selectFilesView.isHidden = true
}
}
@IBAction func clearnAllFiles(_ sender: NSButton) {
fileURLs = []
selectilesTextView.string = ""
}
@IBAction func selectFiles(_ sender: Any) {
let openPanel = NSOpenPanel()
openPanel.canChooseFiles = true
openPanel.canChooseDirectories = false
openPanel.allowsMultipleSelection = true
openPanel.beginSheetModal(for: self.view.window!) { [self] (modalResponse) in
if modalResponse == .OK {
self.fileURLs?.append(contentsOf: openPanel.urls)
self.updateFilesView()
}
}
}
func updateFilesView() {
fileURLs?.forEach({ file in
let path = file.lastPathComponent
selectilesTextView.string.append(path + "\n")
})
selectilesTextView.scrollRangeToVisible(NSMakeRange(selectilesTextView.string.count, 0))
}
}
// MARK: - DropZoneViewDelegate
extension EmailToolVC: DropZoneViewDelegate {
func receivedFile(dropZoneView: DropZoneView, fileURL: URL) {
fileURLs = [fileURL]
}
func receivedMouseDown(dropZoneView: DropZoneView, theEvent: NSEvent) {
let openPanel = NSOpenPanel()
openPanel.canChooseFiles = true
openPanel.canChooseDirectories = false
openPanel.allowsMultipleSelection = false
openPanel.beginSheetModal(for: self.view.window!) { (modalResponse) in
if modalResponse == .OK {
if let fileURL = openPanel.url {
self.fileURLs = [fileURL]
dropZoneView.setFile(fileURL)
}
}
}
}
}
@@ -0,0 +1,174 @@
//
// APIPAUploadVC.swift
// AppleParty
//
// Created by HTC on 2022/5/12.
// Copyright © 2022 37 Mobile Games. All rights reserved.
//
import Cocoa
class APIPAUploadVC: NSViewController {
@IBOutlet weak var appIdTextView: NSTextField!
@IBOutlet weak var appIdTextField: NSTextField!
@IBOutlet weak var spasswordLbl: NSTextField!
@IBOutlet weak var submitBtn: NSButton!
//通过外界传入的 apple id时,不需要用户填写
var apple_id: String? {
didSet {
if let appId = apple_id {
appIdTextView.stringValue = appId
appIdTextView.isHidden = false
appIdTextField.isHidden = true
} else {
appIdTextView.stringValue = ""
appIdTextView.isHidden = true
appIdTextField.isHidden = false
}
}
}
private var ipaFileURL: URL?
private var fileDropZoneView = DropZoneView(fileTypes: [".ipa"], text: "点击或拖拽IPA到这里")
private var uploadModel = XMLModel()
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
updateSPasswordUI()
}
func setupUI() {
fileDropZoneView.translatesAutoresizingMaskIntoConstraints = false
fileDropZoneView.delegate = self
view.addSubview(fileDropZoneView)
fileDropZoneView.snp.makeConstraints { (make) in
make.top.equalTo(submitBtn.snp.bottom).offset(15)
make.left.equalToSuperview().offset(20)
make.right.equalToSuperview().offset(-20)
make.bottom.equalToSuperview().offset(-30)
}
}
func updateSPasswordUI() {
if let sp = UserCenter.shared.currentSPassword {
spasswordLbl.stringValue = "(当前选择:\(sp.account))"
} else {
spasswordLbl.stringValue = "(错误:当前未指定专用密码!)"
}
}
@IBAction func clickedSPasswordBtn(_ sender: NSButton) {
let vc = APSPasswordSettingVC()
vc.updateCompletion = { [weak self] ps in
self?.updateSPasswordUI()
}
presentAsSheet(vc)
}
@IBAction func clickedSubmitBtn(_ sender: NSButton) {
uploadIpaFile()
}
}
// MARK: - Private Method
extension APIPAUploadVC {
private func uploadIpaFile() {
var appId = appIdTextField.stringValue
if let appleId = apple_id {
appId = appleId
}
guard !appId.isEmpty else {
APHUD.hide(message: "请先填写 app id ~", delayTime: 1)
return
}
guard let sp = UserCenter.shared.currentSPassword else {
let vc = APSPasswordSettingVC()
vc.updateCompletion = { [weak self] spassword in
self?.uploadIpaFile()
}
presentAsSheet(vc)
APHUD.hide(message: "请先设置或指定专用密码~", delayTime: 1)
return
}
guard let ipaFileURL = ipaFileURL else {
APHUD.hide(message: "请先上传 ipa 文件~", delayTime: 1)
return
}
APHUD.show(message: "上传中", view: self.view)
DispatchQueue.global(qos: .userInitiated).async { [self] in
uploadModel = XMLModel()
uploadModel.apple_id = appId
uploadModel.ipa_size = ipaFileURL.fileSize()
uploadModel.ipa_md5 = ipaFileURL.fileMD5() ?? ""
uploadModel.filePaths = ["ipa.ipa": ipaFileURL.path]
// 获取创建 itms 文件的路径
let filePath = XMLManager.getIpaPath(appId)
// 先删除旧的文档
XMLManager.deleteITMS(filePath)
uploadModel.createIpaFile(directoryPath: filePath)
let result = XMLManager.uploadITMS(account: sp.account, pwd: sp.password, filePath: filePath)
DispatchQueue.main.async {
APHUD.hide()
self.closeSelfAndCallBack(result)
}
}
}
func closeSelfAndCallBack(_ result: (Int32, String?)) {
if result.0 == 0 {
NSAlert.show("ipa文件上传成功!稍后可在苹果后台查看~")
}else {
let sb = NSStoryboard(name: "APDebugVC", bundle: Bundle(for: self.classForCoder))
let newWC = sb.instantiateController(withIdentifier: "APDebugWC") as? NSWindowController
let logVC = newWC?.contentViewController as? APDebugVC
newWC?.window?.title = "ipa上传错误日志"
logVC?.debugLog = result.1 ?? ""
newWC?.showWindow(self)
}
}
}
// MARK: - DropZoneViewDelegate
extension APIPAUploadVC: DropZoneViewDelegate {
func receivedFile(dropZoneView: DropZoneView, fileURL: URL) {
ipaFileURL = fileURL
}
func receivedMouseDown(dropZoneView: DropZoneView, theEvent: NSEvent) {
let openPanel = NSOpenPanel()
openPanel.canChooseFiles = true
openPanel.canChooseDirectories = false
openPanel.allowsMultipleSelection = false
openPanel.allowedFileTypes = ["ipa"]
openPanel.beginSheetModal(for: self.view.window!) { (modalResponse) in
if modalResponse == .OK {
if let fileURL = openPanel.url {
self.ipaFileURL = fileURL
dropZoneView.setFile(fileURL)
}
}
}
}
}
@@ -0,0 +1,159 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Window Controller-->
<scene sceneID="BXd-4M-62l">
<objects>
<windowController storyboardIdentifier="IPAUpload" id="l7J-hr-7bm" sceneMemberID="viewController">
<window key="window" title="包体工具" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="5Sf-fH-lyY">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<rect key="contentRect" x="425" y="462" width="600" height="450"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
<view key="contentView" id="UUK-JK-BBb">
<rect key="frame" x="0.0" y="0.0" width="600" height="450"/>
<autoresizingMask key="autoresizingMask"/>
</view>
<connections>
<outlet property="delegate" destination="l7J-hr-7bm" id="Vt0-Yd-g5X"/>
</connections>
</window>
<connections>
<segue destination="xPR-tb-wUt" kind="relationship" relationship="window.shadowedContentViewController" id="eDd-Pz-xpA"/>
</connections>
</windowController>
<customObject id="D9C-vY-7M4" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-31" y="405"/>
</scene>
<!--UploadVC-->
<scene sceneID="VOc-VL-qdq">
<objects>
<viewController id="xPR-tb-wUt" customClass="APIPAUploadVC" customModule="AppleParty" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="ctU-VA-UqD">
<rect key="frame" x="0.0" y="0.0" width="600" height="450"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Te0-Hx-wTO">
<rect key="frame" x="33" y="406" width="82" height="19"/>
<constraints>
<constraint firstAttribute="height" constant="19" id="Rgk-aT-Iwk"/>
<constraint firstAttribute="width" constant="78" id="qg2-Md-a3M"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" title="Apple ID:" id="fkc-Rs-IVT">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" contentType="oneTimeCode" translatesAutoresizingMaskIntoConstraints="NO" id="Lhz-Ap-f59">
<rect key="frame" x="123" y="405" width="210" height="21"/>
<constraints>
<constraint firstAttribute="height" constant="21" id="n3H-wV-TtL"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" drawsBackground="YES" id="0Lr-Jh-f79">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Jq1-9w-hV3">
<rect key="frame" x="121" y="405" width="214" height="21"/>
<constraints>
<constraint firstAttribute="height" constant="21" id="6lr-jv-iUD"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" title="Label" id="uJY-N7-9LD">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZET-hK-4Dw">
<rect key="frame" x="33" y="362" width="82" height="19"/>
<constraints>
<constraint firstAttribute="height" constant="19" id="IIt-f5-FPd"/>
<constraint firstAttribute="width" constant="78" id="u0I-9g-53R"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" title="专用密码:" id="bhu-jM-Adb">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="F8M-wi-RjZ">
<rect key="frame" x="116" y="355" width="92" height="32"/>
<buttonCell key="cell" type="push" title="设置" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="pSe-lK-tax">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="clickedSPasswordBtn:" target="xPR-tb-wUt" id="0Uf-7X-evH"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="qR2-3g-X5d">
<rect key="frame" x="445" y="347" width="142" height="48"/>
<constraints>
<constraint firstAttribute="width" constant="128" id="PrA-4z-KZy"/>
<constraint firstAttribute="height" constant="36" id="xi3-s8-gg0"/>
</constraints>
<buttonCell key="cell" type="push" title="提交上传" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="nTI-pN-mGw">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
DQ
</string>
</buttonCell>
<connections>
<action selector="clickedSubmitBtn:" target="xPR-tb-wUt" id="xf5-GT-zom"/>
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="KEp-13-nh2">
<rect key="frame" x="209" y="364" width="237" height="16"/>
<constraints>
<constraint firstAttribute="height" constant="16" id="NPB-8q-bdI"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" title="Label" id="Ecv-9j-HZC">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="qR2-3g-X5d" secondAttribute="trailing" constant="20" symbolic="YES" id="0Uy-Lw-xpg"/>
<constraint firstItem="Jq1-9w-hV3" firstAttribute="centerY" secondItem="Te0-Hx-wTO" secondAttribute="centerY" id="6dM-w3-SvM"/>
<constraint firstItem="Lhz-Ap-f59" firstAttribute="leading" secondItem="Te0-Hx-wTO" secondAttribute="trailing" constant="10" id="8Ge-q1-Fzi"/>
<constraint firstItem="Jq1-9w-hV3" firstAttribute="width" secondItem="ctU-VA-UqD" secondAttribute="width" multiplier="0.35" id="FJr-Ge-3pT"/>
<constraint firstItem="Jq1-9w-hV3" firstAttribute="leading" secondItem="Te0-Hx-wTO" secondAttribute="trailing" constant="10" id="I4f-um-gMO"/>
<constraint firstItem="F8M-wi-RjZ" firstAttribute="centerY" secondItem="ZET-hK-4Dw" secondAttribute="centerY" id="ISv-kZ-IpT"/>
<constraint firstItem="F8M-wi-RjZ" firstAttribute="leading" secondItem="ZET-hK-4Dw" secondAttribute="trailing" constant="10" id="KHu-g2-Cm7"/>
<constraint firstItem="qR2-3g-X5d" firstAttribute="centerY" secondItem="F8M-wi-RjZ" secondAttribute="centerY" id="Nje-9G-sfa"/>
<constraint firstItem="Lhz-Ap-f59" firstAttribute="centerY" secondItem="Te0-Hx-wTO" secondAttribute="centerY" id="dKg-Mn-cCx"/>
<constraint firstItem="qR2-3g-X5d" firstAttribute="leading" secondItem="KEp-13-nh2" secondAttribute="trailing" constant="8" symbolic="YES" id="emp-Rf-Ffv"/>
<constraint firstItem="Te0-Hx-wTO" firstAttribute="top" secondItem="ctU-VA-UqD" secondAttribute="top" constant="25" id="fW0-CL-6J3"/>
<constraint firstItem="F8M-wi-RjZ" firstAttribute="width" secondItem="ZET-hK-4Dw" secondAttribute="width" id="kKn-eJ-49J"/>
<constraint firstItem="Lhz-Ap-f59" firstAttribute="width" secondItem="ctU-VA-UqD" secondAttribute="width" multiplier="0.35" id="kUG-Gm-30o"/>
<constraint firstItem="KEp-13-nh2" firstAttribute="centerY" secondItem="F8M-wi-RjZ" secondAttribute="centerY" id="rtO-eP-MzZ"/>
<constraint firstItem="ZET-hK-4Dw" firstAttribute="leading" secondItem="ctU-VA-UqD" secondAttribute="leading" constant="35" id="uFt-Iq-dPR"/>
<constraint firstItem="ZET-hK-4Dw" firstAttribute="top" secondItem="Te0-Hx-wTO" secondAttribute="bottom" constant="25" id="uXz-9Y-XME"/>
<constraint firstItem="Te0-Hx-wTO" firstAttribute="leading" secondItem="ctU-VA-UqD" secondAttribute="leading" constant="35" id="zPb-Pz-oFy"/>
<constraint firstItem="KEp-13-nh2" firstAttribute="leading" secondItem="F8M-wi-RjZ" secondAttribute="trailing" constant="10" id="zpc-3A-CaC"/>
</constraints>
</view>
<connections>
<outlet property="appIdTextField" destination="Lhz-Ap-f59" id="hfK-xk-cJS"/>
<outlet property="appIdTextView" destination="Jq1-9w-hV3" id="SFH-1T-gQ9"/>
<outlet property="spasswordLbl" destination="KEp-13-nh2" id="m2o-KO-BYO"/>
<outlet property="submitBtn" destination="qR2-3g-X5d" id="bA7-h8-oEq"/>
</connections>
</viewController>
<customObject id="NBS-xt-Oic" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="664" y="405"/>
</scene>
</scenes>
</document>
+15
View File
@@ -0,0 +1,15 @@
<?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>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>SUFeedURL</key>
<string>https://raw.githubusercontent.com/37iOS/AppleParty/main/AppleParty/SparkleUpdate/update.xml</string>
<key>SUPublicEDKey</key>
<string>ItVUtr4L9w9VfMGlzg7+cIcvSkruiygDcarlq8PTF7I=</string>
</dict>
</plist>
@@ -0,0 +1,199 @@
//
// APLogin2FAVC.swift
// AppleParty
//
// Created by HTC on 2022/3/17.
// Copyright © 2022 37 Mobile Games. All rights reserved.
//
import Cocoa
class APLogin2FAVC: NSViewController {
public var cancelHandle: (() -> Void)?
public var successHandle: (() -> Void)?
@IBOutlet weak var phoneListBtn: NSPopUpButton!
@IBOutlet weak var sendCodeBtn: NSButton!
@IBOutlet weak var voiceCodeBtn: NSButton!
@IBOutlet weak var phoneCodeView: NSTextField!
@IBOutlet weak var tipsWarningView: NSTextField!
@IBOutlet weak var trusDeviceBtn: NSButton!
@IBOutlet weak var indicatorView: NSProgressIndicator!
@IBOutlet weak var verifyBtn: NSButton!
private var numbers: [PNumber] = [] //验证手机号码列表
private var isPhoneSecurity = false //是否通过手机验证码来验证
// 验证码倒计时
private var verifyCodeTimer: Timer?
private var lastTime: Int = 30
override func viewDidLoad() {
super.viewDidLoad()
phoneCodeView.delegate = self
fetchPhoneList()
trusDeviceBtn.state = InfoCenter.shared.trusDevice ? .on : .off
}
@IBAction func clickedCancelBtn(_ sender: NSButton) {
closeView()
cancelHandle?()
}
@IBAction func clickedSendCodeBtn(_ sender: NSButton) {
submitSecurityCode()
}
@IBAction func changeVoiceCodeBtn(_ sender: NSButton) {
sendCodeBtn.title = sender.state == .on ? "拨打语音验证码" : "发送短信验证码"
}
@IBAction func clickedVerifyBtn(_ sender: NSButton) {
verifySecurityCode()
}
@IBAction func clickedTrusDeviceBtn(_ sender: NSButton) {
InfoCenter.shared.trusDevice = sender.state == .on ? true : false
}
}
// MARK: - 网络请求
extension APLogin2FAVC {
func fetchPhoneList() {
APClient.verifySecurityPhone(mode: "sms", phoneid: 0).request(showLoading: true) { [weak self] result, response, error in
if let err = error, let type = APClientErrorCode(rawValue: err.code) {
switch type {
case .privacyAcknowledgementRequired:
// 传了无效phoneid,进入选择手机号的流程
self?.phoneListBtn.removeAllItems()
let model = PhoneNumbers(body: result)
self?.numbers = model.numbers
for number in model.numbers {
self?.phoneListBtn.addItem(withTitle: number.num)
}
self?.phoneListBtn.selectItem(at: 0)
self?.showTips("一条包含验证码的信息已发送至您的设备。可输入设备验证码后点击验证以继续。\n或者点击“发送短信验证码”获取短信验证码。")
default:
APHUD.hide(message: err.localizedDescription)
}
}
}
}
func submitSecurityCode() {
isPhoneSecurity = true
let mode = voiceCodeBtn.state == .on ? "voice" : "sms"
let phoneId = numbers[phoneListBtn.indexOfSelectedItem].id
APClient.verifySecurityPhone(mode: mode, phoneid: phoneId).request(showLoading: true) { [weak self] result, response, error in
let code = response?.statusCode
if [200, 423].contains(code) {
let msg = self?.voiceCodeBtn.state == .on ? "请求拨打语音电话,请收听~" : "验证码已发送,请查收~"
self?.showTips(msg)
self?.verifyCodeCountdown()
} else {
self?.showTips("\(code ?? 0),\(error.debugDescription)")
}
}
}
func verifySecurityCode() {
let code = phoneCodeView.stringValue
let phoneId = numbers[phoneListBtn.indexOfSelectedItem].id
let mode = voiceCodeBtn.state == .on ? "voice" : "sms"
let type = isPhoneSecurity ? APClient.SecurityCode.sms(code: code, phoneNumberId: phoneId, mode: mode) : APClient.SecurityCode.device(code: code)
viewEnabled(false)
APClient.submitSecurityCode(code: type).request { [weak self] result, response, error in
self?.viewEnabled(true)
let code = response?.statusCode
switch code {
case 200, 201, 202, 203, 204:
self?.validateSession()
case 400:
let errors = dictionaryArray(result["service_errors"])
let msg = string(from: errors.first?["message"])
self?.showTips(msg)
default:
self?.showTips("\(code ?? 0),\(error.debugDescription)")
}
}
}
func validateSession() {
viewEnabled(false)
APClient.signInSession.request { [weak self] result, response, error in
self?.viewEnabled(true)
let code = response?.statusCode
switch code {
case 200, 201:
UserCenter.shared.isAuthorized = true
self?.successHandle?()
self?.closeView()
default:
let errors = dictionaryArray(result["serviceErrors"])
let msg = string(from: errors.first?["message"])
self?.showTips(msg.isEmpty ? error.debugDescription : msg)
}
}
}
}
// MARK: - 内部方法
extension APLogin2FAVC {
func closeView() {
guard let window = view.window, let parent = window.sheetParent
else { return }
parent.endSheet(window)
}
func showTips(_ text: String) {
if text.isEmpty {
tipsWarningView.isHidden = true
tipsWarningView.stringValue = ""
} else {
tipsWarningView.stringValue = text
tipsWarningView.isHidden = false
}
}
func viewEnabled(_ isEnabled: Bool) {
showTips("")
verifyBtn.isEnabled = isEnabled
isEnabled ? indicatorView.stopAnimation(nil) : indicatorView.startAnimation(nil)
}
func verifyCodeCountdown() {
self.lastTime = 30
self.sendCodeBtn.title = "\(self.lastTime)s 后重试"
self.sendCodeBtn.isEnabled = false
self.verifyCodeTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.verifyCodeTime), userInfo: nil, repeats: true)
}
// 验证码倒计时
@objc func verifyCodeTime() {
lastTime -= 1
sendCodeBtn.title = "\(self.lastTime)s 后重试"
if lastTime <= 0 {
sendCodeBtn.title = "重新发送验证码"
sendCodeBtn.isEnabled = true
verifyCodeTimer?.invalidate()
}
}
}
// MARK: - NSTextFieldDelegate
extension APLogin2FAVC: NSTextFieldDelegate {
func controlTextDidChange(_ obj: Notification) {
if phoneCodeView.stringValue.count == 6 {
verifyBtn.isEnabled = true
} else {
verifyBtn.isEnabled = false
}
}
}
@@ -0,0 +1,201 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="APLogin2FAVC" customModule="AppleParty" customModuleProvider="target">
<connections>
<outlet property="indicatorView" destination="rsC-Tg-KKs" id="OOV-8V-cF0"/>
<outlet property="phoneCodeView" destination="npy-jX-GQb" id="aXK-hj-Jg3"/>
<outlet property="phoneListBtn" destination="YPd-lO-U4e" id="Sg5-Hb-iUb"/>
<outlet property="sendCodeBtn" destination="rx9-Oh-Gtq" id="cYT-hl-Kc7"/>
<outlet property="tipsWarningView" destination="ijo-CH-sfg" id="PLO-uS-eBh"/>
<outlet property="trusDeviceBtn" destination="8SV-WX-fvn" id="kyK-aJ-vJK"/>
<outlet property="verifyBtn" destination="E5o-kz-kvG" id="kcn-Ez-LwY"/>
<outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/>
<outlet property="voiceCodeBtn" destination="kff-Qr-scI" id="khK-sS-hCc"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView id="Hz6-mo-xeY">
<rect key="frame" x="0.0" y="0.0" width="511" height="352"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BiF-4T-JA0">
<rect key="frame" x="53" y="8" width="94" height="37"/>
<constraints>
<constraint firstAttribute="height" constant="25" id="43L-Br-dDy"/>
<constraint firstAttribute="width" constant="80" id="OjC-db-Fqd"/>
</constraints>
<buttonCell key="cell" type="push" title="取消" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="IIa-Xh-1A4">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
Gw
</string>
</buttonCell>
<connections>
<action selector="clickedCancelBtn:" target="-2" id="KIQ-L6-769"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="E5o-kz-kvG">
<rect key="frame" x="364" y="8" width="94" height="37"/>
<constraints>
<constraint firstAttribute="height" constant="25" id="8Om-Qw-Vr5"/>
<constraint firstAttribute="width" constant="80" id="vyG-oy-cDO"/>
</constraints>
<buttonCell key="cell" type="push" title="验证" bezelStyle="rounded" alignment="center" enabled="NO" borderStyle="border" inset="2" id="HKe-2K-33K">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
DQ
</string>
</buttonCell>
<connections>
<action selector="clickedVerifyBtn:" target="-2" id="9hq-Pv-iKB"/>
</connections>
</button>
<progressIndicator maxValue="100" displayedWhenStopped="NO" indeterminate="YES" controlSize="small" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="rsC-Tg-KKs">
<rect key="frame" x="427" y="20" width="16" height="16"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="16" id="P2w-80-9Ta"/>
<constraint firstAttribute="height" constant="16" id="Qu4-Tu-Jfl"/>
<constraint firstAttribute="width" constant="16" id="WQE-Yc-Xrv"/>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="16" id="dQk-O7-QHd"/>
</constraints>
</progressIndicator>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BC7-jj-els">
<rect key="frame" x="28" y="307" width="455" height="25"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="25" id="gUT-eG-Utf"/>
<constraint firstAttribute="height" constant="25" id="s9O-0v-3Ni"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="双重认证" id="FQq-Av-0eD">
<font key="font" metaFont="system" size="18"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="YPd-lO-U4e">
<rect key="frame" x="187" y="248" width="268" height="30"/>
<constraints>
<constraint firstAttribute="height" constant="25" id="yKX-Nl-Hdr"/>
</constraints>
<popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" arrowPosition="arrowAtCenter" id="vpb-by-gTu">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="msT-eh-QNY"/>
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="vqk-uI-9AU">
<rect key="frame" x="58" y="257" width="124" height="15"/>
<constraints>
<constraint firstAttribute="height" constant="15" id="DII-jQ-Tud"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" title="选择用手机短信接收:" id="Lbc-GQ-7wH">
<font key="font" metaFont="cellTitle"/>
<color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="npy-jX-GQb">
<rect key="frame" x="60" y="127" width="391" height="35"/>
<constraints>
<constraint firstAttribute="height" constant="35" id="5C0-ZB-9O0"/>
</constraints>
<textFieldCell key="cell" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" focusRingType="none" alignment="center" placeholderString="请输入验证码" bezelStyle="round" id="Ukz-vL-Zpn">
<font key="font" metaFont="systemBold" size="20"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rx9-Oh-Gtq">
<rect key="frame" x="324" y="210" width="134" height="32"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="120" id="D0A-gg-DAF"/>
<constraint firstAttribute="height" constant="20" id="bE0-bG-WSX"/>
</constraints>
<buttonCell key="cell" type="push" title="发送短信验证码" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="54B-tN-4ys">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="clickedSendCodeBtn:" target="-2" id="UE7-hg-Lnj"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="kff-Qr-scI">
<rect key="frame" x="344" y="186" width="107" height="22"/>
<constraints>
<constraint firstAttribute="width" constant="105" id="MxK-Vd-EtE"/>
<constraint firstAttribute="height" constant="20" id="OqC-X2-qa4"/>
</constraints>
<buttonCell key="cell" type="check" title="拨打语音来电" bezelStyle="regularSquare" imagePosition="left" inset="2" id="gVy-us-tZh">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="changeVoiceCodeBtn:" target="-2" id="8jt-HG-YUL"/>
</connections>
</button>
<textField hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ijo-CH-sfg">
<rect key="frame" x="58" y="101" width="395" height="16"/>
<constraints>
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="500" id="53u-f7-HK1"/>
</constraints>
<textFieldCell key="cell" truncatesLastVisibleLine="YES" allowsUndo="NO" title="请输入正确的验证码" id="omC-g0-Aha">
<font key="font" metaFont="system"/>
<color key="textColor" name="systemOrangeColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8SV-WX-fvn">
<rect key="frame" x="283" y="20" width="80" height="16"/>
<constraints>
<constraint firstAttribute="height" constant="16" id="mX6-l7-rZj"/>
<constraint firstAttribute="width" constant="78" id="vDy-Dk-GSE"/>
</constraints>
<buttonCell key="cell" type="check" title="信任设备" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="VBA-AP-I7T">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" size="13" name="STSongti-SC-Regular"/>
</buttonCell>
<connections>
<action selector="clickedTrusDeviceBtn:" target="-2" id="iIm-cY-7Z6"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="BiF-4T-JA0" secondAttribute="bottom" constant="15" id="1Fq-AL-3Ws"/>
<constraint firstItem="E5o-kz-kvG" firstAttribute="centerY" secondItem="BiF-4T-JA0" secondAttribute="centerY" id="612-VZ-uMP"/>
<constraint firstAttribute="trailing" secondItem="YPd-lO-U4e" secondAttribute="trailing" constant="60" id="EJc-Vs-Afo"/>
<constraint firstAttribute="trailing" secondItem="rx9-Oh-Gtq" secondAttribute="trailing" constant="60" id="Euw-SM-wUx"/>
<constraint firstItem="YPd-lO-U4e" firstAttribute="leading" secondItem="vqk-uI-9AU" secondAttribute="trailing" constant="10" id="G4u-U5-hRW"/>
<constraint firstItem="vqk-uI-9AU" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="60" id="Kat-r9-QXI"/>
<constraint firstItem="8SV-WX-fvn" firstAttribute="centerY" secondItem="E5o-kz-kvG" secondAttribute="centerY" id="Pfn-8j-tgF"/>
<constraint firstItem="E5o-kz-kvG" firstAttribute="leading" secondItem="8SV-WX-fvn" secondAttribute="trailing" constant="8" id="Q3C-bW-QSW"/>
<constraint firstAttribute="trailing" secondItem="kff-Qr-scI" secondAttribute="trailing" constant="60" id="Sxo-QH-rVU"/>
<constraint firstAttribute="trailing" secondItem="npy-jX-GQb" secondAttribute="trailing" constant="60" id="WBf-2q-NVj"/>
<constraint firstItem="npy-jX-GQb" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="60" id="WxQ-Kb-Jj4"/>
<constraint firstAttribute="trailing" secondItem="BC7-jj-els" secondAttribute="trailing" constant="30" id="cTr-XH-o6b"/>
<constraint firstAttribute="trailing" secondItem="ijo-CH-sfg" secondAttribute="trailing" constant="60" id="ccV-Qd-svA"/>
<constraint firstItem="YPd-lO-U4e" firstAttribute="centerY" secondItem="vqk-uI-9AU" secondAttribute="centerY" id="eus-h0-cM0"/>
<constraint firstItem="npy-jX-GQb" firstAttribute="top" secondItem="kff-Qr-scI" secondAttribute="bottom" constant="25" id="fDH-b7-v0g"/>
<constraint firstAttribute="trailing" secondItem="E5o-kz-kvG" secondAttribute="trailing" constant="60" id="gdM-HS-dPr"/>
<constraint firstItem="rsC-Tg-KKs" firstAttribute="trailing" secondItem="E5o-kz-kvG" secondAttribute="trailing" constant="-8" id="hFD-yR-GoO"/>
<constraint firstItem="BiF-4T-JA0" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="60" id="nQA-ny-dXA"/>
<constraint firstItem="kff-Qr-scI" firstAttribute="top" secondItem="rx9-Oh-Gtq" secondAttribute="bottom" constant="10" id="oaD-ya-2cr"/>
<constraint firstItem="ijo-CH-sfg" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="60" id="p0O-Ua-B6c"/>
<constraint firstItem="vqk-uI-9AU" firstAttribute="top" secondItem="BC7-jj-els" secondAttribute="bottom" constant="35" id="p9I-Me-6fq"/>
<constraint firstItem="BC7-jj-els" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" constant="20" symbolic="YES" id="qEA-eh-nav"/>
<constraint firstItem="rsC-Tg-KKs" firstAttribute="centerY" secondItem="E5o-kz-kvG" secondAttribute="centerY" id="rlF-J0-c0j"/>
<constraint firstItem="ijo-CH-sfg" firstAttribute="top" secondItem="npy-jX-GQb" secondAttribute="bottom" constant="10" id="voh-Sr-6pf"/>
<constraint firstItem="rx9-Oh-Gtq" firstAttribute="top" secondItem="YPd-lO-U4e" secondAttribute="bottom" constant="15" id="vvK-es-FLv"/>
<constraint firstItem="BC7-jj-els" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="30" id="yL4-AI-CiL"/>
</constraints>
<point key="canvasLocation" x="154.5" y="194"/>
</customView>
</objects>
</document>
@@ -0,0 +1,234 @@
//
// APLoginVC.swift
// AppleParty
//
// Created by HTC on 2022/3/17.
// Copyright © 2022 37 Mobile Games. All rights reserved.
//
import Cocoa
class APLoginVC: NSViewController {
public var cancelHandle: (() -> Void)?
public var successHandle: (() -> Void)?
@IBOutlet weak var accountView: NSTextField!
@IBOutlet weak var passwordView: NSSecureTextField!
// 历史账号
@IBOutlet weak var historyBox: NSBox!
@IBOutlet weak var tableView: NSTableView!
@IBOutlet weak var tipsWarningView: NSTextField!
@IBOutlet weak var autoLoginBtn: NSButton!
@IBOutlet weak var indicatorView: NSProgressIndicator!
@IBOutlet weak var loginBtn: NSButton!
override func viewDidLoad() {
super.viewDidLoad()
accountView.delegate = self
passwordView.delegate = self
tipsWarningView.maximumNumberOfLines = 5
// 最近登录的账号
let user = UserCenter.shared.loginedUser
let name = user.appleid
let pwd = user.password
guard name.count > 0, pwd.count > 0 else { return }
accountView.stringValue = name
passwordView.stringValue = pwd
viewEnabled(true)
// 如果需要自动登录
if UserCenter.shared.isAutoLogin || UserCenter.shared.isFirstTime {
loginAccount()
UserCenter.shared.isFirstTime = false
}
}
@IBAction func clickedCancelBtn(_ sender: NSButton) {
closeView()
cancelHandle?()
}
@IBAction func showAccountHistoryList(_ sender: Any) {
if historyBox.isHidden {
tableView.delegate = self
tableView.dataSource = self
tableView.reloadData()
}
historyBox.isHidden = !historyBox.isHidden
}
@IBAction func clickedLoginBtn(_ sender: NSButton) {
loginAccount()
}
}
// MARK: - 网络请求
extension APLoginVC {
func loginAccount() {
if accountView.stringValue.isEmpty {
showTips("苹果账号不能为空!")
return
}
if passwordView.stringValue.isEmpty {
showTips("密码不能为空!")
return
}
viewEnabled(false)
let account = accountView.stringValue
let pwd = passwordView.stringValue
APClient.signIn(account: account, password: pwd).request { [weak self] result, response, error in
self?.viewEnabled(true)
if let err = error, let type = APClientErrorCode(rawValue: err.code) {
switch type {
case .notAuthorized:
self?.showTips("Apple ID 或密码不正确")
case .twoStepOrFactor:
// 保存账号密码
if self?.autoLoginBtn.state == .on {
UserCenter.shared.isAutoLogin = true
UserCenter.shared.loginedUser = User(appleid: account, password: pwd)
}
// 双重认证
let vc = APLogin2FAVC()
vc.cancelHandle = { [weak self] in
self?.viewEnabled(true)
}
vc.successHandle = { [weak self] in
self?.trusDevice()
self?.successHandle?()
self?.closeView()
}
let pannel = NSPanel(contentViewController: vc)
pannel.setFrame(NSRect(origin: .zero, size: NSSize(width: 500, height: 360)), display: true)
self?.view.window?.beginSheet(pannel, completionHandler: nil)
default:
self?.showTips(err.localizedDescription)
}
return
}
let code = response?.statusCode
// 登陆态有效
if code == 200 {
// 保存账号密码
if self?.autoLoginBtn.state == .on {
UserCenter.shared.isAutoLogin = true
UserCenter.shared.loginedUser = User(appleid: account, password: pwd)
}
self?.validateSession()
} else {
self?.showTips("\(code ?? 0),\(error.debugDescription)")
}
}
}
func validateSession() {
viewEnabled(false)
APClient.signInSession.request { [weak self] result, response, error in
self?.viewEnabled(true)
let code = response?.statusCode
switch code {
case 200, 201:
UserCenter.shared.isAuthorized = true
self?.trusDevice()
self?.successHandle?()
self?.closeView()
default:
let errors = dictionaryArray(result["serviceErrors"])
let msg = string(from: errors.first?["message"])
self?.showTips(msg.isEmpty ? error.debugDescription : msg)
}
}
}
func trusDevice() {
guard InfoCenter.shared.trusDevice else { return}
APClient.trusDevice(isTrus: true).request { result, response, error in
if response?.statusCode == 204 {
debugPrint("信任设备成功~")
}
}
}
}
// MARK: - 内部方法
extension APLoginVC {
func showTips(_ text: String) {
if text.isEmpty {
tipsWarningView.isHidden = true
tipsWarningView.stringValue = ""
} else {
tipsWarningView.stringValue = text
tipsWarningView.isHidden = false
}
}
func viewEnabled(_ isEnabled: Bool) {
showTips("")
loginBtn.isEnabled = isEnabled
historyBox.isHidden = true
isEnabled ? indicatorView.stopAnimation(nil) : indicatorView.startAnimation(nil)
}
func closeView() {
guard let window = view.window, let parent = window.sheetParent
else { return }
parent.endSheet(window)
}
}
// MARK: - NSTextFieldDelegate
extension APLoginVC: NSTextFieldDelegate {
func controlTextDidChange(_ obj: Notification) {
if !accountView.stringValue.isEmpty && !passwordView.stringValue.isEmpty {
loginBtn.isEnabled = true
} else {
loginBtn.isEnabled = false
showTips("")
}
}
}
// MARK: - NSTableViewDelegate
extension APLoginVC: NSTableViewDelegate, NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
return UserCenter.shared.historyUser.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
if tableColumn?.identifier == NSUserInterfaceItemIdentifier(rawValue: "nameColumn") {
let cellIdentifier = NSUserInterfaceItemIdentifier(rawValue: "nameCell")
guard let cellView = tableView.makeView(withIdentifier: cellIdentifier, owner: self) as? NSTableCellView else { return nil }
cellView.textField?.stringValue = UserCenter.shared.historyUser[row].appleid
return cellView
}
return nil
}
func tableViewSelectionDidChange(_ notification: Notification){
let tableView = notification.object as! NSTableView
let clickedRow = tableView.selectedRow
guard clickedRow >= 0 else {
return
}
tableView.deselectRow(clickedRow)
accountView.stringValue = UserCenter.shared.historyUser[clickedRow].appleid
passwordView.stringValue = UserCenter.shared.historyUser[clickedRow].password
showAccountHistoryList(clickedRow)
}
}
@@ -0,0 +1,258 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="APLoginVC" customModule="AppleParty" customModuleProvider="target">
<connections>
<outlet property="accountView" destination="7Gg-CB-YAd" id="HvI-ne-iPZ"/>
<outlet property="autoLoginBtn" destination="aZC-zX-iLA" id="QDv-Kf-saw"/>
<outlet property="historyBox" destination="w4L-Kl-k0V" id="4gE-iK-bgm"/>
<outlet property="indicatorView" destination="3JL-7f-Xfx" id="ebS-gn-1fo"/>
<outlet property="loginBtn" destination="Idm-D5-Bl1" id="V3g-00-OAu"/>
<outlet property="passwordView" destination="Jin-lZ-ObE" id="sWU-2K-aBK"/>
<outlet property="tableView" destination="Hst-cR-jaZ" id="PWz-Me-Trw"/>
<outlet property="tipsWarningView" destination="A1W-oM-xUt" id="O66-oN-G3J"/>
<outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView id="Hz6-mo-xeY">
<rect key="frame" x="0.0" y="0.0" width="480" height="301"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="sBv-Ga-3Ig">
<rect key="frame" x="8" y="256" width="464" height="25"/>
<constraints>
<constraint firstAttribute="height" constant="25" id="dMh-mt-gPl"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="App Store Connect" id="4jB-x9-D95">
<font key="font" metaFont="systemBold" size="18"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" verticalHuggingPriority="750" textCompletion="NO" contentType="username" translatesAutoresizingMaskIntoConstraints="NO" id="7Gg-CB-YAd">
<rect key="frame" x="60" y="176" width="360" height="45"/>
<constraints>
<constraint firstAttribute="height" constant="45" id="S1v-qX-ZK3"/>
</constraints>
<textFieldCell key="cell" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" focusRingType="none" alignment="left" placeholderString="请输入苹果账号" usesSingleLineMode="YES" bezelStyle="round" id="EKF-vP-tSd">
<font key="font" metaFont="system" size="16"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button translatesAutoresizingMaskIntoConstraints="NO" id="oho-cw-blu">
<rect key="frame" x="379" y="186" width="26.5" height="26"/>
<constraints>
<constraint firstAttribute="width" constant="26" id="20z-6h-Nzr"/>
<constraint firstAttribute="height" constant="26" id="tTj-x3-L8V"/>
</constraints>
<buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="NSTouchBarGoDownTemplate" imagePosition="only" alignment="center" state="on" imageScaling="proportionallyUpOrDown" inset="2" id="6R3-hY-fkM">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="showAccountHistoryList:" target="-2" id="KJ0-C0-1ma"/>
</connections>
</button>
<secureTextField focusRingType="none" verticalHuggingPriority="750" contentType="password" translatesAutoresizingMaskIntoConstraints="NO" id="Jin-lZ-ObE">
<rect key="frame" x="60" y="106" width="360" height="45"/>
<constraints>
<constraint firstAttribute="height" constant="45" id="wdb-sF-BFJ"/>
</constraints>
<secureTextFieldCell key="cell" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" focusRingType="none" placeholderString="请输入密码" usesSingleLineMode="YES" bezelStyle="round" id="TVN-dn-g47">
<font key="font" metaFont="system" size="16"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<allowedInputSourceLocales>
<string>NSAllRomanInputSourcesLocaleIdentifier</string>
</allowedInputSourceLocales>
</secureTextFieldCell>
</secureTextField>
<textField hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="A1W-oM-xUt">
<rect key="frame" x="58" y="80" width="364" height="16"/>
<constraints>
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="500" id="ErD-KS-kJh"/>
</constraints>
<textFieldCell key="cell" truncatesLastVisibleLine="YES" allowsUndo="NO" title="请输入正确的苹果账号" id="Los-Ya-nHe">
<font key="font" metaFont="system"/>
<color key="textColor" name="systemOrangeColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Idm-D5-Bl1">
<rect key="frame" x="333" y="13" width="94" height="37"/>
<constraints>
<constraint firstAttribute="width" constant="80" id="VNU-X5-3RU"/>
<constraint firstAttribute="height" constant="25" id="edt-sA-1o8"/>
</constraints>
<buttonCell key="cell" type="push" title="登陆" bezelStyle="rounded" alignment="center" enabled="NO" borderStyle="border" inset="2" id="ack-oU-iyj">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
DQ
</string>
</buttonCell>
<connections>
<action selector="clickedLoginBtn:" target="-2" id="4L9-TV-8kK"/>
</connections>
</button>
<progressIndicator maxValue="100" displayedWhenStopped="NO" indeterminate="YES" controlSize="small" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="3JL-7f-Xfx">
<rect key="frame" x="396" y="25" width="16" height="16"/>
<constraints>
<constraint firstAttribute="height" constant="16" id="Ayq-6g-553"/>
<constraint firstAttribute="width" constant="16" id="La3-z9-Sui"/>
</constraints>
</progressIndicator>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="xXW-Zy-0Qc">
<rect key="frame" x="53" y="13" width="94" height="37"/>
<constraints>
<constraint firstAttribute="width" constant="80" id="ADG-NQ-ah3"/>
<constraint firstAttribute="height" constant="25" id="J7q-04-O9h"/>
</constraints>
<buttonCell key="cell" type="push" title="取消" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="jWN-Ih-eRv">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
Gw
</string>
</buttonCell>
<connections>
<action selector="clickedCancelBtn:" target="-2" id="hIR-7f-UuB"/>
</connections>
</button>
<box hidden="YES" boxType="custom" cornerRadius="3" translatesAutoresizingMaskIntoConstraints="NO" id="w4L-Kl-k0V">
<rect key="frame" x="60" y="89" width="360" height="87"/>
<view key="contentView" id="h0y-kI-zOZ">
<rect key="frame" x="1" y="1" width="358" height="85"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="57z-B0-8Z4">
<rect key="frame" x="0.0" y="0.0" width="358" height="85"/>
<clipView key="contentView" ambiguous="YES" id="cZ6-vs-KNu">
<rect key="frame" x="1" y="1" width="356" height="83"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" ambiguous="YES" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnSelection="YES" multipleSelection="NO" autosaveColumns="NO" rowSizeStyle="automatic" viewBased="YES" id="Hst-cR-jaZ">
<rect key="frame" x="0.0" y="0.0" width="356" height="83"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="gridColor" name="keyboardFocusIndicatorColor" catalog="System" colorSpace="catalog"/>
<tableColumns>
<tableColumn identifier="nameColumn" width="293" minWidth="40" maxWidth="1000" id="Elj-Mz-Pc2">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="7J8-oV-aGc">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView identifier="nameCell" id="vOy-rZ-Yvz">
<rect key="frame" x="1" y="1" width="302" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="VBI-EM-0Az">
<rect key="frame" x="0.0" y="1" width="302" height="16"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="lel-L2-fmK">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="VBI-EM-0Az" firstAttribute="centerY" secondItem="vOy-rZ-Yvz" secondAttribute="centerY" id="b5F-uT-Da1"/>
<constraint firstItem="VBI-EM-0Az" firstAttribute="centerX" secondItem="vOy-rZ-Yvz" secondAttribute="centerX" id="mCL-MA-lFT"/>
<constraint firstItem="VBI-EM-0Az" firstAttribute="leading" secondItem="vOy-rZ-Yvz" secondAttribute="leading" constant="2" id="qWM-YN-llR"/>
</constraints>
<connections>
<outlet property="textField" destination="VBI-EM-0Az" id="yNq-5s-Bj4"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
</tableColumns>
</tableView>
</subviews>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</clipView>
<constraints>
<constraint firstAttribute="height" constant="85" id="3Ms-uE-pue"/>
</constraints>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="NQa-5q-5Ii">
<rect key="frame" x="1" y="118" width="238" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="JCe-qU-DMF">
<rect key="frame" x="224" y="17" width="15" height="102"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="57z-B0-8Z4" secondAttribute="trailing" id="n5T-rg-MeV"/>
<constraint firstItem="57z-B0-8Z4" firstAttribute="top" secondItem="h0y-kI-zOZ" secondAttribute="top" id="sUR-ay-LmY"/>
<constraint firstAttribute="bottom" secondItem="57z-B0-8Z4" secondAttribute="bottom" id="su9-ZN-m2r"/>
<constraint firstItem="57z-B0-8Z4" firstAttribute="leading" secondItem="h0y-kI-zOZ" secondAttribute="leading" id="w9o-Jf-QKB"/>
</constraints>
</view>
<color key="borderColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="fillColor" name="keyboardFocusIndicatorColor" catalog="System" colorSpace="catalog"/>
</box>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="aZC-zX-iLA">
<rect key="frame" x="252" y="24" width="80" height="18"/>
<constraints>
<constraint firstAttribute="height" constant="16" id="L2B-IE-Iw5"/>
<constraint firstAttribute="width" constant="78" id="UAX-pP-1Ux"/>
</constraints>
<buttonCell key="cell" type="check" title="自动登陆" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="1M1-Hc-ZVP">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" size="13" name=".PingFangSC-Regular"/>
</buttonCell>
</button>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="Idm-D5-Bl1" secondAttribute="bottom" constant="20" symbolic="YES" id="2tD-oH-o3G"/>
<constraint firstItem="sBv-Ga-3Ig" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" constant="20" symbolic="YES" id="4dv-jZ-P43"/>
<constraint firstItem="A1W-oM-xUt" firstAttribute="top" secondItem="Jin-lZ-ObE" secondAttribute="bottom" constant="10" id="9xE-bu-hFg"/>
<constraint firstAttribute="trailing" secondItem="sBv-Ga-3Ig" secondAttribute="trailing" constant="10" id="AkC-oy-LGi"/>
<constraint firstItem="oho-cw-blu" firstAttribute="centerY" secondItem="7Gg-CB-YAd" secondAttribute="centerY" id="AmX-5A-xy6"/>
<constraint firstItem="xXW-Zy-0Qc" firstAttribute="leading" secondItem="7Gg-CB-YAd" secondAttribute="leading" id="Bpi-sa-Ze6"/>
<constraint firstItem="aZC-zX-iLA" firstAttribute="centerY" secondItem="Idm-D5-Bl1" secondAttribute="centerY" id="CBg-Vo-SEB"/>
<constraint firstItem="7Gg-CB-YAd" firstAttribute="top" secondItem="sBv-Ga-3Ig" secondAttribute="bottom" constant="35" id="Jcm-UL-9NZ"/>
<constraint firstAttribute="trailing" secondItem="A1W-oM-xUt" secondAttribute="trailing" constant="60" id="QMh-YH-RXq"/>
<constraint firstItem="w4L-Kl-k0V" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="60" id="RNm-RF-ypF"/>
<constraint firstItem="7Gg-CB-YAd" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="60" id="RdU-Dr-MaJ"/>
<constraint firstItem="Idm-D5-Bl1" firstAttribute="leading" secondItem="aZC-zX-iLA" secondAttribute="trailing" constant="8" id="SeL-eF-Ry3"/>
<constraint firstAttribute="trailing" secondItem="7Gg-CB-YAd" secondAttribute="trailing" constant="60" id="TSS-Ed-8Xq"/>
<constraint firstItem="w4L-Kl-k0V" firstAttribute="top" secondItem="7Gg-CB-YAd" secondAttribute="bottom" id="U5l-fh-Scp"/>
<constraint firstItem="xXW-Zy-0Qc" firstAttribute="centerY" secondItem="Idm-D5-Bl1" secondAttribute="centerY" id="UD7-om-oum"/>
<constraint firstItem="Idm-D5-Bl1" firstAttribute="trailing" secondItem="7Gg-CB-YAd" secondAttribute="trailing" id="cvS-ic-j7R"/>
<constraint firstItem="3JL-7f-Xfx" firstAttribute="centerY" secondItem="Idm-D5-Bl1" secondAttribute="centerY" id="dyq-1d-Hfr"/>
<constraint firstItem="A1W-oM-xUt" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="60" id="eP5-pI-Qi5"/>
<constraint firstItem="oho-cw-blu" firstAttribute="trailing" secondItem="7Gg-CB-YAd" secondAttribute="trailing" constant="-15" id="f7e-fo-miL"/>
<constraint firstItem="3JL-7f-Xfx" firstAttribute="trailing" secondItem="Idm-D5-Bl1" secondAttribute="trailing" constant="-8" id="mnS-Xu-Lmx"/>
<constraint firstItem="sBv-Ga-3Ig" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="10" id="qrQ-Bw-EaS"/>
<constraint firstItem="Jin-lZ-ObE" firstAttribute="top" secondItem="7Gg-CB-YAd" secondAttribute="bottom" constant="25" id="sd5-U5-yuf"/>
<constraint firstAttribute="trailing" secondItem="w4L-Kl-k0V" secondAttribute="trailing" constant="60" id="veQ-tN-yXm"/>
<constraint firstAttribute="trailing" secondItem="Jin-lZ-ObE" secondAttribute="trailing" constant="60" id="zBA-Lg-g3u"/>
<constraint firstItem="Jin-lZ-ObE" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="60" id="zzz-se-IG0"/>
</constraints>
<point key="canvasLocation" x="-202" y="175.5"/>
</customView>
</objects>
<resources>
<image name="NSTouchBarGoDownTemplate" width="16" height="9"/>
</resources>
</document>
@@ -0,0 +1,137 @@
//
// APWebLoginVC.swift
// AppleParty
//
// Created by HTC on 2024/10/29.
// Copyright © 2024 37 Mobile Games. All rights reserved.
//
import Cocoa
class APWebLoginVC: NSViewController {
public var cancelHandle: (() -> Void)?
public var successHandle: (() -> Void)?
private var webCore: AppleWebLoginCore? = nil
@IBOutlet weak var loginBtn: NSButton!
@IBOutlet weak var cancelBtn: NSButton!
@IBOutlet weak var indicatorView: NSProgressIndicator!
@IBOutlet weak var tipsWarningView: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
}
@IBAction func clickedCancelBtn(_ sender: NSButton) {
closeView()
cancelHandle?()
}
@IBAction func clickedLoginBtn(_ sender: NSButton) {
validateSession()
}
// 判断是登陆态是否过期
func validateSession() {
viewEnabled(false)
APClient.signInSession.request { [weak self] result, response, error in
self?.viewEnabled(true)
let code = response?.statusCode
switch code {
case 200, 201:
UserCenter.shared.isAuthorized = true
self?.successHandle?()
self?.closeView()
default:
let errors = dictionaryArray(result["serviceErrors"])
let msg = string(from: errors.first?["message"])
self?.showTips(msg.isEmpty ? error.debugDescription : msg)
// 隐藏按钮透视显示
self?.cancelBtn.isEnabled = false
self?.loginBtn.isEnabled = false
self?.loginWithWeb()
}
}
}
func loginWithWeb() {
let appleWebLoginCore = AppleWebLoginCore()
// 将 webView 添加到视图层次结构中
self.view.addSubview(appleWebLoginCore.webView)
let closeButton = NSButton(title: "取消", target: self, action: #selector(closeButtonClicked))
closeButton.attributedTitle = NSAttributedString(string: "取消", attributes: [NSAttributedString.Key.foregroundColor: NSColor.gray])
closeButton.keyEquivalent = "\u{1B}" // `esc` 快捷键
appleWebLoginCore.webView.addSubview(closeButton)
// 设置 webView 的约束以适应视图
appleWebLoginCore.webView.translatesAutoresizingMaskIntoConstraints = false
closeButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
appleWebLoginCore.webView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
appleWebLoginCore.webView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
appleWebLoginCore.webView.topAnchor.constraint(equalTo: self.view.topAnchor),
appleWebLoginCore.webView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
closeButton.trailingAnchor.constraint(equalTo: appleWebLoginCore.webView.trailingAnchor, constant: -10),
closeButton.topAnchor.constraint(equalTo: appleWebLoginCore.webView.topAnchor, constant: 10)
])
// 关闭按钮
appleWebLoginCore.installFirstLoadCompleteTrap {
// 处理首次加载完成的逻辑
print("First load complete")
}
appleWebLoginCore.installCredentialPopulationTrap { token, cookies in
// 处理凭据填充的逻辑
print("Received cookies: \(cookies)")
print("Received token: \(token)")
if let cks = APClientSession.shared.config.httpCookieStorage?.cookies {
for ck in cks {
APClientSession.shared.config.httpCookieStorage?.deleteCookie(ck)
}
}
for cookie in cookies {
APClientSession.shared.config.httpCookieStorage?.setCookie(cookie)
}
// APClientSession.shared.config.headers.update(name: "Cookie", value: "myacinfo=\(token);")
self.validateSession()
}
self.webCore = appleWebLoginCore
}
@objc func closeButtonClicked() {
// 处理关闭按钮的点击事件
print("关闭按钮被点击")
closeView()
}
func viewEnabled(_ isEnabled: Bool) {
showTips("")
loginBtn.isEnabled = isEnabled
isEnabled ? indicatorView.stopAnimation(nil) : indicatorView.startAnimation(nil)
}
func showTips(_ text: String) {
if text.isEmpty {
tipsWarningView.isHidden = true
tipsWarningView.stringValue = ""
} else {
tipsWarningView.stringValue = text
tipsWarningView.isHidden = false
}
}
func closeView() {
guard let window = view.window, let parent = window.sheetParent
else { return }
parent.endSheet(window)
}
}
@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="23094" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="23094"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="APWebLoginVC" customModule="AppleParty" customModuleProvider="target">
<connections>
<outlet property="cancelBtn" destination="qxM-hm-PWT" id="UAd-lY-N0a"/>
<outlet property="indicatorView" destination="dtJ-5J-DxP" id="zQo-tM-9d5"/>
<outlet property="loginBtn" destination="umT-YS-2Tw" id="oY4-Wq-xzj"/>
<outlet property="tipsWarningView" destination="3FV-OQ-nKf" id="NkT-JR-Dnu"/>
<outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView id="Hz6-mo-xeY">
<rect key="frame" x="0.0" y="0.0" width="480" height="300"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="1rM-sz-rlk">
<rect key="frame" x="8" y="260" width="464" height="25"/>
<constraints>
<constraint firstAttribute="height" constant="25" id="tve-kf-cij"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="App Store Connect" id="VHb-gH-1GO">
<font key="font" metaFont="systemBold" size="18"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="umT-YS-2Tw">
<rect key="frame" x="193" y="65" width="94" height="37"/>
<buttonCell key="cell" type="push" title="登录" bezelStyle="rounded" alignment="center" borderStyle="border" inset="2" id="ReJ-dw-xtp">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
DQ
</string>
</buttonCell>
<constraints>
<constraint firstAttribute="width" constant="80" id="Ebj-j4-bbZ"/>
<constraint firstAttribute="height" constant="25" id="erd-oj-9Nc"/>
</constraints>
<connections>
<action selector="clickedLoginBtn:" target="-2" id="bzx-ik-z4B"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="qxM-hm-PWT">
<rect key="frame" x="193" y="28" width="94" height="37"/>
<buttonCell key="cell" type="push" title="取消" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Fhv-gb-seN">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
Gw
</string>
</buttonCell>
<constraints>
<constraint firstAttribute="height" constant="25" id="Zo8-yT-7mB"/>
<constraint firstAttribute="width" constant="80" id="aWd-Lq-3SZ"/>
</constraints>
<connections>
<action selector="clickedCancelBtn:" target="-2" id="Y33-Od-paG"/>
</connections>
</button>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="3FV-OQ-nKf">
<rect key="frame" x="13" y="120" width="454" height="80"/>
<constraints>
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="550" id="Fad-vK-Ru4"/>
<constraint firstAttribute="height" constant="80" id="tdV-WD-iKH"/>
</constraints>
<textFieldCell key="cell" truncatesLastVisibleLine="YES" enabled="NO" allowsUndo="NO" alignment="center" id="i97-j7-mQi">
<font key="font" size="14" name=".PingFangUITextSC-Regular"/>
<string key="title">提示:
登录通过 https://appstoreconnect.apple.com 网页进行授权</string>
<color key="textColor" name="systemOrangeColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<progressIndicator maxValue="100" displayedWhenStopped="NO" indeterminate="YES" controlSize="small" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="dtJ-5J-DxP">
<rect key="frame" x="262" y="77" width="16" height="16"/>
<constraints>
<constraint firstAttribute="height" constant="16" id="5Gc-Ad-yiX"/>
<constraint firstAttribute="width" constant="16" id="l8L-wm-OxQ"/>
</constraints>
</progressIndicator>
</subviews>
<constraints>
<constraint firstItem="dtJ-5J-DxP" firstAttribute="centerY" secondItem="umT-YS-2Tw" secondAttribute="centerY" id="2ql-XU-Oy4"/>
<constraint firstAttribute="bottom" secondItem="qxM-hm-PWT" secondAttribute="bottom" constant="35" id="6oR-TQ-5jE"/>
<constraint firstItem="3FV-OQ-nKf" firstAttribute="centerY" secondItem="Hz6-mo-xeY" secondAttribute="centerY" constant="-10" id="Zus-xv-zaI"/>
<constraint firstItem="dtJ-5J-DxP" firstAttribute="centerX" secondItem="Hz6-mo-xeY" secondAttribute="centerX" constant="30" id="azK-M6-XaT"/>
<constraint firstAttribute="trailing" secondItem="3FV-OQ-nKf" secondAttribute="trailing" constant="15" id="div-BZ-Amk"/>
<constraint firstItem="1rM-sz-rlk" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="10" id="dld-UK-NGQ"/>
<constraint firstItem="1rM-sz-rlk" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" constant="15" id="err-Ny-q6f"/>
<constraint firstItem="3FV-OQ-nKf" firstAttribute="centerX" secondItem="Hz6-mo-xeY" secondAttribute="centerX" id="f7R-Fn-bmc"/>
<constraint firstItem="umT-YS-2Tw" firstAttribute="centerX" secondItem="Hz6-mo-xeY" secondAttribute="centerX" id="fsS-52-w7f"/>
<constraint firstItem="3FV-OQ-nKf" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="15" id="fyj-Ld-adi"/>
<constraint firstAttribute="trailing" secondItem="1rM-sz-rlk" secondAttribute="trailing" constant="10" id="g6G-H2-CGw"/>
<constraint firstItem="qxM-hm-PWT" firstAttribute="centerX" secondItem="Hz6-mo-xeY" secondAttribute="centerX" id="l1Y-kM-2vx"/>
<constraint firstItem="qxM-hm-PWT" firstAttribute="top" secondItem="umT-YS-2Tw" secondAttribute="bottom" constant="12" id="ul4-6v-5hV"/>
</constraints>
<point key="canvasLocation" x="69" y="154"/>
</customView>
</objects>
</document>
@@ -0,0 +1,127 @@
//
// AppleWebLoginCore.swift
// AppleWebLogin
//
// Created by 秋星桥 on 2024/10/23.
// ref: https://github.com/Lakr233/AppleWebLogin
import Combine
@preconcurrency import WebKit
//private let loginURL = URL(string: "https://account.apple.com/sign-in")!
private let loginURL = URL(string: "https://appstoreconnect.apple.com/login")!
public class AppleWebLoginCore: NSObject, WKUIDelegate, WKNavigationDelegate {
var webView: WKWebView {
associatedWebView
}
private let associatedWebView: WKWebView
private var dataPopulationTimer: Timer? = nil
private var firstLoadComplete = false
public private(set) var onFirstLoadComplete: (() -> Void)?
public var onCredentialPopulation: ((String, [HTTPCookie]) -> Void)?
override public init() {
let contentController = WKUserContentController()
let configuration = WKWebViewConfiguration()
configuration.defaultWebpagePreferences.allowsContentJavaScript = true
configuration.userContentController = contentController
configuration.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs")
configuration.websiteDataStore = .nonPersistent()
associatedWebView = .init(
frame: CGRect(x: 0, y: 0, width: 1920, height: 1080),
configuration: configuration
)
associatedWebView.isHidden = true
super.init()
associatedWebView.uiDelegate = self
associatedWebView.navigationDelegate = self
associatedWebView.load(.init(url: loginURL))
#if DEBUG
if associatedWebView.responds(to: Selector(("setInspectable:"))) {
associatedWebView.perform(Selector(("setInspectable:")), with: true)
}
#endif
let dataPopulationTimer = Timer(timeInterval: 1, repeats: true) { [weak self] _ in
guard let self else { return }
removeUnwantedElements()
populateData()
}
RunLoop.main.add(dataPopulationTimer, forMode: .common)
self.dataPopulationTimer = dataPopulationTimer
}
deinit {
dataPopulationTimer?.invalidate()
onCredentialPopulation = nil
}
public func webView(_: WKWebView, didFinish _: WKNavigation!) {
guard !firstLoadComplete else { return }
defer { firstLoadComplete = true }
associatedWebView.isHidden = false
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.onFirstLoadComplete?()
self.onFirstLoadComplete = nil
}
}
// public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, preferences: WKWebpagePreferences, decisionHandler: @escaping @MainActor (WKNavigationActionPolicy, WKWebpagePreferences) -> Void) {
// let request = navigationAction.request
// if let headers = request.allHTTPHeaderFields {
// print(request.url?.absoluteString)
// print("headers: \(headers)")
// if let scntValue = headers["scnt"] {
// print("scnt value: \(scntValue)")
// }
// }
// decisionHandler(.allow, preferences)
// }
public func installFirstLoadCompleteTrap(_ block: @escaping () -> Void) {
onFirstLoadComplete = block
}
public func installCredentialPopulationTrap(_ block: @escaping (String, [HTTPCookie]) -> Void) {
onCredentialPopulation = block
}
private func removeUnwantedElements() {
let removeElements = """
Element.prototype.remove = function() {
this.parentElement.removeChild(this);
}
NodeList.prototype.remove = HTMLCollection.prototype.remove = function() {
for(var i = this.length - 1; i >= 0; i--) {
if(this[i] && this[i].parentElement) {
this[i].parentElement.removeChild(this[i]);
}
}
}
document.getElementById("header").remove();
document.getElementsByClassName('landing__animation').remove();
"""
associatedWebView.evaluateJavaScript(removeElements) { _, _ in
}
}
private func populateData() {
guard let onCredentialPopulation else { return }
associatedWebView.configuration.websiteDataStore.httpCookieStore.getAllCookies { cookies in
//print(cookies)
for cookie in cookies where cookie.name == "myacinfo" {
let value = cookie.value
onCredentialPopulation(value, cookies)
self.onCredentialPopulation = nil
}
}
}
}
@@ -0,0 +1,27 @@
//
// PhoneNumbers.swift
// AppleParty
//
// Created by 易承 on 2021/6/2.
//
import Foundation
struct PNumber {
var num: String
var id: Int
}
// MARK: - 双重绑定手机号码
struct PhoneNumbers {
var numbers: [PNumber]
init(body: [String: Any]) {
numbers = [PNumber]()
let trustedPhoneNumbers = dictionaryArray(body["trustedPhoneNumbers"])
for phone in trustedPhoneNumbers {
numbers.append(PNumber(num: string(from: phone["numberWithDialCode"], defaultValue: "未知手机号"),
id: int(from: phone["id"]) ?? 0 ))
}
}
}
@@ -0,0 +1,271 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="f4w-gm-NjX">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Window Controller-->
<scene sceneID="Dz3-j1-bnD">
<objects>
<customObject id="Wso-0F-bWE" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
<windowController storyboardIdentifier="APQRcode" id="f4w-gm-NjX" sceneMemberID="viewController">
<window key="window" title="二维码工具" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="r7n-lN-NFg">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<rect key="contentRect" x="425" y="462" width="475" height="364"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
<view key="contentView" id="EJY-HM-BUX">
<rect key="frame" x="0.0" y="0.0" width="475" height="364"/>
<autoresizingMask key="autoresizingMask"/>
</view>
<connections>
<outlet property="delegate" destination="f4w-gm-NjX" id="C4p-du-RcV"/>
</connections>
</window>
<connections>
<segue destination="JMg-NC-UdZ" kind="relationship" relationship="window.shadowedContentViewController" id="989-Yb-rd2"/>
</connections>
</windowController>
</objects>
<point key="canvasLocation" x="-344.5" y="156"/>
</scene>
<!--二维码工具-->
<scene sceneID="9pW-9x-UcR">
<objects>
<viewController title="二维码工具" id="JMg-NC-UdZ" customClass="APQRcodeVC" customModule="AppleParty" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="YL6-zb-Bcj">
<rect key="frame" x="0.0" y="0.0" width="698" height="403"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ug2-kb-Ht1">
<rect key="frame" x="20" y="341" width="552" height="42"/>
<constraints>
<constraint firstAttribute="height" constant="42" id="5u9-zd-zBc"/>
</constraints>
<textFieldCell key="cell" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="border" alignment="left" placeholderString="粘贴需要生成二维码的内容" drawsBackground="YES" id="Eeo-iN-PpZ">
<font key="font" metaFont="system" size="15"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="kXS-QG-QKh">
<rect key="frame" x="573" y="345" width="109" height="33"/>
<constraints>
<constraint firstAttribute="width" constant="95" id="37D-fG-MjE"/>
<constraint firstAttribute="height" constant="21" id="H9R-dc-W0a"/>
</constraints>
<buttonCell key="cell" type="push" title="生成二维码" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Tw0-oT-hAN">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
DQ
</string>
</buttonCell>
<connections>
<action selector="createQrcode:" target="JMg-NC-UdZ" id="eqd-Jm-wdw"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="aIK-jb-PcK">
<rect key="frame" x="411" y="201" width="214" height="37"/>
<constraints>
<constraint firstAttribute="height" constant="25" id="9OI-Oe-yQA"/>
</constraints>
<buttonCell key="cell" type="push" title="复制二维码" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="ucb-TM-gYn">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="copyQrcode:" target="JMg-NC-UdZ" id="m9E-O6-wh4"/>
</connections>
</button>
<scrollView hidden="YES" borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" translatesAutoresizingMaskIntoConstraints="NO" id="RMS-8O-hMW">
<rect key="frame" x="28" y="16" width="345" height="299"/>
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="Rb9-Io-7Mc">
<rect key="frame" x="0.0" y="0.0" width="345" height="299"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textView importsGraphics="NO" richText="NO" verticallyResizable="YES" spellingCorrection="YES" smartInsertDelete="YES" id="g7E-G6-qr7">
<rect key="frame" x="0.0" y="0.0" width="345" height="299"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<size key="minSize" width="345" height="299"/>
<size key="maxSize" width="345" height="10000000"/>
<color key="insertionPointColor" name="textColor" catalog="System" colorSpace="catalog"/>
</textView>
</subviews>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="Esa-gt-bwB">
<rect key="frame" x="-100" y="-100" width="225" height="15"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="6mG-be-32t">
<rect key="frame" x="329" y="0.0" width="16" height="299"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="FZk-nU-Hui">
<rect key="frame" x="50" y="15" width="301" height="301"/>
<constraints>
<constraint firstAttribute="width" secondItem="FZk-nU-Hui" secondAttribute="height" multiplier="1:1" id="Gbh-Ch-V3c"/>
</constraints>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="QRcode" id="gvL-Ja-sIo"/>
</imageView>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Fdf-Fu-jC2">
<rect key="frame" x="411" y="153" width="214" height="37"/>
<constraints>
<constraint firstAttribute="height" constant="25" id="n0o-Oe-hbP"/>
</constraints>
<buttonCell key="cell" type="push" title="保存二维码" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="ixZ-mG-dr1">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="saveQrcode:" target="JMg-NC-UdZ" id="NMx-l9-AQT"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="DO5-oD-0Zz">
<rect key="frame" x="411" y="105" width="214" height="37"/>
<constraints>
<constraint firstAttribute="height" constant="25" id="IBz-lN-MrF"/>
<constraint firstAttribute="height" constant="25" id="OlD-PH-AMy"/>
</constraints>
<buttonCell key="cell" type="push" title="分享二维码" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="OFP-an-Kde">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="shareQrcode:" target="JMg-NC-UdZ" id="E7u-EL-760"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="bhy-Kt-6FM">
<rect key="frame" x="411" y="57" width="214" height="37"/>
<constraints>
<constraint firstAttribute="height" constant="25" id="EK2-O2-r9Z"/>
<constraint firstAttribute="height" constant="25" id="LdC-mT-hfw"/>
</constraints>
<buttonCell key="cell" type="push" title="AirDrop分享" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Ss3-R8-8ZV">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="shareQrcodeByAirDrop:" target="JMg-NC-UdZ" id="qck-Qz-CDH"/>
</connections>
</button>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="kqV-kg-e6O">
<rect key="frame" x="515" y="252" width="107" height="25"/>
<popUpButtonCell key="cell" type="push" title="1024x1024" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="1024" imageScaling="proportionallyDown" inset="2" selectedItem="Ezh-Eq-5N3" id="tDM-tj-Cnk">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" title="分辨率" id="iDl-wk-ACt">
<items>
<menuItem title="300x300" tag="300" id="cd3-Tx-sIl"/>
<menuItem title="666x666" tag="680" id="J4k-L2-eI9"/>
<menuItem title="1024x1024" state="on" tag="1024" id="Ezh-Eq-5N3"/>
<menuItem title="1680x1680" tag="1680" id="yKW-h3-FzF"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nkA-C8-cXN">
<rect key="frame" x="416" y="258" width="94" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="90" id="H24-4r-oi6"/>
<constraint firstAttribute="height" constant="16" id="svz-da-sgJ"/>
</constraints>
<textFieldCell key="cell" title="二维码分辨率:" id="lpe-Oy-Zj8">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="oFq-OZ-56q">
<rect key="frame" x="418" y="307" width="202" height="16"/>
<textFieldCell key="cell" id="M8S-NJ-r4H">
<font key="font" metaFont="system"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Vpq-0F-Rre">
<rect key="frame" x="411" y="9" width="214" height="37"/>
<constraints>
<constraint firstAttribute="height" constant="25" id="DX1-8F-PWN"/>
<constraint firstAttribute="height" constant="25" id="RZy-Lp-VQi"/>
</constraints>
<buttonCell key="cell" type="push" title="扫描屏幕上的二维码" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="IE9-17-MVr">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="scanQrcode:" target="JMg-NC-UdZ" id="nKQ-rX-scz"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="RMS-8O-hMW" firstAttribute="height" secondItem="FZk-nU-Hui" secondAttribute="height" multiplier="0.996337" id="0lC-La-Fe2"/>
<constraint firstAttribute="trailing" secondItem="Vpq-0F-Rre" secondAttribute="trailing" constant="80" id="3HB-cl-Bo7"/>
<constraint firstItem="kqV-kg-e6O" firstAttribute="leading" secondItem="nkA-C8-cXN" secondAttribute="trailing" constant="10" id="5Hd-0w-UDL"/>
<constraint firstItem="aIK-jb-PcK" firstAttribute="leading" secondItem="FZk-nU-Hui" secondAttribute="trailing" constant="67" id="5pi-bF-JY8"/>
<constraint firstAttribute="trailing" secondItem="aIK-jb-PcK" secondAttribute="trailing" constant="80" id="5xg-F6-DUj"/>
<constraint firstAttribute="trailing" secondItem="DO5-oD-0Zz" secondAttribute="trailing" constant="80" id="7vu-Wm-qhQ"/>
<constraint firstItem="Ug2-kb-Ht1" firstAttribute="top" secondItem="YL6-zb-Bcj" secondAttribute="top" constant="20" symbolic="YES" id="81I-ze-g3K"/>
<constraint firstAttribute="trailing" secondItem="Fdf-Fu-jC2" secondAttribute="trailing" constant="80" id="8PQ-BB-8bV"/>
<constraint firstAttribute="trailing" secondItem="Vpq-0F-Rre" secondAttribute="trailing" constant="80" id="GiS-So-8mk"/>
<constraint firstItem="bhy-Kt-6FM" firstAttribute="leading" secondItem="FZk-nU-Hui" secondAttribute="trailing" constant="67" id="LF9-2I-3uN"/>
<constraint firstItem="FZk-nU-Hui" firstAttribute="centerX" secondItem="RMS-8O-hMW" secondAttribute="centerX" id="NMm-m2-f84"/>
<constraint firstItem="oFq-OZ-56q" firstAttribute="leading" secondItem="FZk-nU-Hui" secondAttribute="trailing" constant="69" id="PzN-pg-eqG"/>
<constraint firstItem="FZk-nU-Hui" firstAttribute="centerY" secondItem="RMS-8O-hMW" secondAttribute="centerY" id="QxO-Wo-srO"/>
<constraint firstItem="Fdf-Fu-jC2" firstAttribute="top" secondItem="aIK-jb-PcK" secondAttribute="bottom" constant="23" id="V0h-t7-6Gj"/>
<constraint firstItem="FZk-nU-Hui" firstAttribute="leading" secondItem="YL6-zb-Bcj" secondAttribute="leading" constant="50" id="VDe-Q8-9PN"/>
<constraint firstAttribute="trailing" secondItem="oFq-OZ-56q" secondAttribute="trailing" constant="80" id="XJ6-yE-Ntt"/>
<constraint firstItem="Vpq-0F-Rre" firstAttribute="leading" secondItem="FZk-nU-Hui" secondAttribute="trailing" constant="67" id="Y7x-4H-RjY"/>
<constraint firstItem="kqV-kg-e6O" firstAttribute="top" secondItem="Ug2-kb-Ht1" secondAttribute="bottom" constant="65" id="YEL-BY-ut6"/>
<constraint firstItem="DO5-oD-0Zz" firstAttribute="top" secondItem="Fdf-Fu-jC2" secondAttribute="bottom" constant="23" id="Ypl-LZ-Mq5"/>
<constraint firstAttribute="trailing" secondItem="bhy-Kt-6FM" secondAttribute="trailing" constant="80" id="a0a-Oa-9kH"/>
<constraint firstAttribute="trailing" secondItem="kqV-kg-e6O" secondAttribute="trailing" constant="80" id="dBG-ER-u2w"/>
<constraint firstItem="nkA-C8-cXN" firstAttribute="leading" secondItem="FZk-nU-Hui" secondAttribute="trailing" constant="67" id="feM-R3-Qk4"/>
<constraint firstItem="oFq-OZ-56q" firstAttribute="top" secondItem="Ug2-kb-Ht1" secondAttribute="bottom" constant="18" id="g4b-8B-jEz"/>
<constraint firstItem="Vpq-0F-Rre" firstAttribute="leading" secondItem="FZk-nU-Hui" secondAttribute="trailing" constant="67" id="gBB-bU-bqb"/>
<constraint firstAttribute="trailing" secondItem="kXS-QG-QKh" secondAttribute="trailing" constant="23" id="gz3-mW-f9A"/>
<constraint firstItem="kXS-QG-QKh" firstAttribute="centerY" secondItem="Ug2-kb-Ht1" secondAttribute="centerY" id="iya-pl-bDW"/>
<constraint firstItem="RMS-8O-hMW" firstAttribute="width" secondItem="FZk-nU-Hui" secondAttribute="width" multiplier="1.14286" id="l0c-4A-01h"/>
<constraint firstAttribute="bottom" secondItem="FZk-nU-Hui" secondAttribute="bottom" constant="15" id="ohm-Zu-1WD"/>
<constraint firstItem="Vpq-0F-Rre" firstAttribute="top" secondItem="bhy-Kt-6FM" secondAttribute="bottom" constant="23" id="ooP-O8-GCs"/>
<constraint firstItem="bhy-Kt-6FM" firstAttribute="top" secondItem="DO5-oD-0Zz" secondAttribute="bottom" constant="23" id="ovb-Bw-TRf"/>
<constraint firstItem="kqV-kg-e6O" firstAttribute="centerY" secondItem="nkA-C8-cXN" secondAttribute="centerY" id="q3U-Le-a73"/>
<constraint firstItem="FZk-nU-Hui" firstAttribute="top" secondItem="Ug2-kb-Ht1" secondAttribute="bottom" constant="25" id="qQE-gB-m3H"/>
<constraint firstItem="aIK-jb-PcK" firstAttribute="top" secondItem="kqV-kg-e6O" secondAttribute="bottom" constant="23" id="rWH-hA-TD5"/>
<constraint firstItem="Fdf-Fu-jC2" firstAttribute="leading" secondItem="FZk-nU-Hui" secondAttribute="trailing" constant="67" id="sSR-g2-znd"/>
<constraint firstItem="Vpq-0F-Rre" firstAttribute="leading" secondItem="FZk-nU-Hui" secondAttribute="trailing" constant="67" id="tKR-17-heC"/>
<constraint firstItem="Ug2-kb-Ht1" firstAttribute="leading" secondItem="YL6-zb-Bcj" secondAttribute="leading" constant="20" id="tMb-EV-T0G"/>
<constraint firstItem="DO5-oD-0Zz" firstAttribute="leading" secondItem="FZk-nU-Hui" secondAttribute="trailing" constant="67" id="wNM-xz-93l"/>
<constraint firstItem="kXS-QG-QKh" firstAttribute="leading" secondItem="Ug2-kb-Ht1" secondAttribute="trailing" constant="8" id="xPZ-gg-ekQ"/>
<constraint firstAttribute="trailing" secondItem="Vpq-0F-Rre" secondAttribute="trailing" constant="80" id="zbv-Ik-hNs"/>
</constraints>
</view>
<connections>
<outlet property="copyQrcodeBtn" destination="aIK-jb-PcK" id="gof-pb-gv8"/>
<outlet property="createQrcodeBtn" destination="kXS-QG-QKh" id="4d9-ha-E2M"/>
<outlet property="inputTextField" destination="Ug2-kb-Ht1" id="dIn-HQ-wDi"/>
<outlet property="messageLbl" destination="oFq-OZ-56q" id="xKI-yE-tXi"/>
<outlet property="qrcodeImageView" destination="FZk-nU-Hui" id="bbk-Km-JV9"/>
<outlet property="qrcodeSizeBtn" destination="kqV-kg-e6O" id="Lac-Nf-7aT"/>
<outlet property="saveQrcodeBtn" destination="Fdf-Fu-jC2" id="DBR-Bz-crE"/>
<outlet property="scanQrcodeBtn" destination="Vpq-0F-Rre" id="Ew1-De-IaS"/>
<outlet property="shareQrcodeBtn" destination="DO5-oD-0Zz" id="bvQ-kp-gDU"/>
<outlet property="shareQrcodeByAirDropBtn" destination="bhy-Kt-6FM" id="xVg-Aw-9xp"/>
<outlet property="textScrollView" destination="RMS-8O-hMW" id="0yW-gv-qtS"/>
<outlet property="textView" destination="g7E-G6-qr7" id="vJy-Tg-oLR"/>
</connections>
</viewController>
<customObject id="3RX-1S-TLr" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="372" y="156.5"/>
</scene>
</scenes>
<resources>
<image name="QRcode" width="145" height="145"/>
</resources>
</document>
@@ -0,0 +1,199 @@
//
// APQRcodeVC.swift
// AppleParty
//
// Created by HTC on 2022/3/24.
// Copyright © 2022 37 Mobile Games. All rights reserved.
//
import Cocoa
class APQRcodeVC: NSViewController {
@IBOutlet weak var inputTextField: NSTextField!
@IBOutlet weak var qrcodeSizeBtn: NSPopUpButton!
@IBOutlet weak var createQrcodeBtn: NSButton!
@IBOutlet weak var qrcodeImageView: NSImageView!
@IBOutlet weak var copyQrcodeBtn: NSButton!
@IBOutlet weak var saveQrcodeBtn: NSButton!
@IBOutlet weak var shareQrcodeBtn: NSButton!
@IBOutlet weak var shareQrcodeByAirDropBtn: NSButton!
@IBOutlet weak var scanQrcodeBtn: NSButton!
@IBOutlet weak var messageLbl: NSTextField!
@IBOutlet weak var textScrollView: NSScrollView!
@IBOutlet weak var textView: NSTextView!
@IBAction func createQrcode(_ sender: Any) {
let str = inputTextField!.stringValue
if str.isEmpty {
enableQrcode(false)
statusMessage("")
NSAlert.show("请输出需要生成二维码的文本!")
return
}
let img = createQRImage(str, NSMakeSize(360, 360))
qrcodeImageView.image = img
enableQrcode(true)
statusMessage("二维码生成成功")
}
@IBAction func copyQrcode(_ sender: Any) {
let str = inputTextField!.stringValue
if !str.isEmpty {
let img = createQRImage(str, getImageSize())
let pb = NSPasteboard.general
pb.clearContents()
if pb.writeObjects([img as NSPasteboardWriting]) {
statusMessage("Copy QRCode to clipboard")
} else {
statusMessage("Failed to copy QRCode to clipboard")
}
}
}
@IBAction func saveQrcode(_ sender: Any) {
let str = inputTextField!.stringValue
if str.isEmpty {
return statusMessage("请填写有效的文本内容!")
}
let savePanel = NSSavePanel()
savePanel.title = "Save QRCode As File"
savePanel.canCreateDirectories = true
savePanel.allowedFileTypes = ["png"]
savePanel.isExtensionHidden = false
savePanel.nameFieldStringValue = getImgaeName() + ".png"
savePanel.becomeKey()
let result = savePanel.runModal()
if (result == .OK && (savePanel.url) != nil) {
let img = createQRImage(str, getImageSize())
let imgRep = NSBitmapImageRep(data: img.tiffRepresentation!)
let data = imgRep?.representation(using: NSBitmapImageRep.FileType.png, properties: [:])
try! data?.write(to: savePanel.url!)
statusMessage("Save QRCode to \(savePanel.url!.absoluteString)")
}
}
@IBAction func shareQrcode(_ sender: Any) {
let str = inputTextField!.stringValue
if str.isEmpty {
return statusMessage("请填写有效的文本内容!")
}
let img = createQRImage(str, getImageSize())
let picker = NSSharingServicePicker(items: [img])
picker.delegate = self
picker.show(relativeTo: .zero, of: sender as! NSView, preferredEdge: .maxX)
}
@IBAction func shareQrcodeByAirDrop(_ sender: Any) {
let str = inputTextField!.stringValue
if str.isEmpty {
return statusMessage("请填写有效的文本内容!")
}
let img = createQRImage(str, getImageSize())
let service = NSSharingService(named: .sendViaAirDrop)!
let items: [NSImage] = [img]
if service.canPerform(withItems: items) {
service.delegate = self
service.perform(withItems: items)
} else {
statusMessage("Cannot perform AirDrop!")
}
}
@IBAction func scanQrcode(_ sender: Any) {
enableQrcode(false)
qrcodeImageView.isHidden = true
textView.string = ""
textScrollView.isHidden = false
// scan QRCode
let dict = scanQRCodeOnScreen() as! [String:Any]
let data = dict["qrcode"] as! Array<String>
if data.isEmpty {
textView.string = "Not found valid QRCode of screen!"
return
}
// output message
appendToTextView("识别到二维码个数:\(data.count)\n", coreText: "\(data.count)")
for (index, element) in data.enumerated() {
let k = index + 1
appendToTextView("\n第\(k)个二维码内容:\n【\n\(element)\n】\n", coreText: element)
}
}
override func viewDidAppear() {
super.viewDidAppear()
inputTextField.becomeFirstResponder()
}
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
func setupUI() {
inputTextField.delegate = self
enableQrcode(false)
qrcodeImageView.image = NSImage(named: "QRcode")
}
func enableQrcode(_ enable: Bool) {
copyQrcodeBtn.isEnabled = enable
saveQrcodeBtn.isEnabled = enable
shareQrcodeBtn.isEnabled = enable
shareQrcodeByAirDropBtn.isEnabled = enable
textScrollView.isHidden = true
qrcodeImageView.isHidden = false
if !enable {
statusMessage("")
qrcodeImageView.image = NSImage(named: "QRcode")
}
}
func statusMessage(_ msg: String) {
messageLbl.stringValue = msg.count == 0 ? "" : "提示:\(msg)"
}
func getImageSize() -> NSSize {
let wh = CGFloat(qrcodeSizeBtn?.selectedTag() ?? 500)
let size = NSMakeSize(wh, wh)
return size
}
func getImgaeName() -> String {
let dateFormatter : DateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH-mm-ss"
let date = Date()
let dateString = dateFormatter.string(from: date)
return "AppleParty_qrcode-" + dateString
}
func appendToTextView(_ text: String, coreText: String) {
let attributes = [NSAttributedString.Key.foregroundColor: NSColor.labelColor]
let secondAttributes = [NSAttributedString.Key.foregroundColor: #colorLiteral(red: 0.3211918473, green: 0.7199308276, blue: 1, alpha: 1)]
let attr = NSMutableAttributedString.init(string: text, attributes: attributes)
attr.addAttributes(secondAttributes, range: (text as NSString).range(of: coreText))
textView.textStorage?.append(attr)
textView.scrollRangeToVisible(NSMakeRange(textView.string.count, 0))
}
}
extension APQRcodeVC: NSTextFieldDelegate {
func controlTextDidChange(_ obj: Notification) {
let textField = obj.object as! NSTextField
if textField.stringValue.isEmpty {
enableQrcode(false)
}
}
}
extension APQRcodeVC: NSSharingServicePickerDelegate, NSSharingServiceDelegate {
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "37M-logo.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "37M-slogan.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "37iOSTeam-Round.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,68 @@
{
"images" : [
{
"idiom" : "mac",
"size" : "16x16",
"filename" : "icon-16.png",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "16x16",
"filename" : "icon-32.png",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "32x32",
"filename" : "icon-32.png",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "32x32",
"filename" : "icon-64.png",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "128x128",
"filename" : "icon-128.png",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "128x128",
"filename" : "icon-256.png",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "256x256",
"filename" : "icon-256.png",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "256x256",
"filename" : "icon-512.png",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "512x512",
"filename" : "icon-512.png",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "512x512",
"filename" : "icon-1024.png",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "icon-256.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "icon-512.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "AppAnalytics@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Apps@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "FinancialReports@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "IPAUpload@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

@@ -0,0 +1,52 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "PlaceholderIcon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "PlaceholderIcon_white@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "QRcode@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "SendEmail@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "VerifyReceipt@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

@@ -0,0 +1,119 @@
<?xml version="1.0" encoding="UTF-8"?>
<package xmlns="http://apple.com/itunes/importer" version="software5.9">
<provider>{team_id}</provider>
<software>
<vendor_id>{SKU}</vendor_id>
<software_metadata>
<in_app_purchases>
<in_app_purchase>
<!-- 可以由字母、数字、下划线(_)和句点(.)构成,最多 255 个字符 -->
<product_id>com.app.1usd</product_id>
<reference_name>1ud(2~64 个字符)</reference_name>
<!-- 消耗型项目consumable 非消耗型non-consumable 自动续期订阅auto-renewable 非续期订阅subscription -->
<type>consumable</type>
<products>
<product>
<cleared_for_sale>true</cleared_for_sale>
<wholesale_price_tier>3</wholesale_price_tier>
</product>
</products>
<locales>
<locale name="en-US">
<title>2~30个字符</title>
<description>至少10~45个字符</description>
</locale>
<locale name="zh-Hans">
<title>中文2~30个字符</title>
<description>至少10~45个字符</description>
</locale>
</locales>
<review_screenshot>
<!-- Supported dimensions are: 1334x750, 750x1334, 1024x768, 1024x748, 768x1024, 768x1004, 2048x1536, 2048x1496, 1536x2048, 1536x2008, 2224x1668, 1668x2224, 960x640, 960x600, 640x960, 640x920, 2208x1242, 1242x2208, 2436x1125, 1125x2436, 312x390, 1136x640, 1136x600, 640x1136, 640x1096, 2732x2048, 2048x2732" -->
<size>636132</size>
<file_name>IMG_5180.PNG</file_name>
<checksum type="md5">xxxxx</checksum>
</review_screenshot>
<review_notes>Some notes for the reviewer.(2~4000字符)</review_notes>
</in_app_purchase>
<in_app_purchase>
<!-- 可以由字母、数字、下划线(_)和句点(.)构成,最多 255 个字符 -->
<product_id>com.app.3usd</product_id>
<reference_name>3ud(2~64 个字符)</reference_name>
<type>non-consumable</type>
<products>
<product>
<cleared_for_sale>true</cleared_for_sale>
<wholesale_price_tier>3</wholesale_price_tier>
</product>
</products>
<locales>
<locale name="ko">
<title>2~30个字符</title>
<description>至少10~45个字符</description>
</locale>
</locales>
<review_screenshot>
<!-- Supported dimensions are: 1334x750, 750x1334, 1024x768, 1024x748, 768x1024, 768x1004, 2048x1536, 2048x1496, 1536x2048, 1536x2008, 2224x1668, 1668x2224, 960x640, 960x600, 640x960, 640x920, 2208x1242, 1242x2208, 2436x1125, 1125x2436, 312x390, 1136x640, 1136x600, 640x1136, 640x1096, 2732x2048, 2048x2732" -->
<size>636132</size>
<file_name>IMG_5180.PNG</file_name>
<checksum type="md5">xxxxx</checksum>
</review_screenshot>
<review_notes>Some notes for the reviewer.(2~4000字符)</review_notes>
</in_app_purchase>
<subscription_group name="订阅群组参考名称">
<locales>
<locale name="en-US">
<title>订阅群组显示名称(2-75)</title>
<app_name>App 名称显示选项:使用自定义名称(2-30)</app_name>
</locale>
</locales>
<in_app_purchase>
<product_id>every_movie_in_the_world_plus_1month</product_id>
<reference_name>参考名称</reference_name>
<type>auto-renewable</type>
<duration>1 Month</duration>
<bonus_duration>7 Days</bonus_duration>
<cleared_for_sale>true</cleared_for_sale>
<locales>
<locale name="en-US">
<title>订阅显示名称</title>
<description>订阅描述Every movie ever plus.</description>
</locale>
</locales>
<review_screenshot>
<size>636132</size>
<file_name>IMG_5180.PNG</file_name>
<checksum type="md5">xxxxx</checksum>
</review_screenshot>
<review_notes>Some notes for the reviewer.(2~4000字符)</review_notes>
</in_app_purchase>
</subscription_group>
<in_app_purchase>
<!-- 可以由字母、数字、下划线(_)和句点(.)构成,最多 255 个字符 -->
<product_id>com.app.6usd</product_id>
<reference_name>6ud(2~64 个字符)</reference_name>
<type>subscription</type>
<products>
<product>
<cleared_for_sale>true</cleared_for_sale>
<wholesale_price_tier>49</wholesale_price_tier>
</product>
</products>
<locales>
<locale name="ko">
<title>2~30个字符</title>
<description>至少10~45个字符</description>
</locale>
</locales>
<review_screenshot>
<!-- Supported dimensions are: 1334x750, 750x1334, 1024x768, 1024x748, 768x1024, 768x1004, 2048x1536, 2048x1496, 1536x2048, 1536x2008, 2224x1668, 1668x2224, 960x640, 960x600, 640x960, 640x920, 2208x1242, 1242x2208, 2436x1125, 1125x2436, 312x390, 1136x640, 1136x600, 640x1136, 640x1096, 2732x2048, 2048x2732" -->
<size>636132</size>
<file_name>IMG_5180.PNG</file_name>
<checksum type="md5">xxxxx</checksum>
</review_screenshot>
<review_notes>Some notes for the reviewer.(2~4000字符)</review_notes>
</in_app_purchase>
</in_app_purchases>
</software_metadata>
</software>
</package>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<package version="software5.10" xmlns="http://apple.com/itunes/importer">
<software_assets apple_id="{apple_id}" app_platform="{app_platform}">
<asset type="{archive_type}">
<data_file>
<size>{file_size}</size>
<file_name>{file_name}</file_name>
<checksum type="md5">{file_md5}</checksum>
</data_file>
</asset>
</software_assets>
</package>
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<package version="software5.10" xmlns="http://apple.com/itunes/importer">
<provider>{provider}</provider>
<team_id>{provider}</team_id>
<software>
<vendor_id>{vendor_id}</vendor_id>
<software_metadata app_platform="{app_platform}">
<versions>
<version string="{app_version}">
<locales>
<locale name="{app_locale}">
<title>{title}</title>
<app_previews>
<app_preview display_target="{iOS-5.5-in}" position="{1}">
<data_file role="source">
<size>{video_size}</size>
<file_name>{video_name}</file_name>
<checksum>{video_md5}</checksum>
</data_file>
<preview_image_time format="30/1:1/nonDrop">{00:00:05:00}</preview_image_time>
</app_preview>
</app_previews>
<software_screenshots>
<software_screenshot display_target="{iOS-5.5-in}" position="{1}">
<size>{image_size}</size>
<file_name>{image_name}</file_name>
<checksum type="md5">{image_md5}</checksum>
</software_screenshot>
</software_screenshots>
</locale>
</locales>
</version>
</versions>
</software_metadata>
</software>
</package>
@@ -0,0 +1,61 @@
//
// APRootCollectionAdapter.swift
// AppleParty
//
// Created by HTC on 2022/3/14.
// Copyright © 2022 37 Mobile Games. All rights reserved.
//
import Cocoa
class APRootCollectionAdapter: NSObject {
fileprivate static let numberOfSections = 1
fileprivate static let itemId = "APRootCollectionCell"
fileprivate var items = [APRootCollectionModel]() {
didSet {
collectionView.reloadData()
}
}
private var collectionView: NSCollectionView
init(collectionView: NSCollectionView) {
self.collectionView = collectionView
super.init()
self.collectionView.dataSource = self
self.collectionView.delegate = self
self.collectionView.register(APRootCollectionCell.self, forItemWithIdentifier: NSUserInterfaceItemIdentifier(rawValue: APRootCollectionAdapter.itemId))
}
func set(items: [APRootCollectionModel]) {
self.items = items
}
}
extension APRootCollectionAdapter: NSCollectionViewDataSource, NSCollectionViewDelegate {
func numberOfSectionsInCollectionView(collectionView: NSCollectionView) -> Int {
return APRootCollectionAdapter.numberOfSections
}
func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
let item = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: APRootCollectionAdapter.itemId), for: indexPath)
guard let collectionViewItem = item as? APRootCollectionCell else { return item }
let name = items[indexPath.item].name
let icon = items[indexPath.item].icon
collectionViewItem.configure(name: name, icon: icon)
return item
}
func collectionView(_ collectionView: NSCollectionView, didSelectItemsAt indexPaths: Set<IndexPath>) {
collectionView.deselectItems(at: indexPaths)
if let item = indexPaths.first?.item, let handler = items[item].handler {
handler()
}
}
}
@@ -0,0 +1,25 @@
//
// APRootCollectionCell.swift
// AppleParty
//
// Created by HTC on 2022/3/14.
// Copyright © 2022 37 Mobile Games. All rights reserved.
//
import Cocoa
class APRootCollectionCell: NSCollectionViewItem {
@IBOutlet weak var imgView: NSImageView!
@IBOutlet weak var nameView: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
}
func configure(name: String, icon: String) {
nameView.stringValue = name
imgView?.image = NSImage(named: icon)
}
}
Loaded 100 of 165 files, more files were not shown because too many files have changed in this diff. Show more