Skip to content
This repository was archived by the owner on Mar 7, 2026. It is now read-only.

Commit b62f5af

Browse files
authored
Update ProStore to have multi-view with 'Signer' and 'About'
1 parent 0b6ba8a commit b62f5af

1 file changed

Lines changed: 218 additions & 57 deletions

File tree

Sources/prostore/prostore.swift

Lines changed: 218 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,192 @@ import UniformTypeIdentifiers
33
import ZIPFoundation
44
import ZsignSwift
55

6+
// MARK: - Small models
67
struct FileItem {
78
var url: URL?
89
var name: String { url?.lastPathComponent ?? "" }
910
}
1011

12+
struct Credit: Identifiable {
13+
var id = UUID()
14+
var name: String
15+
var role: String
16+
var profileURL: URL
17+
var avatarURL: URL
18+
}
19+
20+
// MARK: - App
1121
@main
1222
struct ZsignOnDeviceApp: App {
1323
var body: some Scene {
1424
WindowGroup {
15-
ContentView()
16-
.navigationViewStyle(StackNavigationViewStyle()) // Fix iPad sidebar bug
25+
MainTabView()
26+
}
27+
}
28+
}
29+
30+
// MARK: - Main tab view
31+
struct MainTabView: View {
32+
var body: some View {
33+
TabView {
34+
NavigationView {
35+
AboutView()
36+
}
37+
.tabItem {
38+
Image(systemName: "info.circle")
39+
Text("About")
40+
}
41+
42+
NavigationView {
43+
SignerView()
44+
}
45+
.tabItem {
46+
Image(systemName: "hammer")
47+
Text("Signer")
48+
}
49+
}
50+
}
51+
}
52+
53+
// MARK: - About view
54+
struct AboutView: View {
55+
// credits data
56+
private let credits: [Credit] = [
57+
Credit(
58+
name: "zhlynn",
59+
role: "Original zsign",
60+
profileURL: URL(string: "https://github.com/zhlynn")!,
61+
avatarURL: URL(string: "https://github.com/zhlynn.png")!
62+
),
63+
Credit(
64+
name: "Khcrysalis",
65+
role: "Zsign-Package (fork)",
66+
profileURL: URL(string: "https://github.com/khcrysalis")!,
67+
avatarURL: URL(string: "https://github.com/khcrysalis.png")!
68+
),
69+
Credit(
70+
name: "SuperGamer474",
71+
role: "Developer",
72+
profileURL: URL(string: "https://github.com/SuperGamer474")!,
73+
avatarURL: URL(string: "https://github.com/SuperGamer474.png")!
74+
)
75+
]
76+
77+
private var appIconURL: URL? {
78+
URL(string: "https://raw.githubusercontent.com/ProStore-iOS/ProStore/main/Sources/prostore/Assets.xcassets/AppIcon.appiconset/Icon-1024.png")
79+
}
80+
81+
private var versionString: String {
82+
let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "1.0"
83+
let build = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? ""
84+
return build.isEmpty ? version : "\(version) (\(build))"
85+
}
86+
87+
var body: some View {
88+
List {
89+
VStack(spacing: 8) {
90+
if let url = appIconURL {
91+
AsyncImage(url: url) { phase in
92+
if let img = phase.image {
93+
img
94+
.resizable()
95+
.scaledToFit()
96+
.frame(width: 120, height: 120)
97+
.clipShape(RoundedRectangle(cornerRadius: 20, style: .continuous))
98+
.shadow(radius: 6)
99+
} else if phase.error != nil {
100+
// fallback
101+
Image(systemName: "app.fill")
102+
.resizable()
103+
.scaledToFit()
104+
.frame(width: 80, height: 80)
105+
.foregroundColor(.secondary)
106+
} else {
107+
ProgressView()
108+
.frame(width: 80, height: 80)
109+
}
110+
}
111+
}
112+
113+
Text("ProStore")
114+
.font(.title2)
115+
.fontWeight(.semibold)
116+
117+
Text("Version \(versionString)")
118+
.font(.footnote)
119+
.foregroundColor(.secondary)
120+
}
121+
.frame(maxWidth: .infinity)
122+
.padding(.vertical, 20)
123+
.listRowInsets(EdgeInsets())
124+
125+
Section(header: Text("Credits")) {
126+
ForEach(credits) { c in
127+
CreditRow(credit: c)
128+
}
129+
}
130+
131+
// optional sponsors or other sections can go here
132+
}
133+
.listStyle(InsetGroupedListStyle())
134+
.navigationTitle("About")
135+
}
136+
}
137+
138+
struct CreditRow: View {
139+
let credit: Credit
140+
@Environment(\.openURL) var openURL
141+
142+
var body: some View {
143+
HStack(spacing: 12) {
144+
AsyncImage(url: credit.avatarURL) { phase in
145+
if let img = phase.image {
146+
img
147+
.resizable()
148+
.scaledToFill()
149+
} else if phase.error != nil {
150+
Image(systemName: "person.crop.circle.fill")
151+
.resizable()
152+
.scaledToFill()
153+
} else {
154+
ProgressView()
155+
}
156+
}
157+
.frame(width: 44, height: 44)
158+
.clipShape(Circle())
159+
.overlay(Circle().stroke(Color(UIColor.separator), lineWidth: 0.5))
160+
161+
VStack(alignment: .leading, spacing: 2) {
162+
Text(credit.name)
163+
.font(.body)
164+
Text(credit.role)
165+
.font(.subheadline)
166+
.foregroundColor(.secondary)
167+
}
168+
169+
Spacer()
170+
171+
Button {
172+
openURL(credit.profileURL)
173+
} label: {
174+
Image(systemName: "arrow.up.right.square")
175+
.imageScale(.large)
176+
.foregroundColor(.accentColor)
177+
}
178+
.buttonStyle(BorderlessButtonStyle())
17179
}
180+
.padding(.vertical, 8)
18181
}
19182
}
20183

