Clarify Apple verification with a focused code entry card

This commit is contained in:
Erick Sanchez committed 2026-10-27 14:57:49 -06:00
1 parent fa8e1d471b
commit bf77e3967e
4 files changed
+82 -14

No files matched your search

+10
View File
@@ -12,6 +12,16 @@ enum EasyLanguage: String, CaseIterable {
struct EasyStrings {
static let english: [String:String] = [
"Verifica tu cuenta":"Verify your account",
"Introduce los 6 dígitos que Apple muestra en tu dispositivo de confianza o envía por SMS.":"Enter the 6 digits Apple shows on your trusted device or sends by text message.",
"Puedes pegar el código completo.":"You can paste the entire code.",
"Verificar y continuar":"Verify and continue",
"¿No recibiste el código?":"Did not receive a code?",
"Elige dónde quieres recibir uno nuevo.":"Choose where to receive a new one.",
"Mostrar en mis dispositivos Apple":"Show on my Apple devices",
"Enviar SMS al número terminado en":"Text the number ending in",
"Solicitar otro código":"Request another code",
"El código se envía a Apple y no se guarda.":"The code is sent to Apple and is not saved.",
"Prepara la IPA y te guiamos para instalarla.":"Prepare the IPA, then follow the guided installation.",
"Una app completa. Preparar y jugar en este Mac.":"One complete app. Build and play on this Mac.",
"Conviene recompilar":"Rebuild recommended",
+58 -12
View File
@@ -458,7 +458,6 @@ struct SideloadView:View {
@Environment(\.dismiss) private var dismiss
@State private var installStep=0
@State private var password=""
@State private var code=""
@State private var remember=true
@State private var autoRefresh=true
@State private var loginItem=SMAppService.mainApp.status == .enabled
@@ -487,7 +486,7 @@ struct SideloadView:View {
}
if accountOnly || installStep==1 {
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 {
if !model.authenticated && !model.codeRequired {
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)}
Toggle(text("Recordar sesión en el Llavero; no guardar la contraseña"),isOn:$remember).disabled(model.busy).onChange(of:remember){_,value in if !value {autoRefresh=false}}
@@ -497,7 +496,7 @@ struct SideloadView:View {
}
if model.hasRememberedSession {Button(text("Usar sesión del Llavero")){model.restoreSession()}.disabled(model.busy)}
Button(text("Iniciar sesión")){model.signIn(password:password,remember:remember);password=""}.buttonStyle(.borderedProminent).disabled(model.busy || model.email.isEmpty || password.isEmpty)
} else {
} else if model.authenticated {
Label(text("Sesión iniciada"),systemImage:"person.crop.circle.badge.checkmark")
Text(model.email).font(.caption).textSelection(.enabled)
Picker(text("Tu equipo personal"),selection:$model.team) {Text(text("Selecciona tu equipo")).tag("");ForEach(model.teams){Text($0.name+" · "+$0.id).tag($0.id)}}.disabled(model.busy)
@@ -510,13 +509,8 @@ struct SideloadView:View {
Text(text("Apple no informa los cupos restantes en esta consulta. Mostramos los dispositivos registrados, sin inventar disponibilidad ni eliminar registros.")).font(.caption).foregroundStyle(.secondary)
Button(text("Cerrar sesión")){model.signOut()}.disabled(model.busy)
}
if model.codeRequired {
TextField(text("Código de verificación"),text:$code)
HStack {Button(text("Confirmar código")){model.submitCode(code);code=""};Button(text("Reenviar código")){model.resend()}}
Button(text("Enviar a mis dispositivos")){model.sendToDevices()}
ForEach(model.smsNumbers) {number in Button("SMS · …"+number.digits){model.sendSMS(number.id)}}
}
if !accountOnly {Button(text("Continuar")){installStep=2}.buttonStyle(.borderedProminent).disabled(model.busy || !model.authenticated || model.team.isEmpty)}
if model.codeRequired {AppleVerificationView(model:model,text:text)}
if !accountOnly && !model.codeRequired {Button(text("Continuar")){installStep=2}.buttonStyle(.borderedProminent).disabled(model.busy || !model.authenticated || model.team.isEmpty)}
}
if !accountOnly && installStep==2 {
Label(text("Revisa antes de instalar"),systemImage:"checklist").font(.title2.bold())
@@ -537,8 +531,8 @@ struct SideloadView:View {
}
}
if !accountOnly && installStep==3 && !model.busy {Button(text("Revisar e intentar de nuevo")){installStep=2}}
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 !accountOnly && !model.codeRequired {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 !model.codeRequired {InstallActivityView(model:model,text:text)}
if !model.busy {Text(text(model.phase)).font(.callout)}
}.padding(24)
}
@@ -548,6 +542,58 @@ struct SideloadView:View {
}
struct AppleVerificationView:View {
@ObservedObject var model:SideloadModel
let text:(String)->String
@State private var code=""
@FocusState private var focused:Bool
private var valid:Bool {code.count==6 && code.utf8.allSatisfy {48...57 ~= $0}}
private func confirm() {guard valid else {return};let submitted=code;code="";model.submitCode(submitted)}
var body:some View {
VStack(alignment:.leading,spacing:22) {
HStack(alignment:.top,spacing:14) {
Image(systemName:"lock.shield").font(.system(size:28,weight:.light)).foregroundStyle(Color(red:0.79,green:0.95,blue:0.37)).frame(width:48,height:48).background(.white.opacity(0.04),in:RoundedRectangle(cornerRadius:12))
VStack(alignment:.leading,spacing:6) {
Text(text("Verifica tu cuenta")).font(.title2.bold())
if !model.email.isEmpty {Text(model.email).font(.caption).foregroundStyle(.secondary)}
Text(text("Introduce los 6 dígitos que Apple muestra en tu dispositivo de confianza o envía por SMS.")).font(.callout).foregroundStyle(.secondary).fixedSize(horizontal:false,vertical:true)
}
}
VStack(alignment:.leading,spacing:10) {
Text(text("Código de verificación")).font(.callout.weight(.semibold))
TextField("······",text:$code).textFieldStyle(.plain).font(.system(size:30,weight:.medium,design:.monospaced)).tracking(7)
.padding(.horizontal,18).padding(.vertical,15).frame(width:250)
.background(.black.opacity(0.18),in:RoundedRectangle(cornerRadius:11))
.overlay(RoundedRectangle(cornerRadius:11).strokeBorder(focused ? Color(red:0.79,green:0.95,blue:0.37).opacity(0.65) : .white.opacity(0.12)))
.focused($focused).accessibilityLabel(text("Código de verificación"))
.onChange(of:code){_,value in code=String(value.utf8.filter {48...57 ~= $0}.prefix(6).map {Character(UnicodeScalar($0))})}
.onSubmit {confirm()}
Text(text("Puedes pegar el código completo.")).font(.caption).foregroundStyle(.secondary)
}
Button(action:confirm){Label(text("Verificar y continuar"),systemImage:"arrow.right").font(.callout.bold()).padding(.horizontal,18).frame(minHeight:44)
.foregroundStyle(valid ? Color.black : Color.secondary)
.background(valid ? Color(red:0.79,green:0.95,blue:0.37) : .white.opacity(0.06),in:RoundedRectangle(cornerRadius:10))}
.buttonStyle(.plain).disabled(!valid)
Divider().overlay(.white.opacity(0.06))
DisclosureGroup(text("¿No recibiste el código?")) {
VStack(alignment:.leading,spacing:12) {
Text(text("Elige dónde quieres recibir uno nuevo.")).font(.caption).foregroundStyle(.secondary)
Button {code="";model.sendToDevices()} label:{Label(text("Mostrar en mis dispositivos Apple"),systemImage:"ipad.and.iphone")}
ForEach(model.smsNumbers) {number in
Button {code="";model.sendSMS(number.id)} label:{Label(text("Enviar SMS al número terminado en")+" "+number.digits,systemImage:"message")}
}
Button(text("Solicitar otro código")){code="";model.resend()}.buttonStyle(.link)
}.padding(.top,12)
}.font(.callout)
Text(text("El código se envía a Apple y no se guarda.")).font(.caption).foregroundStyle(.secondary)
}.padding(24).frame(maxWidth:.infinity,alignment:.leading)
.background(.white.opacity(0.035),in:RoundedRectangle(cornerRadius:18))
.overlay(RoundedRectangle(cornerRadius:18).strokeBorder(.white.opacity(0.06)))
.onAppear {focused=true}
}
}
struct SideloadLibraryView:View {
@ObservedObject var model:SideloadModel
let text:(String)->String
+2 -2
View File
@@ -1,5 +1,5 @@
{
"version": "1.13.0",
"build": 20,
"version": "1.13.1",
"build": 21,
"minimumMacOSMajor": 26
}
+12
View File
@@ -307,3 +307,15 @@ app. Support for the new game version still depends on the engine/profile.
A lightweight IPA still needs the complete RomFS on the phone; it makes app
signing/reinstallation smaller, not the game data optional. Full-IPA mode embeds
those data and retransfers the larger app on renewal.
## Apple verification presentation (1.13.1)
An active verification challenge replaces the password form with one card:
account context, six-digit ASCII code input, paste/Enter support and one primary
Verify and continue action. It remains disabled until six digits are entered.
Alternative trusted-device/SMS delivery and resend live under Did not receive a
code, rather than competing with verification. The code is cleared on submission
or requesting another delivery and is never persisted or logged. No password,
provider/session, request transport or delivery behavior changed. Generic busy
activity and the installer Continue button do not compete with a pending code.
The update does not restart a running launcher or interrupt its in-memory login.