Update: Setup file processing is now supported, but Intel chips are not yet supported.
@@ -7,7 +7,7 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
3CCC3AE02CC67B8F006E22B4 /* Adobe-Downloader.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Adobe-Downloader.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3CCC3AE02CC67B8F006E22B4 /* Adobe Downloader.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Adobe Downloader.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
@@ -46,7 +46,7 @@
|
||||
3CCC3AE12CC67B8F006E22B4 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3CCC3AE02CC67B8F006E22B4 /* Adobe-Downloader.app */,
|
||||
3CCC3AE02CC67B8F006E22B4 /* Adobe Downloader.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -54,9 +54,9 @@
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
3CCC3ADF2CC67B8F006E22B4 /* Adobe-Downloader */ = {
|
||||
3CCC3ADF2CC67B8F006E22B4 /* Adobe Downloader */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 3CCC3B052CC67B91006E22B4 /* Build configuration list for PBXNativeTarget "Adobe-Downloader" */;
|
||||
buildConfigurationList = 3CCC3B052CC67B91006E22B4 /* Build configuration list for PBXNativeTarget "Adobe Downloader" */;
|
||||
buildPhases = (
|
||||
3CCC3ADC2CC67B8F006E22B4 /* Sources */,
|
||||
3CCC3ADD2CC67B8F006E22B4 /* Frameworks */,
|
||||
@@ -70,11 +70,11 @@
|
||||
3CCC3AE22CC67B8F006E22B4 /* Adobe Downloader */,
|
||||
3CCC3B112CC67F7A006E22B4 /* Localizables */,
|
||||
);
|
||||
name = "Adobe-Downloader";
|
||||
name = "Adobe Downloader";
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = "Adobe-Downloader";
|
||||
productReference = 3CCC3AE02CC67B8F006E22B4 /* Adobe-Downloader.app */;
|
||||
productReference = 3CCC3AE02CC67B8F006E22B4 /* Adobe Downloader.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
@@ -106,7 +106,7 @@
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
3CCC3ADF2CC67B8F006E22B4 /* Adobe-Downloader */,
|
||||
3CCC3ADF2CC67B8F006E22B4 /* Adobe Downloader */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -326,7 +326,7 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
3CCC3B052CC67B91006E22B4 /* Build configuration list for PBXNativeTarget "Adobe-Downloader" */ = {
|
||||
3CCC3B052CC67B91006E22B4 /* Build configuration list for PBXNativeTarget "Adobe Downloader" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
3CCC3B062CC67B91006E22B4 /* Debug */,
|
||||
|
||||
@@ -3,6 +3,7 @@ import SwiftUI
|
||||
@main
|
||||
struct Adobe_DownloaderApp: App {
|
||||
@StateObject private var networkManager = NetworkManager()
|
||||
@State private var showBackupAlert = false
|
||||
|
||||
init() {
|
||||
if UserDefaults.standard.object(forKey: "useDefaultLanguage") == nil {
|
||||
@@ -17,7 +18,25 @@ struct Adobe_DownloaderApp: App {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
.environmentObject(networkManager)
|
||||
.frame(width: 850, height: 700)
|
||||
.frame(width: 850, height: 800)
|
||||
.tint(.blue)
|
||||
.onAppear {
|
||||
if ModifySetup.checkSetupBackup() {
|
||||
showBackupAlert = true
|
||||
}
|
||||
}
|
||||
.alert("Setup未备份提示", isPresented: $showBackupAlert) {
|
||||
Button("OK") {
|
||||
ModifySetup.backupSetupFile { success, message in
|
||||
if !success {
|
||||
print(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
Button("取消", role: .cancel) {}
|
||||
} message: {
|
||||
Text("检测到Setup文件尚未备份,如果你需要安装程序,则Setup必须被处理,点击确定后你需要输入密码,Adobe Downloader将自动处理并备份为Setup.original")
|
||||
}
|
||||
}
|
||||
.windowStyle(.hiddenTitleBar)
|
||||
.windowResizability(.contentSize)
|
||||
|
||||
@@ -8,4 +8,4 @@
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "acrobat-pro.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="256px" height="256px" viewBox="0 0 256 256" style="enable-background:new 0 0 256 256;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#B30B00;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0" d="M45.5,3.2h165.1c24.7,0,45.5,19.9,45.5,45.5v158.7c0,24.7-19.9,45.5-45.5,45.5H45.5
|
||||
C20.7,252.8,0,232.9,0,207.4V48.6C0,23.1,19.9,3.2,45.5,3.2z"/>
|
||||
<path class="st1" d="M204.2,147.5c-12-12.8-44.7-7.2-52.6-6.4c-11.2-11.2-19.1-23.9-22.3-28.7c4-12,7.2-25.5,7.2-38.3
|
||||
c0-12-4.8-23.9-17.5-23.9c-4.8,0-8.8,2.4-11.2,6.4c-5.6,9.6-3.2,28.7,5.6,48.6c-4.8,14.4-12.8,35.9-22.3,52.6
|
||||
c-12.8,4.8-40.7,17.5-43.1,31.9c-0.8,4,0.8,8.8,4,11.2c3.2,3.2,7.2,4,11.2,4c16.7,0,33.5-23.1,45.5-43.9c9.6-3.2,24.7-8,39.9-10.4
|
||||
c17.5,16,33.5,18.3,41.5,18.3c11.2,0,15.2-4.8,16.7-8.8C208.9,156.3,207.4,150.7,204.2,147.5z M193,155.5c-0.8,3.2-4.8,6.4-12,4.8
|
||||
c-8.8-2.4-16.7-6.4-23.1-12c5.6-0.8,19.1-2.4,28.7-0.8C189.8,148.3,193.8,150.7,193,155.5z M115.6,59.8c0.8-1.6,2.4-2.4,4-2.4
|
||||
c4,0,4.8,4.8,4.8,8.8c-0.8,9.6-2.4,19.9-5.6,28.7C112.4,77.4,113.2,64.6,115.6,59.8z M114.8,149.9c4-7.2,8.8-20.7,10.4-25.5
|
||||
c4,7.2,11.2,15.2,14.4,19.1C140.4,142.8,126,145.9,114.8,149.9z M87.7,168.3C76.6,185.8,66.2,197,59.8,197c-0.8,0-2.4,0-3.2-0.8
|
||||
c-0.8-1.6-1.6-3.2-0.8-4.8C56.6,185,69.4,176.2,87.7,168.3z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "192x192.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "after-effects.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 240 234" style="enable-background:new 0 0 240 234;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#00005B;}
|
||||
.st1{fill:#9999FF;}
|
||||
</style>
|
||||
<g id="Layer_2_1_">
|
||||
<g id="Surfaces">
|
||||
<g id="Video_Audio_Surface">
|
||||
<g id="Outline_no_shadow">
|
||||
<path class="st0" d="M42.5,0h155C221,0,240,19,240,42.5v149c0,23.5-19,42.5-42.5,42.5h-155C19,234,0,215,0,191.5v-149
|
||||
C0,19,19,0,42.5,0z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Outlined_Mnemonics_Logos">
|
||||
<g id="Ae">
|
||||
<path class="st1" d="M96.4,140H59.2l-7.6,23.6c-0.2,0.9-1,1.5-1.9,1.4H30.9c-1.1,0-1.4-0.6-1.1-1.8L62,70.9c0.3-1,0.6-1.9,1-3.1
|
||||
c0.4-2.1,0.6-4.3,0.6-6.5c-0.1-0.5,0.3-1,0.8-1.1c0.1,0,0.2,0,0.3,0h25.6c0.7,0,1.2,0.3,1.3,0.8l36.5,102.5c0.3,1.1,0,1.6-1,1.6
|
||||
h-20.9c-0.7,0.1-1.4-0.4-1.6-1.1L96.4,140z M65,120.1h25.4c-0.6-2.1-1.4-4.6-2.3-7.2c-0.9-2.7-1.8-5.6-2.7-8.6
|
||||
c-1-3.1-1.9-6.1-2.9-9.2c-1-3.1-1.9-6-2.7-8.9c-0.8-2.8-1.5-5.4-2.2-7.8h-0.2c-0.9,4.3-2,8.6-3.4,12.9c-1.5,4.8-3,9.8-4.6,14.8
|
||||
C68.1,111.2,66.5,115.9,65,120.1z"/>
|
||||
<path class="st1" d="M187.4,130.8h-31.7c0.4,3.1,1.4,6.2,3.1,8.9c1.8,2.7,4.3,4.8,7.3,6c4,1.7,8.4,2.6,12.8,2.5
|
||||
c3.5-0.1,7-0.4,10.4-1.1c3.1-0.4,6.1-1.2,8.9-2.3c0.5-0.4,0.8-0.2,0.8,0.8v15.3c0,0.4-0.1,0.8-0.2,1.2c-0.2,0.3-0.4,0.5-0.7,0.7
|
||||
c-3.2,1.4-6.5,2.4-10,3c-4.7,0.9-9.4,1.3-14.2,1.2c-7.6,0-14-1.2-19.2-3.5c-4.9-2.1-9.2-5.4-12.6-9.5c-3.2-3.9-5.5-8.3-6.9-13.1
|
||||
c-1.4-4.7-2.1-9.6-2.1-14.6c0-5.4,0.8-10.7,2.5-15.9c1.6-5,4.1-9.6,7.5-13.7c3.3-4,7.4-7.2,12.1-9.5c4.7-2.3,10.3-3.1,16.7-3.1
|
||||
c5.3-0.1,10.6,0.9,15.5,3.1c4.1,1.8,7.7,4.5,10.5,8c2.6,3.4,4.7,7.2,6,11.4c1.3,4,1.9,8.1,1.9,12.2c0,2.4-0.1,4.5-0.2,6.4
|
||||
c-0.2,1.9-0.3,3.3-0.4,4.2c-0.1,0.7-0.7,1.3-1.4,1.3c-0.6,0-1.7,0.1-3.3,0.2c-1.6,0.2-3.5,0.3-5.8,0.3
|
||||
C192.4,131.2,190,130.8,187.4,130.8z M155.7,116.2h21.1c2.6,0,4.5,0,5.7-0.1c0.8-0.1,1.6-0.3,2.3-0.8v-1c0-1.3-0.2-2.5-0.6-3.7
|
||||
c-1.8-5.6-7.1-9.4-13-9.2c-5.5-0.3-10.7,2.6-13.3,7.6C156.7,111.3,156,113.7,155.7,116.2z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.2 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "animate.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 234"><defs><style>.cls-1{fill:#00005b;}.cls-2{fill:#99f;}</style></defs><title>Asset 148</title><g id="Layer_2" data-name="Layer 2"><g id="Surfaces"><g id="Video_Audio_Surface" data-name="Video/Audio Surface"><g id="Outline_no_shadow" data-name="Outline no shadow"><rect class="cls-1" width="240" height="234" rx="42.5"/></g></g></g><g id="Outlined_Mnemonics_Logos" data-name="Outlined Mnemonics & Logos"><g id="An"><path class="cls-2" d="M97.04315,140H59.85223l-7.6377,23.46769a1.89429,1.89429,0,0,1-1.93212,1.449H31.44549q-1.61133,0-1.127-1.771L62.65236,70.87622c.32227-.96582.644-1.82674.96631-3.06209a34.47808,34.47808,0,0,0,.644-6.52027.99642.99642,0,0,1,1.127-1.12719H90.9883q1.125,0,1.28808.80517l36.41325,102.33472q.48267,1.61133-.96631,1.61011H106.79315a1.48825,1.48825,0,0,1-1.60987-1.127ZM65.71144,120.14233h25.438q-.96606-3.21863-2.25391-7.24511-1.29053-4.02246-2.73681-8.61353-1.44946-4.58826-2.89844-9.177-1.44873-4.58826-2.65625-8.855-1.20777-4.26379-2.17334-7.80835h-.16113a130.10721,130.10721,0,0,1-3.38086,12.87988q-2.2566,7.24512-4.58887,14.812Q67.96413,113.70484,65.71144,120.14233Z"/><path class="cls-2" d="M139.95981,163.38436V104.04224q0-2.7356-.08007-6.11792-.0835-3.38086-.2417-6.43994c-.1084-2.03785-.21631-3.93059-.32178-5.113a.9562.9562,0,0,1,.16064-.96607,1.31358,1.31358,0,0,1,.96631-.322h16.07145a3.59919,3.59919,0,0,1,1.61036.322,1.51637,1.51637,0,0,1,.80468,1.127q.32081.96606.72461,2.17358a16.52607,16.52607,0,0,1,.56348,3.2614,32.34487,32.34487,0,0,1,10.626-6.19849A36.57379,36.57379,0,0,1,182.919,83.75635a30.62737,30.62737,0,0,1,9.41846,1.52954,23.79268,23.79268,0,0,1,15.21435,14.812c1.60987,4.13306,2.24138,9.47363,2.24138,16.01929v47.26717q0,1.44909-1.28809,1.449L189.6924,165a1.42233,1.42233,0,0,1-1.60986-1.61011V118.544a22.1973,22.1973,0,0,0-1.94385-8.70557,11.50377,11.50377,0,0,0-4.10547-4.991,12.1157,12.1157,0,0,0-6.84229-1.771,20.031,20.031,0,0,0-7.56689,1.3684,19.08763,19.08763,0,0,0-5.957,3.78345v55.15609q0,1.44909-1.28809,1.449H141.409A1.28093,1.28093,0,0,1,139.95981,163.38436Z"/></g></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 2.1 KiB |
@@ -1,61 +1,61 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "16.png",
|
||||
"filename" : "16pt-1.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"filename" : "32 2x.png",
|
||||
"filename" : "32pt-1.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"filename" : "32 1x.png",
|
||||
"filename" : "32pt-2.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"filename" : "64 1.png",
|
||||
"filename" : "64pt-1.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"filename" : "128.png",
|
||||
"filename" : "128pt-1.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"filename" : "256 2x.png",
|
||||
"filename" : "256pt-1.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"filename" : "256 1x.png",
|
||||
"filename" : "256pt-2.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"filename" : "512 2x.png",
|
||||
"filename" : "512pt-1.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"filename" : "Adobe Downloader- 512 x 513.png",
|
||||
"filename" : "512pt-2.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "512x512"
|
||||
},
|
||||
{
|
||||
"filename" : "Adobe Downloader - 1024 x 1025.png",
|
||||
"filename" : "1024pt-1.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "512x512"
|
||||
|
||||
|
Before Width: | Height: | Size: 3.0 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "192x192 (1).png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "audition.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 234"><defs><style>.cls-1{fill:#00005b;}.cls-2{fill:#99f;}</style></defs><title>Artboard 1</title><rect class="cls-1" width="240" height="234" rx="42.5"/><path class="cls-2" d="M94.32324,140H57.13184l-7.5669,23.551A1.89358,1.89358,0,0,1,47.63281,165H28.7959q-1.61133,0-1.127-1.771L59.86914,70.87622q.48267-1.44873.96582-3.30054a29.74633,29.74633,0,0,0,.78573-6.37858.99662.99662,0,0,1,1.12695-1.12719H88.34676q1.12426,0,1.28809.80517L126.04,163.38989q.48267,1.61133-.96582,1.61011H104.144a1.48792,1.48792,0,0,1-1.60986-1.12695Zm-31.395-19.85767h25.438q-.96606-3.21863-2.25439-7.24511-1.29054-4.02246-2.73682-8.61353-1.44873-4.58826-2.89795-9.177-1.44873-4.58826-2.65674-8.855-1.207-4.26379-2.17334-7.80835h-.16113A129.83529,129.83529,0,0,1,72.105,91.32324q-2.25659,7.24512-4.58838,14.812Q65.18091,113.70484,62.92822,120.14233Z"/><path class="cls-2" d="M202.83691,86.21053v61.30168q0,4.02648.08057,7.16455.07909,3.13953.32178,5.47412c.16113,1.55713.29443,2.4895.40283,3.561.10547.86036-.269,1.28809-1.12695,1.28809h-17.227a1.8896,1.8896,0,0,1-1.93213-1.12695,24.25482,24.25482,0,0,1-.56348-2.49561,10.58926,10.58926,0,0,1-.2417-2.11206,28.73423,28.73423,0,0,1-11.27,6.19849,43.559,43.559,0,0,1-11.10888,1.52954,33.20536,33.20536,0,0,1-10.86719-1.69068,21.20047,21.20047,0,0,1-8.53321-5.47387,25.79177,25.79177,0,0,1-5.63476-9.66016,44.18714,44.18714,0,0,1-2.0127-14.24829v-49.549a1.1382,1.1382,0,0,1,1.28809-1.28809h19.13314a1.13916,1.13916,0,0,1,1.28808,1.28809v46.97282q0,6.60351,2.898,10.38452c1.93213,2.52319,5.82194,3.78345,10.32976,3.78345a18.26761,18.26761,0,0,0,6.76171-1.20752,25.49323,25.49323,0,0,0,6.34392-3.3003V86.21053c0-.74976.48291-1.1272,1.44922-1.1272H201.71A.9976.9976,0,0,1,202.83691,86.21053Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "192x192.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "bridge.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 234"><defs><style>.cls-1{fill:#000b1d;}.cls-2{fill:#fff;}</style></defs><title>Artboard 1</title><rect class="cls-1" width="240" height="234" rx="42.5"/><path class="cls-2" d="M55.0625,164.1665,55,62.03052q0-1.44873.96631-1.60987,2.73559-.15857,8.37158-.24145c3.75586-.053,7.4303-.10571,11.83264-.16113q6.59985-.07911,12.07471-.08057c8.7998,0,16.00476.63477,21.21228,2.56689q7.80688,2.89783,11.99463,7.32544a24.1801,24.1801,0,0,1,5.91113,9.25757,31.04394,31.04394,0,0,1,1.44922,8.855,25.61447,25.61447,0,0,1-1.88672,9.41846,25.135,25.135,0,0,1-3.86377,6.84253,18.37823,18.37823,0,0,1-4.34717,4.0249,32.122,32.122,0,0,1,6.35938,4.42749,26.24885,26.24885,0,0,1,6.0376,7.96948,25.958,25.958,0,0,1,2.77453,12.19861,28.992,28.992,0,0,1-5.59192,17.26453q-5.39574,7.407-15.45605,11.51147A63.4326,63.4326,0,0,1,88.769,165.9375H78.0625c-3.38086,0-6.231-.02759-9.07324-.08057q-4.26783-.08313-7.40625-.16089-3.13917-.08313-5.39356-.08056Q55.06153,165.45692,55.0625,164.1665ZM76.83264,78.87024l.10486,22.27405H87.481q4.34693,0,8.5332.16089a29.9335,29.9335,0,0,1,5.95654.644,14.59966,14.59966,0,0,0,3.46192-4.34693,12.49237,12.49237,0,0,0,1.63659-6.1715,12.28643,12.28643,0,0,0-2.3612-7.35242,12.52579,12.52579,0,0,0-6.0376-4.10547,32.23991,32.23991,0,0,0-10.16736-1.26379H83.83411c-1.396,0-2.252.02783-3.37891.08056Q78.76477,78.8728,76.83264,78.87024Zm.10486,41.06726v26.76978c1.82422.10815,3.24316.18872,5.06934.24145q2.7356.08313,6.60107.08057a42.59823,42.59823,0,0,0,11.67236-1.449,15.09155,15.09155,0,0,0,7.64746-4.508,11.7948,11.7948,0,0,0,3.13477-7.5669,13.99712,13.99712,0,0,0-2.65137-7.56714c-1.39648-2.14575-3.97217-3.90771-7.728-4.87378a39.35908,39.35908,0,0,0-4.42774-.80493,46.77008,46.77008,0,0,0-5.87646-.322Z"/><path class="cls-2" d="M148.17627,85.01489H165.7251a2.18992,2.18992,0,0,1,2.09277,1.61011,7.78189,7.78189,0,0,1,.80518,2.25391,26.02512,26.02512,0,0,1,.48291,3.13964c.10595,1.127.16113,2.6858.16113,3.97364a38.56036,38.56036,0,0,1,10.70654-8.9646c4.34717-2.415,8.93262-2.96509,14.51514-2.96509a1.28058,1.28058,0,0,1,1.44873,1.449v19.17481q0,1.1283-1.60986,1.127a36.87973,36.87973,0,0,0-10.168.72461,39.61157,39.61157,0,0,0-8.37207,2.6565,20.00151,20.00151,0,0,0-5.9126,3.70288V163.551q0,1.44909-1.28809,1.449H149.625a1.42233,1.42233,0,0,1-1.60986-1.61011V108.3894q0-3.53979-.08057-7.48657-.08277-3.94335-.2417-7.80859a57.31046,57.31046,0,0,0-.64355-6.95215.925.925,0,0,1,1.12695-1.1272Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 9.7 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "192x192 (5).png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 3.0 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "192x192 (1).png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 234"><defs><style>.cls-1{fill:#00005b;}.cls-2{fill:#99f;}</style></defs><title>Artboard 1</title><rect class="cls-1" width="240" height="234" rx="42.5"/><path class="cls-2" d="M114.83333,145.2583v16.583a2.124,2.124,0,0,1-1.28759,2.2539,40.74266,40.74266,0,0,1-9.48275,2.17359,103.546,103.546,0,0,1-12.63867.72461,71.17638,71.17638,0,0,1-17.79053-2.17359,52.60115,52.60115,0,0,1-15.37549-6.60107,46.89238,46.89238,0,0,1-11.91357-10.86743,48.17125,48.17125,0,0,1-7.728-14.9729,63.083,63.083,0,0,1-2.73731-19.32007q0-16.25976,7.084-28.57764a48.83838,48.83838,0,0,1,20.125-19.15893,66.09514,66.09514,0,0,1,31.07324-7.23845,101.80561,101.80561,0,0,1,12.47754.64405,37.34879,37.34879,0,0,1,8.45215,1.93213,2.07222,2.07222,0,0,1,.80517,1.93188V80.69727c0,.96606-.37744,1.28808-1.12695.96606a35.60969,35.60969,0,0,0-9.61291-2.93152,73.87457,73.87457,0,0,0-11.75293-.8855,36.76712,36.76712,0,0,0-18.40125,4.46106A30.14377,30.14377,0,0,0,63.08838,94.30176a37.1591,37.1591,0,0,0-4.186,18.11255,39.19347,39.19347,0,0,0,2.0127,13.2019,31.3052,31.3052,0,0,0,5.55469,9.82105,27.51331,27.51331,0,0,0,8.0498,6.60107,39.61266,39.61266,0,0,0,9.57959,3.62256A43.452,43.452,0,0,0,94,147.16667a108.58423,108.58423,0,0,0,10.86768-.48316,37.49154,37.49154,0,0,0,8.67806-2.23014q.64086-.483.96582-.24146A1.28143,1.28143,0,0,1,114.83333,145.2583Z"/><path class="cls-2" d="M151.7998,107.26221v56.4497c0,.86036-.37744,1.28809-1.12695,1.28809H130.58333a1.28093,1.28093,0,0,1-1.44922-1.449V52.36133c0-.74976.42774-1.127,1.28809-1.127h20.25065a.99742.99742,0,0,1,1.12695,1.127l.11687,37.99609a46.019,46.019,0,0,1,9.30159-4.74951c3.38086-1.23267,7.3252-1.52458,11.8335-1.52458a32.15363,32.15363,0,0,1,9.41845,1.449,22.33874,22.33874,0,0,1,8.77442,4.74455c2.68164,2.415,4.5638,5.69018,6.17366,9.821q2.41481,6.19959,2.415,16.01929V163.551q0,1.44909-1.28808,1.449H179.6932a1.42233,1.42233,0,0,1-1.60987-1.61011V118.37134a20.19262,20.19262,0,0,0-1.52978-8.45264,11.9783,11.9783,0,0,0-4.186-5.07129,13.74062,13.74062,0,0,0-7.36605-1.771,23.842,23.842,0,0,0-7.084,1.04638A19.51536,19.51536,0,0,0,151.7998,107.26221Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 2.2 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Character Animator.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "dreamweaver.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 234"><defs><style>.cls-1{fill:#470137;}.cls-2{fill:#ff61f6;}</style></defs><title>Asset 108</title><g id="Layer_2" data-name="Layer 2"><g id="Surfaces"><g id="UI_UX_Surface" data-name="UI/UX Surface"><g id="Outline_no_shadow" data-name="Outline no shadow"><rect class="cls-1" width="240" height="234" rx="42.5"/></g></g></g><g id="Outlined_Mnemonics_Logos" data-name="Outlined Mnemonics & Logos"><g id="Dw"><path class="cls-2" d="M22.79165,163.77319V61.3772a1.154,1.154,0,0,1,1.07061-1.28784q2.29406-.15858,6.72978-.24146,4.43435-.07947,10.40037-.16113,5.9651-.07911,12.69512-.08057,18.35382,0,30.43711,6.60108a43.38642,43.38642,0,0,1,18.12421,18.11254q6.04026,11.51257,6.04187,26.48438a61.904,61.904,0,0,1-3.13574,20.52759,48.50033,48.50033,0,0,1-8.56531,15.37549,51.60105,51.60105,0,0,1-12.31242,10.70654A52.6561,52.6561,0,0,1,69.594,163.69287a61.57744,61.57744,0,0,1-15.44771,2.01245H42.75139q-6.04373,0-11.24182-.08056-5.20112-.08313-7.80039-.24146Q22.79142,165.3833,22.79165,163.77319Zm21.10689-84.364v66.65405q1.68105,0,3.0592.08057,1.37629.08313,2.82959.16089,1.45144.08313,3.28836.08056a39.105,39.105,0,0,0,13.61264-2.2539,27.65111,27.65111,0,0,0,10.47691-6.762A30.60459,30.60459,0,0,0,83.895,126.26025a44.13817,44.13817,0,0,0,2.37083-14.9729,39.56635,39.56635,0,0,0-2.29429-14.08764A26.24931,26.24931,0,0,0,66.91748,81.01929,43.24974,43.24974,0,0,0,53.68753,79.0874q-3.06153,0-5.12388.08032Q46.49851,79.25085,43.89854,79.40918Z"/><path class="cls-2" d="M197.8317,165.39883H179.11545a1.42488,1.42488,0,0,1-1.19792-.40259,4.85239,4.85239,0,0,1-.59851-1.20752q-2.84653-12.0747-4.79168-21.33227-1.94741-9.25635-3.21912-15.69751-1.27376-6.43762-2.09613-10.948-.82488-4.50549-1.27285-7.56714h-.1494q-1.49853,6.60353-2.77,12.79956-1.27443,6.19959-2.77,12.47754-1.49853,6.27906-3.369,13.84595-1.87385,7.56959-4.11779,16.42187c-.20117,1.07422-.74836,1.61011-1.647,1.61011H132.39975a2.25187,2.25187,0,0,1-1.42224-.322,2.7092,2.7092,0,0,1-.67389-1.12695l-19.46506-77.28q-.44889-1.28687,1.19792-1.28809h18.8661q1.34732,0,1.49717.96607,2.99367,13.20336,5.016,22.78149,2.02165,9.58082,3.1442,16.34155,1.12322,6.76209,1.8718,11.02857.74723,4.26746,1.19792,7.00341h.29926a41.10162,41.10162,0,0,1,.74881-4.66894q.59668-2.89819,1.49717-7.48657.89844-4.58862,2.246-10.86743,1.34732-6.27906,3.06927-14.57056,1.71991-8.29029,4.5669-18.91748a3.4155,3.4155,0,0,1,.37463-1.28809c.1494-.21386.524-.322,1.12254-.322h19.61492c.599,0,.94726.37744,1.04807,1.1272q2.54411,10.95043,4.26719,19.2395,1.71992,8.29395,2.9948,14.65088,1.27035,6.36217,1.94628,10.948.67366,4.58862,1.27285,7.728a48.99622,48.99622,0,0,1,.74881,5.07154h.29925q.74723-3.0564,1.34778-7.084.596-4.02246,1.57209-9.09668.972-5.07129,2.09614-11.18945,1.12321-6.11646,2.77-13.68481,1.64566-7.56592,3.74316-16.42212.29834-1.28687,1.34777-1.28809h17.51833q1.34732,0,1.04807,1.449l-21.5612,77.11914a3.5396,3.5396,0,0,1-.599,1.12695A1.6416,1.6416,0,0,1,197.8317,165.39883Z"/></g></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "192x192 (3).png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.9 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "192x192 (2).png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "illustrator.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 234"><defs><style>.cls-1{fill:#300;}.cls-2{fill:#ff9a00;}</style></defs><title>Asset 104</title><g id="Layer_2" data-name="Layer 2"><g id="Surfaces"><g id="Drawing_Surface" data-name="Drawing Surface"><g id="Outline_no_shadow" data-name="Outline no shadow"><rect class="cls-1" width="240" height="234" rx="42.5"/></g></g></g><g id="Outlined_Mnemonics_Logos" data-name="Outlined Mnemonics & Logos"><g id="Ai"><path class="cls-2" d="M116.30029,140.42822H79.10938l-7.5669,23.50611a1.89431,1.89431,0,0,1-1.93213,1.449H50.77344q-1.61133,0-1.127-1.771L81.84619,70.87622q.4834-1.44873.96631-3.30054a34.478,34.478,0,0,0,.644-6.52026.99643.99643,0,0,1,1.127-1.1272h25.59863q1.125,0,1.28808.80518l36.54737,103.03979q.48267,1.61133-.96631,1.61011H126.12109a1.48824,1.48824,0,0,1-1.60986-1.12695Zm-31.395-20.28589h25.438q-.96606-3.21863-2.2539-7.24511-1.29054-4.02246-2.73682-8.61353-1.44947-4.58826-2.89844-9.177-1.44873-4.58826-2.65625-8.855-1.20776-4.26379-2.17334-7.80835h-.16113a130.10721,130.10721,0,0,1-3.38086,12.87988q-2.2566,7.24512-4.58887,14.812Q87.158,113.70484,84.90527,120.14233Z"/><path class="cls-2" d="M169.75049,76.99438a11.67783,11.67783,0,0,1-8.855-3.542,12.73665,12.73665,0,0,1-3.38135-9.177,11.813,11.813,0,0,1,3.62256-8.93555,12.44131,12.44131,0,0,1,8.93555-3.46142q5.79638,0,9.09668,3.46142a12.4294,12.4294,0,0,1,3.30029,8.93555,12.57378,12.57378,0,0,1-3.46143,9.177A12.3536,12.3536,0,0,1,169.75049,76.99438Zm-11.10938,86.77881v-76.958c0-.96582.42774-1.449,1.28809-1.449h19.80322q1.28687,0,1.28809,1.449v76.958q0,1.61133-1.28809,1.61011H160.09033Q158.6416,165.3833,158.64111,163.77319Z"/></g></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "incopy.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 234"><defs><style>.cls-1{fill:#49021f;}.cls-2{fill:#f36;}</style></defs><title>Asset 100</title><g id="Layer_2" data-name="Layer 2"><g id="Surfaces"><g id="Publishing_Surface" data-name="Publishing Surface"><g id="Outline_no_shadow" data-name="Outline no shadow"><rect class="cls-1" width="240" height="234" rx="42.5"/></g></g></g><g id="Outlined_Mnemonics_Logos" data-name="Outlined Mnemonics & Logos"><g id="Ic"><path class="cls-2" d="M96.49609,61.21631V163.77319q0,1.61133-1.44873,1.61011H75.56592q-1.28981,0-1.2876-1.61011V61.21631q0-1.28686,1.44873-1.28809H95.208A1.13916,1.13916,0,0,1,96.49609,61.21631Z"/><path class="cls-2" d="M173.293,146.54639v16.583a1.58323,1.58323,0,0,1-.80469,1.449,32.223,32.223,0,0,1-8.5332,1.93188q-4.82959.483-9.33789.48316a49.15747,49.15747,0,0,1-17.8711-3.05908,36.90879,36.90879,0,0,1-13.36279-8.6941,39.42369,39.42369,0,0,1-8.45264-13.2019,43.96084,43.96084,0,0,1-2.97851-16.261,41.341,41.341,0,0,1,5.63525-21.65454,40.06428,40.06428,0,0,1,15.8584-14.89258q10.22241-5.47155,23.9082-5.47387a61.44138,61.44138,0,0,1,9.499.56347,44.25928,44.25928,0,0,1,5.47412,1.20752,1.8953,1.8953,0,0,1,.80517,1.771l-.16113,16.583q0,1.44909-1.28808,1.127a29.04977,29.04977,0,0,0-5.47413-1.36841,49.79892,49.79892,0,0,0-8.0498-.56347,28.46821,28.46821,0,0,0-11.91406,2.41479,18.9705,18.9705,0,0,0-8.37207,7.24512q-3.06006,4.83-3.05909,12.23608,0,8.373,3.62256,13.20191a19.61436,19.61436,0,0,0,9.17725,6.84253,32.59531,32.59531,0,0,0,11.18945,2.01245,68.49913,68.49913,0,0,0,7.728-.40259,28.146,28.146,0,0,0,5.47412-1.04639Q173.2915,145.25953,173.293,146.54639Z"/></g></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "192x192.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "indesign.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><defs><style>.cls-1{fill:#49021f;}.cls-2{fill:#f36;}</style></defs><title>256</title><g id="Surfaces"><g id="Publishing_Surface" data-name="Publishing Surface"><g id="Outline_no_shadow" data-name="Outline no shadow"><rect class="cls-1" x="8" y="4" width="240" height="234" rx="42.5"/></g></g></g><g id="Outlined_Mnemonics_Logos" data-name="Outlined Mnemonics & Logos"><g id="Id"><path class="cls-2" d="M95.1582,65.21631V167.77319q0,1.61133-1.44873,1.61011H74.228q-1.28981,0-1.2876-1.61011V65.21631q0-1.28686,1.44873-1.28809h19.481A1.13916,1.13916,0,0,1,95.1582,65.21631Z"/><path class="cls-2" d="M152.79639,170.99341a49.88223,49.88223,0,0,1-21.49366-4.50806,34.17219,34.17219,0,0,1-15.05322-13.60449q-5.47559-9.09411-5.47412-22.78149a42.04123,42.04123,0,0,1,5.47412-21.09107,40.1871,40.1871,0,0,1,15.939-15.45605q10.46337-5.796,25.27685-5.7959.80347,0,2.09278.08056,1.28687.08277,3.05908.24146v-31.717c0-.74976.32226-1.127.96631-1.127h20.28564a.854.854,0,0,1,.96631.96582v95.15112q0,2.73962.24121,5.957.24169,3.22228.40283,5.7959a1.66418,1.66418,0,0,1-.96631,1.61011,79.86,79.86,0,0,1-16.26074,4.82983A87.29931,87.29931,0,0,1,152.79639,170.99341Zm9.8208-19.96411V107.07642a15.97072,15.97072,0,0,0-2.65625-.48316,32.10968,32.10968,0,0,0-3.30078-.16089,24.86085,24.86085,0,0,0-11.27,2.57593,22.00524,22.00524,0,0,0-8.45215,7.406q-3.30176,4.83-3.30078,12.719a28.39124,28.39124,0,0,0,1.69043,10.304,19.58772,19.58772,0,0,0,4.5083,7.084,17.16656,17.16656,0,0,0,6.76172,4.02515,26.49038,26.49038,0,0,0,8.2915,1.28784q2.25293,0,4.186-.16089A17.23154,17.23154,0,0,0,162.61719,151.0293Z"/></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Lightroom Classic.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 234"><defs><style>.cls-1{fill:#001e36;}.cls-2{fill:#31a8ff;}</style></defs><title>Asset 180</title><g id="Layer_2" data-name="Layer 2"><g id="Surfaces"><g id="Photo_Surface" data-name="Photo Surface"><g id="Outline_no_shadow" data-name="Outline no shadow"><rect class="cls-1" width="240" height="234" rx="42.5"/></g></g></g><g id="Outlined_Mnemonics_Logos" data-name="Outlined Mnemonics & Logos"><g id="LrC"><path class="cls-2" d="M84.192,165.3833H27.10131q-1.44954,0-1.44888-1.771V61.3772q0-1.44873,1.3043-1.449H44.63461c.67456,0,1.01425.37744,1.01425,1.1272v84.042H87.23519q1.30386,0,1.01426,1.449L85.6413,163.93433q-.14634.96606-.57964,1.20752A1.77863,1.77863,0,0,1,84.192,165.3833Z"/><path class="cls-2" d="M98.53617,85.36621h15.79394q1.30386,0,1.8835,1.61011a8.35226,8.35226,0,0,1,.72466,2.2539,28.71923,28.71923,0,0,1,.43462,3.13965q.143,1.69044.145,3.62232a34.80174,34.80174,0,0,1,9.63589-8.61328,24.98991,24.98991,0,0,1,13.40332-3.62256q1.30385,0,1.30385,1.449v19.481q0,1.1283-1.44887,1.127a32.23561,32.23561,0,0,0-9.49087.72461,33.15877,33.15877,0,0,0-7.53487,2.6565,15.39786,15.39786,0,0,0-4.85419,3.70288v51.03711q0,1.44909-1.15928,1.449H99.84q-1.4502,0-1.44888-1.61011V108.3894q0-3.53979-.07251-7.48657-.07449-3.94335-.21753-7.80859a56.54581,56.54581,0,0,0-.5792-6.60083,1.35031,1.35031,0,0,1,.21709-.8855A1.0463,1.0463,0,0,1,98.53617,85.36621Z"/><path class="cls-2" d="M212.62149,70.60112a25.83378,25.83378,0,0,0-6.67765-1.70558,75.39944,75.39944,0,0,0-10.19685-.58619c-9.80813,0-18.388,2.10233-25.50092,6.24858a42.56967,42.56967,0,0,0-16.555,17.51031c-3.85522,7.44733-5.81023,16.20145-5.81023,26.01991a63.596,63.596,0,0,0,2.24138,17.56717,46.1497,46.1497,0,0,0,6.353,13.67058,40.59588,40.59588,0,0,0,9.81019,9.9415,41.323,41.323,0,0,0,12.65638,6.03819,53.131,53.131,0,0,0,14.58865,1.98137,76.40988,76.40988,0,0,0,10.32505-.65856,32.8752,32.8752,0,0,0,8.05575-2.0005,2.55936,2.55936,0,0,0,1.4629-2.65854V147.05247a1.80324,1.80324,0,0,0-.50762-1.467c-.25019-.20729-.78056-.4549-1.56008.14267a28.74176,28.74176,0,0,1-6.96711,1.65984,78.829,78.829,0,0,1-8.72258.43008,30.76187,30.76187,0,0,1-7.84072-1.06279A29.61771,29.61771,0,0,1,180.1948,143.57a22.35682,22.35682,0,0,1-6.323-5.76526,28.55726,28.55726,0,0,1-4.38973-8.62954,38.36439,38.36439,0,0,1-1.59729-11.667,35.99278,35.99278,0,0,1,3.31762-15.99054,25.412,25.412,0,0,1,9.39562-10.5127,27.17413,27.17413,0,0,1,14.62793-3.8056,53.422,53.422,0,0,1,9.39355.78573,23.36188,23.36188,0,0,1,7.18113,2.3998,1.26058,1.26058,0,0,0,1.27991.00931,1.72374,1.72374,0,0,0,.60893-1.49443V72.96811A2.56436,2.56436,0,0,0,212.62149,70.60112Z"/></g></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 2.6 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "lightroom.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 240 234" style="enable-background:new 0 0 240 234;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#001E36;}
|
||||
.st1{fill:#31A8FF;}
|
||||
</style>
|
||||
<g id="Layer_2_1_">
|
||||
<g id="Surfaces">
|
||||
<g id="Photo_Surface">
|
||||
<g id="Outline_no_shadow">
|
||||
<path class="st0" d="M42.5,0h155C221,0,240,19,240,42.5v149c0,23.5-19,42.5-42.5,42.5h-155C19,234,0,215,0,191.5v-149
|
||||
C0,19,19,0,42.5,0z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Outlined_Mnemonics_Logos">
|
||||
<g id="Lr">
|
||||
<path class="st1" d="M126,165.4H62.6c-1.1,0-1.6-0.6-1.6-1.8V61.4c-0.1-0.7,0.4-1.3,1.1-1.4c0.1,0,0.2,0,0.4,0h19.6
|
||||
c0.5-0.1,1.1,0.3,1.1,0.8c0,0.1,0,0.2,0,0.3v84h46.2c1,0,1.3,0.5,1.1,1.4l-2.9,17.4c0,0.5-0.3,0.9-0.6,1.2
|
||||
C126.7,165.3,126.4,165.4,126,165.4z"/>
|
||||
<path class="st1" d="M142,85.4h17.5c1,0,1.8,0.7,2.1,1.6c0.4,0.7,0.7,1.5,0.8,2.3c0.2,1,0.4,2.1,0.5,3.1c0.1,1.1,0.2,2.3,0.2,3.6
|
||||
c3-3.5,6.6-6.4,10.7-8.6c4.6-2.5,9.7-3.7,14.9-3.6c0.7-0.1,1.3,0.4,1.4,1.1c0,0.1,0,0.2,0,0.4v19.5c0,0.8-0.5,1.1-1.6,1.1
|
||||
c-6.5-0.4-13,0.8-18.9,3.4c-2,0.9-3.9,2.1-5.4,3.7v51c0,1-0.4,1.4-1.3,1.4h-19.5c-0.8,0.1-1.5-0.4-1.6-1.2c0-0.1,0-0.3,0-0.4
|
||||
v-55.4c0-2.4,0-4.9-0.1-7.5c-0.1-2.6-0.1-5.2-0.2-7.8c-0.1-2.2-0.3-4.4-0.6-6.6c-0.1-0.5,0.2-1,0.7-1.1
|
||||
C141.7,85.3,141.8,85.3,142,85.4L142,85.4z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "192x192 (1).png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "media-encoder.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 234"><defs><style>.cls-1{fill:#00005b;}.cls-2{fill:#99f;}</style></defs><title>Asset 106</title><g id="Layer_2" data-name="Layer 2"><g id="Surfaces"><g id="Video_Audio_Surface" data-name="Video/Audio Surface"><g id="Outline_no_shadow" data-name="Outline no shadow"><rect class="cls-1" width="240" height="234" rx="42.5"/></g></g></g><g id="Outlined_Mnemonics_Logos" data-name="Outlined Mnemonics & Logos"><g id="Me"><path class="cls-2" d="M33.84038,60.89844a1.07979,1.07979,0,0,1,1.22368-.96606H63.20688a1.52823,1.52823,0,0,1,1.68245,1.1272q1.068,4.18835,2.6,10.0625,1.52729,5.87879,3.28836,12.96045,1.75689,7.08654,3.5945,14.49,1.83554,7.40735,3.59451,14.40942,1.7562,7.00343,2.9822,12.88013,1.22253,5.87879,1.9886,9.74048h.15308q.60882-3.38123,2.14121-9.01612,1.52658-5.63379,3.5175-12.63843,1.98651-7.00341,4.05327-14.40966,2.06514-7.40333,4.05328-14.65088,1.9858-7.24512,3.74712-13.44361,1.75689-6.19592,2.82959-10.38427a1.33567,1.33567,0,0,1,1.37675-1.1272h27.37791a1.21606,1.21606,0,0,1,1.37676,1.1272l3.67058,102.8789a1.073,1.073,0,0,1-.22915,1.04639,1.31054,1.31054,0,0,1-.99453.40259H116.12763a1.19507,1.19507,0,0,1-.76491-.24146,1.069,1.069,0,0,1-.30569-.8855q0-8.21118-.07654-17.22705-.07932-9.01464-.22962-18.11255-.15377-9.09375,0-17.4685.151-8.36938.15308-15.45606,0-7.08252.07654-12.3164.07515-5.22986.07654-7.8086H114.904q-.91776,4.0265-2.44736,10.54566-1.53077,6.52038-3.59405,14.65088-2.06512,8.13171-4.43549,16.82446-2.37337,8.69421-4.58858,17.22705-2.21891,8.53564-4.28288,15.939-2.06445,7.40735-3.44143,13.041a1.69161,1.69161,0,0,1-1.68245,1.28809H73.14848a1.6445,1.6445,0,0,1-1.83552-1.28809q-1.37629-5.63379-3.13528-13.041-1.76037-7.40332-3.59451-15.45581-1.83481-8.049-3.90019-16.50269-2.06443-8.45252-3.9002-16.34131-1.83482-7.88672-3.28835-14.812-1.45562-6.92066-2.67651-12.23608h-.30616v12.719q0,7.40734-.22915,16.34155-.2296,8.93555-.45876,18.676-.22962,9.743-.5353,20.125-.30825,10.38427-.76492,20.52734c0,.86035-.40867,1.28809-1.22368,1.28809H29.558a1.83142,1.83142,0,0,1-.99406-.24146q-.38409-.24169-.22962-1.20752Z"/><path class="cls-2" d="M196.67432,130.25122h-31.7168a20.74008,20.74008,0,0,0,3.05908,8.45264,16.56436,16.56436,0,0,0,7.3252,6.03735q4.90942,2.2566,12.7998,2.25415a58.43054,58.43054,0,0,0,10.38428-.8855,43.29305,43.29305,0,0,0,9.41846-2.81762c.53564-.42749.80517-.16089.80517.80517v15.29492a2.38427,2.38427,0,0,1-.2417,1.20752,2.30981,2.30981,0,0,1-.72461.72437,45.14814,45.14814,0,0,1-10.46484,3.46167,70.76093,70.76093,0,0,1-14.168,1.20752q-11.43384,0-19.15918-3.542a34.14679,34.14679,0,0,1-12.55762-9.499,37.19779,37.19779,0,0,1-6.92334-13.12158,51.668,51.668,0,0,1-2.09277-14.57056,50.74576,50.74576,0,0,1,2.49561-15.85839,41.42459,41.42459,0,0,1,7.48632-13.68506,35.96394,35.96394,0,0,1,12.0752-9.499,37.76341,37.76341,0,0,1,16.74365-3.46142,36.33453,36.33453,0,0,1,15.53662,3.05884,28.09974,28.09974,0,0,1,10.54541,8.2915,36.61534,36.61534,0,0,1,5.957,11.35059,40.44772,40.44772,0,0,1,1.93213,12.23608q0,3.54309-.2417,6.43994-.241,2.89784-.40234,4.186a1.45875,1.45875,0,0,1-1.44922,1.28808q-.96606,0-3.30029.24146-2.3357.24133-5.7959.322Q200.53565,130.25365,196.67432,130.25122Zm-31.7168-15.61694h21.09082q3.86425,0,5.71533-.08057a11.3564,11.3564,0,0,0,2.335-.24145v-.96607a12.88035,12.88035,0,0,0-.644-3.70288,13.15261,13.15261,0,0,0-13.041-9.177,13.98587,13.98587,0,0,0-13.28271,7.56689A20.32457,20.32457,0,0,0,164.95752,114.63428Z"/></g></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "192x192 (2).png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "photoshop.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 234"><defs><style>.cls-1{fill:#001e36;}.cls-2{fill:#31a8ff;}</style></defs><title>Asset 116</title><g id="Layer_2" data-name="Layer 2"><g id="Surfaces"><g id="Photo_Surface" data-name="Photo Surface"><g id="Outline_no_shadow" data-name="Outline no shadow"><rect class="cls-1" width="240" height="234" rx="42.5"/></g></g></g><g id="Outlined_Mnemonics_Logos" data-name="Outlined Mnemonics & Logos"><g id="Ps"><path class="cls-2" d="M54.04167,164.09521V61.21631c0-.74976.32226-1.127.96631-1.127,1.71533,0,3.28157-.02515,5.64388-.08057q3.53979-.07911,7.64746-.16089,4.106-.07947,8.69433-.16113,4.5879-.07911,9.09619-.08057,12.23366,0,20.60791,3.05908a35.755,35.755,0,0,1,13.44385,8.21094,31.496,31.496,0,0,1,7.3252,11.35059,37.64894,37.64894,0,0,1,2.25439,12.96045q0,12.88256-5.957,21.252a33.65844,33.65844,0,0,1-16.1001,12.15552c-6.7622,2.52319-14.27636,3.3789-22.54,3.3789q-3.54345,0-4.99121-.08056-1.44873-.07947-4.34668-.08057v32.12183a1.28093,1.28093,0,0,1-1.44922,1.449H55.16862C54.41667,165.3833,54.04167,164.95557,54.04167,164.09521Zm21.74446-84.686v33.55493q2.09034.16224,3.86377.16089h5.313a37.7594,37.7594,0,0,0,11.51172-1.83765,17.35824,17.35824,0,0,0,8.21094-5.313q3.13915-3.70167,3.13965-10.304a16.28281,16.28281,0,0,0-2.335-8.85522,15.01394,15.01394,0,0,0-7.00341-5.71534A29.83951,29.83951,0,0,0,86.73389,79.0874q-3.86427,0-6.84229.08032Q76.91065,79.25085,75.78613,79.40918Z"/><path class="cls-2" d="M191.97114,106.863a37.6431,37.6431,0,0,0-9.57959-3.3811,50.875,50.875,0,0,0-11.18946-1.28809,20.82175,20.82175,0,0,0-6.03759.72461,5.42475,5.42475,0,0,0-3.13965,2.01245,5.0699,5.0699,0,0,0-.80469,2.73706,4.27537,4.27537,0,0,0,.96582,2.57593,10.95825,10.95825,0,0,0,3.38086,2.65649,67.449,67.449,0,0,0,7.084,3.30054,70.20083,70.20083,0,0,1,15.37549,7.32544,23.38242,23.38242,0,0,1,7.88916,8.2915A22.10738,22.10738,0,0,1,198.25,142.122a23.143,23.143,0,0,1-3.86377,13.28247,25.41573,25.41573,0,0,1-11.18995,8.93531q-7.32788,3.219-18.1123,3.22021a65.50368,65.50368,0,0,1-13.60449-1.28808,43.40843,43.40843,0,0,1-10.22363-3.22,2.08508,2.08508,0,0,1-1.127-1.93213V143.73187a.94571.94571,0,0,1,.40283-.8855.781.781,0,0,1,.88526.08057,43.01131,43.01131,0,0,0,12.397,4.9104,51.12181,51.12181,0,0,0,11.75293,1.52954q5.63379,0,8.2915-1.449a4.5512,4.5512,0,0,0,2.65674-4.186q0-2.09034-2.415-4.02491-2.41479-1.93212-9.82129-4.66918a59.18392,59.18392,0,0,1-14.24853-7.24488,24.5718,24.5718,0,0,1-7.5669-8.45263,22.20192,22.20192,0,0,1-2.33447-10.22339,23.08045,23.08045,0,0,1,3.38086-12.075,24.57046,24.57046,0,0,1,10.46533-9.177q7.08252-3.53943,17.71-3.542a78.40115,78.40115,0,0,1,12.397.8855,32.49681,32.49681,0,0,1,8.63066,2.33447,1.46829,1.46829,0,0,1,.96582.8855,4.44869,4.44869,0,0,1,.16113,1.20752v16.261a1.08221,1.08221,0,0,1-.48291.96606A1.556,1.556,0,0,1,191.97114,106.863Z"/></g></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "192x192 (3).png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "premiere-pro.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 240 234" style="enable-background:new 0 0 240 234;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#00005B;}
|
||||
.st1{fill:#9999FF;}
|
||||
</style>
|
||||
<g id="Layer_2_1_">
|
||||
<g id="Surfaces">
|
||||
<g id="Video_Audio_Surface">
|
||||
<g id="Outline_no_shadow">
|
||||
<path class="st0" d="M42.5,0h155C221,0,240,19,240,42.5v149c0,23.5-19,42.5-42.5,42.5h-155C19,234,0,215,0,191.5v-149
|
||||
C0,19,19,0,42.5,0z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Outlined_Mnemonics_Logos">
|
||||
<g id="Pr">
|
||||
<path class="st1" d="M57,164.1V61.2c0-0.7,0.3-1.1,1-1.1c1.7,0,3.3,0,5.6-0.1c2.4-0.1,4.9-0.1,7.6-0.2c2.7-0.1,5.6-0.1,8.7-0.2
|
||||
c3.1-0.1,6.1-0.1,9.1-0.1c8.2,0,15,1,20.6,3.1c5,1.7,9.6,4.5,13.4,8.2c3.2,3.2,5.7,7.1,7.3,11.4c1.5,4.2,2.3,8.5,2.3,13
|
||||
c0,8.6-2,15.7-6,21.3s-9.6,9.8-16.1,12.2c-6.8,2.5-14.3,3.4-22.5,3.4c-2.4,0-4,0-5-0.1s-2.4-0.1-4.3-0.1V164
|
||||
c0.1,0.7-0.4,1.3-1.1,1.4c-0.1,0-0.2,0-0.4,0h-19C57.4,165.4,57,165,57,164.1z M78.8,79.4V113c1.4,0.1,2.7,0.2,3.9,0.2H88
|
||||
c3.9,0,7.8-0.6,11.5-1.8c3.2-0.9,6-2.8,8.2-5.3c2.1-2.5,3.1-5.9,3.1-10.3c0.1-3.1-0.7-6.2-2.3-8.9c-1.7-2.6-4.1-4.6-7-5.7
|
||||
c-3.7-1.5-7.7-2.1-11.8-2c-2.6,0-4.9,0-6.8,0.1C80.9,79.2,79.5,79.3,78.8,79.4L78.8,79.4z"/>
|
||||
<path class="st1" d="M146.6,85.2h17.5c1,0,1.8,0.7,2.1,1.6c0.3,0.8,0.5,1.6,0.6,2.5c0.2,1,0.4,2.1,0.5,3.1
|
||||
c0.1,1.1,0.2,2.3,0.2,3.6c3-3.5,6.6-6.4,10.7-8.6c4.6-2.6,9.9-3.9,15.2-3.9c0.7-0.1,1.3,0.4,1.4,1.1c0,0.1,0,0.2,0,0.4v19.5
|
||||
c0,0.8-0.5,1.1-1.6,1.1c-3.6-0.1-7.3,0.2-10.8,1c-2.9,0.6-5.7,1.5-8.4,2.7c-1.9,0.9-3.7,2.1-5.1,3.7v51c0,1-0.4,1.4-1.3,1.4
|
||||
h-19.7c-0.8,0.1-1.5-0.4-1.6-1.2c0-0.1,0-0.3,0-0.4v-55.4c0-2.4,0-4.9-0.1-7.5s-0.1-5.2-0.2-7.8c0-2.3-0.2-4.5-0.4-6.8
|
||||
c-0.1-0.5,0.2-1,0.7-1.1C146.3,85.1,146.5,85.1,146.6,85.2L146.6,85.2z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "192x192 (4).png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Premiere Rush.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><defs><style>.cls-1{fill:#00005b;}.cls-2{fill:#99f;}</style></defs><title>256</title><g id="Surfaces"><g id="Video_Audio_Surface" data-name="Video/Audio Surface"><g id="Outline_no_shadow" data-name="Outline no shadow"><rect class="cls-1" x="8" y="4" width="240" height="234" rx="42.5"/></g></g></g><g id="Live_Text" data-name="Live Text"><g id="Ru"><path class="cls-2" d="M68.67,169.38H49.52c-.86,0-1.29-.48-1.29-1.45V65.54c0-1,.32-1.45,1-1.45q5.64-.17,9.66-.24l7.73-.16c2.47-.06,5-.08,7.65-.08H83q13.53,0,22.7,3.78a30.67,30.67,0,0,1,14.09,11Q124.7,85.5,124.7,96A28.09,28.09,0,0,1,122.13,108a30.3,30.3,0,0,1-7.09,9.58,34.67,34.67,0,0,1-10.46,6.52q1.61,2.1,4.51,6.36t6.36,9.5q3.45,5.24,6.92,10.55t6.28,9.82c1.87,3,3.3,5.37,4.26,7.08a1.75,1.75,0,0,1,.41,1.29.66.66,0,0,1-.73.64H109.25a3.36,3.36,0,0,1-1.45-.24,2.14,2.14,0,0,1-.81-.72c-1.18-1.72-2.65-4-4.43-6.76s-3.64-5.91-5.63-9.34-4-6.82-6-10.15-3.73-6.33-5.23-9A11,11,0,0,0,83.89,131a3.24,3.24,0,0,0-2-.56H70v37.51C70,168.9,69.53,169.38,68.67,169.38Zm1.29-58H82.52a32.18,32.18,0,0,0,10.55-1.53,13.89,13.89,0,0,0,6.84-4.75,13,13,0,0,0,2.41-8q0-7.4-5.47-10.7t-14.49-3.3c-2.58,0-5,0-7.16.08s-4,.13-5.24.24Z"/><path class="cls-2" d="M209.55,90.49v61c0,2.69,0,5.07.08,7.17s.16,3.92.32,5.47.3,2.87.4,3.95c.11.86-.27,1.28-1.12,1.28H192a1.88,1.88,0,0,1-1.93-1.12c-.22-.75-.4-1.59-.57-2.5a14.48,14.48,0,0,1-.24-2.49A28.61,28.61,0,0,1,178,169.46,43.45,43.45,0,0,1,166.88,171,33.08,33.08,0,0,1,156,169.3a21.18,21.18,0,0,1-8.54-5.47,25.83,25.83,0,0,1-5.63-9.66,44.05,44.05,0,0,1-2-14.25V90.65a1.13,1.13,0,0,1,1.28-1.28h19.81a1.13,1.13,0,0,1,1.28,1.28v46.69q0,6.61,2.9,10.39t9.66,3.78a18.15,18.15,0,0,0,6.76-1.21,21.07,21.07,0,0,0,5.64-3.3V90.49c0-.75.48-1.12,1.45-1.12h19.8A1,1,0,0,1,209.55,90.49Z"/></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Substance 3D Sampler (Beta) - 96x96.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Substance 3D Sampler (Beta) - 192x192.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Substance 3D Sampler - 96x96.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Substance 3D Sampler - 192x192.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -1,9 +1,11 @@
|
||||
//
|
||||
// Adobe-Downloader
|
||||
// Adobe Downloader
|
||||
//
|
||||
// Created by X1a0He on 2024/10/30.
|
||||
//
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
enum PackageStatus: Equatable {
|
||||
case waiting
|
||||
case downloading
|
||||
@@ -11,7 +13,7 @@ enum PackageStatus: Equatable {
|
||||
case completed
|
||||
case failed(String)
|
||||
|
||||
var description: String {
|
||||
var description: LocalizedStringKey {
|
||||
switch self {
|
||||
case .waiting: return "等待中"
|
||||
case .downloading: return "下载中"
|
||||
@@ -68,6 +70,7 @@ enum NetworkError: Error, LocalizedError {
|
||||
case applicationInfoError(String, Error?)
|
||||
case unsupportedPlatform(String)
|
||||
case incompatibleVersion(String, String)
|
||||
case cancelled
|
||||
|
||||
var errorCode: Int {
|
||||
switch self {
|
||||
@@ -93,6 +96,7 @@ enum NetworkError: Error, LocalizedError {
|
||||
case .applicationInfoError: return 7001
|
||||
case .unsupportedPlatform: return 7002
|
||||
case .incompatibleVersion: return 7003
|
||||
case .cancelled: return 5004
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,6 +155,8 @@ enum NetworkError: Error, LocalizedError {
|
||||
return NSLocalizedString("不支持的平台: \(platform)", comment: "Unsupported platform")
|
||||
case .incompatibleVersion(let current, let required):
|
||||
return NSLocalizedString("版本不兼容: 当前版本 \(current), 需要版本 \(required)", comment: "Incompatible version")
|
||||
case .cancelled:
|
||||
return NSLocalizedString("下载已取消", comment: "Download cancelled")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,11 @@
|
||||
//
|
||||
// Adobe-Downloader
|
||||
// Adobe Downloader
|
||||
//
|
||||
// Created by X1a0He on 2024/10/30.
|
||||
//
|
||||
import Foundation
|
||||
import AppKit
|
||||
|
||||
extension FileManager {
|
||||
func volumeAvailableCapacity(for url: URL) throws -> Int64 {
|
||||
let resourceValues = try url.resourceValues(forKeys: [.volumeAvailableCapacityKey])
|
||||
return Int64(resourceValues.volumeAvailableCapacity ?? 0)
|
||||
}
|
||||
}
|
||||
|
||||
extension Sap.Versions {
|
||||
var size: Int64 {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
extension NewDownloadTask {
|
||||
var startTime: Date {
|
||||
switch totalStatus {
|
||||
@@ -30,13 +17,11 @@ extension NewDownloadTask {
|
||||
return info.timestamp
|
||||
case .paused(let info):
|
||||
return info.timestamp
|
||||
case .retrying(let info):
|
||||
return info.nextRetryDate.addingTimeInterval(-60)
|
||||
case .failed(let info):
|
||||
return info.timestamp
|
||||
case .waiting:
|
||||
return Date()
|
||||
case .none:
|
||||
case .retrying(let info):
|
||||
return info.nextRetryDate.addingTimeInterval(-60)
|
||||
case .waiting, .none:
|
||||
return createAt
|
||||
}
|
||||
}
|
||||
@@ -54,7 +39,7 @@ extension NetworkManager {
|
||||
for task in self.downloadTasks {
|
||||
if case .paused(let info) = task.status,
|
||||
info.reason == .networkIssue {
|
||||
await self.resumeDownload(taskId: task.id)
|
||||
await self.downloadUtils.resumeDownloadTask(taskId: task.id)
|
||||
}
|
||||
}
|
||||
} else if wasConnected && !self.isConnected {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// Adobe-Downloader
|
||||
// Adobe Downloader
|
||||
//
|
||||
// Created by X1a0He on 2024/10/30.
|
||||
//
|
||||
@@ -30,4 +30,12 @@ struct AppStatics {
|
||||
("hu_HU", "Magyar"),
|
||||
("ALL", "所有语言")
|
||||
]
|
||||
|
||||
static func isValidLanguageCode(_ code: String) -> Bool {
|
||||
return supportedLanguages.contains { $0.code == code }
|
||||
}
|
||||
|
||||
static func getLanguageName(for code: String) -> String? {
|
||||
return supportedLanguages.first { $0.code == code }?.name
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// Adobe-Downloader
|
||||
// Adobe Downloader
|
||||
//
|
||||
// Created by X1a0He on 2024/10/30.
|
||||
//
|
||||
@@ -97,6 +97,13 @@ class Package: Identifiable, ObservableObject {
|
||||
var hasValidSize: Bool {
|
||||
downloadSize > 0
|
||||
}
|
||||
|
||||
func updateStatus(_ status: PackageStatus) {
|
||||
Task { @MainActor in
|
||||
self.status = status
|
||||
objectWillChange.send()
|
||||
}
|
||||
}
|
||||
}
|
||||
class ProductsToDownload: ObservableObject {
|
||||
var sapCode: String
|
||||
|
||||
@@ -29,12 +29,13 @@ struct ContentView: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
HStack(spacing: 16) {
|
||||
HStack() {
|
||||
Text("Adobe Downloader")
|
||||
.font(.title2)
|
||||
.fontWeight(.bold)
|
||||
.frame(width: 180)
|
||||
|
||||
.fixedSize()
|
||||
|
||||
|
||||
SettingsView(
|
||||
useDefaultLanguage: $useDefaultLanguage,
|
||||
useDefaultDirectory: $useDefaultDirectory,
|
||||
@@ -45,7 +46,6 @@ struct ContentView: View {
|
||||
|
||||
HStack(spacing: 8) {
|
||||
SearchField(text: $searchText)
|
||||
.frame(width: 140)
|
||||
|
||||
Button(action: refreshData) {
|
||||
Image(systemName: "arrow.clockwise")
|
||||
@@ -73,10 +73,8 @@ struct ContentView: View {
|
||||
}
|
||||
)
|
||||
}
|
||||
.frame(width: 200)
|
||||
}
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 8)
|
||||
.background(Color(NSColor.windowBackgroundColor))
|
||||
|
||||
ZStack {
|
||||
@@ -107,12 +105,10 @@ struct ContentView: View {
|
||||
Button(action: {
|
||||
networkManager.retryFetchData()
|
||||
}) {
|
||||
HStack(spacing: 8) {
|
||||
HStack() {
|
||||
Image(systemName: "arrow.clockwise")
|
||||
Text("重试")
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.controlSize(.large)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// Adobe-Downloader
|
||||
// Adobe Downloader
|
||||
//
|
||||
// Created by X1a0He on 2024/10/30.
|
||||
//
|
||||
|
||||
@@ -4,75 +4,6 @@ import Combine
|
||||
import AppKit
|
||||
import SwiftUI
|
||||
|
||||
|
||||
private class DownloadDelegate: NSObject, URLSessionDownloadDelegate {
|
||||
var completionHandler: (URL?, URLResponse?, Error?) -> Void
|
||||
var progressHandler: ((Int64, Int64, Int64) -> Void)?
|
||||
var destinationDirectory: URL
|
||||
var fileName: String
|
||||
|
||||
init(destinationDirectory: URL,
|
||||
fileName: String,
|
||||
completionHandler: @escaping (URL?, URLResponse?, Error?) -> Void,
|
||||
progressHandler: ((Int64, Int64, Int64) -> Void)? = nil) {
|
||||
self.destinationDirectory = destinationDirectory
|
||||
self.fileName = fileName
|
||||
self.completionHandler = completionHandler
|
||||
self.progressHandler = progressHandler
|
||||
super.init()
|
||||
}
|
||||
|
||||
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
|
||||
do {
|
||||
if !FileManager.default.fileExists(atPath: destinationDirectory.path) {
|
||||
try FileManager.default.createDirectory(at: destinationDirectory, withIntermediateDirectories: true)
|
||||
}
|
||||
let destinationURL = destinationDirectory.appendingPathComponent(fileName)
|
||||
if FileManager.default.fileExists(atPath: destinationURL.path) {
|
||||
try FileManager.default.removeItem(at: destinationURL)
|
||||
}
|
||||
try FileManager.default.moveItem(at: location, to: destinationURL)
|
||||
Thread.sleep(forTimeInterval: 0.5)
|
||||
if FileManager.default.fileExists(atPath: destinationURL.path) {
|
||||
_ = try FileManager.default.attributesOfItem(atPath: destinationURL.path)[.size] as? Int64 ?? 0
|
||||
completionHandler(destinationURL, downloadTask.response, nil)
|
||||
} else {
|
||||
completionHandler(nil, downloadTask.response, NetworkError.fileSystemError("文件移动后不存在", nil))
|
||||
}
|
||||
} catch {
|
||||
completionHandler(nil, downloadTask.response, error)
|
||||
}
|
||||
}
|
||||
|
||||
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
|
||||
guard let error = error else { return }
|
||||
switch (error as NSError).code {
|
||||
case NSURLErrorCancelled:
|
||||
return
|
||||
case NSURLErrorTimedOut:
|
||||
completionHandler(nil, task.response, NetworkError.downloadError("下载超时", error))
|
||||
case NSURLErrorNotConnectedToInternet:
|
||||
completionHandler(nil, task.response, NetworkError.noConnection)
|
||||
default:
|
||||
completionHandler(nil, task.response, error)
|
||||
}
|
||||
}
|
||||
|
||||
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask,
|
||||
didWriteData bytesWritten: Int64,
|
||||
totalBytesWritten: Int64,
|
||||
totalBytesExpectedToWrite: Int64) {
|
||||
guard totalBytesExpectedToWrite > 0 else { return }
|
||||
guard bytesWritten > 0 else { return }
|
||||
|
||||
progressHandler?(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite)
|
||||
}
|
||||
func cleanup() {
|
||||
completionHandler = { _, _, _ in }
|
||||
progressHandler = nil
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
class NetworkManager: ObservableObject {
|
||||
typealias ProgressUpdate = (bytesWritten: Int64, totalWritten: Int64, expectedToWrite: Int64)
|
||||
@@ -110,9 +41,10 @@ class NetworkManager: ObservableObject {
|
||||
await fetchProductsWithRetry()
|
||||
}
|
||||
func startDownload(sap: Sap, selectedVersion: String, language: String, destinationURL: URL) async throws {
|
||||
guard let productInfo = self.saps[sap.sapCode]?.versions[selectedVersion] else {
|
||||
throw NetworkError.invalidData("无法获取产品信息")
|
||||
guard let productInfo = self.saps[sap.sapCode]?.versions[selectedVersion] else {
|
||||
throw NetworkError.invalidData("无法获取产品信息")
|
||||
}
|
||||
|
||||
let task = NewDownloadTask(
|
||||
sapCode: sap.sapCode,
|
||||
version: selectedVersion,
|
||||
@@ -121,11 +53,7 @@ class NetworkManager: ObservableObject {
|
||||
directory: destinationURL,
|
||||
productsToDownload: [],
|
||||
createAt: Date(),
|
||||
totalStatus: .preparing(DownloadStatus.PrepareInfo(
|
||||
message: "正在准备下载...",
|
||||
timestamp: Date(),
|
||||
stage: .initializing
|
||||
)),
|
||||
totalStatus: .preparing(DownloadStatus.PrepareInfo(message: "正在准备下载...", timestamp: Date(), stage: .initializing)),
|
||||
totalProgress: 0,
|
||||
totalDownloadedSize: 0,
|
||||
totalSize: 0,
|
||||
@@ -136,171 +64,7 @@ class NetworkManager: ObservableObject {
|
||||
updateDockBadge()
|
||||
|
||||
do {
|
||||
if task.sapCode == "APRO" {
|
||||
try await downloadUtils.downloadAPRO(task: task, productInfo: productInfo)
|
||||
return
|
||||
}
|
||||
|
||||
try downloadUtils.createInstallerApp(
|
||||
for: task.sapCode,
|
||||
version: task.version,
|
||||
language: task.language,
|
||||
at: task.directory
|
||||
)
|
||||
|
||||
var productsToDownload: [ProductsToDownload] = []
|
||||
|
||||
productsToDownload.append(ProductsToDownload(
|
||||
sapCode: sap.sapCode,
|
||||
version: selectedVersion,
|
||||
buildGuid: productInfo.buildGuid
|
||||
))
|
||||
|
||||
for dependency in productInfo.dependencies {
|
||||
if let dependencyVersions = saps[dependency.sapCode]?.versions {
|
||||
let sortedVersions = dependencyVersions.sorted { first, second in
|
||||
first.value.productVersion.compare(second.value.productVersion, options: .numeric) == .orderedDescending
|
||||
}
|
||||
|
||||
var firstGuid = ""
|
||||
var buildGuid = ""
|
||||
|
||||
for (_, versionInfo) in sortedVersions where versionInfo.baseVersion == dependency.version {
|
||||
if firstGuid.isEmpty { firstGuid = versionInfo.buildGuid }
|
||||
|
||||
if allowedPlatform.contains(versionInfo.apPlatform) {
|
||||
buildGuid = versionInfo.buildGuid
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if buildGuid.isEmpty { buildGuid = firstGuid }
|
||||
|
||||
if !buildGuid.isEmpty {
|
||||
productsToDownload.append(ProductsToDownload(
|
||||
sapCode: dependency.sapCode,
|
||||
version: dependency.version,
|
||||
buildGuid: buildGuid
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for product in productsToDownload {
|
||||
await MainActor.run {
|
||||
task.setStatus(.preparing(DownloadStatus.PrepareInfo(
|
||||
message: "正在处理 \(product.sapCode) 的产品信息...",
|
||||
timestamp: Date(),
|
||||
stage: .fetchingInfo
|
||||
)))
|
||||
objectWillChange.send()
|
||||
}
|
||||
|
||||
let productDir = task.directory.appendingPathComponent("Contents/Resources/products/\(product.sapCode)")
|
||||
if !FileManager.default.fileExists(atPath: productDir.path) {
|
||||
try FileManager.default.createDirectory(at: productDir, withIntermediateDirectories: true)
|
||||
}
|
||||
|
||||
await MainActor.run {
|
||||
task.setStatus(.preparing(DownloadStatus.PrepareInfo(
|
||||
message: "正在下载 \(product.sapCode) 的产品信息...",
|
||||
timestamp: Date(),
|
||||
stage: .fetchingInfo
|
||||
)))
|
||||
objectWillChange.send()
|
||||
}
|
||||
|
||||
let jsonString = try await getApplicationInfo(buildGuid: product.buildGuid)
|
||||
|
||||
let jsonURL = productDir.appendingPathComponent("application.json")
|
||||
try jsonString.write(to: jsonURL, atomically: true, encoding: .utf8)
|
||||
|
||||
guard let jsonData = jsonString.data(using: .utf8),
|
||||
let appInfo = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any],
|
||||
let packages = appInfo["Packages"] as? [String: Any],
|
||||
let packageArray = packages["Package"] as? [[String: Any]] else {
|
||||
throw NetworkError.invalidData("无法解析产品信息")
|
||||
}
|
||||
|
||||
let totalPackages = packageArray.count
|
||||
task.totalPackages = totalPackages
|
||||
|
||||
await MainActor.run {
|
||||
task.setStatus(.downloading(DownloadStatus.DownloadInfo(
|
||||
fileName: task.currentPackage?.fullPackageName ?? "",
|
||||
currentPackageIndex: task.completedPackages,
|
||||
totalPackages: task.totalPackages,
|
||||
startTime: Date(),
|
||||
estimatedTimeRemaining: nil
|
||||
)))
|
||||
objectWillChange.send()
|
||||
}
|
||||
|
||||
for package in packageArray {
|
||||
let fullPackageName: String
|
||||
if let name = package["fullPackageName"] as? String, !name.isEmpty {
|
||||
fullPackageName = name
|
||||
} else if let name = package["PackageName"] as? String, !name.isEmpty {
|
||||
fullPackageName = "\(name).zip"
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
let packageType = package["Type"] as? String ?? "non-core"
|
||||
|
||||
let isLanguageSuitable: Bool
|
||||
if packageType == "core" {
|
||||
isLanguageSuitable = true
|
||||
} else {
|
||||
let condition = package["Condition"] as? String ?? ""
|
||||
let osLang = Locale.current.identifier
|
||||
isLanguageSuitable = (
|
||||
task.language == "ALL" ||
|
||||
condition.isEmpty ||
|
||||
!condition.contains("[installLanguage]") ||
|
||||
condition.contains("[installLanguage]==\(task.language)") ||
|
||||
condition.contains("[installLanguage]==\(osLang)")
|
||||
)
|
||||
}
|
||||
|
||||
if isLanguageSuitable {
|
||||
let downloadSize: Int64
|
||||
if let sizeNumber = package["DownloadSize"] as? NSNumber {
|
||||
downloadSize = sizeNumber.int64Value
|
||||
} else if let sizeString = package["DownloadSize"] as? String,
|
||||
let parsedSize = Int64(sizeString) {
|
||||
downloadSize = parsedSize
|
||||
} else if let sizeInt = package["DownloadSize"] as? Int {
|
||||
downloadSize = Int64(sizeInt)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
guard let downloadURL = package["Path"] as? String,
|
||||
!downloadURL.isEmpty else {
|
||||
continue
|
||||
}
|
||||
|
||||
let newPackage = Package(
|
||||
type: packageType,
|
||||
fullPackageName: fullPackageName,
|
||||
downloadSize: downloadSize,
|
||||
downloadURL: downloadURL
|
||||
)
|
||||
product.packages.append(newPackage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task.productsToDownload = productsToDownload
|
||||
task.totalSize = productsToDownload.reduce(0) { productSum, product in
|
||||
productSum + product.packages.reduce(0) { packageSum, pkg in
|
||||
packageSum + (pkg.downloadSize > 0 ? pkg.downloadSize : 0)
|
||||
}
|
||||
}
|
||||
|
||||
await downloadUtils.startDownloadProcess(task: task)
|
||||
|
||||
try await downloadUtils.handleDownload(task: task, productInfo: productInfo, allowedPlatform: allowedPlatform, saps: saps)
|
||||
} catch {
|
||||
await MainActor.run {
|
||||
task.setStatus(.failed(DownloadStatus.FailureInfo(
|
||||
@@ -315,483 +79,6 @@ class NetworkManager: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
private func validateAndStartDownload(task: NewDownloadTask) async throws {
|
||||
try downloadUtils.createInstallerApp(
|
||||
for: task.sapCode,
|
||||
version: task.version,
|
||||
language: task.language,
|
||||
at: task.directory
|
||||
)
|
||||
await startDownloadProcess(task: task)
|
||||
}
|
||||
|
||||
internal func startDownloadProcess(task: NewDownloadTask) async {
|
||||
task.totalStatus = .preparing(DownloadStatus.PrepareInfo(
|
||||
message: "正在准备下载...",
|
||||
timestamp: Date(),
|
||||
stage: .initializing
|
||||
))
|
||||
|
||||
for product in task.productsToDownload {
|
||||
let sapCode = product.sapCode
|
||||
|
||||
let productDir = task.directory.appendingPathComponent("Contents/Resources/products/\(sapCode)")
|
||||
try? FileManager.default.createDirectory(at: productDir, withIntermediateDirectories: true)
|
||||
|
||||
for package in product.packages {
|
||||
task.currentPackage = package
|
||||
|
||||
let downloadURL = cdn + package.downloadURL
|
||||
guard let url = URL(string: downloadURL) else { continue }
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
NetworkConstants.downloadHeaders.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) }
|
||||
|
||||
let delegate = DownloadDelegate(
|
||||
destinationDirectory: task.directory.appendingPathComponent("Contents/Resources/products/\(sapCode)"),
|
||||
fileName: package.fullPackageName,
|
||||
completionHandler: { [weak self] localURL, response, error in
|
||||
if let error = error {
|
||||
Task {
|
||||
await self?.handleError(task.id, error)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
package.downloaded = true
|
||||
package.progress = 1.0
|
||||
package.status = .completed
|
||||
|
||||
let totalDownloaded = task.productsToDownload.reduce(0) { sum, product in
|
||||
sum + product.packages.reduce(0) { sum, pkg in
|
||||
sum + (pkg.downloaded ? pkg.downloadSize : 0)
|
||||
}
|
||||
}
|
||||
let totalSize = task.productsToDownload.reduce(0) { sum, product in
|
||||
sum + product.packages.reduce(0) { sum, pkg in pkg.downloadSize }
|
||||
}
|
||||
task.totalProgress = Double(totalDownloaded) / Double(totalSize)
|
||||
},
|
||||
progressHandler: { bytesWritten, totalBytesWritten, totalBytesExpectedToWrite in
|
||||
package.downloadedSize = totalBytesWritten
|
||||
package.progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)
|
||||
package.speed = Double(bytesWritten)
|
||||
package.status = .downloading
|
||||
|
||||
task.totalDownloadedSize = totalBytesWritten
|
||||
task.totalSize = totalBytesExpectedToWrite
|
||||
task.totalSpeed = Double(bytesWritten)
|
||||
}
|
||||
)
|
||||
|
||||
let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil)
|
||||
let downloadTask = session.downloadTask(with: request)
|
||||
downloadTask.resume()
|
||||
|
||||
await withCheckedContinuation { continuation in
|
||||
delegate.completionHandler = { _, _, _ in
|
||||
continuation.resume()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let driverXml = downloadUtils.generateDriverXML(
|
||||
sapCode: task.sapCode,
|
||||
version: task.version,
|
||||
language: task.language,
|
||||
productInfo: (self.saps[task.sapCode]?.versions[task.version])!,
|
||||
displayName: task.displayName
|
||||
)
|
||||
|
||||
try? driverXml.write(
|
||||
to: task.directory.appendingPathComponent("Contents/Resources/products/driver.xml"),
|
||||
atomically: true,
|
||||
encoding: .utf8
|
||||
)
|
||||
|
||||
task.totalStatus = .completed(DownloadStatus.CompletionInfo(
|
||||
timestamp: Date(),
|
||||
totalTime: Date().timeIntervalSince(task.createAt),
|
||||
totalSize: task.totalSize
|
||||
))
|
||||
}
|
||||
|
||||
private func performDownload(task: NewDownloadTask) async throws {
|
||||
if task.sapCode == "APRO" {
|
||||
if let productInfo = self.saps[task.sapCode]?.versions[task.version] {
|
||||
try await downloadUtils.downloadAPRO(task: task, productInfo: productInfo)
|
||||
} else {
|
||||
throw NetworkError.invalidData("无法获取APRO产品信息")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
try downloadUtils.createInstallerApp(
|
||||
for: task.sapCode,
|
||||
version: task.version,
|
||||
language: task.language,
|
||||
at: task.directory
|
||||
)
|
||||
|
||||
try await downloadUtils.signApp(at: task.directory)
|
||||
|
||||
let productsDir = task.directory.appendingPathComponent("Contents/Resources/products")
|
||||
try FileManager.default.createDirectory(at: productsDir, withIntermediateDirectories: true)
|
||||
|
||||
for product in task.productsToDownload {
|
||||
let sapCode = product.sapCode
|
||||
let version = product.version
|
||||
|
||||
print("[\(sapCode)_\(version)] Downloading application.json")
|
||||
let jsonString = try await getApplicationInfo(buildGuid: product.buildGuid)
|
||||
|
||||
print("[\(sapCode)_\(version)] Creating folder for product")
|
||||
let productDir = productsDir.appendingPathComponent(sapCode)
|
||||
try FileManager.default.createDirectory(at: productDir, withIntermediateDirectories: true)
|
||||
|
||||
print("[\(sapCode)_\(version)] Saving application.json")
|
||||
try jsonString.write(to: productDir.appendingPathComponent("application.json"),
|
||||
atomically: true,
|
||||
encoding: .utf8)
|
||||
}
|
||||
|
||||
if let productInfo = self.saps[task.sapCode]?.versions[task.version] {
|
||||
let driverXml = downloadUtils.generateDriverXML(
|
||||
sapCode: task.sapCode,
|
||||
version: task.version,
|
||||
language: task.language,
|
||||
productInfo: productInfo,
|
||||
displayName: task.displayName
|
||||
)
|
||||
|
||||
try driverXml.write(
|
||||
to: productsDir.appendingPathComponent("driver.xml"),
|
||||
atomically: true,
|
||||
encoding: .utf8
|
||||
)
|
||||
}
|
||||
|
||||
await resumeDownload(taskId: task.id)
|
||||
}
|
||||
|
||||
func pauseDownload(taskId: UUID) {
|
||||
Task {
|
||||
if let task = downloadTasks.first(where: { $0.id == taskId }) {
|
||||
await MainActor.run {
|
||||
task.setStatus(.paused(DownloadStatus.PauseInfo(
|
||||
reason: .userRequested,
|
||||
timestamp: Date(),
|
||||
resumable: true
|
||||
)))
|
||||
updateDockBadge()
|
||||
objectWillChange.send()
|
||||
}
|
||||
await cancelTracker.pause(taskId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func resumeDownload(taskId: UUID) async {
|
||||
if let task = downloadTasks.first(where: { $0.id == taskId }) {
|
||||
await MainActor.run {
|
||||
task.setStatus(.downloading(DownloadStatus.DownloadInfo(
|
||||
fileName: task.currentPackage?.fullPackageName ?? "",
|
||||
currentPackageIndex: 0,
|
||||
totalPackages: task.productsToDownload.reduce(0) { $0 + $1.packages.count },
|
||||
startTime: Date(),
|
||||
estimatedTimeRemaining: nil
|
||||
)))
|
||||
updateDockBadge()
|
||||
objectWillChange.send()
|
||||
}
|
||||
|
||||
await downloadUtils.startDownloadProcess(task: task)
|
||||
}
|
||||
}
|
||||
|
||||
func cancelDownload(taskId: UUID, removeFiles: Bool = true) {
|
||||
Task {
|
||||
if let task = downloadTasks.first(where: { $0.id == taskId }) {
|
||||
await MainActor.run {
|
||||
task.setStatus(.failed(DownloadStatus.FailureInfo(
|
||||
message: "下载已取消",
|
||||
error: NetworkError.downloadCancelled,
|
||||
timestamp: Date(),
|
||||
recoverable: false
|
||||
)))
|
||||
updateDockBadge()
|
||||
objectWillChange.send()
|
||||
}
|
||||
await cancelTracker.cancel(taskId)
|
||||
if removeFiles {
|
||||
try? FileManager.default.removeItem(at: task.directory)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func clearCompletedTasks() {
|
||||
Task {
|
||||
for task in downloadTasks {
|
||||
if case .completed = task.status {
|
||||
try? FileManager.default.removeItem(at: task.directory)
|
||||
}
|
||||
}
|
||||
await MainActor.run {
|
||||
downloadTasks.removeAll { task in
|
||||
if case .completed = task.status {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
updateDockBadge()
|
||||
objectWillChange.send()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func setupNetworkMonitoring() {
|
||||
configureNetworkMonitor()
|
||||
}
|
||||
|
||||
private func handleDownloadError(taskId: UUID, error: Error) async {
|
||||
guard let index = downloadTasks.firstIndex(where: { $0.id == taskId }) else { return }
|
||||
let task = downloadTasks[index]
|
||||
|
||||
let (errorMessage, isRecoverable) = classifyError(error)
|
||||
|
||||
if isRecoverable && task.retryCount < NetworkConstants.maxRetryAttempts {
|
||||
task.retryCount += 1
|
||||
let nextRetryDate = Date().addingTimeInterval(TimeInterval(NetworkConstants.retryDelay / 1_000_000_000))
|
||||
task.setStatus(.retrying(DownloadStatus.RetryInfo(
|
||||
attempt: task.retryCount,
|
||||
maxAttempts: NetworkConstants.maxRetryAttempts,
|
||||
reason: errorMessage,
|
||||
nextRetryDate: nextRetryDate
|
||||
)))
|
||||
|
||||
Task {
|
||||
do {
|
||||
try await Task.sleep(nanoseconds: NetworkConstants.retryDelay)
|
||||
if await !cancelTracker.isCancelled(taskId) {
|
||||
await downloadUtils.resumeDownloadTask(taskId: taskId)
|
||||
}
|
||||
} catch {
|
||||
print("Retry cancelled for task: \(taskId)")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
task.setStatus(.failed(DownloadStatus.FailureInfo(
|
||||
message: errorMessage,
|
||||
error: error,
|
||||
timestamp: Date(),
|
||||
recoverable: isRecoverable
|
||||
)))
|
||||
|
||||
progressObservers[taskId]?.invalidate()
|
||||
progressObservers.removeValue(forKey: taskId)
|
||||
|
||||
if let currentPackage = task.currentPackage {
|
||||
let destinationDir = task.directory
|
||||
.appendingPathComponent("Contents/Resources/products/\(task.sapCode)")
|
||||
let fileURL = destinationDir.appendingPathComponent(currentPackage.fullPackageName)
|
||||
try? FileManager.default.removeItem(at: fileURL)
|
||||
}
|
||||
|
||||
updateDockBadge()
|
||||
objectWillChange.send()
|
||||
}
|
||||
}
|
||||
|
||||
private func classifyError(_ error: Error) -> (message: String, recoverable: Bool) {
|
||||
switch error {
|
||||
case let networkError as NetworkError:
|
||||
switch networkError {
|
||||
case .noConnection:
|
||||
return ("网络连接已断开", true)
|
||||
case .timeout:
|
||||
return ("下载超时", true)
|
||||
case .serverUnreachable:
|
||||
return ("服务器无法访问", true)
|
||||
case .insufficientStorage:
|
||||
return ("存储空间不足", false)
|
||||
case .filePermissionDenied:
|
||||
return ("没有入权限", false)
|
||||
default:
|
||||
return (networkError.localizedDescription, false)
|
||||
}
|
||||
case let urlError as URLError:
|
||||
switch urlError.code {
|
||||
case .notConnectedToInternet:
|
||||
return ("网络连接已开", true)
|
||||
case .timedOut:
|
||||
return ("连接超时", true)
|
||||
case .cancelled:
|
||||
return ("下载已取消", false)
|
||||
default:
|
||||
return (urlError.localizedDescription, true)
|
||||
}
|
||||
default:
|
||||
return (error.localizedDescription, false)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateProgress(for taskId: UUID, progress: ProgressUpdate) {
|
||||
guard let index = downloadTasks.firstIndex(where: { $0.id == taskId }) else { return }
|
||||
let task = downloadTasks[index]
|
||||
|
||||
guard let currentPackage = task.currentPackage else { return }
|
||||
|
||||
let now = Date()
|
||||
let timeDiff = now.timeIntervalSince(currentPackage.lastUpdated)
|
||||
|
||||
if timeDiff >= NetworkConstants.progressUpdateInterval {
|
||||
Task { @MainActor in
|
||||
|
||||
currentPackage.updateProgress(
|
||||
downloadedSize: progress.totalWritten,
|
||||
speed: Double(progress.bytesWritten)
|
||||
)
|
||||
|
||||
let totalDownloaded = task.productsToDownload.reduce(Int64(0)) { sum, prod in
|
||||
sum + prod.packages.reduce(Int64(0)) { sum, pkg in
|
||||
if pkg.downloaded {
|
||||
return sum + pkg.downloadSize
|
||||
} else if pkg.id == currentPackage.id {
|
||||
return sum + progress.totalWritten
|
||||
}
|
||||
return sum
|
||||
}
|
||||
}
|
||||
|
||||
task.totalDownloadedSize = totalDownloaded
|
||||
task.totalProgress = Double(totalDownloaded) / Double(task.totalSize)
|
||||
task.totalSpeed = currentPackage.speed
|
||||
|
||||
currentPackage.lastRecordedSize = progress.totalWritten
|
||||
currentPackage.lastUpdated = now
|
||||
|
||||
task.objectWillChange.send()
|
||||
objectWillChange.send()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func updateTaskStatus(_ taskId: UUID, _ status: DownloadStatus) async {
|
||||
await MainActor.run {
|
||||
if let index = downloadTasks.firstIndex(where: { $0.id == taskId }) {
|
||||
downloadTasks[index].setStatus(status)
|
||||
|
||||
switch status {
|
||||
case .completed:
|
||||
progressObservers[taskId]?.invalidate()
|
||||
progressObservers.removeValue(forKey: taskId)
|
||||
if activeDownloadTaskId == taskId {
|
||||
activeDownloadTaskId = nil
|
||||
}
|
||||
|
||||
case .failed:
|
||||
progressObservers[taskId]?.invalidate()
|
||||
progressObservers.removeValue(forKey: taskId)
|
||||
if activeDownloadTaskId == taskId {
|
||||
activeDownloadTaskId = nil
|
||||
}
|
||||
|
||||
case .downloading:
|
||||
activeDownloadTaskId = taskId
|
||||
|
||||
case .paused:
|
||||
if activeDownloadTaskId == taskId {
|
||||
activeDownloadTaskId = nil
|
||||
}
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
updateDockBadge()
|
||||
objectWillChange.send()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func clampProgress(_ value: Double) -> Double {
|
||||
min(1.0, max(0.0, value))
|
||||
}
|
||||
|
||||
func retryFetchData() {
|
||||
Task {
|
||||
isFetchingProducts = false
|
||||
loadingState = .idle
|
||||
await fetchProducts()
|
||||
}
|
||||
}
|
||||
|
||||
func getActiveTaskId() async -> UUID? {
|
||||
await MainActor.run { activeDownloadTaskId }
|
||||
}
|
||||
|
||||
func setTaskStatus(_ taskId: UUID, _ status: DownloadStatus) async {
|
||||
await updateTaskStatus(taskId, status)
|
||||
}
|
||||
|
||||
func getTasks() async -> [NewDownloadTask] {
|
||||
await MainActor.run { downloadTasks }
|
||||
}
|
||||
|
||||
func handleError(_ taskId: UUID, _ error: Error) async {
|
||||
guard let index = downloadTasks.firstIndex(where: { $0.id == taskId }) else { return }
|
||||
let task = downloadTasks[index]
|
||||
|
||||
let (errorMessage, isRecoverable) = classifyError(error)
|
||||
|
||||
if isRecoverable && task.retryCount < NetworkConstants.maxRetryAttempts {
|
||||
task.retryCount += 1
|
||||
let nextRetryDate = Date().addingTimeInterval(TimeInterval(NetworkConstants.retryDelay / 1_000_000_000))
|
||||
task.setStatus(.retrying(DownloadStatus.RetryInfo(
|
||||
attempt: task.retryCount,
|
||||
maxAttempts: NetworkConstants.maxRetryAttempts,
|
||||
reason: errorMessage,
|
||||
nextRetryDate: nextRetryDate
|
||||
)))
|
||||
|
||||
Task {
|
||||
do {
|
||||
try await Task.sleep(nanoseconds: NetworkConstants.retryDelay)
|
||||
if await !cancelTracker.isCancelled(taskId) {
|
||||
await downloadUtils.resumeDownloadTask(taskId: taskId)
|
||||
}
|
||||
} catch {
|
||||
print("Retry cancelled for task: \(taskId)")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
task.setStatus(.failed(DownloadStatus.FailureInfo(
|
||||
message: errorMessage,
|
||||
error: error,
|
||||
timestamp: Date(),
|
||||
recoverable: isRecoverable
|
||||
)))
|
||||
|
||||
progressObservers[taskId]?.invalidate()
|
||||
progressObservers.removeValue(forKey: taskId)
|
||||
|
||||
if let currentPackage = task.currentPackage {
|
||||
let destinationDir = task.directory
|
||||
.appendingPathComponent("Contents/Resources/products/\(task.sapCode)")
|
||||
let fileURL = destinationDir.appendingPathComponent(currentPackage.fullPackageName)
|
||||
try? FileManager.default.removeItem(at: fileURL)
|
||||
}
|
||||
|
||||
updateDockBadge()
|
||||
objectWillChange.send()
|
||||
}
|
||||
}
|
||||
func updateDownloadProgress(for taskId: UUID, progress: ProgressUpdate) {
|
||||
updateProgress(for: taskId, progress: progress)
|
||||
}
|
||||
|
||||
var cdnUrl: String {
|
||||
get async {
|
||||
await MainActor.run { cdn }
|
||||
@@ -1059,14 +346,6 @@ class NetworkManager: ObservableObject {
|
||||
return result
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func updateTaskStatus(_ taskId: UUID, status: DownloadStatus) {
|
||||
if let index = downloadTasks.firstIndex(where: { $0.id == taskId }) {
|
||||
downloadTasks[index].setStatus(status)
|
||||
objectWillChange.send()
|
||||
}
|
||||
}
|
||||
|
||||
func isVersionDownloaded(sap: Sap, version: String, language: String) -> URL? {
|
||||
let platform = sap.versions[version]?.apPlatform ?? "unknown"
|
||||
var fileName = ""
|
||||
@@ -1111,4 +390,16 @@ class NetworkManager: ObservableObject {
|
||||
NSApplication.shared.dockTile.badgeLabel = nil
|
||||
}
|
||||
}
|
||||
|
||||
private func setupNetworkMonitoring() {
|
||||
configureNetworkMonitor()
|
||||
}
|
||||
|
||||
func retryFetchData() {
|
||||
Task {
|
||||
isFetchingProducts = false
|
||||
loadingState = .idle
|
||||
await fetchProducts()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// Adobe-Downloader
|
||||
// Adobe Downloader
|
||||
//
|
||||
// Created by X1a0He on 2024/10/30.
|
||||
//
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// Adobe-Downloader
|
||||
// Adobe Downloader
|
||||
//
|
||||
// Created by X1a0He on 2024/10/30.
|
||||
//
|
||||
@@ -103,41 +103,55 @@ class DownloadUtils {
|
||||
}
|
||||
|
||||
func pauseDownloadTask(taskId: UUID, reason: DownloadStatus.PauseInfo.PauseReason) async {
|
||||
if let task = await networkManager?.downloadTasks.first(where: { $0.id == taskId }) {
|
||||
task.setStatus(.paused(DownloadStatus.PauseInfo(
|
||||
reason: reason,
|
||||
timestamp: Date(),
|
||||
resumable: true
|
||||
)))
|
||||
await cancelTracker.pause(taskId)
|
||||
await MainActor.run {
|
||||
if let task = networkManager?.downloadTasks.first(where: { $0.id == taskId }) {
|
||||
task.setStatus(.paused(DownloadStatus.PauseInfo(
|
||||
reason: reason,
|
||||
timestamp: Date(),
|
||||
resumable: true
|
||||
)))
|
||||
}
|
||||
}
|
||||
await cancelTracker.pause(taskId)
|
||||
}
|
||||
|
||||
func resumeDownloadTask(taskId: UUID) async {
|
||||
await MainActor.run {
|
||||
if let task = networkManager?.downloadTasks.first(where: { $0.id == taskId }) {
|
||||
task.setStatus(.downloading(DownloadStatus.DownloadInfo(
|
||||
fileName: task.currentPackage?.fullPackageName ?? "",
|
||||
currentPackageIndex: 0,
|
||||
totalPackages: task.productsToDownload.reduce(0) { $0 + $1.packages.count },
|
||||
startTime: Date(),
|
||||
estimatedTimeRemaining: nil
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
if let task = await networkManager?.downloadTasks.first(where: { $0.id == taskId }) {
|
||||
task.setStatus(.downloading(DownloadStatus.DownloadInfo(
|
||||
fileName: task.currentPackage?.fullPackageName ?? "",
|
||||
currentPackageIndex: 0,
|
||||
totalPackages: task.productsToDownload.reduce(0) { $0 + $1.packages.count },
|
||||
startTime: Date(),
|
||||
estimatedTimeRemaining: nil
|
||||
)))
|
||||
await startDownloadProcess(task: task)
|
||||
}
|
||||
}
|
||||
|
||||
func cancelDownloadTask(taskId: UUID, removeFiles: Bool = false) async {
|
||||
await cancelTracker.cancel(taskId)
|
||||
if let task = await networkManager?.downloadTasks.first(where: { $0.id == taskId }) {
|
||||
if removeFiles {
|
||||
try? FileManager.default.removeItem(at: task.directory)
|
||||
|
||||
await MainActor.run {
|
||||
if let task = networkManager?.downloadTasks.first(where: { $0.id == taskId }) {
|
||||
if removeFiles {
|
||||
try? FileManager.default.removeItem(at: task.directory)
|
||||
}
|
||||
|
||||
task.setStatus(.failed(DownloadStatus.FailureInfo(
|
||||
message: "下载已取消",
|
||||
error: NetworkError.downloadCancelled,
|
||||
timestamp: Date(),
|
||||
recoverable: false
|
||||
)))
|
||||
|
||||
networkManager?.updateDockBadge()
|
||||
networkManager?.objectWillChange.send()
|
||||
}
|
||||
task.setStatus(.failed(DownloadStatus.FailureInfo(
|
||||
message: "下载已取消",
|
||||
error: NetworkError.downloadCancelled,
|
||||
timestamp: Date(),
|
||||
recoverable: false
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,14 +165,11 @@ class DownloadUtils {
|
||||
|
||||
func createInstallerApp(for sapCode: String, version: String, language: String, at destinationURL: URL) throws {
|
||||
let parentDirectory = destinationURL.deletingLastPathComponent()
|
||||
print(parentDirectory)
|
||||
if !FileManager.default.fileExists(atPath: parentDirectory.path) {
|
||||
try FileManager.default.createDirectory(at: parentDirectory, withIntermediateDirectories: true)
|
||||
}
|
||||
|
||||
if FileManager.default.fileExists(atPath: destinationURL.path) {
|
||||
try FileManager.default.removeItem(at: destinationURL)
|
||||
}
|
||||
if FileManager.default.fileExists(atPath: destinationURL.path) { try FileManager.default.removeItem(at: destinationURL) }
|
||||
|
||||
let process = Process()
|
||||
process.executableURL = URL(fileURLWithPath: "/usr/bin/osacompile")
|
||||
@@ -166,29 +177,20 @@ class DownloadUtils {
|
||||
let tempScriptURL = FileManager.default.temporaryDirectory.appendingPathComponent("installer.js")
|
||||
try NetworkConstants.INSTALL_APP_APPLE_SCRIPT.write(to: tempScriptURL, atomically: true, encoding: .utf8)
|
||||
|
||||
process.arguments = [
|
||||
"-l", "JavaScript",
|
||||
"-o", destinationURL.path,
|
||||
tempScriptURL.path
|
||||
]
|
||||
process.arguments = ["-l", "JavaScript", "-o", destinationURL.path, tempScriptURL.path]
|
||||
|
||||
try process.run()
|
||||
process.waitUntilExit()
|
||||
|
||||
if process.terminationStatus != 0 {
|
||||
throw NetworkError.fileSystemError(
|
||||
"Failed to create installer app: Exit code \(process.terminationStatus)",
|
||||
nil
|
||||
)
|
||||
throw NetworkError.fileSystemError("Failed to create installer app: Exit code \(process.terminationStatus)", nil)
|
||||
}
|
||||
|
||||
try? FileManager.default.removeItem(at: tempScriptURL)
|
||||
|
||||
let iconDestination = destinationURL.appendingPathComponent("Contents/Resources/applet.icns")
|
||||
if FileManager.default.fileExists(atPath: iconDestination.path) {
|
||||
try FileManager.default.removeItem(at: iconDestination)
|
||||
}
|
||||
|
||||
if FileManager.default.fileExists(atPath: iconDestination.path) { try FileManager.default.removeItem(at: iconDestination) }
|
||||
|
||||
if FileManager.default.fileExists(atPath: NetworkConstants.ADOBE_CC_MAC_ICON_PATH) {
|
||||
try FileManager.default.copyItem(
|
||||
at: URL(fileURLWithPath: NetworkConstants.ADOBE_CC_MAC_ICON_PATH),
|
||||
@@ -270,14 +272,8 @@ class DownloadUtils {
|
||||
internal func startDownloadProcess(task: NewDownloadTask) async {
|
||||
actor DownloadProgress {
|
||||
var currentPackageIndex: Int = 0
|
||||
|
||||
func increment() {
|
||||
currentPackageIndex += 1
|
||||
}
|
||||
|
||||
func get() -> Int {
|
||||
return currentPackageIndex
|
||||
}
|
||||
func increment() { currentPackageIndex += 1 }
|
||||
func get() -> Int { return currentPackageIndex }
|
||||
}
|
||||
|
||||
let progress = DownloadProgress()
|
||||
@@ -304,13 +300,8 @@ class DownloadUtils {
|
||||
productInfo: productInfo,
|
||||
displayName: task.displayName
|
||||
)
|
||||
|
||||
do {
|
||||
try driverXml.write(
|
||||
to: driverPath,
|
||||
atomically: true,
|
||||
encoding: .utf8
|
||||
)
|
||||
try driverXml.write(to: driverPath, atomically: true, encoding: .utf8)
|
||||
} catch {
|
||||
print("Error generating driver.xml:", error.localizedDescription)
|
||||
await MainActor.run {
|
||||
@@ -360,7 +351,7 @@ class DownloadUtils {
|
||||
try await downloadPackage(package: package, task: task, product: product, url: url)
|
||||
} catch {
|
||||
print("Error downloading \(package.fullPackageName): \(error.localizedDescription)")
|
||||
await networkManager?.handleError(task.id, error)
|
||||
await self.handleError(task.id, error)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -422,8 +413,8 @@ class DownloadUtils {
|
||||
task.totalProgress = Double(totalDownloaded) / Double(totalSize)
|
||||
task.totalSpeed = 0
|
||||
|
||||
let allCompleted = task.productsToDownload.allSatisfy { product in
|
||||
product.packages.allSatisfy { $0.downloaded }
|
||||
let allCompleted = task.productsToDownload.allSatisfy {
|
||||
product in product.packages.allSatisfy { $0.downloaded }
|
||||
}
|
||||
|
||||
if allCompleted {
|
||||
@@ -516,7 +507,7 @@ class DownloadUtils {
|
||||
if let cdn = await networkManager?.cdnUrl {
|
||||
try await downloadPackage(package: package, task: task, product: product, url: URL(string: cdn + package.downloadURL)!)
|
||||
} else {
|
||||
throw NetworkError.invalidData("无法获取 CDN 地址")
|
||||
throw NetworkError.invalidData("无法取 CDN 地址")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -525,7 +516,6 @@ class DownloadUtils {
|
||||
guard let networkManager = networkManager else { return }
|
||||
|
||||
let manifestURL = await networkManager.cdnUrl + productInfo.buildGuid
|
||||
print("Manifest URL:", manifestURL)
|
||||
guard let url = URL(string: manifestURL) else {
|
||||
throw NetworkError.invalidURL(manifestURL)
|
||||
}
|
||||
@@ -670,4 +660,320 @@ class DownloadUtils {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleDownload(task: NewDownloadTask, productInfo: Sap.Versions, allowedPlatform: [String], saps: [String: Sap]) async throws {
|
||||
if task.sapCode == "APRO" {
|
||||
try await downloadAPRO(task: task, productInfo: productInfo)
|
||||
return
|
||||
}
|
||||
|
||||
try createInstallerApp(
|
||||
for: task.sapCode,
|
||||
version: task.version,
|
||||
language: task.language,
|
||||
at: task.directory
|
||||
)
|
||||
|
||||
var productsToDownload: [ProductsToDownload] = []
|
||||
|
||||
productsToDownload.append(ProductsToDownload(
|
||||
sapCode: task.sapCode,
|
||||
version: task.version,
|
||||
buildGuid: productInfo.buildGuid
|
||||
))
|
||||
|
||||
for dependency in productInfo.dependencies {
|
||||
if let dependencyVersions = saps[dependency.sapCode]?.versions {
|
||||
let sortedVersions = dependencyVersions.sorted { first, second in
|
||||
first.value.productVersion.compare(second.value.productVersion, options: .numeric) == .orderedDescending
|
||||
}
|
||||
|
||||
var firstGuid = "", buildGuid = ""
|
||||
|
||||
for (_, versionInfo) in sortedVersions where versionInfo.baseVersion == dependency.version {
|
||||
if firstGuid.isEmpty { firstGuid = versionInfo.buildGuid }
|
||||
|
||||
if allowedPlatform.contains(versionInfo.apPlatform) {
|
||||
buildGuid = versionInfo.buildGuid
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if buildGuid.isEmpty { buildGuid = firstGuid }
|
||||
|
||||
if !buildGuid.isEmpty {
|
||||
productsToDownload.append(ProductsToDownload(
|
||||
sapCode: dependency.sapCode,
|
||||
version: dependency.version,
|
||||
buildGuid: buildGuid
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for product in productsToDownload {
|
||||
await MainActor.run {
|
||||
task.setStatus(.preparing(DownloadStatus.PrepareInfo(
|
||||
message: "正在处理 \(product.sapCode) 的包信息...",
|
||||
timestamp: Date(),
|
||||
stage: .fetchingInfo
|
||||
)))
|
||||
}
|
||||
|
||||
let productDir = task.directory.appendingPathComponent("Contents/Resources/products/\(product.sapCode)")
|
||||
if !FileManager.default.fileExists(atPath: productDir.path) {
|
||||
try FileManager.default.createDirectory(at: productDir, withIntermediateDirectories: true)
|
||||
}
|
||||
|
||||
let jsonString = try await getApplicationInfo(buildGuid: product.buildGuid)
|
||||
let jsonURL = productDir.appendingPathComponent("application.json")
|
||||
try jsonString.write(to: jsonURL, atomically: true, encoding: .utf8)
|
||||
|
||||
guard let jsonData = jsonString.data(using: .utf8),
|
||||
let appInfo = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any],
|
||||
let packages = appInfo["Packages"] as? [String: Any],
|
||||
let packageArray = packages["Package"] as? [[String: Any]] else {
|
||||
throw NetworkError.invalidData("无法解析产品信息")
|
||||
}
|
||||
|
||||
for package in packageArray {
|
||||
let fullPackageName: String
|
||||
if let name = package["fullPackageName"] as? String, !name.isEmpty {
|
||||
fullPackageName = name
|
||||
} else if let name = package["PackageName"] as? String, !name.isEmpty {
|
||||
fullPackageName = "\(name).zip"
|
||||
} else { continue }
|
||||
|
||||
let packageType = package["Type"] as? String ?? "non-core"
|
||||
let isLanguageSuitable: Bool
|
||||
if packageType == "core" {
|
||||
isLanguageSuitable = true
|
||||
} else {
|
||||
let condition = package["Condition"] as? String ?? ""
|
||||
let osLang = Locale.current.identifier
|
||||
isLanguageSuitable = (
|
||||
task.language == "ALL" || condition.isEmpty ||
|
||||
!condition.contains("[installLanguage]") || condition.contains("[installLanguage]==\(task.language)") ||
|
||||
condition.contains("[installLanguage]==\(osLang)")
|
||||
)
|
||||
}
|
||||
|
||||
if isLanguageSuitable {
|
||||
let downloadSize: Int64
|
||||
if let sizeNumber = package["DownloadSize"] as? NSNumber {
|
||||
downloadSize = sizeNumber.int64Value
|
||||
} else if let sizeString = package["DownloadSize"] as? String,
|
||||
let parsedSize = Int64(sizeString) {
|
||||
downloadSize = parsedSize
|
||||
} else if let sizeInt = package["DownloadSize"] as? Int {
|
||||
downloadSize = Int64(sizeInt)
|
||||
} else { continue }
|
||||
|
||||
guard let downloadURL = package["Path"] as? String, !downloadURL.isEmpty else { continue }
|
||||
|
||||
let newPackage = Package(
|
||||
type: packageType,
|
||||
fullPackageName: fullPackageName,
|
||||
downloadSize: downloadSize,
|
||||
downloadURL: downloadURL
|
||||
)
|
||||
product.packages.append(newPackage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let finalProducts = productsToDownload
|
||||
let totalSize = finalProducts.reduce(0) { productSum, product in
|
||||
productSum + product.packages.reduce(0) { packageSum, pkg in
|
||||
packageSum + (pkg.downloadSize > 0 ? pkg.downloadSize : 0)
|
||||
}
|
||||
}
|
||||
|
||||
await MainActor.run {
|
||||
task.productsToDownload = finalProducts
|
||||
task.totalSize = totalSize
|
||||
}
|
||||
|
||||
await startDownloadProcess(task: task)
|
||||
}
|
||||
|
||||
func getApplicationInfo(buildGuid: String) async throws -> String {
|
||||
guard let url = URL(string: NetworkConstants.applicationJsonURL) else {
|
||||
throw NetworkError.invalidURL(NetworkConstants.applicationJsonURL)
|
||||
}
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "GET"
|
||||
|
||||
var headers = NetworkConstants.adobeRequestHeaders
|
||||
headers["x-adobe-build-guid"] = buildGuid
|
||||
headers["Cookie"] = await networkManager?.generateCookie() ?? ""
|
||||
|
||||
headers.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) }
|
||||
|
||||
let (data, response) = try await URLSession.shared.data(for: request)
|
||||
|
||||
guard let httpResponse = response as? HTTPURLResponse else {
|
||||
throw NetworkError.invalidResponse
|
||||
}
|
||||
|
||||
guard (200...299).contains(httpResponse.statusCode) else {
|
||||
throw NetworkError.httpError(httpResponse.statusCode, String(data: data, encoding: .utf8))
|
||||
}
|
||||
|
||||
guard let jsonString = String(data: data, encoding: .utf8) else {
|
||||
throw NetworkError.invalidData("无法将响应数据转换为json字符串")
|
||||
}
|
||||
|
||||
return jsonString
|
||||
}
|
||||
|
||||
func handleError(_ taskId: UUID, _ error: Error) async {
|
||||
guard let task = await networkManager?.downloadTasks.first(where: { $0.id == taskId }) else { return }
|
||||
|
||||
let (errorMessage, isRecoverable) = classifyError(error)
|
||||
|
||||
if isRecoverable && task.retryCount < NetworkConstants.maxRetryAttempts {
|
||||
task.retryCount += 1
|
||||
let nextRetryDate = Date().addingTimeInterval(TimeInterval(NetworkConstants.retryDelay / 1_000_000_000))
|
||||
task.setStatus(.retrying(DownloadStatus.RetryInfo(
|
||||
attempt: task.retryCount,
|
||||
maxAttempts: NetworkConstants.maxRetryAttempts,
|
||||
reason: errorMessage,
|
||||
nextRetryDate: nextRetryDate
|
||||
)))
|
||||
|
||||
Task {
|
||||
do {
|
||||
try await Task.sleep(nanoseconds: NetworkConstants.retryDelay)
|
||||
if await !cancelTracker.isCancelled(taskId) {
|
||||
await resumeDownloadTask(taskId: taskId)
|
||||
}
|
||||
} catch {
|
||||
print("Retry cancelled for task: \(taskId)")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await MainActor.run {
|
||||
task.setStatus(.failed(DownloadStatus.FailureInfo(
|
||||
message: errorMessage,
|
||||
error: error,
|
||||
timestamp: Date(),
|
||||
recoverable: isRecoverable
|
||||
)))
|
||||
|
||||
if let currentPackage = task.currentPackage {
|
||||
let destinationDir = task.directory
|
||||
.appendingPathComponent("Contents/Resources/products/\(task.sapCode)")
|
||||
let fileURL = destinationDir.appendingPathComponent(currentPackage.fullPackageName)
|
||||
try? FileManager.default.removeItem(at: fileURL)
|
||||
}
|
||||
|
||||
networkManager?.updateDockBadge()
|
||||
networkManager?.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func classifyError(_ error: Error) -> (message: String, recoverable: Bool) {
|
||||
switch error {
|
||||
case let networkError as NetworkError:
|
||||
switch networkError {
|
||||
case .noConnection:
|
||||
return ("网络连接已断开", true)
|
||||
case .timeout:
|
||||
return ("下载超时", true)
|
||||
case .serverUnreachable:
|
||||
return ("服务器无法访问", true)
|
||||
case .insufficientStorage:
|
||||
return ("存储空间不足", false)
|
||||
case .filePermissionDenied:
|
||||
return ("没有入权限", false)
|
||||
default:
|
||||
return (networkError.localizedDescription, false)
|
||||
}
|
||||
case let urlError as URLError:
|
||||
switch urlError.code {
|
||||
case .notConnectedToInternet:
|
||||
return ("网络连接已开", true)
|
||||
case .timedOut:
|
||||
return ("连接超时", true)
|
||||
case .cancelled:
|
||||
return ("下载已取消", false)
|
||||
default:
|
||||
return (urlError.localizedDescription, true)
|
||||
}
|
||||
default:
|
||||
return (error.localizedDescription, false)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func updateProgress(for taskId: UUID, progress: ProgressUpdate) {
|
||||
guard let task = networkManager?.downloadTasks.first(where: { $0.id == taskId }),
|
||||
let currentPackage = task.currentPackage else { return }
|
||||
|
||||
let now = Date()
|
||||
let timeDiff = now.timeIntervalSince(currentPackage.lastUpdated)
|
||||
|
||||
if timeDiff >= NetworkConstants.progressUpdateInterval {
|
||||
currentPackage.updateProgress(
|
||||
downloadedSize: progress.totalWritten,
|
||||
speed: Double(progress.bytesWritten)
|
||||
)
|
||||
|
||||
let totalDownloaded = task.productsToDownload.reduce(Int64(0)) { sum, prod in
|
||||
sum + prod.packages.reduce(Int64(0)) { sum, pkg in
|
||||
if pkg.downloaded {
|
||||
return sum + pkg.downloadSize
|
||||
} else if pkg.id == currentPackage.id {
|
||||
return sum + progress.totalWritten
|
||||
}
|
||||
return sum
|
||||
}
|
||||
}
|
||||
|
||||
task.totalDownloadedSize = totalDownloaded
|
||||
task.totalProgress = Double(totalDownloaded) / Double(task.totalSize)
|
||||
task.totalSpeed = currentPackage.speed
|
||||
|
||||
currentPackage.lastRecordedSize = progress.totalWritten
|
||||
currentPackage.lastUpdated = now
|
||||
|
||||
task.objectWillChange.send()
|
||||
networkManager?.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func updateTaskStatus(_ taskId: UUID, _ status: DownloadStatus) async {
|
||||
guard let networkManager = networkManager else { return }
|
||||
|
||||
if let index = networkManager.downloadTasks.firstIndex(where: { $0.id == taskId }) {
|
||||
networkManager.downloadTasks[index].setStatus(status)
|
||||
|
||||
switch status {
|
||||
case .completed, .failed:
|
||||
networkManager.progressObservers[taskId]?.invalidate()
|
||||
networkManager.progressObservers.removeValue(forKey: taskId)
|
||||
if networkManager.activeDownloadTaskId == taskId {
|
||||
networkManager.activeDownloadTaskId = nil
|
||||
}
|
||||
|
||||
case .downloading:
|
||||
networkManager.activeDownloadTaskId = taskId
|
||||
|
||||
case .paused:
|
||||
if networkManager.activeDownloadTaskId == taskId {
|
||||
networkManager.activeDownloadTaskId = nil
|
||||
}
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
networkManager.updateDockBadge()
|
||||
networkManager.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// Adobe-Downloader
|
||||
// Adobe Downloader
|
||||
//
|
||||
// Created by X1a0He on 2024/10/30.
|
||||
//
|
||||
@@ -14,210 +14,56 @@ actor InstallManager {
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .setupNotFound:
|
||||
return "找不到安装程序"
|
||||
case .installationFailed(let message):
|
||||
return message
|
||||
case .cancelled:
|
||||
return "安装已取消"
|
||||
case .permissionDenied:
|
||||
return "权限被拒绝"
|
||||
case .setupNotFound: return "找不到安装程序"
|
||||
case .installationFailed(let message): return message
|
||||
case .cancelled: return "安装已取消"
|
||||
case .permissionDenied: return "权限被拒绝"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var installationProcess: Process?
|
||||
private var progressHandler: ((Double, String) -> Void)?
|
||||
private let setupPath = "/Library/Application Support/Adobe/Adobe Desktop Common/HDBox/Setup"
|
||||
|
||||
func install(at appPath: URL,
|
||||
progressHandler: @escaping (Double, String) -> Void,
|
||||
logHandler: @escaping (String) -> Void) async throws {
|
||||
self.progressHandler = progressHandler
|
||||
|
||||
let setupPath = "/Library/Application Support/Adobe/Adobe Desktop Common/HDBox/Setup"
|
||||
let driverPath = appPath.appendingPathComponent("Contents/Resources/products/driver.xml").path
|
||||
|
||||
private func executeInstallation(
|
||||
at appPath: URL,
|
||||
withSudo: Bool = true,
|
||||
password: String? = nil,
|
||||
progressHandler: @escaping (Double, String) -> Void,
|
||||
logHandler: @escaping (String) -> Void
|
||||
) async throws {
|
||||
guard FileManager.default.fileExists(atPath: setupPath) else {
|
||||
throw InstallError.setupNotFound
|
||||
}
|
||||
|
||||
let authProcess = Process()
|
||||
authProcess.executableURL = URL(fileURLWithPath: "/usr/bin/osascript")
|
||||
let authScript = """
|
||||
tell application "System Events"
|
||||
display dialog "请输入管理员密码以继续安装" default answer "" with hidden answer ¬
|
||||
buttons {"取消", "确定"} default button "确定" ¬
|
||||
with icon caution ¬
|
||||
with title "需要管理员权限"
|
||||
if button returned of result is "确定" then
|
||||
return text returned of result
|
||||
else
|
||||
error "用户取消了操作"
|
||||
end if
|
||||
end tell
|
||||
"""
|
||||
|
||||
let authPipe = Pipe()
|
||||
authProcess.standardOutput = authPipe
|
||||
authProcess.standardError = Pipe()
|
||||
authProcess.arguments = ["-e", authScript]
|
||||
|
||||
do {
|
||||
try authProcess.run()
|
||||
authProcess.waitUntilExit()
|
||||
|
||||
if authProcess.terminationStatus != 0 {
|
||||
throw InstallError.cancelled
|
||||
}
|
||||
|
||||
guard let passwordData = try? authPipe.fileHandleForReading.readToEnd(),
|
||||
let password = String(data: passwordData, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
!password.isEmpty else {
|
||||
throw InstallError.cancelled
|
||||
}
|
||||
|
||||
let installProcess = Process()
|
||||
installProcess.executableURL = URL(fileURLWithPath: "/usr/bin/sudo")
|
||||
installProcess.arguments = ["-S", setupPath, "--install=1", "--driverXML=\(driverPath)"]
|
||||
print(installProcess)
|
||||
|
||||
let inputPipe = Pipe()
|
||||
let outputPipe = Pipe()
|
||||
installProcess.standardInput = inputPipe
|
||||
installProcess.standardOutput = outputPipe
|
||||
installProcess.standardError = outputPipe
|
||||
|
||||
installationProcess = installProcess
|
||||
|
||||
await MainActor.run {
|
||||
progressHandler(0.0, "正在准备安装...")
|
||||
}
|
||||
|
||||
try installProcess.run()
|
||||
try inputPipe.fileHandleForWriting.write(contentsOf: "\(password)\n".data(using: .utf8)!)
|
||||
inputPipe.fileHandleForWriting.closeFile()
|
||||
|
||||
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
|
||||
Task.detached {
|
||||
do {
|
||||
for try await line in outputPipe.fileHandleForReading.bytes.lines {
|
||||
await MainActor.run {
|
||||
logHandler(line)
|
||||
}
|
||||
|
||||
if line.contains("incorrect password") || line.contains("sudo: 1 incorrect password attempt") {
|
||||
installProcess.terminate()
|
||||
continuation.resume(throwing: InstallError.permissionDenied)
|
||||
return
|
||||
}
|
||||
|
||||
if let range = line.range(of: "Exit Code: (-?[0-9]+)", options: .regularExpression),
|
||||
let codeStr = line[range].split(separator: ":").last?.trimmingCharacters(in: .whitespaces),
|
||||
let code = Int32(codeStr) {
|
||||
if code != 0 {
|
||||
let errorMessage = code == -1
|
||||
? "安装程序调用失败,请联系X1a0He"
|
||||
: "(退出代码: \(code))"
|
||||
|
||||
installProcess.terminate()
|
||||
continuation.resume(throwing: InstallError.installationFailed(errorMessage))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if let progress = await self.parseProgress(from: line) {
|
||||
await MainActor.run {
|
||||
progressHandler(progress.progress, progress.status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
installProcess.waitUntilExit()
|
||||
|
||||
if installProcess.terminationStatus == 0 {
|
||||
continuation.resume()
|
||||
} else {
|
||||
continuation.resume(throwing: InstallError.installationFailed(
|
||||
"安装失败 (退出代码: \(installProcess.terminationStatus))"
|
||||
))
|
||||
}
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await MainActor.run {
|
||||
progressHandler(1.0, "安装完成")
|
||||
}
|
||||
|
||||
} catch {
|
||||
if error.localizedDescription.contains("用户取消了操作") {
|
||||
throw InstallError.cancelled
|
||||
}
|
||||
if let installError = error as? InstallError {
|
||||
throw installError
|
||||
}
|
||||
throw InstallError.installationFailed(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
installationProcess?.terminate()
|
||||
}
|
||||
|
||||
private func parseProgress(from line: String) -> (progress: Double, status: String)? {
|
||||
if let range = line.range(of: "Exit Code: (-?[0-9]+)", options: .regularExpression),
|
||||
let codeStr = line[range].split(separator: ":").last?.trimmingCharacters(in: .whitespaces),
|
||||
let exitCode = Int(codeStr) {
|
||||
if exitCode == 0 {
|
||||
return (1.0, "安装完成")
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if let range = line.range(of: "Progress: ([0-9]{1,3})%", options: .regularExpression),
|
||||
let progressStr = line[range].split(separator: ":").last?.trimmingCharacters(in: .whitespaces),
|
||||
let progressValue = Double(progressStr.replacingOccurrences(of: "%", with: "")) {
|
||||
return (progressValue / 100.0, "正在安装...")
|
||||
}
|
||||
|
||||
if line.contains("Installing packages") {
|
||||
return (0.0, "正在安装包...")
|
||||
} else if line.contains("Preparing") {
|
||||
return (0.0, "正在准备...")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func retry(at appPath: URL,
|
||||
progressHandler: @escaping (Double, String) -> Void,
|
||||
logHandler: @escaping (String) -> Void) async throws {
|
||||
self.progressHandler = progressHandler
|
||||
|
||||
let setupPath = "/Library/Application Support/Adobe/Adobe Desktop Common/HDBox/Setup"
|
||||
let driverPath = appPath.appendingPathComponent("Contents/Resources/products/driver.xml").path
|
||||
|
||||
guard FileManager.default.fileExists(atPath: setupPath) else {
|
||||
throw InstallError.setupNotFound
|
||||
}
|
||||
|
||||
let installProcess = Process()
|
||||
installProcess.executableURL = URL(fileURLWithPath: "/usr/bin/sudo")
|
||||
installProcess.arguments = [setupPath, "--install=1", "--driverXML=\(driverPath)"]
|
||||
|
||||
print("执行重试命令: \(installProcess.executableURL!.path) \(installProcess.arguments!.joined(separator: " "))")
|
||||
|
||||
if withSudo {
|
||||
installProcess.executableURL = URL(fileURLWithPath: "/usr/bin/sudo")
|
||||
installProcess.arguments = password != nil ? ["-S"] : []
|
||||
installProcess.arguments?.append(contentsOf: [setupPath, "--install=1", "--driverXML=\(driverPath)"])
|
||||
} else {
|
||||
installProcess.executableURL = URL(fileURLWithPath: setupPath)
|
||||
installProcess.arguments = ["--install=1", "--driverXML=\(driverPath)"]
|
||||
}
|
||||
|
||||
let outputPipe = Pipe()
|
||||
installProcess.standardOutput = outputPipe
|
||||
installProcess.standardError = outputPipe
|
||||
|
||||
if let password = password {
|
||||
let inputPipe = Pipe()
|
||||
installProcess.standardInput = inputPipe
|
||||
try inputPipe.fileHandleForWriting.write(contentsOf: "\(password)\n".data(using: .utf8)!)
|
||||
inputPipe.fileHandleForWriting.closeFile()
|
||||
}
|
||||
|
||||
installationProcess = installProcess
|
||||
|
||||
await MainActor.run {
|
||||
progressHandler(0.0, "正在重试安装...")
|
||||
progressHandler(0.0, withSudo ? "正在准备安装..." : "正在重试安装...")
|
||||
}
|
||||
|
||||
try installProcess.run()
|
||||
@@ -226,8 +72,12 @@ actor InstallManager {
|
||||
Task.detached {
|
||||
do {
|
||||
for try await line in outputPipe.fileHandleForReading.bytes.lines {
|
||||
await MainActor.run {
|
||||
logHandler(line)
|
||||
await MainActor.run { logHandler(line) }
|
||||
|
||||
if line.contains("incorrect password") || line.contains("sudo: 1 incorrect password attempt") {
|
||||
installProcess.terminate()
|
||||
continuation.resume(throwing: InstallError.permissionDenied)
|
||||
return
|
||||
}
|
||||
|
||||
if let range = line.range(of: "Exit Code: (-?[0-9]+)", options: .regularExpression),
|
||||
@@ -256,9 +106,10 @@ actor InstallManager {
|
||||
if installProcess.terminationStatus == 0 {
|
||||
continuation.resume()
|
||||
} else {
|
||||
throw InstallError.installationFailed(
|
||||
"重试失败,需要重新输入密码"
|
||||
)
|
||||
let errorMessage = withSudo
|
||||
? "安装失败 (退出代码: \(installProcess.terminationStatus))"
|
||||
: "重试失败,需要重新输入密码"
|
||||
continuation.resume(throwing: InstallError.installationFailed(errorMessage))
|
||||
}
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
@@ -270,5 +121,100 @@ actor InstallManager {
|
||||
progressHandler(1.0, "安装完成")
|
||||
}
|
||||
}
|
||||
|
||||
func install(
|
||||
at appPath: URL,
|
||||
progressHandler: @escaping (Double, String) -> Void,
|
||||
logHandler: @escaping (String) -> Void
|
||||
) async throws {
|
||||
self.progressHandler = progressHandler
|
||||
|
||||
let password = try await requestPassword()
|
||||
try await executeInstallation(
|
||||
at: appPath,
|
||||
withSudo: true,
|
||||
password: password,
|
||||
progressHandler: progressHandler,
|
||||
logHandler: logHandler
|
||||
)
|
||||
}
|
||||
|
||||
func retry(
|
||||
at appPath: URL,
|
||||
progressHandler: @escaping (Double, String) -> Void,
|
||||
logHandler: @escaping (String) -> Void
|
||||
) async throws {
|
||||
self.progressHandler = progressHandler
|
||||
try await executeInstallation(
|
||||
at: appPath,
|
||||
withSudo: false,
|
||||
progressHandler: progressHandler,
|
||||
logHandler: logHandler
|
||||
)
|
||||
}
|
||||
|
||||
private func requestPassword() async throws -> String {
|
||||
let authProcess = Process()
|
||||
authProcess.executableURL = URL(fileURLWithPath: "/usr/bin/osascript")
|
||||
let authScript = """
|
||||
tell application "System Events"
|
||||
display dialog "请输入管理员密码以继续安装" default answer "" with hidden answer ¬
|
||||
buttons {"取消", "确定"} default button "确定" ¬
|
||||
with icon caution ¬
|
||||
with title "需要管理员权限"
|
||||
if button returned of result is "确定" then
|
||||
return text returned of result
|
||||
else
|
||||
error "用户取消了操作"
|
||||
end if
|
||||
end tell
|
||||
"""
|
||||
|
||||
let authPipe = Pipe()
|
||||
authProcess.standardOutput = authPipe
|
||||
authProcess.standardError = Pipe()
|
||||
authProcess.arguments = ["-e", authScript]
|
||||
|
||||
try authProcess.run()
|
||||
authProcess.waitUntilExit()
|
||||
|
||||
if authProcess.terminationStatus != 0 {
|
||||
throw InstallError.cancelled
|
||||
}
|
||||
|
||||
guard let passwordData = try? authPipe.fileHandleForReading.readToEnd(),
|
||||
let password = String(data: passwordData, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
!password.isEmpty else {
|
||||
throw InstallError.cancelled
|
||||
}
|
||||
|
||||
return password
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
installationProcess?.terminate()
|
||||
}
|
||||
|
||||
private func parseProgress(from line: String) -> (progress: Double, status: String)? {
|
||||
if let range = line.range(of: "Exit Code: (-?[0-9]+)", options: .regularExpression),
|
||||
let codeStr = line[range].split(separator: ":").last?.trimmingCharacters(in: .whitespaces),
|
||||
let exitCode = Int(codeStr) {
|
||||
return exitCode == 0 ? (1.0, "安装完成") : nil
|
||||
}
|
||||
|
||||
if let range = line.range(of: "Progress: ([0-9]{1,3})%", options: .regularExpression),
|
||||
let progressStr = line[range].split(separator: ":").last?.trimmingCharacters(in: .whitespaces),
|
||||
let progressValue = Double(progressStr.replacingOccurrences(of: "%", with: "")) {
|
||||
return (progressValue / 100.0, "正在安装...")
|
||||
}
|
||||
|
||||
if line.contains("Installing packages") {
|
||||
return (0.0, "正在安装包...")
|
||||
} else if line.contains("Preparing") {
|
||||
return (0.0, "正在准备...")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
85
Adobe Downloader/Utils/ModifySetup.swift
Normal file
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// ModifySetup.swift
|
||||
// Adobe Downloader
|
||||
//
|
||||
// Created by X1a0He on 11/5/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
class ModifySetup {
|
||||
static func checkSetupBackup() -> Bool {
|
||||
let setupPath = "/Library/Application Support/Adobe/Adobe Desktop Common/HDBox/Setup"
|
||||
let backupPath = "/Library/Application Support/Adobe/Adobe Desktop Common/HDBox/Setup.original"
|
||||
|
||||
return FileManager.default.fileExists(atPath: setupPath) &&
|
||||
!FileManager.default.fileExists(atPath: backupPath)
|
||||
}
|
||||
|
||||
static func backupSetupFile(completion: @escaping (Bool, String) -> Void) {
|
||||
let setupQueue = DispatchQueue(label: "com.x1a0he.adobedownloader.setup")
|
||||
|
||||
setupQueue.async {
|
||||
let setupPath = "/Library/Application Support/Adobe/Adobe Desktop Common/HDBox/Setup"
|
||||
let backupPath = "/Library/Application Support/Adobe/Adobe Desktop Common/HDBox/Setup.original"
|
||||
|
||||
let shellScript = """
|
||||
#!/bin/bash
|
||||
function hex() {
|
||||
echo ''$1'' | perl -0777pe 's|([0-9a-zA-Z]{2}+(?![^\\(]*\\)))|\\\\x${1}|gs'
|
||||
}
|
||||
|
||||
function replace() {
|
||||
declare -r dom=$(hex "$2")
|
||||
declare -r sub=$(hex "$3")
|
||||
perl -0777pi -e 'BEGIN{$/=\\1e8} s|'$dom'|'$sub'|gs' "$1"
|
||||
}
|
||||
|
||||
function prep() {
|
||||
codesign --remove-signature "$1"
|
||||
codesign -f -s - --timestamp=none --all-architectures --deep "$1"
|
||||
xattr -cr "$1"
|
||||
}
|
||||
|
||||
cp '\(setupPath)' '\(backupPath)'
|
||||
|
||||
# M 系列的修改,暂时缺少Intel机型的特征码
|
||||
replace '\(setupPath)' 'FFC300D1F44F01A9FD7B02A9FD830091F30300AA1F2003D568A11D58080140F9E80700F9' '200080D2C0035FD6FD7B02A9FD830091F30300AA1F2003D568A11D58080140F9E80700F9'
|
||||
|
||||
prep '\(setupPath)'
|
||||
"""
|
||||
|
||||
let tempScriptPath = FileManager.default.temporaryDirectory.appendingPathComponent("setup_script.sh")
|
||||
do {
|
||||
try shellScript.write(to: tempScriptPath, atomically: true, encoding: .utf8)
|
||||
|
||||
let script = """
|
||||
do shell script "chmod +x '\(tempScriptPath.path)' && sudo '\(tempScriptPath.path)'" with administrator privileges
|
||||
"""
|
||||
|
||||
var scriptError: NSDictionary?
|
||||
DispatchQueue.main.sync {
|
||||
let appleScript = NSAppleScript(source: script)
|
||||
scriptError = nil
|
||||
_ = appleScript?.executeAndReturnError(&scriptError)
|
||||
}
|
||||
|
||||
try? FileManager.default.removeItem(at: tempScriptPath)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if let error = scriptError {
|
||||
completion(false, "操作执行时发生错误: \(error)")
|
||||
} else {
|
||||
completion(true, "所有操作已成功完成")
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
completion(false, "创建脚本文件时发生错误: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// Adobe-Downloader
|
||||
// Adobe Downloader
|
||||
//
|
||||
// Created by X1a0He on 2024/10/30.
|
||||
//
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// Adobe-Downloader
|
||||
// Adobe Downloader
|
||||
//
|
||||
// Created by X1a0He on 2024/10/30.
|
||||
//
|
||||
@@ -133,7 +133,12 @@ struct AboutAppView: View {
|
||||
destination: URL(string: "https://github.com/Drovosek01/adobe-packager")!)
|
||||
.font(.caption)
|
||||
.foregroundColor(.blue)
|
||||
|
||||
|
||||
Link("Thanks QiuChenly: InjectLib",
|
||||
destination: URL(string: "https://github.com/QiuChenly/InjectLib")!)
|
||||
.font(.caption)
|
||||
.foregroundColor(.blue)
|
||||
|
||||
Text("Released under GPLv3.")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// Adobe-Downloader
|
||||
// Adobe Downloader
|
||||
//
|
||||
// Created by X1a0He on 2024/10/30.
|
||||
//
|
||||
@@ -8,50 +8,60 @@ import SwiftUI
|
||||
|
||||
class IconCache {
|
||||
static let shared = IconCache()
|
||||
private var cache: [String: NSImage] = [:]
|
||||
private let queue = DispatchQueue(label: "com.adobe.downloader.iconcache")
|
||||
private var cache = NSCache<NSString, NSImage>()
|
||||
|
||||
func getIcon(for url: String) -> NSImage? {
|
||||
queue.sync {
|
||||
return cache[url]
|
||||
}
|
||||
cache.object(forKey: url as NSString)
|
||||
}
|
||||
|
||||
func setIcon(_ image: NSImage, for url: String) {
|
||||
queue.sync {
|
||||
self.cache[url] = image
|
||||
}
|
||||
cache.setObject(image, forKey: url as NSString)
|
||||
}
|
||||
}
|
||||
|
||||
struct AppCardView: View {
|
||||
class AppCardViewModel: ObservableObject {
|
||||
@Published var iconImage: NSImage?
|
||||
@Published var showError = false
|
||||
@Published var errorMessage = ""
|
||||
@Published var showVersionPicker = false
|
||||
@Published var selectedVersion = ""
|
||||
@Published var showLanguagePicker = false
|
||||
@Published var selectedLanguage = ""
|
||||
@Published var showExistingFileAlert = false
|
||||
@Published var existingFilePath: URL?
|
||||
@Published var pendingVersion = ""
|
||||
@Published var pendingLanguage = ""
|
||||
@Published var showRedownloadConfirm = false
|
||||
|
||||
let sap: Sap
|
||||
@EnvironmentObject private var networkManager: NetworkManager
|
||||
@AppStorage("defaultDirectory") private var defaultDirectory: String = ""
|
||||
@AppStorage("useDefaultDirectory") private var useDefaultDirectory: Bool = true
|
||||
@AppStorage("useDefaultLanguage") private var useDefaultLanguage: Bool = true
|
||||
@AppStorage("defaultLanguage") private var defaultLanguage: String = "zh_CN"
|
||||
@AppStorage("confirmRedownload") private var confirmRedownload: Bool = true
|
||||
@State private var showError: Bool = false
|
||||
@State private var errorMessage: String = ""
|
||||
@State private var showVersionPicker = false
|
||||
@State private var selectedVersion: String = ""
|
||||
@State private var iconImage: NSImage? = nil
|
||||
@State private var showLanguagePicker = false
|
||||
@State private var selectedLanguage = ""
|
||||
@State private var showExistingFileAlert = false
|
||||
@State private var existingFilePath: URL? = nil
|
||||
@State private var pendingVersion: String = ""
|
||||
@State private var pendingLanguage: String = ""
|
||||
@State private var showRedownloadConfirm = false
|
||||
|
||||
private var isDownloading: Bool {
|
||||
networkManager.downloadTasks.contains(where: isTaskDownloading)
|
||||
weak var networkManager: NetworkManager?
|
||||
|
||||
@Published var isDownloading = false
|
||||
private let userDefaults = UserDefaults.standard
|
||||
|
||||
var useDefaultDirectory: Bool {
|
||||
get { userDefaults.bool(forKey: "useDefaultDirectory") }
|
||||
}
|
||||
|
||||
|
||||
var defaultDirectory: String {
|
||||
get { userDefaults.string(forKey: "defaultDirectory") ?? "" }
|
||||
}
|
||||
|
||||
init(sap: Sap, networkManager: NetworkManager?) {
|
||||
self.sap = sap
|
||||
self.networkManager = networkManager
|
||||
loadIcon()
|
||||
}
|
||||
|
||||
func updateDownloadingStatus() {
|
||||
Task { @MainActor in
|
||||
isDownloading = networkManager?.downloadTasks.contains(where: isTaskDownloading) ?? false
|
||||
}
|
||||
}
|
||||
|
||||
private func isTaskDownloading(_ task: NewDownloadTask) -> Bool {
|
||||
guard task.sapCode == sap.sapCode else { return false }
|
||||
|
||||
|
||||
switch task.totalStatus {
|
||||
case .downloading, .preparing, .waiting, .retrying:
|
||||
return true
|
||||
@@ -60,51 +70,44 @@ struct AppCardView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var dependenciesCount: Int {
|
||||
if let firstVersion = sap.versions.first?.value {
|
||||
return firstVersion.dependencies.count
|
||||
func getDestinationURL(version: String, language: String, useDefaultDirectory: Bool, defaultDirectory: String) async throws -> URL {
|
||||
let platform = sap.versions[version]?.apPlatform ?? "unknown"
|
||||
let installerName = sap.sapCode == "APRO"
|
||||
? "Install \(sap.sapCode)_\(version)_\(platform).dmg"
|
||||
: "Install \(sap.sapCode)_\(version)-\(language)-\(platform).app"
|
||||
|
||||
if useDefaultDirectory && !defaultDirectory.isEmpty {
|
||||
return URL(fileURLWithPath: defaultDirectory)
|
||||
.appendingPathComponent(installerName)
|
||||
}
|
||||
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
DispatchQueue.main.async {
|
||||
let panel = NSOpenPanel()
|
||||
panel.title = "选择保存位置"
|
||||
panel.canCreateDirectories = true
|
||||
panel.canChooseDirectories = true
|
||||
panel.canChooseFiles = false
|
||||
panel.directoryURL = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first
|
||||
|
||||
if panel.runModal() == .OK, let selectedURL = panel.url {
|
||||
continuation.resume(returning: selectedURL.appendingPathComponent(installerName))
|
||||
} else {
|
||||
continuation.resume(throwing: NetworkError.cancelled)
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
CardContent(
|
||||
sap: sap,
|
||||
iconImage: iconImage,
|
||||
loadIcon: loadIcon,
|
||||
dependenciesCount: dependenciesCount,
|
||||
isDownloading: isDownloading,
|
||||
showVersionPicker: $showVersionPicker
|
||||
)
|
||||
.padding()
|
||||
.frame(width: 250, height: 200)
|
||||
.background(RoundedRectangle(cornerRadius: 10).fill(Color.black.opacity(0.05)))
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.stroke(Color.gray.opacity(0.1), lineWidth: 2)
|
||||
)
|
||||
.applyModifiers(
|
||||
showVersionPicker: $showVersionPicker,
|
||||
showLanguagePicker: $showLanguagePicker,
|
||||
showExistingFileAlert: $showExistingFileAlert,
|
||||
showError: $showError,
|
||||
sap: sap,
|
||||
existingFilePath: existingFilePath,
|
||||
pendingVersion: pendingVersion,
|
||||
pendingLanguage: pendingLanguage,
|
||||
errorMessage: errorMessage,
|
||||
selectedVersion: selectedVersion,
|
||||
selectedLanguage: $selectedLanguage,
|
||||
handleDownloadRequest: handleDownloadRequest,
|
||||
checkAndStartDownload: checkAndStartDownload,
|
||||
startDownload: startDownload,
|
||||
createCompletedTask: createCompletedTask,
|
||||
confirmRedownload: confirmRedownload,
|
||||
showRedownloadConfirm: $showRedownloadConfirm
|
||||
)
|
||||
func handleError(_ error: Error) {
|
||||
Task { @MainActor in
|
||||
if case NetworkError.cancelled = error { return }
|
||||
errorMessage = error.localizedDescription
|
||||
showError = true
|
||||
}
|
||||
}
|
||||
|
||||
private func loadIcon() {
|
||||
func loadIcon() {
|
||||
if let bestIcon = sap.getBestIcon(),
|
||||
let iconURL = URL(string: bestIcon.url) {
|
||||
|
||||
@@ -144,83 +147,61 @@ struct AppCardView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func handleDownloadRequest(_ version: String) {
|
||||
if useDefaultLanguage {
|
||||
checkAndStartDownload(version: version, language: defaultLanguage)
|
||||
} else {
|
||||
selectedVersion = version
|
||||
showLanguagePicker = true
|
||||
func handleDownloadRequest(_ version: String, useDefaultLanguage: Bool, defaultLanguage: String) async {
|
||||
await MainActor.run {
|
||||
if useDefaultLanguage {
|
||||
Task {
|
||||
await checkAndStartDownload(version: version, language: defaultLanguage)
|
||||
}
|
||||
} else {
|
||||
selectedVersion = version
|
||||
showLanguagePicker = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func checkAndStartDownload(version: String, language: String) {
|
||||
if let existingPath = networkManager.isVersionDownloaded(sap: sap, version: version, language: language) {
|
||||
existingFilePath = existingPath
|
||||
pendingVersion = version
|
||||
pendingLanguage = language
|
||||
showExistingFileAlert = true
|
||||
} else {
|
||||
startDownload(version, language)
|
||||
func checkAndStartDownload(version: String, language: String) async {
|
||||
if let networkManager = networkManager {
|
||||
if let existingPath = await networkManager.isVersionDownloaded(sap: sap, version: version, language: language) {
|
||||
await MainActor.run {
|
||||
existingFilePath = existingPath
|
||||
pendingVersion = version
|
||||
pendingLanguage = language
|
||||
showExistingFileAlert = true
|
||||
}
|
||||
} else {
|
||||
startDownload(version, language)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func startDownload(_ version: String, _ language: String) {
|
||||
func startDownload(_ version: String, _ language: String) {
|
||||
Task {
|
||||
do {
|
||||
let destinationURL: URL
|
||||
let platform = sap.versions[version]?.apPlatform ?? "unknown"
|
||||
|
||||
if useDefaultDirectory && !defaultDirectory.isEmpty {
|
||||
var appName = ""
|
||||
if(sap.sapCode=="APRO"){
|
||||
appName = "Install \(sap.sapCode)_\(version)_\(platform).dmg"
|
||||
} else {
|
||||
appName = "Install \(sap.sapCode)_\(version)-\(language)-\(platform).app"
|
||||
}
|
||||
destinationURL = URL(fileURLWithPath: defaultDirectory)
|
||||
.appendingPathComponent(appName)
|
||||
} else {
|
||||
let panel = NSOpenPanel()
|
||||
panel.title = "选择保存位置"
|
||||
panel.canCreateDirectories = true
|
||||
panel.canChooseDirectories = true
|
||||
panel.canChooseFiles = false
|
||||
panel.directoryURL = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first
|
||||
|
||||
guard await MainActor.run(body: { panel.runModal() == .OK }),
|
||||
let selectedURL = panel.url else {
|
||||
return
|
||||
}
|
||||
var appName = ""
|
||||
if(sap.sapCode=="APRO"){
|
||||
appName = "Install \(sap.sapCode)_\(version)_\(platform).dmg"
|
||||
} else {
|
||||
appName = "Install \(sap.sapCode)_\(version)-\(language)-\(platform).app"
|
||||
}
|
||||
destinationURL = URL(fileURLWithPath: defaultDirectory)
|
||||
.appendingPathComponent(appName)
|
||||
}
|
||||
|
||||
try await networkManager.startDownload(
|
||||
let destinationURL = try await getDestinationURL(
|
||||
version: version,
|
||||
language: language,
|
||||
useDefaultDirectory: useDefaultDirectory,
|
||||
defaultDirectory: defaultDirectory
|
||||
)
|
||||
|
||||
try await networkManager?.startDownload(
|
||||
sap: sap,
|
||||
selectedVersion: version,
|
||||
language: language,
|
||||
destinationURL: destinationURL
|
||||
)
|
||||
} catch {
|
||||
await MainActor.run {
|
||||
errorMessage = error.localizedDescription
|
||||
showError = true
|
||||
}
|
||||
handleError(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func createCompletedTask(_ path: URL) {
|
||||
guard let productInfo = sap.versions[pendingVersion] else { return }
|
||||
func createCompletedTask(_ path: URL) async {
|
||||
guard let networkManager = networkManager,
|
||||
let productInfo = sap.versions[pendingVersion] else { return }
|
||||
|
||||
var productsToDownload: [ProductsToDownload] = []
|
||||
|
||||
let mainProduct = ProductsToDownload(
|
||||
sapCode: sap.sapCode,
|
||||
version: pendingVersion,
|
||||
@@ -229,14 +210,14 @@ struct AppCardView: View {
|
||||
productsToDownload.append(mainProduct)
|
||||
|
||||
for dependency in productInfo.dependencies {
|
||||
if let dependencyVersions = networkManager.saps[dependency.sapCode]?.versions {
|
||||
if let dependencyVersions = await networkManager.saps[dependency.sapCode]?.versions {
|
||||
let sortedVersions = dependencyVersions.sorted { first, second in
|
||||
first.value.productVersion.compare(second.value.productVersion, options: .numeric) == .orderedDescending
|
||||
}
|
||||
|
||||
var buildGuid = ""
|
||||
for (_, versionInfo) in sortedVersions where versionInfo.baseVersion == dependency.version {
|
||||
if networkManager.allowedPlatform.contains(versionInfo.apPlatform) {
|
||||
if await networkManager.allowedPlatform.contains(versionInfo.apPlatform) {
|
||||
buildGuid = versionInfo.buildGuid
|
||||
break
|
||||
}
|
||||
@@ -273,11 +254,67 @@ struct AppCardView: View {
|
||||
totalSpeed: 0
|
||||
)
|
||||
|
||||
Task { @MainActor in
|
||||
await MainActor.run {
|
||||
networkManager.downloadTasks.append(completedTask)
|
||||
networkManager.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
|
||||
var dependenciesCount: Int {
|
||||
if let firstVersion = sap.versions.first?.value {
|
||||
return firstVersion.dependencies.count
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
struct AppCardView: View {
|
||||
@StateObject private var viewModel: AppCardViewModel
|
||||
@EnvironmentObject private var networkManager: NetworkManager
|
||||
@AppStorage("useDefaultLanguage") private var useDefaultLanguage = true
|
||||
@AppStorage("defaultLanguage") private var defaultLanguage: String = "zh_CN"
|
||||
|
||||
init(sap: Sap) {
|
||||
_viewModel = StateObject(wrappedValue: AppCardViewModel(sap: sap, networkManager: nil))
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
CardContent(
|
||||
sap: viewModel.sap,
|
||||
iconImage: viewModel.iconImage,
|
||||
loadIcon: viewModel.loadIcon,
|
||||
dependenciesCount: viewModel.dependenciesCount,
|
||||
isDownloading: viewModel.isDownloading,
|
||||
showVersionPicker: $viewModel.showVersionPicker
|
||||
)
|
||||
.padding()
|
||||
.frame(width: 250, height: 200)
|
||||
.background(RoundedRectangle(cornerRadius: 10).fill(Color.black.opacity(0.05)))
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.stroke(Color.gray.opacity(0.1), lineWidth: 2)
|
||||
)
|
||||
.modifier(AlertModifier(viewModel: viewModel, confirmRedownload: true))
|
||||
.sheet(isPresented: $viewModel.showVersionPicker) {
|
||||
VersionPickerView(sap: viewModel.sap) { version in
|
||||
Task {
|
||||
await viewModel.handleDownloadRequest(version, useDefaultLanguage: useDefaultLanguage, defaultLanguage: defaultLanguage)
|
||||
}
|
||||
}
|
||||
.environmentObject(networkManager)
|
||||
}
|
||||
.sheet(isPresented: $viewModel.showLanguagePicker) {
|
||||
LanguagePickerView(languages: AppStatics.supportedLanguages) { language in
|
||||
Task {
|
||||
await viewModel.checkAndStartDownload(version: viewModel.selectedVersion, language: language)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
viewModel.networkManager = networkManager
|
||||
viewModel.updateDownloadingStatus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct CardContent: View {
|
||||
@@ -302,87 +339,8 @@ private struct CardContent: View {
|
||||
}
|
||||
|
||||
private extension View {
|
||||
func applyModifiers(
|
||||
showVersionPicker: Binding<Bool>,
|
||||
showLanguagePicker: Binding<Bool>,
|
||||
showExistingFileAlert: Binding<Bool>,
|
||||
showError: Binding<Bool>,
|
||||
sap: Sap,
|
||||
existingFilePath: URL?,
|
||||
pendingVersion: String,
|
||||
pendingLanguage: String,
|
||||
errorMessage: String,
|
||||
selectedVersion: String,
|
||||
selectedLanguage: Binding<String>,
|
||||
handleDownloadRequest: @escaping (String) -> Void,
|
||||
checkAndStartDownload: @escaping (String, String) -> Void,
|
||||
startDownload: @escaping (String, String) -> Void,
|
||||
createCompletedTask: @escaping (URL) -> Void,
|
||||
confirmRedownload: Bool,
|
||||
showRedownloadConfirm: Binding<Bool>
|
||||
) -> some View {
|
||||
self
|
||||
.sheet(isPresented: showVersionPicker) {
|
||||
VersionPickerView(sap: sap, onSelect: handleDownloadRequest)
|
||||
}
|
||||
.sheet(isPresented: showLanguagePicker) {
|
||||
LanguagePickerView(languages: AppStatics.supportedLanguages) { language in
|
||||
selectedLanguage.wrappedValue = language
|
||||
showLanguagePicker.wrappedValue = false
|
||||
if !selectedVersion.isEmpty {
|
||||
checkAndStartDownload(selectedVersion, language)
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert("安装程序已存在", isPresented: showExistingFileAlert) {
|
||||
Button("使用现有程序") {
|
||||
if let path = existingFilePath,
|
||||
!pendingVersion.isEmpty && !pendingLanguage.isEmpty {
|
||||
createCompletedTask(path)
|
||||
}
|
||||
}
|
||||
Button("重新下载") {
|
||||
if !pendingVersion.isEmpty && !pendingLanguage.isEmpty {
|
||||
if confirmRedownload {
|
||||
showRedownloadConfirm.wrappedValue = true
|
||||
} else {
|
||||
startDownload(pendingVersion, pendingLanguage)
|
||||
}
|
||||
}
|
||||
}
|
||||
Button("取消", role: .cancel) {}
|
||||
} message: {
|
||||
VStack(alignment: .leading) {
|
||||
Text("在以下位置找到现有的安装程序:")
|
||||
if let path = existingFilePath {
|
||||
Text(path.path)
|
||||
.foregroundColor(.blue)
|
||||
.onTapGesture {
|
||||
NSWorkspace.shared.selectFile(path.path, inFileViewerRootedAtPath: path.deletingLastPathComponent().path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert("确认重新下载", isPresented: showRedownloadConfirm) {
|
||||
Button("取消", role: .cancel) { }
|
||||
Button("确认") {
|
||||
if !pendingVersion.isEmpty && !pendingLanguage.isEmpty {
|
||||
startDownload(pendingVersion, pendingLanguage)
|
||||
}
|
||||
}
|
||||
} message: {
|
||||
Text("是否确认重新下载?这将覆盖现有的安装程序。")
|
||||
}
|
||||
.alert("下载错误", isPresented: showError) {
|
||||
Button("确定", role: .cancel) { }
|
||||
Button("重试") {
|
||||
if !selectedVersion.isEmpty {
|
||||
startDownload(selectedVersion, selectedLanguage.wrappedValue)
|
||||
}
|
||||
}
|
||||
} message: {
|
||||
Text(errorMessage)
|
||||
}
|
||||
func applyModifiers(viewModel: AppCardViewModel) -> some View {
|
||||
self.modifier(AlertModifier(viewModel: viewModel, confirmRedownload: true))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,7 +410,8 @@ private struct DownloadButton: View {
|
||||
}
|
||||
|
||||
#Preview {
|
||||
AppCardView(sap: Sap(
|
||||
let networkManager = NetworkManager()
|
||||
let sap = Sap(
|
||||
hidden: false,
|
||||
displayName: "Photoshop",
|
||||
sapCode: "PHSP",
|
||||
@@ -476,6 +435,68 @@ private struct DownloadButton: View {
|
||||
url: "https://ffc-static-cdn.oobesaas.adobe.com/icons/PHSP/25.0.0/192x192.png"
|
||||
)
|
||||
]
|
||||
))
|
||||
.environmentObject(NetworkManager())
|
||||
)
|
||||
|
||||
return AppCardView(sap: sap)
|
||||
.environmentObject(networkManager)
|
||||
}
|
||||
|
||||
struct AlertModifier: ViewModifier {
|
||||
@ObservedObject var viewModel: AppCardViewModel
|
||||
let confirmRedownload: Bool
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.alert("安装程序已存在", isPresented: $viewModel.showExistingFileAlert) {
|
||||
Button("使用现有程序") {
|
||||
if let path = viewModel.existingFilePath,
|
||||
!viewModel.pendingVersion.isEmpty && !viewModel.pendingLanguage.isEmpty {
|
||||
Task {
|
||||
await viewModel.createCompletedTask(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
Button("重新下载") {
|
||||
if !viewModel.pendingVersion.isEmpty && !viewModel.pendingLanguage.isEmpty {
|
||||
if confirmRedownload {
|
||||
viewModel.showRedownloadConfirm = true
|
||||
} else {
|
||||
viewModel.startDownload(viewModel.pendingVersion, viewModel.pendingLanguage)
|
||||
}
|
||||
}
|
||||
}
|
||||
Button("取消", role: .cancel) {}
|
||||
} message: {
|
||||
VStack(alignment: .leading) {
|
||||
Text("在以下位置找到现有的安装程序:")
|
||||
if let path = viewModel.existingFilePath {
|
||||
Text(path.path)
|
||||
.foregroundColor(.blue)
|
||||
.onTapGesture {
|
||||
NSWorkspace.shared.selectFile(path.path, inFileViewerRootedAtPath: path.deletingLastPathComponent().path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert("确认重新下载", isPresented: $viewModel.showRedownloadConfirm) {
|
||||
Button("取消", role: .cancel) { }
|
||||
Button("确认") {
|
||||
if !viewModel.pendingVersion.isEmpty && !viewModel.pendingLanguage.isEmpty {
|
||||
viewModel.startDownload(viewModel.pendingVersion, viewModel.pendingLanguage)
|
||||
}
|
||||
}
|
||||
} message: {
|
||||
Text("是否确认重新下载?这将覆盖现有的安装程序。")
|
||||
}
|
||||
.alert("下载错误", isPresented: $viewModel.showError) {
|
||||
Button("确定", role: .cancel) { }
|
||||
Button("重试") {
|
||||
if !viewModel.selectedVersion.isEmpty {
|
||||
viewModel.startDownload(viewModel.selectedVersion, viewModel.selectedLanguage)
|
||||
}
|
||||
}
|
||||
} message: {
|
||||
Text(viewModel.errorMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// Adobe-Downloader
|
||||
// Adobe Downloader
|
||||
//
|
||||
// Created by X1a0He on 2024/10/30.
|
||||
//
|
||||
@@ -27,8 +27,7 @@ struct DownloadManagerView: View {
|
||||
}
|
||||
|
||||
private func removeTask(_ task: NewDownloadTask) {
|
||||
networkManager.downloadTasks.removeAll { $0.id == task.id }
|
||||
networkManager.updateDockBadge()
|
||||
networkManager.removeTask(taskId: task.id)
|
||||
}
|
||||
|
||||
private func sortTasks(_ tasks: [NewDownloadTask]) -> [NewDownloadTask] {
|
||||
@@ -47,35 +46,60 @@ struct DownloadManagerView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 12) {
|
||||
VStack(spacing: 0) {
|
||||
HStack {
|
||||
Text("下载管理")
|
||||
.font(.headline)
|
||||
Spacer()
|
||||
Menu {
|
||||
ForEach([SortOrder.addTime, .name, .status], id: \.self) { order in
|
||||
Button(action: {
|
||||
sortOrder = order
|
||||
}) {
|
||||
HStack {
|
||||
Text(order.description)
|
||||
if sortOrder == order {
|
||||
Image(systemName: "checkmark")
|
||||
HStack(){
|
||||
Menu {
|
||||
ForEach([SortOrder.addTime, .name, .status], id: \.self) { order in
|
||||
Button(action: {
|
||||
sortOrder = order
|
||||
}) {
|
||||
HStack {
|
||||
Text(order.description)
|
||||
if sortOrder == order {
|
||||
Image(systemName: "checkmark")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: "arrow.up.arrow.down")
|
||||
Text(sortOrder.description)
|
||||
.font(.caption)
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: "arrow.up.arrow.down")
|
||||
Text(sortOrder.description)
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 120)
|
||||
.fixedSize()
|
||||
|
||||
Button("全部暂停", action: {})
|
||||
Button("全部继续", action: {})
|
||||
Button("清理已完成", action: {
|
||||
Button("全部暂停") {
|
||||
Task {
|
||||
for task in networkManager.downloadTasks {
|
||||
if case .downloading = task.status {
|
||||
await networkManager.downloadUtils.pauseDownloadTask(
|
||||
taskId: task.id,
|
||||
reason: .userRequested
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button("全部继续") {
|
||||
Task {
|
||||
for task in networkManager.downloadTasks {
|
||||
if case .paused = task.status {
|
||||
await networkManager.downloadUtils.resumeDownloadTask(taskId: task.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button("清理已完成") {
|
||||
networkManager.downloadTasks.removeAll { task in
|
||||
if case .completed = task.status {
|
||||
return true
|
||||
@@ -83,13 +107,14 @@ struct DownloadManagerView: View {
|
||||
return false
|
||||
}
|
||||
networkManager.updateDockBadge()
|
||||
})
|
||||
}
|
||||
|
||||
Button("关闭") {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.padding(.horizontal)
|
||||
.padding(.vertical, 8)
|
||||
|
||||
ScrollView(showsIndicators: false) {
|
||||
LazyVStack(spacing: 8) {
|
||||
@@ -97,19 +122,26 @@ struct DownloadManagerView: View {
|
||||
DownloadProgressView(
|
||||
task: task,
|
||||
onCancel: {
|
||||
networkManager.cancelDownload(taskId: task.id)
|
||||
Task {
|
||||
await networkManager.downloadUtils.cancelDownloadTask(taskId: task.id)
|
||||
}
|
||||
},
|
||||
onPause: {
|
||||
networkManager.pauseDownload(taskId: task.id)
|
||||
Task {
|
||||
await networkManager.downloadUtils.pauseDownloadTask(
|
||||
taskId: task.id,
|
||||
reason: .userRequested
|
||||
)
|
||||
}
|
||||
},
|
||||
onResume: {
|
||||
Task {
|
||||
await networkManager.resumeDownload(taskId: task.id)
|
||||
await networkManager.downloadUtils.resumeDownloadTask(taskId: task.id)
|
||||
}
|
||||
},
|
||||
onRetry: {
|
||||
Task {
|
||||
await networkManager.resumeDownload(taskId: task.id)
|
||||
await networkManager.downloadUtils.resumeDownloadTask(taskId: task.id)
|
||||
}
|
||||
},
|
||||
onRemove: {
|
||||
@@ -119,11 +151,11 @@ struct DownloadManagerView: View {
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom, 12)
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
.background(Color(NSColor.windowBackgroundColor))
|
||||
}
|
||||
.frame(width: 600, height: 500)
|
||||
.frame(width:800, height: 600)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// Adobe-Downloader
|
||||
// Adobe Downloader
|
||||
//
|
||||
// Created by X1a0He on 2024/10/30.
|
||||
//
|
||||
@@ -305,7 +305,7 @@ struct DownloadProgressView: View {
|
||||
}
|
||||
|
||||
let lastComponents = components.suffix(2)
|
||||
return ".../" + lastComponents.joined(separator: "/")
|
||||
return "/" + lastComponents.joined(separator: "/")
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@@ -499,6 +499,35 @@ struct ProductRow: View {
|
||||
struct PackageRow: View {
|
||||
@ObservedObject var package: Package
|
||||
|
||||
private func statusView() -> some View {
|
||||
Group {
|
||||
switch package.status {
|
||||
case .waiting:
|
||||
HStack {
|
||||
Image(systemName: "hourglass.circle.fill")
|
||||
Text(package.status.description)
|
||||
}
|
||||
.foregroundColor(.secondary)
|
||||
case .downloading:
|
||||
HStack {
|
||||
Text("\(Int(package.progress * 100))%")
|
||||
}
|
||||
.foregroundColor(.blue)
|
||||
case .completed:
|
||||
HStack {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
Text(package.status.description)
|
||||
}
|
||||
.foregroundColor(.green)
|
||||
default:
|
||||
HStack {
|
||||
Text(package.status.description)
|
||||
}
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func formatSpeed(_ bytesPerSecond: Double) -> String {
|
||||
let formatter = ByteCountFormatter()
|
||||
formatter.countStyle = .file
|
||||
@@ -529,24 +558,8 @@ struct PackageRow: View {
|
||||
|
||||
Spacer()
|
||||
|
||||
if package.status == .downloading {
|
||||
Text("\(Int(package.progress * 100))%")
|
||||
.font(.caption)
|
||||
} else {
|
||||
if package.status == .waiting {
|
||||
Text("\(Image(systemName: "hourglass.circle.fill")) \(package.status.description)")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
} else if package.status == .completed {
|
||||
Text("\(Image(systemName: "checkmark.circle.fill")) \(package.status.description)")
|
||||
.font(.caption)
|
||||
.foregroundColor(.green)
|
||||
} else {
|
||||
Text("\(package.status.description)")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
statusView()
|
||||
.font(.caption)
|
||||
}
|
||||
|
||||
if package.status == .downloading {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// Adobe-Downloader
|
||||
// Adobe Downloader
|
||||
//
|
||||
// Created by X1a0He on 2024/10/30.
|
||||
//
|
||||
@@ -119,7 +119,7 @@ private struct LogSection: View {
|
||||
|
||||
var body: some View {
|
||||
ScrollViewReader { proxy in
|
||||
ScrollView {
|
||||
ScrollView(showsIndicators: false) {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
ForEach(Array(logs.enumerated()), id: \.offset) { index, log in
|
||||
Text(log)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// Adobe-Downloader
|
||||
// Adobe Downloader
|
||||
//
|
||||
// Created by X1a0He on 2024/10/30.
|
||||
//
|
||||
@@ -9,8 +9,8 @@ struct LanguagePickerView: View {
|
||||
let languages: [(code: String, name: String)]
|
||||
let onLanguageSelected: (String) -> Void
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@AppStorage("defaultLanguage") private var defaultLanguage: String = "zh_CN"
|
||||
@State private var searchText: String = ""
|
||||
@State private var selectedLanguage: String = ""
|
||||
|
||||
private var filteredLanguages: [(code: String, name: String)] {
|
||||
guard !searchText.isEmpty else {
|
||||
@@ -64,9 +64,9 @@ struct LanguagePickerView: View {
|
||||
ForEach(Array(filteredLanguages.enumerated()), id: \.element.code) { index, language in
|
||||
LanguageRow(
|
||||
language: language,
|
||||
isSelected: language.code == defaultLanguage,
|
||||
isSelected: language.code == selectedLanguage,
|
||||
onSelect: {
|
||||
defaultLanguage = language.code
|
||||
selectedLanguage = language.code
|
||||
onLanguageSelected(language.code)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// Adobe-Downloader
|
||||
// Adobe Downloader
|
||||
//
|
||||
// Created by X1a0He on 2024/10/30.
|
||||
//
|
||||
@@ -17,9 +17,9 @@ struct SettingsView: View {
|
||||
private let languageMap: [(code: String, name: String)] = AppStatics.supportedLanguages
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 12) {
|
||||
HStack(spacing: 12) {
|
||||
HStack(spacing: 4) {
|
||||
VStack() {
|
||||
HStack() {
|
||||
HStack() {
|
||||
Toggle(isOn: $useDefaultLanguage) {
|
||||
Text("语言:")
|
||||
.fixedSize()
|
||||
@@ -30,17 +30,16 @@ struct SettingsView: View {
|
||||
Text(getLanguageName(code: defaultLanguage))
|
||||
.foregroundColor(.secondary)
|
||||
.lineLimit(1)
|
||||
.frame(width: 80, alignment: .leading)
|
||||
|
||||
.frame(alignment: .leading)
|
||||
Spacer()
|
||||
Button("选择", action: onSelectLanguage)
|
||||
.buttonStyle(.borderless)
|
||||
.fixedSize()
|
||||
}
|
||||
|
||||
Divider()
|
||||
.frame(height: 16)
|
||||
|
||||
HStack(spacing: 4) {
|
||||
HStack() {
|
||||
Toggle(isOn: $useDefaultDirectory) {
|
||||
Text("目录:")
|
||||
.fixedSize()
|
||||
@@ -51,10 +50,9 @@ struct SettingsView: View {
|
||||
Text(formatPath(defaultDirectory.isEmpty ? "未设置" : defaultDirectory))
|
||||
.foregroundColor(.secondary)
|
||||
.lineLimit(1)
|
||||
.frame(width: 120, alignment: .leading)
|
||||
|
||||
.frame(alignment: .leading)
|
||||
Spacer()
|
||||
Button("选择", action: onSelectDirectory)
|
||||
.buttonStyle(.borderless)
|
||||
.fixedSize()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// Adobe-Downloader
|
||||
// Adobe Downloader
|
||||
//
|
||||
// Created by X1a0He on 2024/10/30.
|
||||
//
|
||||
|
||||
@@ -33,22 +33,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"%@ %@" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "%1$@ %2$@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"%@ 安装失败" : {
|
||||
|
||||
},
|
||||
"%@ 安装完成" : {
|
||||
|
||||
},
|
||||
"%lld" : {
|
||||
|
||||
@@ -104,205 +88,643 @@
|
||||
},
|
||||
"Github: Adobe Downloader" : {
|
||||
|
||||
},
|
||||
"OK" : {
|
||||
|
||||
},
|
||||
"Released under GPLv3." : {
|
||||
|
||||
},
|
||||
"Setup未备份提示" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Setup not backed up"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Thanks Drovosek01: adobe-packager" : {
|
||||
|
||||
},
|
||||
"Thanks QiuChenly: InjectLib" : {
|
||||
|
||||
},
|
||||
"Welcome to Adobe Downloader" : {
|
||||
|
||||
},
|
||||
"下载" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Download"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"下载中" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Downloading"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"下载已取消" : {
|
||||
"comment" : "Download cancelled"
|
||||
"comment" : "Download cancelled",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Download cancelled"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"下载此版本" : {
|
||||
|
||||
"comment" : "版本选择页面",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Download"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"下载管理" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Download management"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"下载设置" : {
|
||||
|
||||
"comment" : "设置页面",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Settings"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"下载错误" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Download error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"产品和包列表" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Products and Packages"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"使用现有程序" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Use existing"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"使用默认目录" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Default directory"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"使用默认语言" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Default language"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"依赖包:" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Dependencies"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"依赖包: %lld" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Dependencies: %lld"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"全部暂停" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Pause All"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"全部继续" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Continue All"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"关闭" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Close"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"删除" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Delete"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"加载失败" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Loading failed"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"取消" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Cancel"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"可用版本: %lld" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Availables: %lld"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"在以下位置找到现有的安装程序:" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Locate the existing installer at:"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"失败: %@" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Failed: %@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"安装" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Install"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"安装程序已存在" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Installer already exists"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"尝试使用不同的搜索关键词" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "try using different keywords"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"尝试其他搜索关键词" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "try other search keywords"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"已存在" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Existed"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"已完成" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Completed"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"已暂停" : {
|
||||
"comment" : "Download status paused"
|
||||
"comment" : "Download status paused",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Paused"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"搜索应用" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Search"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"搜索语言" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "search language"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"是否确认重新下载?这将覆盖现有的安装程序。" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Are you sure you want to re-download? This will overwrite the existing installer"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"是否要安装 %@?" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Do you want to install %@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"暂停" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Pause"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"服务器响应无效" : {
|
||||
"comment" : "Invalid response"
|
||||
"comment" : "Invalid response",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Invalid server response"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"未找到语言" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "language not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"检测到Setup文件尚未备份,如果你需要安装程序,则Setup必须被处理,点击确定后你需要输入密码,Adobe Downloader将自动处理并备份为Setup.original" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "It is detected that the Setup file has not been backed up. If you need to install the program, the Setup must be processed. After clicking OK, you need to enter the password. Adobe Downloader will automatically process and back up the Setup.original."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"正在下载 %@ (%d/%d)" : {
|
||||
"comment" : "Download status downloading",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "正在下载 %1$@ (%2$d/%3$d)"
|
||||
"state" : "translated",
|
||||
"value" : "Downloading %1$@ (%2$d/%3$d)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"正在加载..." : {
|
||||
|
||||
},
|
||||
"正在安装 %@" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Loading…"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"没有找到产品" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "No products found"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"没有网络连接" : {
|
||||
"comment" : "Network error"
|
||||
"comment" : "Network error",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Network error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"清理已完成" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Cleanup Completed"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"目录:" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Directory: "
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"确定" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Confirm"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"确认" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Confirm"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"确认重新下载" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Confirm re-download"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"移除" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Delete"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"等待中" : {
|
||||
"comment" : "Download status waiting"
|
||||
"comment" : "Download status waiting",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Waiting…"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"系统休眠" : {
|
||||
"comment" : "Download status system sleep"
|
||||
"comment" : "Download status system sleep",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "System hibernation"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"继续" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Continue"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"网络中断" : {
|
||||
"comment" : "Download status network paused"
|
||||
"comment" : "Download status network paused",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Network interrupt"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"语言:" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Language: "
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"选择" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Select"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"选择安装语言" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Select the installation language"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"选择版本" : {
|
||||
|
||||
"comment" : "版本选择页面",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Versions"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"重新下载" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Re-download"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"重新下载时需要确认" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Re-download for second confirmation"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"重试" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "retry"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"重试中 (%d/%d)" : {
|
||||
"comment" : "Download status retrying",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "重试中 (%1$d/%2$d)"
|
||||
"state" : "translated",
|
||||
"value" : "Retrying (%1$d/%2$d)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"错误详情:" : {
|
||||
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Error details:"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"version" : "1.0"
|
||||
|
||||
BIN
imgs/1024pt-1.png
Normal file
BIN
imgs/Adobe Downloader.png
Normal file
|
After Width: | Height: | Size: 6.0 MiB |
BIN
imgs/download.png
Normal file
|
After Width: | Height: | Size: 231 KiB |