21-
struct ContentView: View {
184+
// MARK: - SignerView (your original ContentView moved here)
185+
struct SignerView: View {
22186
@State private var ipa = FileItem()
23187
@State private var p12 = FileItem()
24188
@State private var prov = FileItem()
25189
@State private var p12Password = ""
26190
@State private var isProcessing = false
27-
@State private var progressMessage = "Idle 😎"
191+
@State private var progressMessage = ""
28192
@State private var showActivity = false
29193
@State private var activityURL: URL? = nil
30194
@State private var showPickerFor: PickerKind?
@@ -41,67 +205,64 @@ struct ContentView: View {
41205
}
42206

43207
var body: some View {
44-
NavigationView {
45-
Form {
46-
Section(header: Text("Inputs")) {
47-
HStack {
48-
Text("IPA:")
49-
Spacer()
50-
Text(ipa.name.isEmpty ? "none" : ipa.name).foregroundColor(.secondary)
51-
Button("Pick") { showPickerFor = .ipa }
52-
}
208+
Form {
209+
Section(header: Text("Inputs")) {
210+
HStack {
211+
Text("IPA:")
212+
Spacer()
213+
Text(ipa.name.isEmpty ? "none" : ipa.name).foregroundColor(.secondary)
214+
Button("Pick") { showPickerFor = .ipa }
215+
}
216+
HStack {
217+
Text("P12:")
218+
Spacer()
219+
Text(p12.name.isEmpty ? "none" : p12.name).foregroundColor(.secondary)
220+
Button("Pick") { showPickerFor = .p12 }
221+
}
222+
HStack {
223+
Text("MobileProvision:")
224+
Spacer()
225+
Text(prov.name.isEmpty ? "none" : prov.name).foregroundColor(.secondary)
226+
Button("Pick") { showPickerFor = .prov }
227+
}
228+
SecureField("P12 Password", text: $p12Password)
229+
}
230+
231+
Section {
232+
Button(action: runSign) {
53233
HStack {
54-
Text("P12:")
55234
Spacer()
56-
Text(p12.name.isEmpty ? "none" : p12.name).foregroundColor(.secondary)
57-
Button("Pick") { showPickerFor = .p12 }
58-
}
59-
HStack {
60-
Text("MobileProvision:")
235+
Text("Sign IPA").bold()
61236
Spacer()
62-
Text(prov.name.isEmpty ? "none" : prov.name).foregroundColor(.secondary)
63-
Button("Pick") { showPickerFor = .prov }
64-
}
65-
SecureField("P12 Password", text: $p12Password)
66-
}
67-
68-
Section {
69-
Button(action: runSign) {
70-
HStack {
71-
Spacer()
72-
if isProcessing { ProgressView(progressMessage) }
73-
Text("Sign IPA").bold()
74-
Spacer()
75-
}
76237
}
77-
.disabled(isProcessing || ipa.url == nil || p12.url == nil || prov.url == nil)
78-
}
79-
80-
Section(header: Text("Status")) {
81-
Text(progressMessage).foregroundColor(.primary)
82238
}
239+
.disabled(isProcessing || ipa.url == nil || p12.url == nil || prov.url == nil)
83240
}
84-
.navigationTitle("Zsign On Device")
85-
.navigationViewStyle(StackNavigationViewStyle()) // Fix iPad sidebar issue
86-
.sheet(item: $showPickerFor, onDismiss: nil) { kind in
87-
DocumentPicker(kind: kind, onPick: { url in
88-
switch kind {
89-
case .ipa: ipa.url = url
90-
case .p12: p12.url = url
91-
case .prov: prov.url = url
92-
}
93-
})
241+
242+
Section(header: Text("Status")) {
243+
Text(progressMessage).foregroundColor(.primary)
94244
}
95-
.sheet(isPresented: $showActivity) {
96-
if let u = activityURL {
97-
ActivityView(url: u)
98-
} else {
99-
Text("No file to share")
245+
}
246+
.navigationTitle("Signer")
247+
.sheet(item: $showPickerFor, onDismiss: nil) { kind in
248+
DocumentPicker(kind: kind, onPick: { url in
249+
switch kind {
250+
case .ipa: ipa.url = url
251+
case .p12: p12.url = url
252+
case .prov: prov.url = url
100253
}
254+
})
255+
}
256+
.sheet(isPresented: $showActivity) {
257+
if let u = activityURL {
258+
ActivityView(url: u)
259+
} else {
260+
Text("No file to share")
101261
}
102262
}
103263
}
104264

265+
// --- runSign same as your original; adapted to compile in this struct scope
105266
func runSign() {
106267
guard let ipaURL = ipa.url, let p12URL = p12.url, let provURL = prov.url else {
107268
progressMessage = "Pick all input files first 😅"
@@ -230,9 +391,9 @@ struct ContentView: View {
230391
}
231392
}
232393

233-
// DocumentPicker wrapper
394+
// MARK: - DocumentPicker wrapper (same as yours)
234395
struct DocumentPicker: UIViewControllerRepresentable {
235-
var kind: ContentView.PickerKind
396+
var kind: SignerView.PickerKind
236397
var onPick: (URL) -> Void
237398

238399
func makeCoordinator() -> Coordinator { Coordinator(self) }
@@ -255,11 +416,11 @@ struct DocumentPicker: UIViewControllerRepresentable {
255416
}
256417
}
257418

258-
// ActivityView wrapper
419+
// MARK: - ActivityView wrapper
259420
struct ActivityView: UIViewControllerRepresentable {
260421
let url: URL
261422
func makeUIViewController(context: Context) -> UIActivityViewController {
262423
UIActivityViewController(activityItems: [url], applicationActivities: nil)
263424
}
264425
func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {}
265-
}
426+
}

0 commit comments

Comments
 (0)