Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,7 @@ build/
.agents/
skills-lock.json

# Local portal host — see Config/Portal.xcconfig.example
Config/Portal.xcconfig
# Local portal host. Config/Portal.xcconfig IS committed (empty) so the
# project's base configuration reference resolves in a fresh clone; it
# optionally includes this file, which holds your real host.
Config/Portal.local.xcconfig
12 changes: 12 additions & 0 deletions Config/Portal.xcconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Portal host, injected at compile time. Committed and intentionally empty:
// a base configuration reference must resolve in a fresh clone or xcodebuild
// fails outright with "Unable to open base configuration reference file" —
// it is an error, not a warning, which is how CI stayed red.
//
// Your own host goes in Portal.local.xcconfig (gitignored, optional include
// below). CI passes AMDL_PORTAL_HOST on the xcodebuild command line instead.
// Empty is a supported state: BackendEndpoint returns "" and the app asks the
// user for an address.
AMDL_PORTAL_HOST =

#include? "Portal.local.xcconfig"
3 changes: 0 additions & 3 deletions Config/Portal.xcconfig.example

This file was deleted.

24 changes: 12 additions & 12 deletions amdl-ios.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.1;
MARKETING_VERSION = 3.2;
PRODUCT_BUNDLE_IDENTIFIER = "com.lyjw131.amdl.amdl-ios";
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES;
Expand Down Expand Up @@ -608,7 +608,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.1;
MARKETING_VERSION = 3.2;
PRODUCT_BUNDLE_IDENTIFIER = "com.lyjw131.amdl.amdl-ios";
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES;
Expand Down Expand Up @@ -754,7 +754,7 @@
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_AMDLPortalHost = "$(AMDL_PORTAL_HOST)";
IPHONEOS_DEPLOYMENT_TARGET = 26.4;
MARKETING_VERSION = 3.1;
MARKETING_VERSION = 3.2;
PRODUCT_BUNDLE_IDENTIFIER = "com.lyjw131.amdl.amdl-iosTests";
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO;
Expand All @@ -777,7 +777,7 @@
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_AMDLPortalHost = "$(AMDL_PORTAL_HOST)";
IPHONEOS_DEPLOYMENT_TARGET = 26.4;
MARKETING_VERSION = 3.1;
MARKETING_VERSION = 3.2;
PRODUCT_BUNDLE_IDENTIFIER = "com.lyjw131.amdl.amdl-iosTests";
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO;
Expand All @@ -798,7 +798,7 @@
DEVELOPMENT_TEAM = 2VTXNMR2GL;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_AMDLPortalHost = "$(AMDL_PORTAL_HOST)";
MARKETING_VERSION = 3.1;
MARKETING_VERSION = 3.2;
PRODUCT_BUNDLE_IDENTIFIER = "com.lyjw131.amdl.amdl-iosUITests";
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO;
Expand All @@ -819,7 +819,7 @@
DEVELOPMENT_TEAM = 2VTXNMR2GL;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_AMDLPortalHost = "$(AMDL_PORTAL_HOST)";
MARKETING_VERSION = 3.1;
MARKETING_VERSION = 3.2;
PRODUCT_BUNDLE_IDENTIFIER = "com.lyjw131.amdl.amdl-iosUITests";
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO;
Expand Down Expand Up @@ -850,7 +850,7 @@
"$(inherited)",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.1;
MARKETING_VERSION = 3.2;
PRODUCT_BUNDLE_IDENTIFIER = "com.lyjw131.amdl.amdl-ios.DownloadLiveActivity";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
Expand Down Expand Up @@ -882,7 +882,7 @@
"$(inherited)",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.1;
MARKETING_VERSION = 3.2;
PRODUCT_BUNDLE_IDENTIFIER = "com.lyjw131.amdl.amdl-ios.DownloadLiveActivity";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
Expand Down Expand Up @@ -913,7 +913,7 @@
"$(inherited)",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.1;
MARKETING_VERSION = 3.2;
PRODUCT_BUNDLE_IDENTIFIER = "com.lyjw131.amdl.amdl-ios.ShareDownloadExtension";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
Expand Down Expand Up @@ -944,7 +944,7 @@
"$(inherited)",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.1;
MARKETING_VERSION = 3.2;
PRODUCT_BUNDLE_IDENTIFIER = "com.lyjw131.amdl.amdl-ios.ShareDownloadExtension";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
Expand Down Expand Up @@ -975,7 +975,7 @@
"$(inherited)",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.1;
MARKETING_VERSION = 3.2;
PRODUCT_BUNDLE_IDENTIFIER = "com.lyjw131.amdl.amdl-ios.DownloadNotificationExtension";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
Expand Down Expand Up @@ -1006,7 +1006,7 @@
"$(inherited)",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.1;
MARKETING_VERSION = 3.2;
PRODUCT_BUNDLE_IDENTIFIER = "com.lyjw131.amdl.amdl-ios.DownloadNotificationExtension";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
Expand Down
46 changes: 29 additions & 17 deletions amdl-ios/DownloadArtworkView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,23 +78,35 @@ struct JobArtworkView: View {
}

