Show a global Apple account above connected devices
This commit is contained in:
1 parent
159c960b44
commit
b09028c0c9
4 files changed
+45
-15
No files matched your search
@@ -12,6 +12,10 @@ enum EasyLanguage: String, CaseIterable {
|
||||
|
||||
struct EasyStrings {
|
||||
static let english: [String:String] = [
|
||||
"Cuenta Apple · Todos tus dispositivos":"Apple Account · All your devices",
|
||||
"Sin sesión iniciada":"Not signed in",
|
||||
"Sólo para firmar, instalar y renovar apps. Exportar partidas y copiar romfs no requieren iniciar sesión.":"Only needed to sign, install and renew apps. Exporting saves and copying romfs do not require signing in.",
|
||||
"Una cuenta para todos tus dispositivos en este Mac. Sólo se necesita para firmar, instalar y renovar apps.":"One account for all your devices on this Mac. Only needed to sign, install and renew apps.",
|
||||
"Exportar partidas":"Export saves",
|
||||
"Copiar datos del juego":"Copy game data",
|
||||
"Exportando partidas…":"Exporting saves…",
|
||||
|
||||
@@ -417,6 +417,7 @@ enum RefreshPolicy {
|
||||
struct SideloadView:View {
|
||||
@ObservedObject var model:SideloadModel
|
||||
let text:(String)->String
|
||||
var accountOnly=false
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@State private var password=""
|
||||
@State private var code=""
|
||||
@@ -426,7 +427,8 @@ struct SideloadView:View {
|
||||
var body:some View {
|
||||
ScrollView {
|
||||
VStack(alignment:.leading,spacing:16) {
|
||||
HStack {Text(text(model.ipa==nil ? "Tu cuenta Apple" : "Instalar en iPhone / iPad")).font(.title2.bold());Spacer();Button(text("Cerrar")){dismiss()}.disabled(model.busy)}
|
||||
HStack {Text(text(accountOnly || model.ipa==nil ? "Tu cuenta Apple" : "Instalar en iPhone / iPad")).font(.title2.bold());Spacer();Button(text("Cerrar")){dismiss()}.disabled(model.busy)}
|
||||
if !accountOnly {
|
||||
HStack(spacing:18) {
|
||||
Label(text("1 · Dispositivo"),systemImage:model.device.isEmpty ? "circle" : "checkmark.circle.fill")
|
||||
Label(text("2 · Cuenta Apple"),systemImage:model.authenticated ? "checkmark.circle.fill" : "circle")
|
||||
@@ -439,6 +441,8 @@ struct SideloadView:View {
|
||||
if let selected=model.devices.first(where:{$0.id==model.device}),!selected.detail.isEmpty {Text(selected.detail).font(.caption).foregroundStyle(.secondary)}
|
||||
Button(text("Buscar dispositivos")){model.listDevices()}.disabled(model.busy)
|
||||
Text(text("Desbloquea el dispositivo, confía en este Mac y activa Modo de desarrollador si iOS lo pide.")) .font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
if accountOnly {Text(text("Una cuenta para todos tus dispositivos en este Mac. Sólo se necesita para firmar, instalar y renovar apps.")).foregroundStyle(.secondary)}
|
||||
if !model.authenticated {
|
||||
LabeledContent(text("Cuenta Apple personal")){TextField("name@example.com",text:$model.email).textFieldStyle(.roundedBorder).disabled(model.busy)}
|
||||
LabeledContent(text("Contraseña de Apple")){SecureField(text("Contraseña de Apple"),text:$password).textFieldStyle(.roundedBorder).disabled(model.busy)}
|
||||
@@ -460,7 +464,7 @@ struct SideloadView:View {
|
||||
ForEach(model.accountDevices) {item in Text(item.name+" · …"+String(item.id.suffix(8))+" · "+item.status).font(.caption)}
|
||||
}
|
||||
Text(text("Cuenta gratuita: Apple indica hasta 3 dispositivos y perfiles de 7 días. Esta lista muestra registros reales; no calcula cupos libres ni elimina dispositivos.")).font(.caption).foregroundStyle(.secondary)
|
||||
if model.ipa != nil {
|
||||
if !accountOnly && model.ipa != nil {
|
||||
Picker(text("App existente que actualizarás"),selection:$model.existing) {Text(text("Primera instalación")).tag("");ForEach(model.apps){Text($0.name+" · "+$0.id).tag($0.id)}}.disabled(model.busy)
|
||||
Text(text("Se conserva el identificador efectivo. Si no corresponde al juego y equipo, la instalación se detiene; no se desinstala la app anterior.")) .font(.caption).foregroundStyle(.secondary)
|
||||
Toggle(text("Renovar automáticamente antes de que caduque"),isOn:$autoRefresh).disabled(model.busy || !remember)
|
||||
@@ -468,7 +472,7 @@ struct SideloadView:View {
|
||||
Text(text("La renovación necesita este Mac encendido, nxnative Easy abierto, conexión a Internet y el dispositivo accesible. Conserva la IPA original.")) .font(.caption).foregroundStyle(.secondary)
|
||||
Button(text(model.existing.isEmpty ? "Firmar e instalar" : "Firmar y actualizar")){model.install(autoRefresh:autoRefresh)}.buttonStyle(.borderedProminent).disabled(model.busy || model.device.isEmpty || model.team.isEmpty || model.originalID.isEmpty)
|
||||
}
|
||||
if model.canUpdateAppOnly {
|
||||
if !accountOnly && model.canUpdateAppOnly {
|
||||
Button(text("Actualizar sólo la app; conservar romfs")){model.install(autoRefresh:autoRefresh,includeContent:false)}.disabled(model.busy)
|
||||
Text(text("Para cambios de controles o de la app con la misma versión del juego. Si cambió el juego o DLC, usa la instalación con datos nuevos.")).font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
@@ -480,7 +484,7 @@ struct SideloadView:View {
|
||||
Button(text("Enviar a mis dispositivos")){model.sendToDevices()}
|
||||
ForEach(model.smsNumbers) {number in Button("SMS · …"+number.digits){model.sendSMS(number.id)}}
|
||||
}
|
||||
Text(text("Guarda y cierra el juego antes de instalar. Firma, copia y renovación nativas aún requieren validación completa con una cuenta y dispositivo reales.")).font(.caption).foregroundStyle(.secondary)
|
||||
if !accountOnly {Text(text("Guarda y cierra el juego antes de instalar. Firma, copia y renovación nativas aún requieren validación completa con una cuenta y dispositivo reales.")).font(.caption).foregroundStyle(.secondary)}
|
||||
InstallActivityView(model:model,text:text)
|
||||
if !model.busy {Text(text(model.phase)).font(.callout)}
|
||||
}.padding(24)
|
||||
@@ -498,6 +502,7 @@ struct SideloadLibraryView:View {
|
||||
var artwork:(InstalledGame)->NSImage?={_ in nil}
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@State private var showInstaller=false
|
||||
@State private var accountOnly=false
|
||||
private enum Confirmation {case export(InstalledGame),copy(InstalledGame),forget(RefreshJob)}
|
||||
@State private var confirmation:Confirmation?
|
||||
@State private var search=""
|
||||
@@ -524,6 +529,19 @@ struct SideloadLibraryView:View {
|
||||
}
|
||||
Spacer();if !embedded {Button(text("Cerrar")){dismiss()}.disabled(model.busy)}
|
||||
}.padding(24)
|
||||
VStack(alignment:.leading,spacing:8) {
|
||||
HStack(spacing:12) {
|
||||
Image(systemName:model.authenticated ? "person.crop.circle.badge.checkmark" : "person.crop.circle").font(.title2).foregroundStyle(accent)
|
||||
VStack(alignment:.leading,spacing:4) {
|
||||
Text(text("Cuenta Apple · Todos tus dispositivos")).font(.headline)
|
||||
if model.authenticated {Text(model.email).textSelection(.enabled)}
|
||||
else {Text(text("Sin sesión iniciada")).foregroundStyle(.secondary)}
|
||||
}
|
||||
Spacer()
|
||||
Button(text(model.authenticated ? "Ver cuenta y equipos" : "Iniciar sesión")){accountOnly=true;showInstaller=true}.disabled(model.busy || !canInstall)
|
||||
}
|
||||
Text(text("Sólo para firmar, instalar y renovar apps. Exportar partidas y copiar romfs no requieren iniciar sesión.")).font(.callout).foregroundStyle(.secondary)
|
||||
}.padding(.horizontal,24).padding(.bottom,20)
|
||||
Divider()
|
||||
ScrollView {
|
||||
VStack(alignment:.leading,spacing:20) {
|
||||
@@ -547,18 +565,11 @@ struct SideloadLibraryView:View {
|
||||
}.padding(16).background(model.device==device.id ? accent.opacity(0.08) : .white.opacity(0.035),in:RoundedRectangle(cornerRadius:14))
|
||||
}.buttonStyle(.plain).disabled(model.busy || !canInstall)
|
||||
}
|
||||
HStack(alignment:.top,spacing:16) {
|
||||
VStack(alignment:.leading,spacing:10) {
|
||||
Label(text("Tu cuenta Apple"),systemImage:model.authenticated ? "person.crop.circle.badge.checkmark" : "person.crop.circle").font(.headline)
|
||||
Text(text(model.authenticated ? "Sesión iniciada" : "Inicia sesión aquí para firmar e instalar.")).foregroundStyle(.secondary)
|
||||
Button(text(model.authenticated ? "Ver cuenta y equipos" : "Iniciar sesión")){showInstaller=true}.disabled(model.busy || !canInstall)
|
||||
}.padding(18).frame(maxWidth:.infinity,minHeight:136,alignment:.topLeading).background(.white.opacity(0.04),in:RoundedRectangle(cornerRadius:14))
|
||||
VStack(alignment:.leading,spacing:10) {
|
||||
Label(text("Instalar otro juego"),systemImage:"plus.app").font(.headline)
|
||||
Text(text("Elige tus archivos; prepararemos el juego y abriremos el instalador al terminar.")).foregroundStyle(.secondary)
|
||||
Button(text("Elegir carpeta de juego")){model.cancelPendingUpdate();prepare()}.disabled(model.busy || !canInstall)
|
||||
}.padding(18).frame(maxWidth:.infinity,minHeight:136,alignment:.topLeading).background(.white.opacity(0.04),in:RoundedRectangle(cornerRadius:14))
|
||||
}
|
||||
if !model.device.isEmpty {
|
||||
HStack {Label(text("Tus juegos instalados"),systemImage:"square.grid.2x2").font(.headline);Spacer();Text(String(model.apps.count)).foregroundStyle(.secondary)}
|
||||
TextField(text("Buscar por nombre o identificador"),text:$search).textFieldStyle(.roundedBorder)
|
||||
@@ -591,7 +602,7 @@ struct SideloadLibraryView:View {
|
||||
Text(text(job.expires<=Date() ? "Firma vencida" : "La firma vence")+" "+job.expires.formatted(date:.abbreviated,time:.shortened)).font(.caption)
|
||||
if job.needsLogin {Text(text("Inicia sesión para renovar este juego.")).foregroundStyle(.orange)}
|
||||
Toggle(text("Renovar automáticamente antes de que caduque"),isOn:Binding(get:{model.jobs.first(where:{$0.id==job.id})?.enabled ?? false},set:{model.setRefresh(job,enabled:$0)}))
|
||||
HStack {Button(text("Renovar ahora")){model.renew(job)};Button(text("Revisar cuenta e instalación")){model.select(job);showInstaller=true};Spacer();Button(text("Quitar de esta lista")){confirmation = .forget(job)}}
|
||||
HStack {Button(text("Renovar ahora")){model.renew(job)};Button(text("Revisar cuenta e instalación")){model.select(job);accountOnly=false;showInstaller=true};Spacer();Button(text("Quitar de esta lista")){confirmation = .forget(job)}}
|
||||
}.padding(16).background(.white.opacity(0.04),in:RoundedRectangle(cornerRadius:14)).disabled(model.busy || !canInstall)
|
||||
}
|
||||
}
|
||||
@@ -602,7 +613,7 @@ struct SideloadLibraryView:View {
|
||||
.onChange(of:search){_,_ in appLimit=8}
|
||||
.onChange(of:model.device){_,_ in appLimit=8}
|
||||
.task {if canInstall && !model.busy {model.listDevices()}}
|
||||
.sheet(isPresented:$showInstaller){SideloadView(model:model,text:text)}
|
||||
.sheet(isPresented:$showInstaller){SideloadView(model:model,text:text,accountOnly:accountOnly)}
|
||||
.alert(text(confirmationTitle),isPresented:Binding(get:{confirmation != nil},set:{if !$0 {confirmation=nil}}),presenting:confirmation) {action in
|
||||
Button(text("Cancelar"),role:.cancel){confirmation=nil}
|
||||
switch action {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.11.0",
|
||||
"build": 17,
|
||||
"version": "1.11.1",
|
||||
"build": 18,
|
||||
"minimumMacOSMajor": 26
|
||||
}
|
||||
@@ -546,3 +546,18 @@ for protected data, freeing approximately 35.7 GB before final build-cache
|
||||
cleanup. Original XCI/update NSP, the lightweight unsigned IPA, RomFS, source
|
||||
checkout and exported saves remain outside that deletion. A private cleanup
|
||||
receipt records paths, available space and the IPA hash beside the game tools.
|
||||
|
||||
## Global Apple account placement (1.11.1)
|
||||
|
||||
Devices now has a persistent account header above the device list, showing the
|
||||
signed-in email or an explicit signed-out state. Its account window does not
|
||||
show device selection or installation actions. Installation records still bind
|
||||
the original account/team to each device and app; authentication semantics have
|
||||
not changed. The header explains in English and Spanish that exporting saves
|
||||
and copying RomFS do not require Apple sign-in. These actions retain their
|
||||
existing pairing, known-game scope and closed-game requirements.
|
||||
|
||||
Validation: compiled Swift client/localization fixtures and whole-app Swift
|
||||
type checking passed. This change is UI-only; the existing native helper is
|
||||
reused byte-for-byte in the local bundle. No real Apple login, signing, device
|
||||
installation or game/save mutation was performed.
|
||||
Reference in new issue
Block a user