Skip to content
Merged
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
163 changes: 86 additions & 77 deletions App/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ struct ContentView: View {
@State private var extensionsExpanded = false
@State private var expandedExtensionInfo: String?
@State private var infoTapLocked = false
@State private var infoAutoCloseToken = UUID()
@State private var copiedFilename = false
@State private var copiedBundleID = false
@State private var exportSummary = ""
Expand Down Expand Up @@ -102,7 +103,7 @@ struct ContentView: View {
}

if !hasPendingChanges {
return .red
return .none
}

if !bundleIDChanged {
Expand All @@ -123,7 +124,7 @@ struct ContentView: View {
case .orange:
return .orange
case .green:
return .green
return .blue
case .none:
return .secondary
}
Expand Down Expand Up @@ -171,7 +172,7 @@ struct ContentView: View {
ScrollView {
VStack(spacing: 18) {

VStack(alignment: .leading, spacing: 5) {
VStack(alignment: .leading, spacing: 6) {
HStack(alignment: .firstTextBaseline, spacing: 10) {
Text("IPAID")
.font(.largeTitle.bold())
Expand All @@ -181,24 +182,15 @@ struct ContentView: View {
.foregroundStyle(.secondary)
}

Text("Bundle IDs • Names • Extensions")
.font(.callout.weight(.semibold))
.foregroundStyle(.secondary)
}
.frame(maxWidth: .infinity, alignment: .leading)

if !originalFileName.isEmpty {
VStack(alignment: .leading, spacing: 4) {
Text("Loaded IPA")
.font(.caption.weight(.semibold))
if !originalFileName.isEmpty {
Text("Loaded: \(displayedOriginalFileName)")
.font(.callout.weight(.medium))
.foregroundStyle(.secondary)

Text(displayedOriginalFileName)
.font(.callout)
.lineLimit(1)
.truncationMode(.middle)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
.frame(maxWidth: .infinity, alignment: .leading)

if currentBundleID.isEmpty && !status.isEmpty {
Text(status)
Expand All @@ -207,7 +199,7 @@ struct ContentView: View {
.frame(maxWidth: .infinity, alignment: .leading)
}

HStack(spacing: 10) {
HStack(spacing: 8) {
Button("Select IPA") {
showPicker = true
}
Expand All @@ -219,7 +211,7 @@ struct ContentView: View {
} label: {
Text("Unload")
.fontWeight(.semibold)
.foregroundStyle(.primary.opacity(0.68))
.foregroundStyle(.primary.opacity(0.78))
.padding(.vertical, 9)
.padding(.horizontal, 16)
.background(Color.gray.opacity(0.18))
Expand All @@ -235,7 +227,7 @@ struct ContentView: View {
.frame(maxWidth: .infinity, alignment: .leading)

if !currentBundleID.isEmpty {
VStack(alignment: .leading, spacing: 10) {
VStack(alignment: .leading, spacing: 6) {

Text("Current Bundle ID")
.font(.headline.weight(.semibold))
Expand Down Expand Up @@ -271,7 +263,7 @@ struct ContentView: View {
.font(.headline.weight(.semibold))
.padding(.top, 8)

HStack(spacing: 10) {
HStack(spacing: 8) {
TextField("com.example.app", text: $newBundleID)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
Expand Down Expand Up @@ -327,11 +319,15 @@ struct ContentView: View {
.font(.callout.weight(.semibold))
Spacer()
}
.padding(.vertical, 12)
.padding(.vertical, 11)
.padding(.horizontal, 14)
.foregroundStyle(duplicateMode ? Color.white : Color.primary)
.background(duplicateMode ? Color.blue : Color.gray.opacity(0.18))
.clipShape(RoundedRectangle(cornerRadius: 14))
.foregroundStyle(duplicateMode ? Color.blue : Color.primary)
.background(Color.gray.opacity(0.12))
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(Color.white.opacity(0.06), lineWidth: 1)
)
.clipShape(RoundedRectangle(cornerRadius: 12))
}
.buttonStyle(.plain)

Expand All @@ -341,9 +337,9 @@ struct ContentView: View {

Text("Display Name")
.font(.headline.weight(.semibold))
.padding(.top, 8)
.padding(.top, 2)

HStack(spacing: 10) {
HStack(spacing: 8) {
TextField("App name", text: $displayName)
.font(.body.weight(.regular))
.lineLimit(1)
Expand Down Expand Up @@ -374,9 +370,9 @@ struct ContentView: View {

if !currentChangeMessage.isEmpty {
Text(currentChangeMessage)
.font(.callout.weight(.regular))
.font(.subheadline.weight(.regular))
.foregroundStyle(validationColor)
.padding(.top, 4)
.padding(.top, 3)
}

Button("Export Updated IPA") {
Expand Down Expand Up @@ -456,42 +452,43 @@ struct ContentView: View {
}

private var extensionRemovalSection: some View {
VStack(alignment: .leading, spacing: 10) {
VStack(alignment: .leading, spacing: 8) {
Button {
withAnimation(.easeInOut(duration: 0.2)) {
extensionsExpanded.toggle()
if !extensionsExpanded {
infoAutoCloseToken = UUID()
expandedExtensionInfo = nil
}
}
} label: {
HStack {
Image(systemName: selectedExtensionsToRemove.isEmpty ? "circle" : "checkmark.circle.fill")

VStack(alignment: .leading, spacing: 2) {
Text("Remove Extensions")
.font(.callout.weight(.semibold))

Text("\(selectedExtensionsToRemove.count) of \(foundExtensions.count) selected")
.font(.caption)
.foregroundStyle(selectedExtensionsToRemove.isEmpty ? Color.secondary : Color.white.opacity(0.85))
}
Text("Extensions • \(selectedExtensionsToRemove.count)/\(foundExtensions.count)")
.font(.callout.weight(.semibold))
.lineLimit(1)
.minimumScaleFactor(0.85)

Spacer()

Image(systemName: extensionsExpanded ? "chevron.up" : "chevron.down")
.font(.caption.bold())
}
.padding(.vertical, 12)
.padding(.vertical, 11)
.padding(.horizontal, 14)
.foregroundStyle(selectedExtensionsToRemove.isEmpty ? Color.primary : Color.white)
.background(selectedExtensionsToRemove.isEmpty ? Color.gray.opacity(0.18) : Color.blue)
.clipShape(RoundedRectangle(cornerRadius: 14))
.foregroundStyle(selectedExtensionsToRemove.isEmpty ? Color.primary : Color.blue)
.background(Color.gray.opacity(0.12))
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(Color.white.opacity(0.06), lineWidth: 1)
)
.clipShape(RoundedRectangle(cornerRadius: 12))
}
.buttonStyle(.plain)

if extensionsExpanded {
VStack(alignment: .leading, spacing: 8) {
VStack(alignment: .leading, spacing: 6) {
ForEach(foundExtensions, id: \.self) { path in
extensionRow(path)
}
Expand All @@ -508,24 +505,36 @@ struct ContentView: View {
} label: {
HStack {
Image(systemName: selectedExtensionsToRemove.count == foundExtensions.count ? "checkmark.circle.fill" : "circle")
.font(.title3)
.font(.body)

Text(selectedExtensionsToRemove.count == foundExtensions.count ? "Deselect All Extensions" : "Select All Extensions")
.font(.callout.weight(.semibold))
Text(selectedExtensionsToRemove.count == foundExtensions.count ? "Deselect All" : "Select All")
.font(.subheadline.weight(.semibold))

Spacer()
}
.font(.callout)
.padding(.vertical, 10)
.padding(.horizontal, 12)
.background(Color.gray.opacity(0.12))
.clipShape(RoundedRectangle(cornerRadius: 12))
.padding(.vertical, 7)
.padding(.horizontal, 10)
.background(Color.gray.opacity(0.10))
.clipShape(RoundedRectangle(cornerRadius: 9))
}
.buttonStyle(.plain)

if let infoPath = expandedExtensionInfo {
Text(extensionTip(for: extensionName(from: infoPath)))
.font(.caption2.weight(.regular))
.foregroundStyle(.secondary.opacity(0.86))
.fixedSize(horizontal: false, vertical: true)
.padding(.vertical, 6)
.padding(.horizontal, 9)
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color.gray.opacity(0.075))
.clipShape(RoundedRectangle(cornerRadius: 8))
.transition(.opacity.combined(with: .move(edge: .top)))
}
}
.padding(12)
.background(Color.gray.opacity(0.08))
.clipShape(RoundedRectangle(cornerRadius: 14))
.padding(7)
.background(Color.gray.opacity(0.06))
.clipShape(RoundedRectangle(cornerRadius: 11))
}
}
}
Expand All @@ -535,7 +544,7 @@ struct ContentView: View {
let isExpanded = expandedExtensionInfo == path
let name = extensionName(from: path)

return VStack(alignment: .leading, spacing: 8) {
return VStack(alignment: .leading, spacing: 6) {
HStack(spacing: 0) {
Button {
withAnimation(.easeInOut(duration: 0.18)) {
Expand All @@ -547,9 +556,9 @@ struct ContentView: View {
clearStaleExportState()
}
} label: {
HStack(spacing: 10) {
HStack(spacing: 8) {
Image(systemName: isSelected ? "checkmark.circle.fill" : "circle")
.font(.title3)
.font(.body)
.foregroundStyle(isSelected ? Color.blue : Color.secondary)

Text(name)
Expand All @@ -575,13 +584,21 @@ struct ContentView: View {
infoTapLocked = false
}

withAnimation(.easeInOut(duration: 0.18)) {
expandedExtensionInfo = isExpanded ? nil : path
}
if isExpanded {
infoAutoCloseToken = UUID()
withAnimation(.easeInOut(duration: 0.18)) {
expandedExtensionInfo = nil
}
} else {
let token = UUID()
infoAutoCloseToken = token

withAnimation(.easeInOut(duration: 0.18)) {
expandedExtensionInfo = path
}

if !isExpanded {
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
if expandedExtensionInfo == path {
if infoAutoCloseToken == token && expandedExtensionInfo == path {
withAnimation(.easeInOut(duration: 0.18)) {
expandedExtensionInfo = nil
}
Expand All @@ -591,27 +608,19 @@ struct ContentView: View {
} label: {
Image(systemName: "info.circle.fill")
.foregroundStyle(Color.blue)
.frame(width: 44, height: 38)
.frame(width: 40, height: 30)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
}

if isExpanded {
Text(extensionTip(for: name))
.font(.caption)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
.padding(.leading, 32)
.transition(.opacity.combined(with: .move(edge: .top)))
}
}
.font(.callout)
.padding(.vertical, isExpanded ? 10 : 8)
.padding(.leading, 12)
.padding(.trailing, 6)
.background(Color.gray.opacity(0.12))
.clipShape(RoundedRectangle(cornerRadius: 12))
.font(.subheadline)
.padding(.vertical, 5)
.padding(.leading, 10)
.padding(.trailing, 5)
.background(isExpanded ? Color.gray.opacity(0.14) : Color.gray.opacity(0.10))
.clipShape(RoundedRectangle(cornerRadius: 9))
}


Expand Down
Loading