var body: some View {
ZStack {
// 占位一直垫在最底下,`CachedAsyncImage` 在真正解出 UIImage 之前画的是
// Color.clear —— 这样封面槽位从头到尾都有东西,不会先闪一下白底。
JobArtworkPlaceholder(job: job)

// 即便 fallbackURL 还没解析出来,只要本地缓存里有这个 key 的图,
// CachedAsyncImage 就会优先加载缓存,不会用过期 URL 覆盖当前画面。
CachedAsyncImage(
url: privateRequest == nil ? primaryURL : fallbackURL,
cacheKey: cacheKey,
fallbackCacheKey: fallbackCacheKey
)
.id("\(cacheKey)|\(artworkRevision)")
}
.task(id: "\(job.id)|\(job.artworkURL ?? "")|\(pixelSize)") {
await loadPrivatePlaylistArtwork()
}
// 槽位大小只跟父视图给的建议有关,跟里面装的是什么无关。
//
// 以前这里是 ZStack 直接摞占位图和 `CachedAsyncImage`,两层谁都不声明尺寸:
// 图解出来之前画的是 Color.clear,解出来之后理想尺寸又变成封面的原始像素,
// 于是槽位多大是跟内容协商出来的。列表行只是碰巧写死了 56×56 才一直稳,
// 详情页 Hero 只约束了比例不约束大小,换图、`.id` 重建、压根没有封面,都可能
// 让它跟着动一下。现在由占位图(一个 Shape,尺寸只认建议)撑出正方形槽位,
// 封面放进 overlay —— overlay 不参与布局,谁都顶不动它。
//
// 顺带保住原来的层次:占位一直垫在最底下,`CachedAsyncImage` 在真正解出
// UIImage 之前画的是 Color.clear,封面槽位从头到尾都有东西,不会闪白底。
JobArtworkPlaceholder(job: job)
.aspectRatio(1, contentMode: .fit)
.overlay {
// 即便 fallbackURL 还没解析出来,只要本地缓存里有这个 key 的图,
// CachedAsyncImage 就会优先加载缓存,不会用过期 URL 覆盖当前画面。
CachedAsyncImage(
url: privateRequest == nil ? primaryURL : fallbackURL,
cacheKey: cacheKey,
fallbackCacheKey: fallbackCacheKey
)
.id("\(cacheKey)|\(artworkRevision)")
}
// scaledToFill 出来的图可能比槽位大一圈,就地裁掉溢出的部分;圆角仍然
// 由调用方给(列表 12pt、详情页 10pt),这里只负责不画到槽位外面去。
.clipped()
.task(id: "\(job.id)|\(job.artworkURL ?? "")|\(pixelSize)") {
await loadPrivatePlaylistArtwork()
}
}

private func loadPrivatePlaylistArtwork() async {
Expand Down
Loading
Loading