Compare commits
11 Commits
765b45c0fc
...
2b80fcba0d
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b80fcba0d | |||
| 4c5763647d | |||
| b3ee1cc6dc | |||
| b7a78aa76a | |||
| 18ec3e36fc | |||
| 8b982d1ba8 | |||
| f71575ee91 | |||
| 3f838d224c | |||
| 9ef53abad5 | |||
| f379a8ee8f | |||
| 54b0cc44ff |
@ -9,6 +9,7 @@
|
||||
"enterEmail": "Please enter email address",
|
||||
"enterCode": "Please enter verification code",
|
||||
"enterPassword": "Please enter password",
|
||||
"enterPasswordForPhone": "Please enter password (Password Login)",
|
||||
"reenterPassword": "Please re-enter password",
|
||||
"forgotPassword": "Forgot Password",
|
||||
"codeLogin": "Code Login",
|
||||
@ -18,6 +19,8 @@
|
||||
"privacyPolicy": "Privacy Policy",
|
||||
"next": "Next",
|
||||
"registerNow": "Register Now",
|
||||
"registerAccount": "Register Account",
|
||||
"clickToRegister": "Click to Register",
|
||||
"setAndLogin": "Set and Login",
|
||||
"enterAccount": "Please enter account",
|
||||
"passwordMismatch": "The two passwords do not match",
|
||||
@ -25,7 +28,9 @@
|
||||
"codeSentCountdown": "Code sent {seconds}s",
|
||||
"and": "and",
|
||||
"enterInviteCode": "Enter invite code (optional)",
|
||||
"registerSuccess": "Registration successful"
|
||||
"registerSuccess": "Registration successful",
|
||||
"search": "Search",
|
||||
"selectOtherRegion": "Select other region"
|
||||
},
|
||||
"failure": {
|
||||
"unexpected": "Unexpected Error",
|
||||
@ -171,7 +176,8 @@
|
||||
"friday": "Fri",
|
||||
"saturday": "Sat",
|
||||
"sunday": "Sun"
|
||||
}
|
||||
},
|
||||
"processTrafficFailed": "Failed to process traffic log data"
|
||||
},
|
||||
"message": {
|
||||
"title": "Notifications",
|
||||
@ -367,6 +373,7 @@
|
||||
"initializing": "Initializing...",
|
||||
"networkConnectionFailure": "Network connection failed, please check and retry",
|
||||
"retry": "Retry",
|
||||
"skip": "Skip",
|
||||
"networkPermissionFailed": "Failed to get network permission",
|
||||
"initializationFailed": "Initialization failed"
|
||||
},
|
||||
@ -467,5 +474,33 @@
|
||||
"open_dashboard": "Open Dashboard",
|
||||
"copy_to_terminal": "Copy to Terminal",
|
||||
"exit_app": "Exit Application"
|
||||
},
|
||||
"crisp": {
|
||||
"initializingSystem": "Initializing customer service system...",
|
||||
"initFailed": "Customer service system initialization failed"
|
||||
},
|
||||
"common": {
|
||||
"retry": "Retry",
|
||||
"tryBrowser": "Try opening with browser",
|
||||
"cannotOpenBrowser": "Cannot open browser",
|
||||
"openLinkFailed": "Failed to open link, please try again later",
|
||||
"cannotOpenTelegram": "Cannot open Telegram link"
|
||||
},
|
||||
"subscribe": {
|
||||
"pleaseSelectFirst": "Please select a subscription first",
|
||||
"resetPeriodFailed": "Failed to reset subscription period",
|
||||
"resetPeriodSuccess": "Subscription period reset successfully"
|
||||
},
|
||||
"purchase": {
|
||||
"noAvailablePlans": "No available plans"
|
||||
},
|
||||
"payment": {
|
||||
"cannotOpenLink": "Cannot open payment link",
|
||||
"linkEmpty": "Payment link is empty",
|
||||
"unsupportedType": "Unsupported payment type"
|
||||
},
|
||||
"account": {
|
||||
"accountRequired": "Account cannot be empty",
|
||||
"deleteSuccess": "Account deleted successfully"
|
||||
}
|
||||
}
|
||||
@ -9,6 +9,7 @@
|
||||
"enterEmail": "Please enter email address",
|
||||
"enterCode": "Por favor, ingresa el código de verificación",
|
||||
"enterPassword": "Por favor, ingresa la contraseña",
|
||||
"enterPasswordForPhone": "Por favor ingrese la contraseña (Inicio de sesión con contraseña)",
|
||||
"reenterPassword": "Por favor, reingresa la contraseña",
|
||||
"forgotPassword": "Olvidé mi contraseña",
|
||||
"codeLogin": "Iniciar sesión con código",
|
||||
@ -18,6 +19,8 @@
|
||||
"privacyPolicy": "Política de privacidad",
|
||||
"next": "Siguiente",
|
||||
"registerNow": "Registrarse ahora",
|
||||
"registerAccount": "Registrar Cuenta",
|
||||
"clickToRegister": "Haga clic para registrarse",
|
||||
"setAndLogin": "Configurar e iniciar sesión",
|
||||
"enterAccount": "Por favor, ingresa la cuenta",
|
||||
"passwordMismatch": "Las dos contraseñas no coinciden",
|
||||
@ -25,7 +28,9 @@
|
||||
"codeSentCountdown": "Código enviado {seconds}s",
|
||||
"and": "y",
|
||||
"enterInviteCode": "Ingresa código de invitación (opcional)",
|
||||
"registerSuccess": "Registro exitoso"
|
||||
"registerSuccess": "Registro exitoso",
|
||||
"search": "Buscar",
|
||||
"selectOtherRegion": "Seleccionar otra región"
|
||||
},
|
||||
"failure": {
|
||||
"unexpected": "Error inesperado",
|
||||
@ -187,7 +192,8 @@
|
||||
"friday": "Vie",
|
||||
"saturday": "Sáb",
|
||||
"sunday": "Dom"
|
||||
}
|
||||
},
|
||||
"processTrafficFailed": "Error al procesar datos de registro de tráfico"
|
||||
},
|
||||
"message": {
|
||||
"title": "Notificaciones",
|
||||
@ -373,6 +379,7 @@
|
||||
"initializing": "Inicializando...",
|
||||
"networkConnectionFailure": "Error de conexión de red, verifique e intente nuevamente",
|
||||
"retry": "Reintentar",
|
||||
"skip": "Saltar",
|
||||
"networkPermissionFailed": "Error al obtener permiso de red",
|
||||
"initializationFailed": "Error de inicialización"
|
||||
},
|
||||
@ -481,5 +488,33 @@
|
||||
"open_dashboard": "Abrir panel",
|
||||
"copy_to_terminal": "Copiar al terminal",
|
||||
"exit_app": "Salir de la aplicación"
|
||||
},
|
||||
"crisp": {
|
||||
"initializingSystem": "Inicializando sistema de atención al cliente...",
|
||||
"initFailed": "Error al inicializar el sistema de atención al cliente"
|
||||
},
|
||||
"common": {
|
||||
"retry": "Reintentar",
|
||||
"tryBrowser": "Intentar abrir con navegador",
|
||||
"cannotOpenBrowser": "No se puede abrir el navegador",
|
||||
"openLinkFailed": "Error al abrir el enlace, inténtelo más tarde",
|
||||
"cannotOpenTelegram": "No se puede abrir el enlace de Telegram"
|
||||
},
|
||||
"subscribe": {
|
||||
"pleaseSelectFirst": "Seleccione primero una suscripción",
|
||||
"resetPeriodFailed": "Error al restablecer el período de suscripción",
|
||||
"resetPeriodSuccess": "Período de suscripción restablecido exitosamente"
|
||||
},
|
||||
"purchase": {
|
||||
"noAvailablePlans": "No hay planes disponibles"
|
||||
},
|
||||
"payment": {
|
||||
"cannotOpenLink": "No se puede abrir el enlace de pago",
|
||||
"linkEmpty": "El enlace de pago está vacío",
|
||||
"unsupportedType": "Tipo de pago no compatible"
|
||||
},
|
||||
"account": {
|
||||
"accountRequired": "La cuenta no puede estar vacía",
|
||||
"deleteSuccess": "Cuenta eliminada exitosamente"
|
||||
}
|
||||
}
|
||||
@ -1,441 +0,0 @@
|
||||
{
|
||||
"login": {
|
||||
"welcome": "¡Bienvenido a Hi快VPN!",
|
||||
"verifyPhone": "Verifica tu número de teléfono",
|
||||
"verifyEmail": "Verifica tu correo electrónico",
|
||||
"codeSent": "Se ha enviado un código de 6 dígitos a {account}. Por favor, ingrésalo en los próximos 30 minutos.",
|
||||
"back": "Atrás",
|
||||
"enterEmailOrPhone": "Ingresa correo o teléfono",
|
||||
"enterCode": "Por favor, ingresa el código de verificación",
|
||||
"enterPassword": "Por favor, ingresa la contraseña",
|
||||
"reenterPassword": "Por favor, reingresa la contraseña",
|
||||
"forgotPassword": "Olvidé mi contraseña",
|
||||
"codeLogin": "Iniciar sesión con código",
|
||||
"passwordLogin": "Iniciar sesión con contraseña",
|
||||
"agreeTerms": "Iniciar sesión/Crear cuenta, acepto",
|
||||
"termsOfService": "Términos de servicio",
|
||||
"privacyPolicy": "Política de privacidad",
|
||||
"next": "Siguiente",
|
||||
"registerNow": "Registrarse ahora",
|
||||
"setAndLogin": "Configurar e iniciar sesión",
|
||||
"enterAccount": "Por favor, ingresa la cuenta",
|
||||
"passwordMismatch": "Las dos contraseñas no coinciden",
|
||||
"sendCode": "Enviar código",
|
||||
"codeSentCountdown": "Código enviado {seconds}s",
|
||||
"and": "y",
|
||||
"enterInviteCode": "Ingresa código de invitación (opcional)",
|
||||
"registerSuccess": "Registro exitoso"
|
||||
},
|
||||
"failure": {
|
||||
"unexpected": "Error inesperado",
|
||||
"clash": {
|
||||
"unexpected": "Error inesperado",
|
||||
"core": "Error de Clash ${reason}"
|
||||
},
|
||||
"singbox": {
|
||||
"unexpected": "Error de servicio inesperado",
|
||||
"serviceNotRunning": "Servicio no en ejecución",
|
||||
"missingPrivilege": "Privilegios insuficientes",
|
||||
"missingPrivilegeMsg": "El modo VPN requiere privilegios de administrador. Reinicia la aplicación como administrador o cambia el modo de servicio",
|
||||
"missingGeoAssets": "Faltan recursos GEO",
|
||||
"missingGeoAssetsMsg": "Faltan archivos de recursos GEO. Considera cambiar los recursos activos o descarga los recursos seleccionados en configuración.",
|
||||
"invalidConfigOptions": "Opciones de configuración inválidas",
|
||||
"invalidConfig": "Configuración inválida",
|
||||
"create": "Error al crear servicio",
|
||||
"start": "Error al iniciar servicio"
|
||||
},
|
||||
"connectivity": {
|
||||
"unexpected": "Fallo inesperado",
|
||||
"missingVpnPermission": "Falta permiso VPN",
|
||||
"missingNotificationPermission": "Falta permiso de notificaciones",
|
||||
"core": "Error del núcleo"
|
||||
},
|
||||
"profiles": {
|
||||
"unexpected": "Error inesperado",
|
||||
"notFound": "Perfil no encontrado",
|
||||
"invalidConfig": "Configuración inválida",
|
||||
"invalidUrl": "URL inválida"
|
||||
},
|
||||
"connection": {
|
||||
"unexpected": "Error de conexión inesperado",
|
||||
"timeout": "Tiempo de conexión agotado",
|
||||
"badResponse": "Respuesta incorrecta",
|
||||
"connectionError": "Error de conexión",
|
||||
"badCertificate": "Certificado inválido"
|
||||
},
|
||||
"geoAssets": {
|
||||
"unexpected": "Error inesperado",
|
||||
"notUpdate": "No hay actualizaciones disponibles",
|
||||
"activeNotFound": "No se encontraron recursos GEO activos"
|
||||
}
|
||||
},
|
||||
"userInfo": {
|
||||
"title": "Mi información",
|
||||
"bindingTip": "Correo/teléfono no vinculado",
|
||||
"myAccount": "Mi cuenta",
|
||||
"balance": "Saldo",
|
||||
"noValidSubscription": "No tiene una suscripción válida",
|
||||
"subscribeNow": "Suscribirse ahora",
|
||||
"shortcuts": "Accesos directos",
|
||||
"adBlock": "Bloqueo de anuncios",
|
||||
"dnsUnlock": "Desbloqueo DNS",
|
||||
"contactUs": "Contáctanos",
|
||||
"others": "Otros",
|
||||
"logout": "Cerrar sesión",
|
||||
"logoutConfirmTitle": "Cerrar sesión",
|
||||
"logoutConfirmMessage": "¿Estás seguro de que quieres cerrar sesión?",
|
||||
"logoutCancel": "Cancelar",
|
||||
"vpnWebsite": "Sitio web VPN",
|
||||
"telegram": "Telegram",
|
||||
"mail": "Correo",
|
||||
"phone": "Teléfono",
|
||||
"customerService": "Servicio al cliente",
|
||||
"workOrder": "Enviar ticket",
|
||||
"pleaseLogin": "Por favor, inicia sesión primero",
|
||||
"subscriptionValid": "Suscripción válida",
|
||||
"startTime": "Fecha de inicio:",
|
||||
"expireTime": "Fecha de vencimiento:",
|
||||
"loginNow": "Iniciar sesión ahora",
|
||||
"trialPeriod": "Bienvenido a la prueba Premium",
|
||||
"remainingTime": "Tiempo restante",
|
||||
"trialExpired": "Prueba expirada, conexión desconectada",
|
||||
"subscriptionExpired": "Suscripción expirada, conexión desconectada",
|
||||
"copySuccess": "Copiado con éxito",
|
||||
"notAvailable": "No disponible",
|
||||
"deviceLimit": "Límite de dispositivos: {count}",
|
||||
"reset": "Restablecer",
|
||||
"trafficUsage": "Usado: {used} / {total}",
|
||||
"trafficProgress": {
|
||||
"title": "Uso de tráfico",
|
||||
"unlimited": "Tráfico ilimitado",
|
||||
"limited": "Tráfico usado"
|
||||
},
|
||||
"switchSubscription": "Cambiar Suscripción",
|
||||
"resetTrafficTitle": "Restablecer Tráfico",
|
||||
"resetTrafficMessage": "Ejemplo de restablecimiento de tráfico del plan mensual: restablecer el tráfico del siguiente ciclo mensualmente, y el período de validez de la suscripción se adelantará de {currentTime} a {newTime}",
|
||||
"trialStatus": "Estado de prueba",
|
||||
"trialing": "En período de prueba",
|
||||
"trialEndMessage": "No podrá continuar usando después de que expire el período de prueba",
|
||||
"lastDaySubscriptionStatus": "Suscripción por expirar",
|
||||
"lastDaySubscriptionMessage": "Por expirar",
|
||||
"subscriptionEndMessage": "No podrá continuar usando después de que expire la suscripción",
|
||||
"trialTimeWithDays": "{days}d {hours}h {minutes}m {seconds}s",
|
||||
"trialTimeWithHours": "{hours}h {minutes}m {seconds}s",
|
||||
"trialTimeWithMinutes": "{minutes}m {seconds}s",
|
||||
"refreshLatency": "Actualizar latencia",
|
||||
"testLatency": "Probar latencia",
|
||||
"testing": "Probando latencia",
|
||||
"refreshLatencyDesc": "Actualizar latencia de todos los nodos",
|
||||
"testAllNodesLatency": "Probar la latencia de red de todos los nodos",
|
||||
"autoSelect": "Selección automática",
|
||||
"selected": "Seleccionado",
|
||||
"willBeDeleted": "será eliminado",
|
||||
"deleteAccountWarning": "La eliminación de la cuenta es permanente. Una vez que se elimine su cuenta, no podrá utilizar ninguna función. ¿Continuar?",
|
||||
"requestDelete": "Solicitar eliminación"
|
||||
},
|
||||
"setting": {
|
||||
"title": "Configuración",
|
||||
"vpnConnection": "Conexión VPN",
|
||||
"general": "General",
|
||||
"autoConnect": "Conexión automática",
|
||||
"routeRule": "Reglas de ruta",
|
||||
"countrySelector": "Seleccionar país",
|
||||
"appearance": "Apariencia",
|
||||
"notifications": "Notificaciones",
|
||||
"helpImprove": "Ayúdanos a mejorar",
|
||||
"helpImproveSubtitle": "Subtítulo de ayuda para mejorar",
|
||||
"requestDeleteAccount": "Solicitar eliminación de cuenta",
|
||||
"goToDelete": "Ir a eliminar",
|
||||
"rateUs": "Califícanos en App Store",
|
||||
"iosRating": "Calificación iOS",
|
||||
"version": "Versión",
|
||||
"switchLanguage": "Cambiar idioma",
|
||||
"system": "Sistema",
|
||||
"light": "Claro",
|
||||
"dark": "Oscuro",
|
||||
"vpnModeSmart": "Modo inteligente",
|
||||
"mode": "Modo de salida",
|
||||
"connectionTypeGlobal": "Proxy global",
|
||||
"connectionTypeGlobalRemark": "Cuando está activado, todo el tráfico pasa por el proxy",
|
||||
"connectionTypeRule": "Proxy inteligente",
|
||||
"connectionTypeRuleRemark": "Cuando el [Modo de salida] está configurado en [Proxy inteligente], el sistema dividirá automáticamente el tráfico nacional e internacional según el país seleccionado: las IPs/dominios nacionales se conectan directamente, mientras que las solicitudes extranjeras se acceden a través del proxy",
|
||||
"connectionTypeDirect": "Conexión directa",
|
||||
"connectionTypeDirectRemark": "Cuando está activado, todo el tráfico evita el proxy",
|
||||
"smartMode": "Modo inteligente",
|
||||
"secureMode": "Modo seguro"
|
||||
},
|
||||
"statistics": {
|
||||
"title": "Estadísticas",
|
||||
"vpnStatus": "Estado VPN",
|
||||
"ipAddress": "Dirección IP",
|
||||
"connectionTime": "Tiempo de conexión",
|
||||
"protocol": "Protocolo",
|
||||
"weeklyProtectionTime": "Tiempo de protección semanal",
|
||||
"currentStreak": "Racha actual",
|
||||
"highestStreak": "Mejor racha",
|
||||
"longestConnection": "Conexión más larga",
|
||||
"days": "{days} días",
|
||||
"daysOfWeek": {
|
||||
"monday": "Lun",
|
||||
"tuesday": "Mar",
|
||||
"wednesday": "Mié",
|
||||
"thursday": "Jue",
|
||||
"friday": "Vie",
|
||||
"saturday": "Sáb",
|
||||
"sunday": "Dom"
|
||||
}
|
||||
},
|
||||
"message": {
|
||||
"title": "Notificaciones",
|
||||
"system": "Mensajes del sistema",
|
||||
"promotion": "Mensajes promocionales"
|
||||
},
|
||||
"home": {
|
||||
"welcome": "Bienvenido a Hi快VPN",
|
||||
"disconnected": "Desconectado",
|
||||
"connecting": "Conectando",
|
||||
"connected": "Conectado",
|
||||
"disconnecting": "Desconectando",
|
||||
"currentConnectionTitle": "Conexión actual",
|
||||
"switchNode": "Cambiar nodo",
|
||||
"timeout": "Tiempo de espera agotado",
|
||||
"loading": "Cargando...",
|
||||
"error": "Error de carga",
|
||||
"checkNetwork": "Verifique su conexión de red e intente nuevamente",
|
||||
"retry": "Reintentar",
|
||||
"connectionSectionTitle": "Método de conexión",
|
||||
"dedicatedServers": "Servidores dedicados",
|
||||
"countryRegion": "País/Región",
|
||||
"serverListTitle": "Grupos de servidores dedicados",
|
||||
"nodeListTitle": "Todos los nodos",
|
||||
"countryListTitle": "Lista de países/regiones",
|
||||
"noServers": "No hay servidores disponibles",
|
||||
"noNodes": "No hay nodos disponibles",
|
||||
"noRegions": "No hay regiones disponibles",
|
||||
"subscriptionDescription": "Obtenga acceso premium a la red global de alta velocidad",
|
||||
"subscribe": "Suscribirse",
|
||||
"trialPeriod": "Bienvenido a la versión de prueba Premium",
|
||||
"remainingTime": "Tiempo restante",
|
||||
"trialExpired": "Período de prueba expirado, conexión terminada",
|
||||
"subscriptionExpired": "Suscripción expirada, conexión terminada",
|
||||
"subscriptionUpdated": "Suscripción actualizada",
|
||||
"subscriptionUpdatedMessage": "Su información de suscripción ha sido actualizada, actualice para ver el último estado",
|
||||
"trialStatus": "Estado de prueba",
|
||||
"trialing": "En período de prueba",
|
||||
"trialEndMessage": "No podrá continuar usando después de que expire el período de prueba",
|
||||
"lastDaySubscriptionStatus": "Suscripción por expirar",
|
||||
"lastDaySubscriptionMessage": "Por expirar",
|
||||
"subscriptionEndMessage": "No podrá continuar usando después de que expire la suscripción",
|
||||
"trialTimeWithDays": "{days}d {hours}h {minutes}m {seconds}s",
|
||||
"trialTimeWithHours": "{hours}h {minutes}m {seconds}s",
|
||||
"trialTimeWithMinutes": "{minutes}m {seconds}s",
|
||||
"refreshLatency": "Actualizar latencia",
|
||||
"testLatency": "Probar latencia",
|
||||
"testing": "Probando latencia",
|
||||
"refreshLatencyDesc": "Actualizar latencia de todos los nodos",
|
||||
"testAllNodesLatency": "Probar la latencia de red de todos los nodos",
|
||||
"autoSelect": "Selección automática",
|
||||
"selected": "Seleccionado"
|
||||
},
|
||||
"invite": {
|
||||
"title": "Invitar amigos",
|
||||
"progress": "Progreso de invitación",
|
||||
"inviteStats": "Estadísticas de invitación",
|
||||
"registers": "Registrados",
|
||||
"totalCommission": "Comisión total",
|
||||
"rewardDetails": "Detalles de recompensa >",
|
||||
"steps": "Pasos de invitación",
|
||||
"inviteFriend": "Invitar amigo",
|
||||
"acceptInvite": "El amigo acepta la invitación\ny se registra",
|
||||
"getReward": "Obtener recompensa",
|
||||
"shareLink": "Compartir por enlace",
|
||||
"shareQR": "Compartir por código QR",
|
||||
"rules": "Reglas de invitación",
|
||||
"rule1": "1. Puedes invitar amigos compartiendo tu enlace o código de invitación exclusivo.",
|
||||
"rule2": "2. Después de que tu amigo complete el registro e inicie sesión, la recompensa por invitación se enviará automáticamente a tu cuenta.",
|
||||
"pending": "Pendiente de descarga",
|
||||
"processing": "En proceso",
|
||||
"success": "Exitoso",
|
||||
"expired": "Expirado",
|
||||
"myInviteCode": "Mi código de invitación",
|
||||
"inviteCodeCopied": "Código de invitación copiado al portapapeles"
|
||||
},
|
||||
"purchaseMembership": {
|
||||
"purchasePackage": "Comprar Paquete",
|
||||
"noData": "No hay paquetes disponibles",
|
||||
"myAccount": "Mi Cuenta",
|
||||
"selectPackage": "Seleccionar Paquete",
|
||||
"packageDescription": "Descripción del Paquete",
|
||||
"paymentMethod": "Método de Pago",
|
||||
"cancelAnytime": "Puedes cancelar en cualquier momento en la APP",
|
||||
"startSubscription": "Comenzar Suscripción",
|
||||
"renewNow": "Renovar Ahora",
|
||||
"month": "{quantity} meses",
|
||||
"year": "{quantity} años",
|
||||
"day": "{quantity} días",
|
||||
"unlimitedTraffic": "Tráfico Ilimitado",
|
||||
"unlimitedDevices": "Dispositivos Ilimitados",
|
||||
"devices": "{count} dispositivos",
|
||||
"features": "Características del Paquete",
|
||||
"expand": "Expandir",
|
||||
"collapse": "Colapsar",
|
||||
"confirmPurchase": "Confirmar Compra",
|
||||
"confirmPurchaseDesc": "¿Está seguro de que desea comprar este paquete?"
|
||||
},
|
||||
"orderStatus": {
|
||||
"title": "Estado del Pedido",
|
||||
"pending": {
|
||||
"title": "Pago Pendiente",
|
||||
"description": "Por favor complete el pago"
|
||||
},
|
||||
"paid": {
|
||||
"title": "Pago Recibido",
|
||||
"description": "Procesando su pedido"
|
||||
},
|
||||
"success": {
|
||||
"title": "¡Felicitaciones! Pago Exitoso",
|
||||
"description": "Su paquete ha sido comprado exitosamente"
|
||||
},
|
||||
"closed": {
|
||||
"title": "Pedido Cerrado",
|
||||
"description": "Por favor realice un nuevo pedido"
|
||||
},
|
||||
"failed": {
|
||||
"title": "Pago Fallido",
|
||||
"description": "Por favor intente el pago nuevamente"
|
||||
},
|
||||
"unknown": {
|
||||
"title": "Estado Desconocido",
|
||||
"description": "Por favor contacte al servicio al cliente"
|
||||
},
|
||||
"checkFailed": {
|
||||
"title": "Verificación Fallida",
|
||||
"description": "Por favor intente nuevamente más tarde"
|
||||
},
|
||||
"initial": {
|
||||
"title": "Procesando Pago",
|
||||
"description": "Por favor espere mientras procesamos su pago"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"confirm": "Confirmar",
|
||||
"cancel": "Cancelar",
|
||||
"ok": "OK",
|
||||
"iKnow": "Lo entiendo"
|
||||
},
|
||||
"splash": {
|
||||
"appName": "Hi快VPN",
|
||||
"slogan": "Red global de alta velocidad",
|
||||
"initializing": "Inicializando...",
|
||||
"networkConnectionFailure": "Error de conexión de red, verifique e intente nuevamente",
|
||||
"retry": "Reintentar",
|
||||
"networkPermissionFailed": "Error al obtener permiso de red",
|
||||
"initializationFailed": "Error de inicialización"
|
||||
},
|
||||
"network": {
|
||||
"status": {
|
||||
"connected": "Conectado",
|
||||
"disconnected": "Desconectado",
|
||||
"connecting": "Conectando...",
|
||||
"disconnecting": "Desconectando...",
|
||||
"reconnecting": "Reconectando...",
|
||||
"failed": "Error de conexión"
|
||||
},
|
||||
"permission": {
|
||||
"title": "Permiso de red",
|
||||
"description": "Se requiere permiso de red para proporcionar el servicio VPN",
|
||||
"goToSettings": "Ir a configuración",
|
||||
"cancel": "Cancelar"
|
||||
}
|
||||
},
|
||||
"update": {
|
||||
"title": "Actualización disponible",
|
||||
"content": "¿Actualizar ahora?",
|
||||
"updateNow": "Actualizar ahora",
|
||||
"updateLater": "Más tarde",
|
||||
"defaultContent": "1. Optimización del rendimiento de la aplicación\n2. Corrección de problemas conocidos\n3. Mejora de la experiencia del usuario"
|
||||
},
|
||||
"kr_invite": {
|
||||
"close": "Cerrar",
|
||||
"saveQRCode": "Guardar código QR",
|
||||
"qrCodeSaved": "Código QR guardado",
|
||||
"shareLink": "Compartir enlace",
|
||||
"shareQR": "Compartir código QR",
|
||||
"myInviteCode": "Mi código de invitación"
|
||||
},
|
||||
"country": {
|
||||
"cn": "China",
|
||||
"ir": "Irán",
|
||||
"af": "Afganistán",
|
||||
"ru": "Rusia",
|
||||
"id": "Indonesia",
|
||||
"tr": "Turquía",
|
||||
"br": "Brasil"
|
||||
},
|
||||
"error": {
|
||||
"200": "Éxito",
|
||||
"500": "Error interno del servidor",
|
||||
"10001": "Error de consulta a la base de datos",
|
||||
"10002": "Error de actualización de la base de datos",
|
||||
"10003": "Error de inserción en la base de datos",
|
||||
"10004": "Error de eliminación de la base de datos",
|
||||
"20001": "El usuario ya existe",
|
||||
"20002": "El usuario no existe",
|
||||
"20003": "Contraseña de usuario incorrecta",
|
||||
"20004": "Usuario deshabilitado",
|
||||
"20005": "Saldo insuficiente",
|
||||
"20006": "Registro detenido",
|
||||
"20007": "Telegram no vinculado",
|
||||
"20008": "Usuario no ha vinculado OAuth",
|
||||
"20009": "Código de invitación incorrecto",
|
||||
"30001": "El nodo ya existe",
|
||||
"30002": "El nodo no existe",
|
||||
"30003": "El grupo de nodos ya existe",
|
||||
"30004": "El grupo de nodos no existe",
|
||||
"30005": "El grupo de nodos no está vacío",
|
||||
"400": "Error de parámetros",
|
||||
"40002": "Token de usuario vacío",
|
||||
"40003": "Token de usuario inválido",
|
||||
"40004": "Token de usuario expirado",
|
||||
"40005": "No ha iniciado sesión",
|
||||
"401": "Demasiadas solicitudes",
|
||||
"50001": "El cupón no existe",
|
||||
"50002": "El cupón ya ha sido usado",
|
||||
"50003": "El cupón no coincide",
|
||||
"60001": "Suscripción expirada",
|
||||
"60002": "Suscripción no disponible",
|
||||
"60003": "El usuario ya tiene una suscripción",
|
||||
"60004": "La suscripción ya ha sido usada",
|
||||
"60005": "Límite de suscripción única excedido",
|
||||
"60006": "Límite de cuota de suscripción",
|
||||
"70001": "Código de verificación incorrecto",
|
||||
"80001": "Error al encolar",
|
||||
"90001": "Modo de depuración habilitado",
|
||||
"90002": "Error al enviar SMS",
|
||||
"90003": "Función SMS no habilitada",
|
||||
"90004": "Función de correo electrónico no habilitada",
|
||||
"90005": "Método de inicio de sesión no soportado",
|
||||
"90006": "El autenticador no soporta este método",
|
||||
"90007": "Código de país de teléfono vacío",
|
||||
"90008": "Contraseña vacía",
|
||||
"90009": "Código de país vacío",
|
||||
"90010": "Se requiere contraseña o código de verificación",
|
||||
"90011": "El correo electrónico ya existe",
|
||||
"90012": "El número de teléfono ya existe",
|
||||
"90013": "El dispositivo ya existe",
|
||||
"90014": "Número de teléfono incorrecto",
|
||||
"90015": "Este cuenta ha alcanzado el límite de envío hoy",
|
||||
"90017": "El dispositivo no existe",
|
||||
"90018": "ID de usuario no coincide",
|
||||
"61001": "El pedido no existe",
|
||||
"61002": "Método de pago no encontrado",
|
||||
"61003": "Estado de pedido incorrecto",
|
||||
"61004": "Período de reinicio insuficiente",
|
||||
"61005": "Existe tráfico sin usar"
|
||||
},
|
||||
"tray": {
|
||||
"open_dashboard": "Abrir panel",
|
||||
"copy_to_terminal": "Copiar al terminal",
|
||||
"exit_app": "Salir de la aplicación"
|
||||
}
|
||||
}
|
||||
@ -9,6 +9,7 @@
|
||||
"enterEmail": "Please enter email address",
|
||||
"enterCode": "Palun sisesta kinnituskood",
|
||||
"enterPassword": "Palun sisesta parool",
|
||||
"enterPasswordForPhone": "Palun sisestage parool (Parooli sisselogimine)",
|
||||
"reenterPassword": "Palun sisesta parool uuesti",
|
||||
"forgotPassword": "Unustasid parooli",
|
||||
"codeLogin": "Koodiga sisselogimine",
|
||||
@ -18,6 +19,8 @@
|
||||
"privacyPolicy": "Privaatsuspoliitikaga",
|
||||
"next": "Edasi",
|
||||
"registerNow": "Registreeru kohe",
|
||||
"registerAccount": "Registreeri konto",
|
||||
"clickToRegister": "Klõpsa registreerimiseks",
|
||||
"setAndLogin": "Seadista ja logi sisse",
|
||||
"enterAccount": "Palun sisesta konto",
|
||||
"passwordMismatch": "Kaks sisestatud parooli ei ühti",
|
||||
@ -25,7 +28,9 @@
|
||||
"codeSentCountdown": "Kood saadetud {seconds}s",
|
||||
"and": "ja",
|
||||
"enterInviteCode": "Sisesta kutse kood (valikuline)",
|
||||
"registerSuccess": "Registreerimine õnnestus"
|
||||
"registerSuccess": "Registreerimine õnnestus",
|
||||
"search": "Otsing",
|
||||
"selectOtherRegion": "Valige teine piirkond"
|
||||
},
|
||||
"failure": {
|
||||
"unexpected": "Ootamatu viga",
|
||||
@ -171,7 +176,8 @@
|
||||
"friday": "R",
|
||||
"saturday": "L",
|
||||
"sunday": "P"
|
||||
}
|
||||
},
|
||||
"processTrafficFailed": "Liikluslogiandmete töötlemine ebaõnnestus"
|
||||
},
|
||||
"message": {
|
||||
"title": "Teavitused",
|
||||
@ -364,6 +370,7 @@
|
||||
"initializing": "Initsialiseerimine...",
|
||||
"networkConnectionFailure": "Võrguühenduse viga, kontrollige ja proovige uuesti",
|
||||
"retry": "Proovi uuesti",
|
||||
"skip": "Jäta vahele",
|
||||
"networkPermissionFailed": "Võrguõiguse hankimine ebaõnnestus",
|
||||
"initializationFailed": "Initsialiseerimine ebaõnnestus"
|
||||
},
|
||||
@ -464,5 +471,33 @@
|
||||
"open_dashboard": "Ava armatuurlaud",
|
||||
"copy_to_terminal": "Kopeeri terminali",
|
||||
"exit_app": "Välju rakendusest"
|
||||
},
|
||||
"crisp": {
|
||||
"initializingSystem": "Klienditeeninduse süsteemi initsialiseerimine...",
|
||||
"initFailed": "Klienditeeninduse süsteemi initsialiseerimine ebaõnnestus"
|
||||
},
|
||||
"common": {
|
||||
"retry": "Proovi uuesti",
|
||||
"tryBrowser": "Proovi brauseriga avada",
|
||||
"cannotOpenBrowser": "Ei saa brauserit avada",
|
||||
"openLinkFailed": "Lingi avamine ebaõnnestus, proovige hiljem uuesti",
|
||||
"cannotOpenTelegram": "Ei saa Telegrami linki avada"
|
||||
},
|
||||
"subscribe": {
|
||||
"pleaseSelectFirst": "Palun valige esmalt tellimus",
|
||||
"resetPeriodFailed": "Tellimisperioodi lähtestamine ebaõnnestus",
|
||||
"resetPeriodSuccess": "Tellimisperiood edukalt lähtestatud"
|
||||
},
|
||||
"purchase": {
|
||||
"noAvailablePlans": "Saadaolevad plaanid puuduvad"
|
||||
},
|
||||
"payment": {
|
||||
"cannotOpenLink": "Ei saa makselinki avada",
|
||||
"linkEmpty": "Makselink on tühi",
|
||||
"unsupportedType": "Toetamata maksetüüp"
|
||||
},
|
||||
"account": {
|
||||
"accountRequired": "Konto ei saa olla tühi",
|
||||
"deleteSuccess": "Konto edukalt kustutatud"
|
||||
}
|
||||
}
|
||||
@ -1,423 +0,0 @@
|
||||
{
|
||||
"login": {
|
||||
"welcome": "Tere tulemast Hi快VPN-i!",
|
||||
"verifyPhone": "Kinnita oma telefoninumber",
|
||||
"verifyEmail": "Kinnita oma e-post",
|
||||
"codeSent": "6-kohaline kood on saadetud aadressile {account}. Palun sisesta see 30 minuti jooksul.",
|
||||
"back": "Tagasi",
|
||||
"enterEmailOrPhone": "Sisesta e-post või telefoninumber",
|
||||
"enterCode": "Palun sisesta kinnituskood",
|
||||
"enterPassword": "Palun sisesta parool",
|
||||
"reenterPassword": "Palun sisesta parool uuesti",
|
||||
"forgotPassword": "Unustasid parooli",
|
||||
"codeLogin": "Koodiga sisselogimine",
|
||||
"passwordLogin": "Parooliga sisselogimine",
|
||||
"agreeTerms": "Logi sisse/Loo konto, nõustun",
|
||||
"termsOfService": "Teenusetingimustega",
|
||||
"privacyPolicy": "Privaatsuspoliitikaga",
|
||||
"next": "Edasi",
|
||||
"registerNow": "Registreeru kohe",
|
||||
"setAndLogin": "Seadista ja logi sisse",
|
||||
"enterAccount": "Palun sisesta konto",
|
||||
"passwordMismatch": "Kaks sisestatud parooli ei ühti",
|
||||
"sendCode": "Saada kood",
|
||||
"codeSentCountdown": "Kood saadetud {seconds}s",
|
||||
"and": "ja",
|
||||
"enterInviteCode": "Sisesta kutse kood (valikuline)",
|
||||
"registerSuccess": "Registreerimine õnnestus"
|
||||
},
|
||||
"failure": {
|
||||
"unexpected": "Ootamatu viga",
|
||||
"clash": {
|
||||
"unexpected": "Ootamatu viga",
|
||||
"core": "Clash viga ${reason}"
|
||||
},
|
||||
"singbox": {
|
||||
"unexpected": "Ootamatu teenuse viga",
|
||||
"serviceNotRunning": "Teenus ei tööta",
|
||||
"missingPrivilege": "Puuduvad õigused",
|
||||
"missingPrivilegeMsg": "VPN režiim vajab administraatori õigusi. Taaskäivitage rakendus administraatorina või muutke teenuse režiimi",
|
||||
"missingGeoAssets": "Puuduvad GEO ressursid",
|
||||
"missingGeoAssetsMsg": "Puuduvad GEO ressursifailid. Kaaluge aktiivsete ressursside muutmist või laadige valitud ressursid seadetest alla.",
|
||||
"invalidConfigOptions": "Kehtetud seadistuse valikud",
|
||||
"invalidConfig": "Kehtetu seadistus",
|
||||
"create": "Teenuse loomise viga",
|
||||
"start": "Teenuse käivitamise viga"
|
||||
},
|
||||
"connectivity": {
|
||||
"unexpected": "Ootamatu tõrge",
|
||||
"missingVpnPermission": "Puudub VPN luba",
|
||||
"missingNotificationPermission": "Puudub teavituste luba",
|
||||
"core": "Tuuma viga"
|
||||
},
|
||||
"profiles": {
|
||||
"unexpected": "Ootamatu viga",
|
||||
"notFound": "Profiili ei leitud",
|
||||
"invalidConfig": "Kehtetu seadistus",
|
||||
"invalidUrl": "Kehtetu URL"
|
||||
},
|
||||
"connection": {
|
||||
"unexpected": "Ootamatu ühenduse viga",
|
||||
"timeout": "Ühenduse ajalõpp",
|
||||
"badResponse": "Halb vastus",
|
||||
"connectionError": "Ühenduse viga",
|
||||
"badCertificate": "Kehtetu sertifikaat"
|
||||
},
|
||||
"geoAssets": {
|
||||
"unexpected": "Ootamatu viga",
|
||||
"notUpdate": "Uuendusi pole saadaval",
|
||||
"activeNotFound": "Aktiivseid GEO ressursse ei leitud"
|
||||
}
|
||||
},
|
||||
"userInfo": {
|
||||
"title": "Minu info",
|
||||
"bindingTip": "E-post/telefon sidumata",
|
||||
"myAccount": "Minu konto",
|
||||
"balance": "Jääk",
|
||||
"noValidSubscription": "Teil pole kehtivat tellimust",
|
||||
"subscribeNow": "Telli kohe",
|
||||
"shortcuts": "Otseteed",
|
||||
"adBlock": "Reklaami blokeerimine",
|
||||
"dnsUnlock": "DNS avamine",
|
||||
"contactUs": "Võta meiega ühendust",
|
||||
"others": "Muu",
|
||||
"logout": "Logi välja",
|
||||
"logoutConfirmTitle": "Logi välja",
|
||||
"logoutConfirmMessage": "Kas oled kindel, et soovid välja logida?",
|
||||
"logoutCancel": "Tühista",
|
||||
"vpnWebsite": "VPN veebileht",
|
||||
"telegram": "Telegram",
|
||||
"mail": "E-post",
|
||||
"phone": "Telefon",
|
||||
"customerService": "Klienditugi",
|
||||
"workOrder": "Esita taotlus",
|
||||
"pleaseLogin": "Palun logi esmalt sisse",
|
||||
"subscriptionValid": "Tellimus kehtiv",
|
||||
"startTime": "Algusaeg:",
|
||||
"expireTime": "Aegumisaeg:",
|
||||
"loginNow": "Logi kohe sisse",
|
||||
"trialPeriod": "Tere tulemast Premium prooviperioodi",
|
||||
"remainingTime": "Järelejäänud aeg",
|
||||
"trialExpired": "Prooviperiood on lõppenud, ühendus katkestatud",
|
||||
"subscriptionExpired": "Tellimus on aegunud, ühendus katkestatud",
|
||||
"switchSubscription": "Vaheta tellimust",
|
||||
"resetTrafficTitle": "Lähtesta liiklus",
|
||||
"resetTrafficMessage": "Kuupaketi liikluse lähtestamise näide: lähtesta järgmise tsükli liiklus igakuiselt ja tellimuse kehtivusaeg edasi lükatakse {currentTime} kuni {newTime}",
|
||||
"reset": "Lähtesta",
|
||||
"deviceLimit": "Seadmete limiit: {count}",
|
||||
"trafficUsage": "Kasutatud: {used} / {total}",
|
||||
"trafficProgress": {
|
||||
"title": "Liikluse kasutamine",
|
||||
"unlimited": "Piiramatu liiklus",
|
||||
"limited": "Kasutatud liiklus"
|
||||
},
|
||||
"copySuccess": "Kopeeritud",
|
||||
"notAvailable": "Pole saadaval",
|
||||
"willBeDeleted": "kustutatakse",
|
||||
"deleteAccountWarning": "Konto kustutamine on püsiv. Kui teie konto on kustutatud, ei saa te enam ühtegi funktsiooni kasutada. Jätkata?",
|
||||
"requestDelete": "Taotle kustutamist"
|
||||
},
|
||||
"setting": {
|
||||
"title": "Seaded",
|
||||
"vpnConnection": "VPN ühendus",
|
||||
"general": "Üldine",
|
||||
"autoConnect": "Automaatne ühendus",
|
||||
"routeRule": "Marsruudi reeglid",
|
||||
"countrySelector": "Vali riik",
|
||||
"appearance": "Välimus",
|
||||
"notifications": "Teavitused",
|
||||
"helpImprove": "Aita meil paremaks muutuda",
|
||||
"helpImproveSubtitle": "Aita meil paremaks muutuda alapealkiri",
|
||||
"requestDeleteAccount": "Taotle konto kustutamist",
|
||||
"goToDelete": "Mine kustutama",
|
||||
"rateUs": "Hinda meid App Store'is",
|
||||
"iosRating": "iOS hinnang",
|
||||
"version": "Versioon",
|
||||
"switchLanguage": "Vaheta keelt",
|
||||
"system": "Süsteem",
|
||||
"light": "Hele",
|
||||
"dark": "Tume",
|
||||
"vpnModeSmart": "Nutikas režiim",
|
||||
"mode": "Väljundrežiim",
|
||||
"connectionTypeGlobal": "Globaalne puhverserver",
|
||||
"connectionTypeGlobalRemark": "Lubamisel suunatakse kogu liiklus puhverserveri kaudu",
|
||||
"connectionTypeRule": "Nutikas puhverserver",
|
||||
"connectionTypeRuleRemark": "Lubamisel, kui väljundrežiim on seatud nutikaks puhverserveriks, valitud riigi põhjal, jaotatakse liiklus automaatselt: kohalikud IP-d/domeenid ühenduvad otse, välismaised päringud suunatakse puhverserverisse",
|
||||
"connectionTypeDirect": "Otsene ühendus",
|
||||
"connectionTypeDirectRemark": "Lubamisel suunatakse kogu liiklus otse",
|
||||
"smartMode": "Nutikas režiim",
|
||||
"secureMode": "Turvaline režiim"
|
||||
},
|
||||
"statistics": {
|
||||
"title": "Statistika",
|
||||
"vpnStatus": "VPN olek",
|
||||
"ipAddress": "IP aadress",
|
||||
"connectionTime": "Ühenduse aeg",
|
||||
"protocol": "Protokoll",
|
||||
"weeklyProtectionTime": "Iganädalane kaitseaeg",
|
||||
"currentStreak": "Praegune seeria",
|
||||
"highestStreak": "Parim seeria",
|
||||
"longestConnection": "Pikim ühendus",
|
||||
"days": "{days} päeva",
|
||||
"daysOfWeek": {
|
||||
"monday": "E",
|
||||
"tuesday": "T",
|
||||
"wednesday": "K",
|
||||
"thursday": "N",
|
||||
"friday": "R",
|
||||
"saturday": "L",
|
||||
"sunday": "P"
|
||||
}
|
||||
},
|
||||
"message": {
|
||||
"title": "Teavitused",
|
||||
"system": "Süsteemi sõnumid",
|
||||
"promotion": "Kampaania sõnumid"
|
||||
},
|
||||
"invite": {
|
||||
"title": "Kutsu sõbrad",
|
||||
"progress": "Kutse edenemine",
|
||||
"inviteStats": "Kutse statistika",
|
||||
"registers": "Registreeritud",
|
||||
"totalCommission": "Kogu komisjonitasu",
|
||||
"rewardDetails": "Tasu üksikasjad >",
|
||||
"steps": "Kutse Sammud",
|
||||
"inviteFriend": "Kutsu Sõbrad",
|
||||
"acceptInvite": "Sõbrad aktsepteerivad kutset\nTee tellimus ja registreeru",
|
||||
"getReward": "Saada Tasu",
|
||||
"shareLink": "Jaga Linki",
|
||||
"shareQR": "Jaga QR-koodi",
|
||||
"rules": "Kutse Reeglid",
|
||||
"rule1": "1. Saad kutsuda sõpru meiega liituma, jagades oma erilist kutselinki või kutsekoodi.",
|
||||
"rule2": "2. Pärast seda, kui sõbrad on registreerunud ja sisse loginud, krediteeritakse kutsetasud automaatselt teie kontole.",
|
||||
"pending": "Ootel",
|
||||
"processing": "Töötlemisel",
|
||||
"success": "Õnnestus",
|
||||
"expired": "Aegunud",
|
||||
"myInviteCode": "Minu Kutsekood",
|
||||
"inviteCodeCopied": "Kutsekood kopeeritud lõikelauale",
|
||||
"close": "Sulge",
|
||||
"saveQRCode": "Salvesta QR-kood",
|
||||
"qrCodeSaved": "QR-kood salvestatud",
|
||||
"copiedToClipboard": "Kopeeritud lõikelauale",
|
||||
"getInviteCodeFailed": "Kutsekoodi hankimine ebaõnnestus, proovi hiljem uuesti",
|
||||
"generateQRCodeFailed": "QR-koodi genereerimine ebaõnnestus, proovi hiljem uuesti",
|
||||
"generateShareLinkFailed": "Jagamislinki genereerimine ebaõnnestus, proovi hiljem uuesti"
|
||||
},
|
||||
"purchaseMembership": {
|
||||
"purchasePackage": "Paketi Ostmine",
|
||||
"noData": "Saadaval pakette pole",
|
||||
"myAccount": "Minu Konto",
|
||||
"selectPackage": "Vali Pakett",
|
||||
"packageDescription": "Paketi Kirjeldus",
|
||||
"paymentMethod": "Makseviis",
|
||||
"cancelAnytime": "Saad igal ajal rakenduses tühistada",
|
||||
"startSubscription": "Alusta Tellimust",
|
||||
"renewNow": "Uuenda Kohe",
|
||||
"month": "{months} kuud",
|
||||
"year": "{years} aastat",
|
||||
"day": "{days} päeva",
|
||||
"unlimitedTraffic": "Piiramatu Liiklus",
|
||||
"unlimitedDevices": "Piiramatu Seadmete Arv",
|
||||
"devices": "{count} seadet",
|
||||
"features": "Paketi Funktsioonid",
|
||||
"expand": "Laienda",
|
||||
"collapse": "Sulge",
|
||||
"confirmPurchase": "Kinnita Ost",
|
||||
"confirmPurchaseDesc": "Kas olete kindel, et soovite selle paketi osta?"
|
||||
},
|
||||
"orderStatus": {
|
||||
"title": "Tellimuse olek",
|
||||
"pending": {
|
||||
"title": "Makse ootel",
|
||||
"description": "Palun täitke makse"
|
||||
},
|
||||
"paid": {
|
||||
"title": "Makse vastu võetud",
|
||||
"description": "Tellimust töödeldakse"
|
||||
},
|
||||
"success": {
|
||||
"title": "Palju õnne! Makse õnnestus",
|
||||
"description": "Teie pakett on edukalt ostetud"
|
||||
},
|
||||
"closed": {
|
||||
"title": "Tellimus suletud",
|
||||
"description": "Palun esitage uus tellimus"
|
||||
},
|
||||
"failed": {
|
||||
"title": "Makse ebaõnnestus",
|
||||
"description": "Palun proovige makset uuesti"
|
||||
},
|
||||
"unknown": {
|
||||
"title": "Tundmatu olek",
|
||||
"description": "Palun võtke ühendust klienditeenindusega"
|
||||
},
|
||||
"checkFailed": {
|
||||
"title": "Kontroll ebaõnnestus",
|
||||
"description": "Palun proovige hiljem uuesti"
|
||||
},
|
||||
"initial": {
|
||||
"title": "Makset töödeldakse",
|
||||
"description": "Palun oodake, kui töötleme teie makset"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"welcome": "Tere tulemast Hi快VPN-i",
|
||||
"disconnected": "Ühendus katkestatud",
|
||||
"connecting": "Ühendumine",
|
||||
"connected": "Ühendatud",
|
||||
"disconnecting": "Ühenduse katkestamine",
|
||||
"currentConnectionTitle": "Praegune ühendus",
|
||||
"switchNode": "Vaheta sõlme",
|
||||
"timeout": "Aegumine",
|
||||
"loading": "Laadimine...",
|
||||
"error": "Laadimise viga",
|
||||
"checkNetwork": "Kontrollige võrguühendust ja proovige uuesti",
|
||||
"retry": "Proovi uuesti",
|
||||
"connectionSectionTitle": "Ühendusviis",
|
||||
"dedicatedServers": "Pühendatud serverid",
|
||||
"countryRegion": "Riik/Regioon",
|
||||
"serverListTitle": "Pühendatud serverite grupid",
|
||||
"nodeListTitle": "Kõik sõlmed",
|
||||
"countryListTitle": "Riikide/Regioonide nimekiri",
|
||||
"noServers": "Saadaval pole ühtegi serverit",
|
||||
"noNodes": "Saadaval pole ühtegi sõlme",
|
||||
"noRegions": "Saadaval pole ühtegi regiooni",
|
||||
"subscriptionDescription": "Hankige premium juurdepääs kiirele globaalsele võrgustikule",
|
||||
"subscribe": "Telli",
|
||||
"trialPeriod": "Tere tulemast Premium prooviversiooni",
|
||||
"remainingTime": "Järelejäänud aeg",
|
||||
"trialExpired": "Prooviaeg on lõppenud, ühendus katkestatud",
|
||||
"subscriptionExpired": "Tellimus on aegunud, ühendus katkestatud",
|
||||
"subscriptionUpdated": "Tellimus värskendatud",
|
||||
"subscriptionUpdatedMessage": "Teie tellimuse teave on värskendatud, värskendage viimase oleku vaatamiseks",
|
||||
"trialStatus": "Proovi olek",
|
||||
"trialing": "Proovimas",
|
||||
"trialEndMessage": "Prooviaja lõppedes ei saa enam kasutada",
|
||||
"lastDaySubscriptionStatus": "Tellimus aegub varsti",
|
||||
"lastDaySubscriptionMessage": "Aegub varsti",
|
||||
"subscriptionEndMessage": "Tellimuse lõppedes ei saa enam kasutada",
|
||||
"trialTimeWithDays": "{days}p {hours}t {minutes}m {seconds}s",
|
||||
"trialTimeWithHours": "{hours}t {minutes}m {seconds}s",
|
||||
"trialTimeWithMinutes": "{minutes}m {seconds}s",
|
||||
"refreshLatency": "Värskenda latentsust",
|
||||
"testLatency": "Testi latentsust",
|
||||
"testing": "Latentsuse testimine",
|
||||
"refreshLatencyDesc": "Värskenda kõigi sõlmede latentsust",
|
||||
"testAllNodesLatency": "Testi kõigi sõlmede võrgu latentsust",
|
||||
"autoSelect": "Automaatne valik",
|
||||
"selected": "Valitud"
|
||||
},
|
||||
"dialog": {
|
||||
"confirm": "Kinnita",
|
||||
"cancel": "Tühista",
|
||||
"ok": "OK"
|
||||
},
|
||||
"splash": {
|
||||
"appName": "Hi快VPN",
|
||||
"slogan": "Kiire globaalne võrgustik",
|
||||
"initializing": "Initsialiseerimine...",
|
||||
"networkConnectionFailure": "Võrguühenduse viga, kontrollige ja proovige uuesti",
|
||||
"retry": "Proovi uuesti",
|
||||
"networkPermissionFailed": "Võrguõiguse hankimine ebaõnnestus",
|
||||
"initializationFailed": "Initsialiseerimine ebaõnnestus"
|
||||
},
|
||||
"network": {
|
||||
"status": {
|
||||
"connected": "Ühendatud",
|
||||
"disconnected": "Ühendus katkestatud",
|
||||
"connecting": "Ühendumine...",
|
||||
"disconnecting": "Ühenduse katkestamine...",
|
||||
"reconnecting": "Ühenduse taastamine...",
|
||||
"failed": "Ühenduse viga"
|
||||
},
|
||||
"permission": {
|
||||
"title": "Võrguõigus",
|
||||
"description": "VPN teenuse pakkumiseks on vaja võrguõigust",
|
||||
"goToSettings": "Mine seadetesse",
|
||||
"cancel": "Tühista"
|
||||
}
|
||||
},
|
||||
"update": {
|
||||
"title": "Uuendus saadaval",
|
||||
"content": "Uuendada nüüd?",
|
||||
"updateNow": "Uuenda nüüd",
|
||||
"updateLater": "Hiljem",
|
||||
"defaultContent": "1. Rakenduse jõudluse optimeerimine\n2. Teadaolevate probleemide parandamine\n3. Kasutajamugavuse parandamine"
|
||||
},
|
||||
"country": {
|
||||
"cn": "Hiina",
|
||||
"ir": "Iraan",
|
||||
"af": "Afganistan",
|
||||
"ru": "Venemaa",
|
||||
"id": "Indoneesia",
|
||||
"tr": "Türgi",
|
||||
"br": "Brasiilia"
|
||||
},
|
||||
"error": {
|
||||
"200": "Edukas",
|
||||
"500": "Serveri sisemine viga",
|
||||
"10001": "Andmebaasi päringu viga",
|
||||
"10002": "Andmebaasi uuendamise viga",
|
||||
"10003": "Andmebaasi sisestamise viga",
|
||||
"10004": "Andmebaasi kustutamise viga",
|
||||
"20001": "Kasutaja on juba olemas",
|
||||
"20002": "Kasutajat pole olemas",
|
||||
"20003": "Vale kasutaja parool",
|
||||
"20004": "Kasutaja on keelatud",
|
||||
"20005": "Ebapiisav saldo",
|
||||
"20006": "Registreerimine peatatud",
|
||||
"20007": "Telegram pole seotud",
|
||||
"20008": "Kasutaja pole OAuth-i seostanud",
|
||||
"20009": "Vale kutsekood",
|
||||
"30001": "Sõlm on juba olemas",
|
||||
"30002": "Sõlme pole olemas",
|
||||
"30003": "Sõlme grupp on juba olemas",
|
||||
"30004": "Sõlme gruppi pole olemas",
|
||||
"30005": "Sõlme grupp pole tühi",
|
||||
"400": "Parameetri viga",
|
||||
"40002": "Kasutaja token on tühi",
|
||||
"40003": "Vale kasutaja token",
|
||||
"40004": "Kasutaja token on aegunud",
|
||||
"40005": "Te pole sisse logitud",
|
||||
"401": "Liiga palju päringuid",
|
||||
"50001": "Kupongi pole olemas",
|
||||
"50002": "Kupong on juba kasutatud",
|
||||
"50003": "Kupong ei vasta",
|
||||
"60001": "Tellimus on aegunud",
|
||||
"60002": "Tellimus pole saadaval",
|
||||
"60003": "Kasutajal on juba tellimus",
|
||||
"60004": "Tellimus on juba kasutatud",
|
||||
"60005": "Üksiku tellimuse režiimi limiit ületatud",
|
||||
"60006": "Tellimuse kvoodi limiit",
|
||||
"70001": "Vale kinnituskood",
|
||||
"80001": "Järjekorda lisamise viga",
|
||||
"90001": "Silumisrežiim on lubatud",
|
||||
"90002": "SMS-i saatmise viga",
|
||||
"90003": "SMS funktsioon pole lubatud",
|
||||
"90004": "E-posti funktsioon pole lubatud",
|
||||
"90005": "Toetamata sisselogimise meetod",
|
||||
"90006": "Autentifikaator ei toeta seda meetodit",
|
||||
"90007": "Telefoni riigi kood on tühi",
|
||||
"90008": "Parool on tühi",
|
||||
"90009": "Riigi kood on tühi",
|
||||
"90010": "Parool või kinnituskood on vajalik",
|
||||
"90011": "E-post on juba olemas",
|
||||
"90012": "Telefoninumber on juba olemas",
|
||||
"90013": "Seade on juba olemas",
|
||||
"90014": "Vale telefoninumber",
|
||||
"90015": "See konto on täna saavutanud saatmise limiidi",
|
||||
"90017": "Seadet pole olemas",
|
||||
"90018": "Kasutaja ID ei vasta",
|
||||
"61001": "Tellimust pole olemas",
|
||||
"61002": "Makseviisi ei leitud",
|
||||
"61003": "Vale tellimuse olek",
|
||||
"61004": "Ebapiisav lähtestamise periood",
|
||||
"61005": "Kasutamata liiklus on olemas"
|
||||
},
|
||||
"tray": {
|
||||
"open_dashboard": "Ava armatuurlaud",
|
||||
"copy_to_terminal": "Kopeeri terminali",
|
||||
"exit_app": "Välju rakendusest"
|
||||
}
|
||||
}
|
||||
@ -9,6 +9,7 @@
|
||||
"enterEmail": "Please enter email address",
|
||||
"enterCode": "認証コードを入力してください",
|
||||
"enterPassword": "パスワードを入力してください",
|
||||
"enterPasswordForPhone": "パスワードを入力してください (パスワードログイン)",
|
||||
"reenterPassword": "パスワードを再入力してください",
|
||||
"forgotPassword": "パスワードをお忘れの方",
|
||||
"codeLogin": "認証コードでログイン",
|
||||
@ -18,6 +19,8 @@
|
||||
"privacyPolicy": "プライバシーポリシー",
|
||||
"next": "次へ",
|
||||
"registerNow": "今すぐ登録",
|
||||
"registerAccount": "アカウント登録",
|
||||
"clickToRegister": "クリックして登録",
|
||||
"setAndLogin": "設定してログイン",
|
||||
"enterAccount": "アカウントを入力してください",
|
||||
"passwordMismatch": "2回のパスワード入力が一致しません",
|
||||
@ -25,7 +28,9 @@
|
||||
"codeSentCountdown": "認証コード送信済み {seconds}秒",
|
||||
"and": "および",
|
||||
"enterInviteCode": "招待コードを入力(任意)",
|
||||
"registerSuccess": "登録成功"
|
||||
"registerSuccess": "登録成功",
|
||||
"search": "検索",
|
||||
"selectOtherRegion": "他の地域を選択"
|
||||
},
|
||||
"failure": {
|
||||
"unexpected": "予期せぬエラー",
|
||||
@ -187,7 +192,8 @@
|
||||
"friday": "金",
|
||||
"saturday": "土",
|
||||
"sunday": "日"
|
||||
}
|
||||
},
|
||||
"processTrafficFailed": "トラフィックログデータの処理に失敗しました"
|
||||
},
|
||||
"message": {
|
||||
"title": "通知",
|
||||
@ -381,6 +387,7 @@
|
||||
"initializing": "初期化中...",
|
||||
"networkConnectionFailure": "ネットワーク接続エラー、確認して再試行してください",
|
||||
"retry": "再試行",
|
||||
"skip": "スキップ",
|
||||
"networkPermissionFailed": "ネットワーク権限の取得に失敗しました",
|
||||
"initializationFailed": "初期化に失敗しました"
|
||||
},
|
||||
@ -481,5 +488,33 @@
|
||||
"open_dashboard": "ダッシュボードを開く",
|
||||
"copy_to_terminal": "ターミナルにコピー",
|
||||
"exit_app": "アプリを終了"
|
||||
},
|
||||
"crisp": {
|
||||
"initializingSystem": "カスタマーサービスシステムを初期化しています...",
|
||||
"initFailed": "カスタマーサービスシステムの初期化に失敗しました"
|
||||
},
|
||||
"common": {
|
||||
"retry": "再試行",
|
||||
"tryBrowser": "ブラウザで開いてみる",
|
||||
"cannotOpenBrowser": "ブラウザを開けません",
|
||||
"openLinkFailed": "リンクを開けませんでした。後でもう一度お試しください",
|
||||
"cannotOpenTelegram": "Telegramリンクを開けません"
|
||||
},
|
||||
"subscribe": {
|
||||
"pleaseSelectFirst": "最初にサブスクリプションを選択してください",
|
||||
"resetPeriodFailed": "サブスクリプション期間のリセットに失敗しました",
|
||||
"resetPeriodSuccess": "サブスクリプション期間が正常にリセットされました"
|
||||
},
|
||||
"purchase": {
|
||||
"noAvailablePlans": "利用可能なプランはありません"
|
||||
},
|
||||
"payment": {
|
||||
"cannotOpenLink": "支払いリンクを開けません",
|
||||
"linkEmpty": "支払いリンクが空です",
|
||||
"unsupportedType": "サポートされていない支払いタイプ"
|
||||
},
|
||||
"account": {
|
||||
"accountRequired": "アカウントは空にできません",
|
||||
"deleteSuccess": "アカウントが正常に削除されました"
|
||||
}
|
||||
}
|
||||
@ -1,441 +0,0 @@
|
||||
{
|
||||
"login": {
|
||||
"welcome": "Hi快VPNへようこそ!",
|
||||
"verifyPhone": "電話番号を認証",
|
||||
"verifyEmail": "メールアドレスを認証",
|
||||
"codeSent": "{account}に6桁のコードを送信しました。30分以内に入力してください。",
|
||||
"back": "戻る",
|
||||
"enterEmailOrPhone": "メールアドレスまたは電話番号を入力",
|
||||
"enterCode": "認証コードを入力してください",
|
||||
"enterPassword": "パスワードを入力してください",
|
||||
"reenterPassword": "パスワードを再入力してください",
|
||||
"forgotPassword": "パスワードをお忘れの方",
|
||||
"codeLogin": "認証コードでログイン",
|
||||
"passwordLogin": "パスワードでログイン",
|
||||
"agreeTerms": "ログイン/アカウント作成,に同意します",
|
||||
"termsOfService": "利用規約",
|
||||
"privacyPolicy": "プライバシーポリシー",
|
||||
"next": "次へ",
|
||||
"registerNow": "今すぐ登録",
|
||||
"setAndLogin": "設定してログイン",
|
||||
"enterAccount": "アカウントを入力してください",
|
||||
"passwordMismatch": "2回のパスワード入力が一致しません",
|
||||
"sendCode": "認証コードを送信",
|
||||
"codeSentCountdown": "認証コード送信済み {seconds}秒",
|
||||
"and": "および",
|
||||
"enterInviteCode": "招待コードを入力(任意)",
|
||||
"registerSuccess": "登録成功"
|
||||
},
|
||||
"failure": {
|
||||
"unexpected": "予期せぬエラー",
|
||||
"clash": {
|
||||
"unexpected": "予期せぬエラー",
|
||||
"core": "Clashエラー ${reason}"
|
||||
},
|
||||
"singbox": {
|
||||
"unexpected": "予期せぬサービスエラー",
|
||||
"serviceNotRunning": "サービスが実行されていません",
|
||||
"missingPrivilege": "権限がありません",
|
||||
"missingPrivilegeMsg": "VPNモードには管理者権限が必要です。管理者として再起動するか、サービスモードを変更してください",
|
||||
"missingGeoAssets": "GEOリソースファイルがありません",
|
||||
"missingGeoAssetsMsg": "GEOリソースファイルがありません。アクティブなリソースを変更するか、設定で選択したリソースをダウンロードしてください。",
|
||||
"invalidConfigOptions": "設定オプションが無効です",
|
||||
"invalidConfig": "無効な設定",
|
||||
"create": "サービス作成エラー",
|
||||
"start": "サービス起動エラー"
|
||||
},
|
||||
"connectivity": {
|
||||
"unexpected": "予期せぬ失敗",
|
||||
"missingVpnPermission": "VPN権限がありません",
|
||||
"missingNotificationPermission": "通知権限がありません",
|
||||
"core": "コアエラー"
|
||||
},
|
||||
"profiles": {
|
||||
"unexpected": "予期せぬエラー",
|
||||
"notFound": "プロファイルが見つかりません",
|
||||
"invalidConfig": "無効な設定",
|
||||
"invalidUrl": "無効なURL"
|
||||
},
|
||||
"connection": {
|
||||
"unexpected": "予期せぬ接続エラー",
|
||||
"timeout": "接続タイムアウト",
|
||||
"badResponse": "不正なレスポンス",
|
||||
"connectionError": "接続エラー",
|
||||
"badCertificate": "無効な証明書"
|
||||
},
|
||||
"geoAssets": {
|
||||
"unexpected": "予期せぬエラー",
|
||||
"notUpdate": "利用可能な更新はありません",
|
||||
"activeNotFound": "アクティブなGEOリソースファイルが見つかりません"
|
||||
}
|
||||
},
|
||||
"userInfo": {
|
||||
"title": "マイ情報",
|
||||
"bindingTip": "メール/電話番号が未登録です",
|
||||
"myAccount": "マイアカウント",
|
||||
"balance": "残高",
|
||||
"noValidSubscription": "有効なサブスクリプションがありません",
|
||||
"subscribeNow": "今すぐ購読",
|
||||
"shortcuts": "ショートカット",
|
||||
"adBlock": "広告ブロック",
|
||||
"dnsUnlock": "DNSアンロック",
|
||||
"contactUs": "お問い合わせ",
|
||||
"others": "その他",
|
||||
"logout": "ログアウト",
|
||||
"logoutConfirmTitle": "ログアウト",
|
||||
"logoutConfirmMessage": "ログアウトしますか?",
|
||||
"logoutCancel": "キャンセル",
|
||||
"vpnWebsite": "VPN公式サイト",
|
||||
"telegram": "Telegram",
|
||||
"mail": "メール",
|
||||
"phone": "電話",
|
||||
"customerService": "カスタマーサービス",
|
||||
"workOrder": "チケット送信",
|
||||
"pleaseLogin": "ログインしてください",
|
||||
"subscriptionValid": "サブスクリプション有効",
|
||||
"startTime": "開始時間:",
|
||||
"expireTime": "有効期限:",
|
||||
"loginNow": "今すぐログイン",
|
||||
"trialPeriod": "トライアル期間",
|
||||
"remainingTime": "残り時間",
|
||||
"trialExpired": "トライアル期間が終了しました",
|
||||
"subscriptionExpired": "サブスクリプションが期限切れです",
|
||||
"copySuccess": "コピーしました",
|
||||
"notAvailable": "利用不可",
|
||||
"willBeDeleted": "削除されます",
|
||||
"deleteAccountWarning": "アカウントの削除は永久的です。アカウントを削除すると、すべての機能が使用できなくなります。続行しますか?",
|
||||
"requestDelete": "削除をリクエスト",
|
||||
"deviceLimit": "デバイス制限:{count}",
|
||||
"reset": "リセット",
|
||||
"trafficUsage": "トラフィック:{used} / {total}",
|
||||
"trafficProgress": {
|
||||
"title": "トラフィック使用状況",
|
||||
"unlimited": "無制限",
|
||||
"limited": "使用済み"
|
||||
},
|
||||
"switchSubscription": "サブスクリプションの切り替え",
|
||||
"resetTrafficTitle": "トラフィックリセット",
|
||||
"resetTrafficMessage": "月間プランのトラフィックリセット例:次のサイクルのトラフィックを月次でリセットし、サブスクリプションの有効期限が{currentTime}から{newTime}に繰り上げられます",
|
||||
"trialStatus": "トライアル状態",
|
||||
"trialing": "トライアル中",
|
||||
"trialEndMessage": "トライアル期間終了後は使用できなくなります",
|
||||
"lastDaySubscriptionStatus": "サブスクリプション期限切れ間近",
|
||||
"lastDaySubscriptionMessage": "期限切れ間近",
|
||||
"subscriptionEndMessage": "サブスクリプション終了後は使用できなくなります",
|
||||
"trialTimeWithDays": "{days}日{hours}時間{minutes}分{seconds}秒",
|
||||
"trialTimeWithHours": "{hours}時間{minutes}分{seconds}秒",
|
||||
"trialTimeWithMinutes": "{minutes}分{seconds}秒",
|
||||
"refreshLatency": "レイテンシーを更新",
|
||||
"testLatency": "レイテンシーテスト",
|
||||
"testing": "レイテンシーテスト中",
|
||||
"refreshLatencyDesc": "すべてのノードのレイテンシーを更新",
|
||||
"testAllNodesLatency": "すべてのノードのネットワークレイテンシーをテスト",
|
||||
"autoSelect": "自動選択",
|
||||
"selected": "選択済み"
|
||||
},
|
||||
"setting": {
|
||||
"title": "設定",
|
||||
"vpnConnection": "VPN接続",
|
||||
"countrySelector": "国を選択",
|
||||
"general": "一般",
|
||||
"autoConnect": "自動接続",
|
||||
"routeRule": "ルーティングルール",
|
||||
"appearance": "外観",
|
||||
"notifications": "通知",
|
||||
"helpImprove": "改善にご協力ください",
|
||||
"helpImproveSubtitle": "改善にご協力くださいサブタイトル",
|
||||
"requestDeleteAccount": "アカウント削除をリクエスト",
|
||||
"goToDelete": "削除へ",
|
||||
"rateUs": "App Storeで評価する",
|
||||
"iosRating": "iOS評価",
|
||||
"version": "バージョン",
|
||||
"switchLanguage": "言語を切り替え",
|
||||
"system": "システム",
|
||||
"light": "ライト",
|
||||
"dark": "ダーク",
|
||||
"vpnModeSmart": "スマートモード",
|
||||
"mode": "アウトバウンドモード",
|
||||
"connectionTypeGlobal": "グローバルプロキシ",
|
||||
"connectionTypeGlobalRemark": "有効時、すべてのトラフィックはプロキシ経由でルーティングされます",
|
||||
"connectionTypeRule": "スマートプロキシ",
|
||||
"connectionTypeRuleRemark": "[アウトバウンドモード]が[スマートプロキシ]に設定されている場合、システムは選択された国に基づいて国内と海外のトラフィックを自動的に分割します:国内IP/ドメインは直接接続、海外リクエストはプロキシ経由でアクセス",
|
||||
"connectionTypeDirect": "ダイレクト接続",
|
||||
"connectionTypeDirectRemark": "有効時、すべてのトラフィックはプロキシをバイパスします",
|
||||
"smartMode": "スマートモード",
|
||||
"secureMode": "セキュアモード"
|
||||
},
|
||||
"statistics": {
|
||||
"title": "統計",
|
||||
"vpnStatus": "VPNステータス",
|
||||
"ipAddress": "IPアドレス",
|
||||
"connectionTime": "接続時間",
|
||||
"protocol": "プロトコル",
|
||||
"weeklyProtectionTime": "週間保護時間",
|
||||
"currentStreak": "現在の連続記録",
|
||||
"highestStreak": "最高記録",
|
||||
"longestConnection": "最長接続時間",
|
||||
"days": "{days}日",
|
||||
"daysOfWeek": {
|
||||
"monday": "月",
|
||||
"tuesday": "火",
|
||||
"wednesday": "水",
|
||||
"thursday": "木",
|
||||
"friday": "金",
|
||||
"saturday": "土",
|
||||
"sunday": "日"
|
||||
}
|
||||
},
|
||||
"message": {
|
||||
"title": "通知",
|
||||
"system": "システムメッセージ",
|
||||
"promotion": "プロモーションメッセージ"
|
||||
},
|
||||
"invite": {
|
||||
"title": "友達を招待",
|
||||
"progress": "招待の進捗",
|
||||
"inviteStats": "招待統計",
|
||||
"registers": "登録済み",
|
||||
"totalCommission": "総報酬",
|
||||
"rewardDetails": "報酬の詳細 >",
|
||||
"steps": "招待の手順",
|
||||
"inviteFriend": "友達を招待",
|
||||
"acceptInvite": "友達が招待を受け入れ\n注文して登録",
|
||||
"getReward": "報酬を獲得",
|
||||
"shareLink": "リンクを共有",
|
||||
"shareQR": "QRコードを共有",
|
||||
"rules": "招待ルール",
|
||||
"rule1": "1. 専用の招待リンクまたは招待コードを共有して、友達を招待できます。",
|
||||
"rule2": "2. 友達が登録とログインを完了すると、招待報酬が自動的にアカウントに付与されます。",
|
||||
"pending": "保留中",
|
||||
"processing": "処理中",
|
||||
"success": "成功",
|
||||
"expired": "期限切れ",
|
||||
"myInviteCode": "招待コード",
|
||||
"inviteCodeCopied": "招待コードをクリップボードにコピーしました",
|
||||
"close": "閉じる",
|
||||
"saveQRCode": "QRコードを保存",
|
||||
"qrCodeSaved": "QRコードを保存しました",
|
||||
"copiedToClipboard": "クリップボードにコピーしました",
|
||||
"getInviteCodeFailed": "招待コードの取得に失敗しました。後ほど再試行してください",
|
||||
"generateQRCodeFailed": "QRコードの生成に失敗しました。後ほど再試行してください",
|
||||
"generateShareLinkFailed": "共有リンクの生成に失敗しました。後ほど再試行してください"
|
||||
},
|
||||
"purchaseMembership": {
|
||||
"purchasePackage": "パッケージ購入",
|
||||
"noData": "利用可能なパッケージはありません",
|
||||
"myAccount": "マイアカウント",
|
||||
"selectPackage": "パッケージを選択",
|
||||
"packageDescription": "パッケージ説明",
|
||||
"paymentMethod": "支払い方法",
|
||||
"cancelAnytime": "アプリでいつでもキャンセル可能",
|
||||
"startSubscription": "サブスクリプションを開始",
|
||||
"renewNow": "今すぐ更新",
|
||||
"month": "{months}ヶ月",
|
||||
"year": "{years}年",
|
||||
"day": "{days}日",
|
||||
"unlimitedTraffic": "無制限トラフィック",
|
||||
"unlimitedDevices": "無制限デバイス",
|
||||
"devices": "{count}台",
|
||||
"features": "パッケージ機能",
|
||||
"expand": "展開",
|
||||
"collapse": "折りたたむ",
|
||||
"confirmPurchase": "購入を確認",
|
||||
"confirmPurchaseDesc": "このパッケージを購入してもよろしいですか?",
|
||||
"subscriptionPrivacyInfo": "サブスクリプションとプライバシー情報"
|
||||
},
|
||||
"orderStatus": {
|
||||
"title": "注文状態",
|
||||
"pending": {
|
||||
"title": "支払い待ち",
|
||||
"description": "支払いを完了してください"
|
||||
},
|
||||
"paid": {
|
||||
"title": "支払い完了",
|
||||
"description": "注文を処理中です"
|
||||
},
|
||||
"success": {
|
||||
"title": "おめでとうございます!支払い成功",
|
||||
"description": "パッケージの購入が完了しました"
|
||||
},
|
||||
"closed": {
|
||||
"title": "注文キャンセル",
|
||||
"description": "新規注文をお願いします"
|
||||
},
|
||||
"failed": {
|
||||
"title": "支払い失敗",
|
||||
"description": "支払いを再試行してください"
|
||||
},
|
||||
"unknown": {
|
||||
"title": "不明な状態",
|
||||
"description": "カスタマーサービスにお問い合わせください"
|
||||
},
|
||||
"checkFailed": {
|
||||
"title": "確認失敗",
|
||||
"description": "後でもう一度お試しください"
|
||||
},
|
||||
"initial": {
|
||||
"title": "支払い処理中",
|
||||
"description": "支払い処理中です。お待ちください"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"welcome": "Hi快VPNへようこそ",
|
||||
"disconnected": "未接続",
|
||||
"connecting": "接続中",
|
||||
"connected": "接続済み",
|
||||
"disconnecting": "切断中",
|
||||
"currentConnectionTitle": "現在の接続",
|
||||
"switchNode": "ノード切替",
|
||||
"timeout": "タイムアウト",
|
||||
"loading": "読み込み中...",
|
||||
"error": "読み込みエラー",
|
||||
"checkNetwork": "ネットワーク接続を確認して再試行してください",
|
||||
"retry": "再試行",
|
||||
"connectionSectionTitle": "接続方法",
|
||||
"dedicatedServers": "専用サーバー",
|
||||
"countryRegion": "国/地域",
|
||||
"serverListTitle": "専用サーバーグループ",
|
||||
"nodeListTitle": "全ノード",
|
||||
"countryListTitle": "国/地域リスト",
|
||||
"noServers": "利用可能なサーバーがありません",
|
||||
"noNodes": "利用可能なノードがありません",
|
||||
"noRegions": "利用可能な地域がありません",
|
||||
"subscriptionDescription": "プレミアムアクセスで高速グローバルネットワークを利用",
|
||||
"subscribe": "購読する",
|
||||
"trialPeriod": "プレミアムトライアルへようこそ",
|
||||
"remainingTime": "残り時間",
|
||||
"trialExpired": "トライアル期間が終了し、接続が切断されました",
|
||||
"subscriptionExpired": "サブスクリプションが期限切れとなり、接続が切断されました",
|
||||
"subscriptionUpdated": "サブスクリプションが更新されました",
|
||||
"subscriptionUpdatedMessage": "サブスクリプション情報が更新されました。最新の状態を確認するには更新してください",
|
||||
"trialStatus": "トライアル状態",
|
||||
"trialing": "トライアル中",
|
||||
"trialEndMessage": "トライアル期間終了後は使用できなくなります",
|
||||
"lastDaySubscriptionStatus": "サブスクリプション期限切れ間近",
|
||||
"lastDaySubscriptionMessage": "期限切れ間近",
|
||||
"subscriptionEndMessage": "サブスクリプション終了後は使用できなくなります",
|
||||
"trialTimeWithDays": "{days}日{hours}時間{minutes}分{seconds}秒",
|
||||
"trialTimeWithHours": "{hours}時間{minutes}分{seconds}秒",
|
||||
"trialTimeWithMinutes": "{minutes}分{seconds}秒",
|
||||
"refreshLatency": "レイテンシー更新",
|
||||
"testLatency": "レイテンシーテスト",
|
||||
"testing": "レイテンシーテスト中",
|
||||
"refreshLatencyDesc": "全ノードのレイテンシーを更新",
|
||||
"testAllNodesLatency": "全ノードのネットワークレイテンシーをテスト",
|
||||
"autoSelect": "自動選択",
|
||||
"selected": "選択済み"
|
||||
},
|
||||
"dialog": {
|
||||
"confirm": "確認",
|
||||
"cancel": "キャンセル",
|
||||
"ok": "OK",
|
||||
"iKnow": "分かりました"
|
||||
},
|
||||
"splash": {
|
||||
"appName": "Hi快VPN",
|
||||
"slogan": "高速グローバルネットワーク",
|
||||
"initializing": "初期化中...",
|
||||
"networkConnectionFailure": "ネットワーク接続エラー、確認して再試行してください",
|
||||
"retry": "再試行",
|
||||
"networkPermissionFailed": "ネットワーク権限の取得に失敗しました",
|
||||
"initializationFailed": "初期化に失敗しました"
|
||||
},
|
||||
"network": {
|
||||
"status": {
|
||||
"connected": "接続済み",
|
||||
"disconnected": "未接続",
|
||||
"connecting": "接続中...",
|
||||
"disconnecting": "切断中...",
|
||||
"reconnecting": "再接続中...",
|
||||
"failed": "接続エラー"
|
||||
},
|
||||
"permission": {
|
||||
"title": "ネットワーク権限",
|
||||
"description": "VPNサービスを提供するにはネットワーク権限が必要です",
|
||||
"goToSettings": "設定へ",
|
||||
"cancel": "キャンセル"
|
||||
}
|
||||
},
|
||||
"update": {
|
||||
"title": "アップデートが利用可能",
|
||||
"content": "今すぐアップデートしますか?",
|
||||
"updateNow": "今すぐアップデート",
|
||||
"updateLater": "後で",
|
||||
"defaultContent": "1. アプリのパフォーマンス最適化\n2. 既知の問題の修正\n3. ユーザー体験の向上"
|
||||
},
|
||||
"country": {
|
||||
"cn": "中国",
|
||||
"ir": "イラン",
|
||||
"af": "アフガニスタン",
|
||||
"ru": "ロシア",
|
||||
"id": "インドネシア",
|
||||
"tr": "トルコ",
|
||||
"br": "ブラジル"
|
||||
},
|
||||
"error": {
|
||||
"200": "成功",
|
||||
"500": "サーバー内部エラー",
|
||||
"10001": "データベースクエリエラー",
|
||||
"10002": "データベース更新エラー",
|
||||
"10003": "データベース挿入エラー",
|
||||
"10004": "データベース削除エラー",
|
||||
"20001": "ユーザーは既に存在します",
|
||||
"20002": "ユーザーが存在しません",
|
||||
"20003": "ユーザーパスワードが間違っています",
|
||||
"20004": "ユーザーは無効化されています",
|
||||
"20005": "残高不足",
|
||||
"20006": "登録は停止されています",
|
||||
"20007": "Telegramが未連携です",
|
||||
"20008": "ユーザーがOAuthを連携していません",
|
||||
"20009": "招待コードが間違っています",
|
||||
"30001": "ノードは既に存在します",
|
||||
"30002": "ノードが存在しません",
|
||||
"30003": "ノードグループは既に存在します",
|
||||
"30004": "ノードグループが存在しません",
|
||||
"30005": "ノードグループは空ではありません",
|
||||
"400": "パラメータエラー",
|
||||
"40002": "ユーザートークンが空です",
|
||||
"40003": "ユーザートークンが無効です",
|
||||
"40004": "ユーザートークンの有効期限が切れています",
|
||||
"40005": "ログインしていません",
|
||||
"401": "リクエストが多すぎます",
|
||||
"50001": "クーポンが存在しません",
|
||||
"50002": "クーポンは既に使用されています",
|
||||
"50003": "クーポンが一致しません",
|
||||
"60001": "サブスクリプションの有効期限が切れています",
|
||||
"60002": "サブスクリプションは利用できません",
|
||||
"60003": "ユーザーは既にサブスクリプションを持っています",
|
||||
"60004": "サブスクリプションは既に使用されています",
|
||||
"60005": "単一サブスクリプションモードの制限を超えています",
|
||||
"60006": "サブスクリプションクォータ制限",
|
||||
"70001": "認証コードが間違っています",
|
||||
"80001": "キューイングエラー",
|
||||
"90001": "デバッグモードが有効です",
|
||||
"90002": "SMS送信エラー",
|
||||
"90003": "SMS機能が有効になっていません",
|
||||
"90004": "メール機能が有効になっていません",
|
||||
"90005": "サポートされていないログイン方法",
|
||||
"90006": "認証器がこの方法をサポートしていません",
|
||||
"90007": "電話国番号が空です",
|
||||
"90008": "パスワードが空です",
|
||||
"90009": "国番号が空です",
|
||||
"90010": "パスワードまたは認証コードが必要です",
|
||||
"90011": "メールアドレスは既に存在します",
|
||||
"90012": "電話番号は既に存在します",
|
||||
"90013": "デバイスは既に存在します",
|
||||
"90014": "電話番号が間違っています",
|
||||
"90015": "このアカウントは本日の送信制限に達しました",
|
||||
"90017": "デバイスが存在しません",
|
||||
"90018": "ユーザーIDが一致しません",
|
||||
"61001": "注文が存在しません",
|
||||
"61002": "支払い方法が見つかりません",
|
||||
"61003": "注文状態が間違っています",
|
||||
"61004": "リセット期間が不足しています",
|
||||
"61005": "未使用のトラフィックが存在します"
|
||||
},
|
||||
"tray": {
|
||||
"open_dashboard": "ダッシュボードを開く",
|
||||
"copy_to_terminal": "ターミナルにコピー",
|
||||
"exit_app": "アプリを終了"
|
||||
}
|
||||
}
|
||||
@ -9,6 +9,7 @@
|
||||
"enterEmail": "Please enter email address",
|
||||
"enterCode": "Введите код подтверждения",
|
||||
"enterPassword": "Введите пароль",
|
||||
"enterPasswordForPhone": "Пожалуйста, введите пароль (Вход с паролем)",
|
||||
"reenterPassword": "Повторите пароль",
|
||||
"forgotPassword": "Забыли пароль",
|
||||
"codeLogin": "Вход по коду",
|
||||
@ -18,6 +19,8 @@
|
||||
"privacyPolicy": "Политикой конфиденциальности",
|
||||
"next": "Далее",
|
||||
"registerNow": "Зарегистрироваться",
|
||||
"registerAccount": "Зарегистрировать аккаунт",
|
||||
"clickToRegister": "Нажмите, чтобы зарегистрироваться",
|
||||
"setAndLogin": "Установить и войти",
|
||||
"enterAccount": "Введите аккаунт",
|
||||
"passwordMismatch": "Два введенных пароля не совпадают",
|
||||
@ -25,7 +28,9 @@
|
||||
"codeSentCountdown": "Код отправлен {seconds}с",
|
||||
"and": "и",
|
||||
"enterInviteCode": "Введите код приглашения (необязательно)",
|
||||
"registerSuccess": "Регистрация успешна"
|
||||
"registerSuccess": "Регистрация успешна",
|
||||
"search": "Поиск",
|
||||
"selectOtherRegion": "Выбрать другой регион"
|
||||
},
|
||||
"failure": {
|
||||
"unexpected": "Непредвиденная ошибка",
|
||||
@ -187,7 +192,8 @@
|
||||
"friday": "Пт",
|
||||
"saturday": "Сб",
|
||||
"sunday": "Вс"
|
||||
}
|
||||
},
|
||||
"processTrafficFailed": "Не удалось обработать данные журнала трафика"
|
||||
},
|
||||
"message": {
|
||||
"title": "Уведомления",
|
||||
@ -464,6 +470,7 @@
|
||||
"initializing": "Инициализация...",
|
||||
"networkConnectionFailure": "Ошибка подключения к сети, проверьте и повторите попытку",
|
||||
"retry": "Повторить",
|
||||
"skip": "Пропустить",
|
||||
"networkPermissionFailed": "Не удалось получить разрешение на использование сети",
|
||||
"initializationFailed": "Ошибка инициализации"
|
||||
},
|
||||
@ -482,5 +489,33 @@
|
||||
"goToSettings": "Перейти в настройки",
|
||||
"cancel": "Отмена"
|
||||
}
|
||||
},
|
||||
"crisp": {
|
||||
"initializingSystem": "Инициализация системы обслуживания клиентов...",
|
||||
"initFailed": "Не удалось инициализировать систему обслуживания клиентов"
|
||||
},
|
||||
"common": {
|
||||
"retry": "Повторить",
|
||||
"tryBrowser": "Попробовать открыть в браузере",
|
||||
"cannotOpenBrowser": "Не удается открыть браузер",
|
||||
"openLinkFailed": "Не удалось открыть ссылку, попробуйте позже",
|
||||
"cannotOpenTelegram": "Не удается открыть ссылку Telegram"
|
||||
},
|
||||
"subscribe": {
|
||||
"pleaseSelectFirst": "Сначала выберите подписку",
|
||||
"resetPeriodFailed": "Не удалось сбросить период подписки",
|
||||
"resetPeriodSuccess": "Период подписки успешно сброшен"
|
||||
},
|
||||
"purchase": {
|
||||
"noAvailablePlans": "Нет доступных планов"
|
||||
},
|
||||
"payment": {
|
||||
"cannotOpenLink": "Не удается открыть ссылку для оплаты",
|
||||
"linkEmpty": "Ссылка для оплаты пуста",
|
||||
"unsupportedType": "Неподдерживаемый тип оплаты"
|
||||
},
|
||||
"account": {
|
||||
"accountRequired": "Учетная запись не может быть пустой",
|
||||
"deleteSuccess": "Учетная запись успешно удалена"
|
||||
}
|
||||
}
|
||||
@ -1,442 +0,0 @@
|
||||
{
|
||||
"login": {
|
||||
"welcome": "Добро пожаловать в Hi快VPN!",
|
||||
"verifyPhone": "Подтвердите номер телефона",
|
||||
"verifyEmail": "Подтвердите email",
|
||||
"codeSent": "6-значный код отправлен на {account}. Введите его в течение 30 минут.",
|
||||
"back": "Назад",
|
||||
"enterEmailOrPhone": "Введите email или номер телефона",
|
||||
"enterCode": "Введите код подтверждения",
|
||||
"enterPassword": "Введите пароль",
|
||||
"reenterPassword": "Повторите пароль",
|
||||
"forgotPassword": "Забыли пароль",
|
||||
"codeLogin": "Вход по коду",
|
||||
"passwordLogin": "Вход по паролю",
|
||||
"agreeTerms": "Вход/Создать аккаунт, я соглашаюсь с",
|
||||
"termsOfService": "Условиями использования",
|
||||
"privacyPolicy": "Политикой конфиденциальности",
|
||||
"next": "Далее",
|
||||
"registerNow": "Зарегистрироваться",
|
||||
"setAndLogin": "Установить и войти",
|
||||
"enterAccount": "Введите аккаунт",
|
||||
"passwordMismatch": "Два введенных пароля не совпадают",
|
||||
"sendCode": "Отправить код",
|
||||
"codeSentCountdown": "Код отправлен {seconds}с",
|
||||
"and": "и",
|
||||
"enterInviteCode": "Введите код приглашения (необязательно)",
|
||||
"registerSuccess": "Регистрация успешна"
|
||||
},
|
||||
"failure": {
|
||||
"unexpected": "Непредвиденная ошибка",
|
||||
"clash": {
|
||||
"unexpected": "Непредвиденная ошибка",
|
||||
"core": "Ошибка Clash ${reason}"
|
||||
},
|
||||
"singbox": {
|
||||
"unexpected": "Непредвиденная ошибка сервиса",
|
||||
"serviceNotRunning": "Сервис не запущен",
|
||||
"missingPrivilege": "Отсутствуют привилегии",
|
||||
"missingPrivilegeMsg": "Режим VPN требует прав администратора. Перезапустите приложение с правами администратора или измените режим сервиса",
|
||||
"missingGeoAssets": "Отсутствуют GEO-ресурсы",
|
||||
"missingGeoAssetsMsg": "Отсутствуют файлы GEO-ресурсов. Измените активные ресурсы или загрузите выбранные ресурсы в настройках.",
|
||||
"invalidConfigOptions": "Недопустимые параметры конфигурации",
|
||||
"invalidConfig": "Недопустимая конфигурация",
|
||||
"create": "Ошибка создания сервиса",
|
||||
"start": "Ошибка запуска сервиса"
|
||||
},
|
||||
"connectivity": {
|
||||
"unexpected": "Непредвиденный сбой",
|
||||
"missingVpnPermission": "Отсутствует разрешение VPN",
|
||||
"missingNotificationPermission": "Отсутствует разрешение на уведомления",
|
||||
"core": "Ошибка ядра"
|
||||
},
|
||||
"profiles": {
|
||||
"unexpected": "Непредвиденная ошибка",
|
||||
"notFound": "Профиль не найден",
|
||||
"invalidConfig": "Недопустимая конфигурация",
|
||||
"invalidUrl": "Недопустимый URL"
|
||||
},
|
||||
"connection": {
|
||||
"unexpected": "Непредвиденная ошибка подключения",
|
||||
"timeout": "Тайм-аут подключения",
|
||||
"badResponse": "Некорректный ответ",
|
||||
"connectionError": "Ошибка подключения",
|
||||
"badCertificate": "Недействительный сертификат"
|
||||
},
|
||||
"geoAssets": {
|
||||
"unexpected": "Непредвиденная ошибка",
|
||||
"notUpdate": "Нет доступных обновлений",
|
||||
"activeNotFound": "Активные GEO-ресурсы не найдены"
|
||||
}
|
||||
},
|
||||
"userInfo": {
|
||||
"title": "Моя информация",
|
||||
"bindingTip": "Email/телефон не привязаны",
|
||||
"myAccount": "Мой аккаунт",
|
||||
"balance": "Баланс",
|
||||
"noValidSubscription": "Нет активной подписки",
|
||||
"subscribeNow": "Подписаться сейчас",
|
||||
"shortcuts": "Ярлыки",
|
||||
"adBlock": "Блокировка рекламы",
|
||||
"dnsUnlock": "Разблокировка DNS",
|
||||
"contactUs": "Связаться с нами",
|
||||
"others": "Прочее",
|
||||
"logout": "Выйти",
|
||||
"logoutConfirmTitle": "Выйти",
|
||||
"logoutConfirmMessage": "Вы уверены, что хотите выйти?",
|
||||
"logoutCancel": "Отмена",
|
||||
"vpnWebsite": "Сайт VPN",
|
||||
"telegram": "Telegram",
|
||||
"mail": "Email",
|
||||
"phone": "Телефон",
|
||||
"customerService": "Служба поддержки",
|
||||
"workOrder": "Создать заявку",
|
||||
"pleaseLogin": "Пожалуйста, войдите",
|
||||
"subscriptionValid": "Подписка активна",
|
||||
"startTime": "Время начала:",
|
||||
"expireTime": "Срок действия:",
|
||||
"loginNow": "Войти сейчас",
|
||||
"trialPeriod": "Добро пожаловать в пробную версию Premium",
|
||||
"remainingTime": "Осталось времени",
|
||||
"trialExpired": "Пробный период истёк, соединение разорвано",
|
||||
"subscriptionExpired": "Подписка истекла, соединение разорвано",
|
||||
"switchSubscription": "Сменить подписку",
|
||||
"resetTrafficTitle": "Сброс трафика",
|
||||
"resetTrafficMessage": "Пример сброса трафика месячного плана: сброс трафика следующего цикла ежемесячно, и срок действия подписки будет перенесен с {currentTime} на {newTime}",
|
||||
"reset": "Сбросить",
|
||||
"trafficUsage": "Использовано: {used} / {total}",
|
||||
"trafficProgress": {
|
||||
"title": "Использование трафика",
|
||||
"unlimited": "Безлимитный трафик",
|
||||
"limited": "Использованный трафик"
|
||||
},
|
||||
"deviceLimit": "Лимит устройств: {count}",
|
||||
"copySuccess": "Скопировано успешно",
|
||||
"notAvailable": "Недоступно",
|
||||
"willBeDeleted": "Будет удалено",
|
||||
"deleteAccountWarning": "Удаление аккаунта необратимо. После удаления аккаунта вы не сможете использовать все функции. Продолжить?",
|
||||
"requestDelete": "Запросить удаление",
|
||||
"trialStatus": "Статус пробного периода",
|
||||
"trialing": "Пробный период",
|
||||
"trialEndMessage": "После окончания пробного периода использование будет невозможно",
|
||||
"lastDaySubscriptionStatus": "Подписка скоро истечет",
|
||||
"lastDaySubscriptionMessage": "Скоро истечет",
|
||||
"subscriptionEndMessage": "После окончания подписки использование будет невозможно",
|
||||
"trialTimeWithDays": "{days}д {hours}ч {minutes}м {seconds}с",
|
||||
"trialTimeWithHours": "{hours}ч {minutes}м {seconds}с",
|
||||
"trialTimeWithMinutes": "{minutes}м {seconds}с",
|
||||
"refreshLatency": "Обновить задержку",
|
||||
"testLatency": "Тест задержки",
|
||||
"testing": "Тестирование задержки",
|
||||
"refreshLatencyDesc": "Обновить задержку всех узлов",
|
||||
"testAllNodesLatency": "Тест сетевой задержки всех узлов",
|
||||
"autoSelect": "Автоматический выбор",
|
||||
"selected": "Выбрано"
|
||||
},
|
||||
"setting": {
|
||||
"title": "Настройки",
|
||||
"countrySelector": "Выбрать страну",
|
||||
"vpnConnection": "VPN-подключение",
|
||||
"general": "Общие",
|
||||
"autoConnect": "Автоподключение",
|
||||
"routeRule": "Правила маршрутизации",
|
||||
"appearance": "Внешний вид",
|
||||
"notifications": "Уведомления",
|
||||
"helpImprove": "Помогите нам улучшить",
|
||||
"helpImproveSubtitle": "Подзаголовок помощи в улучшении",
|
||||
"requestDeleteAccount": "Запросить удаление аккаунта",
|
||||
"goToDelete": "Перейти к удалению",
|
||||
"rateUs": "Оцените нас в App Store",
|
||||
"iosRating": "Оценка iOS",
|
||||
"version": "Версия",
|
||||
"switchLanguage": "Сменить язык",
|
||||
"system": "Система",
|
||||
"light": "Светлая",
|
||||
"dark": "Тёмная",
|
||||
"vpnModeSmart": "Умный режим",
|
||||
"mode": "Исходящий режим",
|
||||
"connectionTypeGlobal": "Глобальный прокси",
|
||||
"connectionTypeGlobalRemark": "При включении весь трафик проходит через прокси",
|
||||
"connectionTypeRule": "Умный прокси",
|
||||
"connectionTypeRuleRemark": "Когда [Исходящий режим] установлен на [Умный прокси], система автоматически разделяет внутренний и международный трафик в соответствии с выбранной страной: внутренние IP/домены подключаются напрямую, а зарубежные запросы проходят через прокси",
|
||||
"connectionTypeDirect": "Прямое подключение",
|
||||
"connectionTypeDirectRemark": "При включении весь трафик обходит прокси",
|
||||
"smartMode": "Умный режим",
|
||||
"secureMode": "Безопасный режим"
|
||||
},
|
||||
"statistics": {
|
||||
"title": "Статистика",
|
||||
"vpnStatus": "Статус VPN",
|
||||
"ipAddress": "IP-адрес",
|
||||
"connectionTime": "Время подключения",
|
||||
"protocol": "Протокол",
|
||||
"weeklyProtectionTime": "Время защиты за неделю",
|
||||
"currentStreak": "Текущая серия",
|
||||
"highestStreak": "Рекорд",
|
||||
"longestConnection": "Самое долгое подключение",
|
||||
"days": "{days} дн.",
|
||||
"daysOfWeek": {
|
||||
"monday": "Пн",
|
||||
"tuesday": "Вт",
|
||||
"wednesday": "Ср",
|
||||
"thursday": "Чт",
|
||||
"friday": "Пт",
|
||||
"saturday": "Сб",
|
||||
"sunday": "Вс"
|
||||
}
|
||||
},
|
||||
"message": {
|
||||
"title": "Уведомления",
|
||||
"system": "Системные сообщения",
|
||||
"promotion": "Рекламные сообщения"
|
||||
},
|
||||
"invite": {
|
||||
"title": "Пригласить друзей",
|
||||
"progress": "Прогресс приглашений",
|
||||
"inviteStats": "Статистика приглашений",
|
||||
"registers": "Зарегистрировано",
|
||||
"totalCommission": "Общая комиссия",
|
||||
"rewardDetails": "Детали вознаграждения >",
|
||||
"steps": "Шаги Приглашения",
|
||||
"inviteFriend": "Пригласить Друзей",
|
||||
"acceptInvite": "Друзья принимают приглашение",
|
||||
"getReward": "Получить Вознаграждение",
|
||||
"shareLink": "Поделиться Ссылкой",
|
||||
"shareQR": "Поделиться QR-кодом",
|
||||
"rules": "Правила Приглашения",
|
||||
"rule1": "1. Вы можете приглашать друзей присоединиться к нам, поделившись своей уникальной ссылкой или кодом приглашения.",
|
||||
"rule2": "2. После того, как друзья завершат регистрацию и войдут в систему, вознаграждения за приглашение будут автоматически зачислены на ваш счет.",
|
||||
"pending": "В Ожидании",
|
||||
"processing": "В Обработке",
|
||||
"success": "Успешно",
|
||||
"expired": "Истекло",
|
||||
"myInviteCode": "Мой Код Приглашения",
|
||||
"inviteCodeCopied": "Код приглашения скопирован в буфер обмена",
|
||||
"close": "Закрыть",
|
||||
"saveQRCode": "Сохранить QR-код",
|
||||
"qrCodeSaved": "QR-код сохранен",
|
||||
"copiedToClipboard": "Скопировано в буфер обмена",
|
||||
"getInviteCodeFailed": "Не удалось получить код приглашения, попробуйте позже",
|
||||
"generateQRCodeFailed": "Не удалось сгенерировать QR-код, попробуйте позже",
|
||||
"generateShareLinkFailed": "Не удалось сгенерировать ссылку для общего доступа, попробуйте позже"
|
||||
},
|
||||
"purchaseMembership": {
|
||||
"purchasePackage": "Купить Пакет",
|
||||
"noData": "Нет доступных пакетов",
|
||||
"myAccount": "Мой Аккаунт",
|
||||
"selectPackage": "Выбрать Пакет",
|
||||
"packageDescription": "Описание Пакета",
|
||||
"paymentMethod": "Способ Оплаты",
|
||||
"cancelAnytime": "Вы можете отменить в любое время в приложении",
|
||||
"startSubscription": "Начать Подписку",
|
||||
"subscriptionPrivacyInfo": "Информация о Подписке и Конфиденциальности",
|
||||
"month": "{months} Месяц(ев)",
|
||||
"year": "{years} Год(а)",
|
||||
"day": "{days} день",
|
||||
"unlimitedTraffic": "Безлимитный трафик",
|
||||
"unlimitedDevices": "Безлимитные устройства",
|
||||
"devices": "{count} устройств",
|
||||
"trafficLimit": "Ограничение трафика",
|
||||
"deviceLimit": "Лимит устройств: {count}",
|
||||
"features": "Функции пакета",
|
||||
"expand": "Развернуть",
|
||||
"collapse": "Свернуть",
|
||||
"confirmPurchase": "Подтвердить покупку",
|
||||
"confirmPurchaseDesc": "Вы уверены, что хотите приобрести этот пакет?"
|
||||
},
|
||||
"orderStatus": {
|
||||
"title": "Статус заказа",
|
||||
"pending": {
|
||||
"title": "Ожидание оплаты",
|
||||
"description": "Пожалуйста, завершите оплату"
|
||||
},
|
||||
"paid": {
|
||||
"title": "Оплата получена",
|
||||
"description": "Обработка вашего заказа"
|
||||
},
|
||||
"success": {
|
||||
"title": "Поздравляем! Оплата успешна",
|
||||
"description": "Ваш пакет успешно приобретен"
|
||||
},
|
||||
"closed": {
|
||||
"title": "Заказ закрыт",
|
||||
"description": "Пожалуйста, сделайте новый заказ"
|
||||
},
|
||||
"failed": {
|
||||
"title": "Ошибка оплаты",
|
||||
"description": "Пожалуйста, попробуйте оплату снова"
|
||||
},
|
||||
"unknown": {
|
||||
"title": "Неизвестный статус",
|
||||
"description": "Пожалуйста, свяжитесь со службой поддержки"
|
||||
},
|
||||
"checkFailed": {
|
||||
"title": "Ошибка проверки",
|
||||
"description": "Пожалуйста, попробуйте позже"
|
||||
},
|
||||
"initial": {
|
||||
"title": "Обработка оплаты",
|
||||
"description": "Пожалуйста, подождите, пока мы обрабатываем вашу оплату"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"welcome": "Добро пожаловать в Hi快VPN",
|
||||
"disconnected": "Не подключено",
|
||||
"connecting": "Подключение",
|
||||
"connected": "Подключено",
|
||||
"disconnecting": "Отключение",
|
||||
"currentConnectionTitle": "Текущее подключение",
|
||||
"switchNode": "Сменить узел",
|
||||
"timeout": "Тайм-аут",
|
||||
"loading": "Загрузка...",
|
||||
"error": "Ошибка загрузки",
|
||||
"checkNetwork": "Проверьте подключение к сети и повторите попытку",
|
||||
"retry": "Повторить",
|
||||
"connectionSectionTitle": "Способ подключения",
|
||||
"dedicatedServers": "Выделенные серверы",
|
||||
"countryRegion": "Страна/Регион",
|
||||
"serverListTitle": "Группы выделенных серверов",
|
||||
"nodeListTitle": "Все узлы",
|
||||
"countryListTitle": "Список стран/регионов",
|
||||
"noServers": "Нет доступных серверов",
|
||||
"noNodes": "Нет доступных узлов",
|
||||
"noRegions": "Нет доступных регионов",
|
||||
"subscriptionDescription": "Подпишитесь для доступа к глобальной высокоскоростной сети",
|
||||
"subscribe": "Подписаться сейчас",
|
||||
"trialPeriod": "Добро пожаловать в пробную версию Premium",
|
||||
"remainingTime": "Осталось времени",
|
||||
"trialExpired": "Пробный период истёк, соединение разорвано",
|
||||
"subscriptionExpired": "Подписка истекла, соединение разорвано",
|
||||
"subscriptionUpdated": "Подписка обновлена",
|
||||
"subscriptionUpdatedMessage": "Ваша информация о подписке обновлена, пожалуйста, обновите страницу для просмотра последнего статуса",
|
||||
"trialStatus": "Статус пробного периода",
|
||||
"trialing": "Пробный период",
|
||||
"trialEndMessage": "После окончания пробного периода сервис будет недоступен",
|
||||
"lastDaySubscriptionStatus": "Подписка скоро истекает",
|
||||
"lastDaySubscriptionMessage": "Скоро истекает",
|
||||
"subscriptionEndMessage": "После окончания подписки сервис будет недоступен",
|
||||
"trialTimeWithDays": "{days}д {hours}ч {minutes}м {seconds}с",
|
||||
"trialTimeWithHours": "{hours}ч {minutes}м {seconds}с",
|
||||
"trialTimeWithMinutes": "{minutes}м {seconds}с",
|
||||
"testLatency": "Тест задержки",
|
||||
"testing": "Тестирование задержки",
|
||||
"refreshLatency": "Обновить задержку",
|
||||
"refreshLatencyDesc": "Обновить задержку всех узлов",
|
||||
"testAllNodesLatency": "Тест сетевой задержки всех узлов",
|
||||
"autoSelect": "Автоматический выбор",
|
||||
"selected": "Выбрано"
|
||||
},
|
||||
"dialog": {
|
||||
"confirm": "Подтвердить",
|
||||
"cancel": "Отмена",
|
||||
"ok": "OK",
|
||||
"iKnow": "Я понял"
|
||||
},
|
||||
"update": {
|
||||
"title": "Доступна новая версия",
|
||||
"content": "Хотите обновить сейчас?",
|
||||
"updateNow": "Обновить сейчас",
|
||||
"later": "Позже",
|
||||
"defaultContent": "1. Оптимизация производительности приложения\n2. Исправление известных проблем\n3. Улучшение пользовательского опыта"
|
||||
},
|
||||
"country": {
|
||||
"cn": "Китай",
|
||||
"ir": "Иран",
|
||||
"af": "Афганистан",
|
||||
"ru": "Россия",
|
||||
"id": "Индонезия",
|
||||
"tr": "Турция",
|
||||
"br": "Бразилия"
|
||||
},
|
||||
"error": {
|
||||
"200": "Успех",
|
||||
"500": "Внутренняя ошибка сервера",
|
||||
"10001": "Ошибка запроса базы данных",
|
||||
"10002": "Ошибка обновления базы данных",
|
||||
"10003": "Ошибка вставки в базу данных",
|
||||
"10004": "Ошибка удаления из базы данных",
|
||||
"20001": "Пользователь уже существует",
|
||||
"20002": "Пользователь не существует",
|
||||
"20003": "Ошибка пароля пользователя",
|
||||
"20004": "Пользователь отключен",
|
||||
"20005": "Недостаточно средств",
|
||||
"20006": "Регистрация остановлена",
|
||||
"20007": "Telegram не привязан",
|
||||
"20008": "Пользователь не привязал метод OAuth",
|
||||
"20009": "Ошибка кода приглашения",
|
||||
"30001": "Узел уже существует",
|
||||
"30002": "Узел не существует",
|
||||
"30003": "Группа узлов уже существует",
|
||||
"30004": "Группа узлов не существует",
|
||||
"30005": "Группа узлов не пуста",
|
||||
"400": "Ошибка параметра",
|
||||
"40002": "Токен пользователя пуст",
|
||||
"40003": "Токен пользователя недействителен",
|
||||
"40004": "Срок действия токена пользователя истек",
|
||||
"40005": "Недопустимый доступ",
|
||||
"401": "Слишком много запросов",
|
||||
"50001": "Купон не существует",
|
||||
"50002": "Купон был использован",
|
||||
"50003": "Купон не совпадает",
|
||||
"60001": "Подписка истекла",
|
||||
"60002": "Подписка недоступна",
|
||||
"60003": "У пользователя уже есть подписка",
|
||||
"60004": "Подписка уже использована",
|
||||
"60005": "Режим одной подписки превышает лимит",
|
||||
"60006": "Лимит квоты подписки",
|
||||
"70001": "Ошибка кода подтверждения",
|
||||
"80001": "Ошибка постановки в очередь",
|
||||
"90001": "Режим отладки включен",
|
||||
"90002": "Ошибка отправки SMS",
|
||||
"90003": "SMS не включены",
|
||||
"90004": "Электронная почта не включена",
|
||||
"90005": "Неподдерживаемый метод входа",
|
||||
"90006": "Аутентификатор не поддерживает этот метод",
|
||||
"90007": "Код телефонного региона пуст",
|
||||
"90008": "Пароль пуст",
|
||||
"90009": "Код региона пуст",
|
||||
"90010": "Требуется пароль или код подтверждения",
|
||||
"90011": "Электронная почта уже существует",
|
||||
"90012": "Телефон уже существует",
|
||||
"90013": "Устройство уже существует",
|
||||
"90014": "Ошибка номера телефона",
|
||||
"90015": "Этот аккаунт достиг лимита отправки на сегодня",
|
||||
"90017": "Устройство не существует",
|
||||
"90018": "ID пользователя не совпадает",
|
||||
"61001": "Заказ не существует",
|
||||
"61002": "Способ оплаты не найден",
|
||||
"61003": "Ошибка статуса заказа",
|
||||
"61004": "Недостаточный период сброса",
|
||||
"61005": "Существует неиспользованный трафик"
|
||||
},
|
||||
"tray": {
|
||||
"open_dashboard": "Открыть панель",
|
||||
"copy_to_terminal": "Копировать в терминал",
|
||||
"exit_app": "Выйти из приложения"
|
||||
},
|
||||
"splash": {
|
||||
"appName": "Hi快VPN",
|
||||
"slogan": "Высокоскоростная глобальная сеть",
|
||||
"initializing": "Инициализация...",
|
||||
"networkConnectionFailure": "Ошибка подключения к сети, проверьте и повторите попытку",
|
||||
"retry": "Повторить",
|
||||
"networkPermissionFailed": "Не удалось получить разрешение на использование сети",
|
||||
"initializationFailed": "Ошибка инициализации"
|
||||
},
|
||||
"network": {
|
||||
"status": {
|
||||
"connected": "Подключено",
|
||||
"disconnected": "Отключено",
|
||||
"connecting": "Подключение...",
|
||||
"disconnecting": "Отключение...",
|
||||
"reconnecting": "Переподключение...",
|
||||
"failed": "Ошибка подключения"
|
||||
},
|
||||
"permission": {
|
||||
"title": "Разрешение сети",
|
||||
"description": "Требуется разрешение на использование сети для предоставления VPN-сервиса",
|
||||
"goToSettings": "Перейти в настройки",
|
||||
"cancel": "Отмена"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -56,6 +56,7 @@
|
||||
"enterEmail": "请输入电子邮箱",
|
||||
"enterCode": "请输入验证码",
|
||||
"enterPassword": "请输入密码",
|
||||
"enterPasswordForPhone": "请输入密码 (密码登录)",
|
||||
"reenterPassword": "请再次输入密码",
|
||||
"forgotPassword": "忘记密码",
|
||||
"codeLogin": "验证码登录",
|
||||
@ -65,6 +66,8 @@
|
||||
"privacyPolicy": "隐私政策",
|
||||
"next": "下一步",
|
||||
"registerNow": "立即注册",
|
||||
"registerAccount": "注册账号",
|
||||
"clickToRegister": "点我注册",
|
||||
"setAndLogin": "设置并登录",
|
||||
"enterAccount": "请输入账户",
|
||||
"passwordMismatch": "两次密码输入不一致",
|
||||
@ -73,6 +76,8 @@
|
||||
"and": "和",
|
||||
"enterInviteCode": "请输入邀请码",
|
||||
"registerSuccess": "注册成功"
|
||||
"search": "搜索",
|
||||
"selectOtherRegion": "选择其他地区"
|
||||
},
|
||||
"failure": {
|
||||
"unexpected": "意外错误",
|
||||
@ -234,7 +239,8 @@
|
||||
"friday": "周\n五",
|
||||
"saturday": "周\n六",
|
||||
"sunday": "周\n日"
|
||||
}
|
||||
},
|
||||
"processTrafficFailed": "处理流量日志数据失败"
|
||||
},
|
||||
"message": {
|
||||
"title": "通知",
|
||||
@ -264,6 +270,7 @@
|
||||
"myInviteCode": "我的邀请码",
|
||||
"inviteCodeCopied": "邀请码已复制到剪贴板",
|
||||
"close": "关闭",
|
||||
"pleaseLoginFirst": "请先登录后查看邀请码",
|
||||
"saveQRCode": "保存二维码",
|
||||
"qrCodeSaved": "二维码已保存",
|
||||
"copiedToClipboard": "已复制到剪贴板",
|
||||
@ -383,6 +390,7 @@
|
||||
"initializing": "正在初始化...",
|
||||
"networkConnectionFailure": "网络连接失败,请检查并重试",
|
||||
"retry": "重试",
|
||||
"skip": "跳过",
|
||||
"networkPermissionFailed": "获取网络权限失败",
|
||||
"initializationFailed": "初始化失败"
|
||||
},
|
||||
@ -483,5 +491,33 @@
|
||||
"open_dashboard": "打开面板",
|
||||
"copy_to_terminal": "复制到终端",
|
||||
"exit_app": "退出应用"
|
||||
},
|
||||
"crisp": {
|
||||
"initializingSystem": "正在初始化客服系统...",
|
||||
"initFailed": "客服系统初始化失败"
|
||||
},
|
||||
"common": {
|
||||
"retry": "重试",
|
||||
"tryBrowser": "尝试使用浏览器打开",
|
||||
"cannotOpenBrowser": "无法打开浏览器",
|
||||
"openLinkFailed": "打开链接失败,请稍后重试",
|
||||
"cannotOpenTelegram": "无法打开Telegram链接"
|
||||
},
|
||||
"subscribe": {
|
||||
"pleaseSelectFirst": "请先选择订阅",
|
||||
"resetPeriodFailed": "重置订阅周期失败",
|
||||
"resetPeriodSuccess": "重置订阅周期成功"
|
||||
},
|
||||
"purchase": {
|
||||
"noAvailablePlans": "没有可用的套餐"
|
||||
},
|
||||
"payment": {
|
||||
"cannotOpenLink": "无法打开支付链接",
|
||||
"linkEmpty": "支付链接为空",
|
||||
"unsupportedType": "不支持的支付类型"
|
||||
},
|
||||
"account": {
|
||||
"accountRequired": "账号不能为空",
|
||||
"deleteSuccess": "删除账号成功"
|
||||
}
|
||||
}
|
||||
@ -9,6 +9,7 @@
|
||||
"enterEmail": "請輸入電子郵箱",
|
||||
"enterCode": "請輸入驗證碼",
|
||||
"enterPassword": "請輸入密碼",
|
||||
"enterPasswordForPhone": "請輸入密碼 (密碼登錄)",
|
||||
"reenterPassword": "請再次輸入密碼",
|
||||
"forgotPassword": "忘記密碼",
|
||||
"codeLogin": "驗證碼登錄",
|
||||
@ -18,6 +19,8 @@
|
||||
"privacyPolicy": "隱私政策",
|
||||
"next": "下一步",
|
||||
"registerNow": "立即註冊",
|
||||
"registerAccount": "註冊賬號",
|
||||
"clickToRegister": "點我註冊",
|
||||
"setAndLogin": "設置並登錄",
|
||||
"enterAccount": "請輸入賬戶",
|
||||
"passwordMismatch": "兩次密碼輸入不一致",
|
||||
@ -25,7 +28,9 @@
|
||||
"codeSentCountdown": "驗證碼已發送 {seconds}s",
|
||||
"and": "和",
|
||||
"enterInviteCode": "請輸入邀請碼(選填)",
|
||||
"registerSuccess": "註冊成功"
|
||||
"registerSuccess": "註冊成功",
|
||||
"search": "搜尋",
|
||||
"selectOtherRegion": "選擇其他地區"
|
||||
},
|
||||
"failure": {
|
||||
"unexpected": "意外錯誤",
|
||||
@ -172,7 +177,8 @@
|
||||
"friday": "週\n五",
|
||||
"saturday": "週\n六",
|
||||
"sunday": "週\n日"
|
||||
}
|
||||
},
|
||||
"processTrafficFailed": "處理流量日誌資料失敗"
|
||||
},
|
||||
"message": {
|
||||
"title": "通知",
|
||||
@ -448,6 +454,7 @@
|
||||
"initializing": "正在初始化...",
|
||||
"networkConnectionFailure": "網絡連接失敗,請檢查並重試",
|
||||
"retry": "重試",
|
||||
"skip": "跳過",
|
||||
"networkPermissionFailed": "獲取網絡權限失敗",
|
||||
"initializationFailed": "初始化失敗"
|
||||
},
|
||||
@ -466,5 +473,33 @@
|
||||
"goToSettings": "前往設置",
|
||||
"cancel": "取消"
|
||||
}
|
||||
},
|
||||
"crisp": {
|
||||
"initializingSystem": "正在初始化客服系統...",
|
||||
"initFailed": "客服系統初始化失敗"
|
||||
},
|
||||
"common": {
|
||||
"retry": "重試",
|
||||
"tryBrowser": "嘗試使用瀏覽器開啟",
|
||||
"cannotOpenBrowser": "無法開啟瀏覽器",
|
||||
"openLinkFailed": "開啟連結失敗,請稍後重試",
|
||||
"cannotOpenTelegram": "無法開啟Telegram連結"
|
||||
},
|
||||
"subscribe": {
|
||||
"pleaseSelectFirst": "請先選擇訂閱",
|
||||
"resetPeriodFailed": "重置訂閱週期失敗",
|
||||
"resetPeriodSuccess": "重置訂閱週期成功"
|
||||
},
|
||||
"purchase": {
|
||||
"noAvailablePlans": "沒有可用的套餐"
|
||||
},
|
||||
"payment": {
|
||||
"cannotOpenLink": "無法開啟支付連結",
|
||||
"linkEmpty": "支付連結為空",
|
||||
"unsupportedType": "不支援的支付類型"
|
||||
},
|
||||
"account": {
|
||||
"accountRequired": "帳號不能為空",
|
||||
"deleteSuccess": "刪除帳號成功"
|
||||
}
|
||||
}
|
||||
@ -1,426 +0,0 @@
|
||||
{
|
||||
"login": {
|
||||
"welcome": "歡迎使用 Hi快VPN!",
|
||||
"verifyPhone": "驗證您的手機號",
|
||||
"verifyEmail": "驗證您的郵箱",
|
||||
"codeSent": "已向 {account} 發送6位數代碼。請在接下來的 30 分鐘內輸入。",
|
||||
"back": "返回",
|
||||
"enterEmailOrPhone": "輸入郵箱或者手機號",
|
||||
"enterCode": "請輸入驗證碼",
|
||||
"enterPassword": "請輸入密碼",
|
||||
"reenterPassword": "請再次輸入密碼",
|
||||
"forgotPassword": "忘記密碼",
|
||||
"codeLogin": "驗證碼登錄",
|
||||
"passwordLogin": "密碼登錄",
|
||||
"agreeTerms": "登錄/創建賬戶,即表示我同意",
|
||||
"termsOfService": "服務條款",
|
||||
"privacyPolicy": "隱私政策",
|
||||
"next": "下一步",
|
||||
"registerNow": "立即註冊",
|
||||
"setAndLogin": "設置並登錄",
|
||||
"enterAccount": "請輸入賬戶",
|
||||
"passwordMismatch": "兩次密碼輸入不一致",
|
||||
"sendCode": "發送驗證碼",
|
||||
"codeSentCountdown": "驗證碼已發送 {seconds}s",
|
||||
"and": "和",
|
||||
"enterInviteCode": "請輸入邀請碼(選填)",
|
||||
"registerSuccess": "註冊成功"
|
||||
},
|
||||
"failure": {
|
||||
"unexpected": "意外錯誤",
|
||||
"clash": {
|
||||
"unexpected": "意外錯誤",
|
||||
"core": "Clash 錯誤 ${reason}"
|
||||
},
|
||||
"singbox": {
|
||||
"unexpected": "意外服務錯誤",
|
||||
"serviceNotRunning": "服務未運行",
|
||||
"missingPrivilege": "缺少權限",
|
||||
"missingPrivilegeMsg": "VPN 模式需要管理員權限。以管理員身份重新啟動應用程序或更改服務模式",
|
||||
"missingGeoAssets": "缺失 GEO 資源文件",
|
||||
"missingGeoAssetsMsg": "缺失 GEO 資源文件。請考慮更改激活的資源文件或在設置中下載所選資源文件。",
|
||||
"invalidConfigOptions": "配置選項無效",
|
||||
"invalidConfig": "無效配置",
|
||||
"create": "服務創建錯誤",
|
||||
"start": "服務啟動錯誤"
|
||||
},
|
||||
"connectivity": {
|
||||
"unexpected": "意外失敗",
|
||||
"missingVpnPermission": "缺少 VPN 權限",
|
||||
"missingNotificationPermission": "缺少通知權限",
|
||||
"core": "核心錯誤"
|
||||
},
|
||||
"profiles": {
|
||||
"unexpected": "意外錯誤",
|
||||
"notFound": "未找到配置文件",
|
||||
"invalidConfig": "無效配置",
|
||||
"invalidUrl": "網址無效"
|
||||
},
|
||||
"connection": {
|
||||
"unexpected": "意外連接錯誤",
|
||||
"timeout": "連接超時",
|
||||
"badResponse": "錯誤響應",
|
||||
"connectionError": "連接錯誤",
|
||||
"badCertificate": "證書無效"
|
||||
},
|
||||
"geoAssets": {
|
||||
"unexpected": "意外錯誤",
|
||||
"notUpdate": "無可用更新",
|
||||
"activeNotFound": "未找到激活的 GEO 資源文件"
|
||||
}
|
||||
},
|
||||
"userInfo": {
|
||||
"title": "我的信息",
|
||||
"bindingTip": "未綁定郵箱/手機號",
|
||||
"myAccount": "我的賬號",
|
||||
"balance": "餘額",
|
||||
"noValidSubscription": "您沒有有效的訂閱",
|
||||
"subscribeNow": "立即訂閱",
|
||||
"shortcuts": "快捷鍵",
|
||||
"adBlock": "廣告攔截",
|
||||
"dnsUnlock": "DNS 解鎖",
|
||||
"contactUs": "聯繫我們",
|
||||
"others": "其他",
|
||||
"logout": "退出登錄",
|
||||
"logoutConfirmTitle": "退出登錄",
|
||||
"logoutConfirmMessage": "確定要退出登錄嗎?",
|
||||
"logoutCancel": "取消",
|
||||
"vpnWebsite": "VPN 官網",
|
||||
"telegram": "Telegram",
|
||||
"mail": "郵箱",
|
||||
"phone": "電話",
|
||||
"customerService": "人工客服",
|
||||
"workOrder": "填寫工單",
|
||||
"pleaseLogin": "請先登錄賬號",
|
||||
"subscriptionValid": "訂閱有效",
|
||||
"startTime": "開始時間:",
|
||||
"expireTime": "到期時間:",
|
||||
"loginNow": "立即登錄",
|
||||
"trialPeriod": "歡迎使用高級試用版",
|
||||
"remainingTime": "剩餘時間",
|
||||
"trialExpired": "試用期已結束,連接已斷開",
|
||||
"subscriptionExpired": "訂閱已過期,連接已斷開",
|
||||
"copySuccess": "複製成功",
|
||||
"notAvailable": "暫無",
|
||||
"willBeDeleted": "將被刪除",
|
||||
"deleteAccountWarning": "賬號刪除是永久性的。一旦您的賬號被刪除,您將無法使用任何功能。是否繼續?",
|
||||
"requestDelete": "請求刪除",
|
||||
"switchSubscription": "切換訂閱",
|
||||
"resetTrafficTitle": "重置流量",
|
||||
"resetTrafficMessage": "月付套餐流量重置示例:將下一個週期的流量按月重置,訂閱有效期將從{currentTime}提前至{newTime}",
|
||||
"reset": "重置",
|
||||
"trafficUsage": "已用: {used} / {total}",
|
||||
"trafficProgress": {
|
||||
"title": "流量使用情況",
|
||||
"unlimited": "不限流量",
|
||||
"limited": "已用流量"
|
||||
},
|
||||
"deviceLimit": "設備限制: {count}"
|
||||
},
|
||||
"setting": {
|
||||
"title": "設置",
|
||||
"vpnConnection": "VPN連接",
|
||||
"general": "通用",
|
||||
"autoConnect": "自動連接",
|
||||
"routeRule": "路由規則",
|
||||
"countrySelector": "選擇國家",
|
||||
"appearance": "外觀",
|
||||
"notifications": "通知",
|
||||
"helpImprove": "幫助我們改進",
|
||||
"helpImproveSubtitle": "幫助我們改進的副標題",
|
||||
"requestDeleteAccount": "請求刪除賬號",
|
||||
"goToDelete": "去刪除",
|
||||
"rateUs": "在 App Store 上為我們評分",
|
||||
"iosRating": "iOS評分",
|
||||
"version": "版本",
|
||||
"switchLanguage": "切換語言",
|
||||
"system": "系統",
|
||||
"light": "淺色",
|
||||
"dark": "深色",
|
||||
"vpnModeSmart": "智能模式",
|
||||
"mode": "出站模式",
|
||||
"connectionTypeGlobal": "全域代理",
|
||||
"connectionTypeGlobalRemark": "啟用後,所有流量均通過代理伺服器轉發",
|
||||
"connectionTypeRule": "智能代理",
|
||||
"connectionTypeRuleRemark": "當[出站模式]設置為[智能代理]時,根據所選國家,系統自動分流:國內IP/域名直連,境外請求透過代理訪問",
|
||||
"connectionTypeDirect": "直連",
|
||||
"connectionTypeDirectRemark": "啟用後,所有流量均不經代理直接訪問",
|
||||
"smartMode": "智能模式",
|
||||
"secureMode": "安全模式",
|
||||
"deviceLimit": "設備限制: {count}"
|
||||
},
|
||||
"statistics": {
|
||||
"title": "統計",
|
||||
"vpnStatus": "VPN 狀態",
|
||||
"ipAddress": "IP地址",
|
||||
"connectionTime": "連接時間",
|
||||
"protocol": "協議",
|
||||
"weeklyProtectionTime": "每週保護時間",
|
||||
"currentStreak": "當前連續記錄",
|
||||
"highestStreak": "最高記錄",
|
||||
"longestConnection": "最長連接時間",
|
||||
"days": "{days}天",
|
||||
"daysOfWeek": {
|
||||
"monday": "週\n一",
|
||||
"tuesday": "週\n二",
|
||||
"wednesday": "週\n三",
|
||||
"thursday": "週\n四",
|
||||
"friday": "週\n五",
|
||||
"saturday": "週\n六",
|
||||
"sunday": "週\n日"
|
||||
}
|
||||
},
|
||||
"message": {
|
||||
"title": "通知",
|
||||
"system": "系統消息",
|
||||
"promotion": "促銷消息"
|
||||
},
|
||||
"invite": {
|
||||
"title": "邀請好友",
|
||||
"progress": "邀請進度",
|
||||
"inviteStats": "邀請統計",
|
||||
"registers": "已註冊",
|
||||
"totalCommission": "總佣金",
|
||||
"rewardDetails": "獎勵明細 >",
|
||||
"steps": "邀請步驟",
|
||||
"inviteFriend": "邀請好友",
|
||||
"acceptInvite": "好友接受邀請\n下單並註冊",
|
||||
"getReward": "獲得獎勵",
|
||||
"shareLink": "分享連結",
|
||||
"shareQR": "分享二維碼",
|
||||
"rules": "邀請規則",
|
||||
"rule1": "1、您可以通過分享專屬邀請連結或邀請碼給好友,邀請他們加入我們。",
|
||||
"rule2": "2、好友完成註冊並登錄後,邀請獎勵將自動發放至您的賬戶。",
|
||||
"pending": "待下載",
|
||||
"processing": "在路上",
|
||||
"success": "已成功",
|
||||
"expired": "已失效",
|
||||
"myInviteCode": "我的邀請碼",
|
||||
"inviteCodeCopied": "邀請碼已複製到剪貼板",
|
||||
"close": "關閉",
|
||||
"saveQRCode": "保存二維碼",
|
||||
"qrCodeSaved": "二維碼已保存",
|
||||
"copiedToClipboard": "已複製到剪貼板",
|
||||
"getInviteCodeFailed": "獲取邀請碼失敗,請稍後重試",
|
||||
"generateQRCodeFailed": "生成二維碼失敗,請稍後重試",
|
||||
"generateShareLinkFailed": "生成分享連結失敗,請稍後重試"
|
||||
},
|
||||
"purchaseMembership": {
|
||||
"purchasePackage": "購買套餐",
|
||||
"noData": "暫無可用套餐",
|
||||
"myAccount": "我的賬號",
|
||||
"selectPackage": "選擇套餐",
|
||||
"packageDescription": "套餐描述",
|
||||
"paymentMethod": "支付方式",
|
||||
"cancelAnytime": "您可以隨時在APP上取消",
|
||||
"startSubscription": "開始訂閱",
|
||||
"renewNow": "立即續訂",
|
||||
"month": "{months}個月",
|
||||
"year": "{years}年",
|
||||
"day": "{days}天",
|
||||
"unlimitedTraffic": "不限流量",
|
||||
"unlimitedDevices": "不限設備",
|
||||
"devices": "{count}台",
|
||||
"trafficLimit": "流量限制",
|
||||
"deviceLimit": "設備限制",
|
||||
"features": "套餐特性",
|
||||
"expand": "展開",
|
||||
"collapse": "收起",
|
||||
"confirmPurchase": "確認購買",
|
||||
"confirmPurchaseDesc": "您確定要購買此套餐嗎?"
|
||||
},
|
||||
"home": {
|
||||
"welcome": "歡迎使用 Hi快VPN",
|
||||
"disconnected": "已斷開連接",
|
||||
"connecting": "正在連接",
|
||||
"connected": "已連接",
|
||||
"disconnecting": "正在斷開連接",
|
||||
"currentConnectionTitle": "當前連接",
|
||||
"switchNode": "切換節點",
|
||||
"timeout": "超時",
|
||||
"loading": "載入中...",
|
||||
"error": "載入失敗",
|
||||
"checkNetwork": "請檢查網絡連接並重試",
|
||||
"retry": "重試",
|
||||
"connectionSectionTitle": "連接方式",
|
||||
"dedicatedServers": "專用伺服器",
|
||||
"countryRegion": "國家/地區",
|
||||
"serverListTitle": "專用伺服器群組",
|
||||
"nodeListTitle": "所有節點",
|
||||
"countryListTitle": "國家/地區列表",
|
||||
"noServers": "暫無可用伺服器",
|
||||
"noNodes": "暫無可用節點",
|
||||
"noRegions": "暫無可用地區",
|
||||
"subscriptionDescription": "訂閱會員,暢享全球高速網絡",
|
||||
"subscribe": "立即訂閱",
|
||||
"trialPeriod": "歡迎使用 Premium 試用版",
|
||||
"remainingTime": "剩餘時間",
|
||||
"trialExpired": "試用期已結束,已斷開連接",
|
||||
"subscriptionExpired": "訂閱已過期,已斷開連接",
|
||||
"subscriptionUpdated": "訂閱已更新",
|
||||
"subscriptionUpdatedMessage": "您的訂閱信息已更新,請刷新頁面查看最新狀態",
|
||||
"trialStatus": "試用狀態",
|
||||
"trialing": "試用中",
|
||||
"trialEndMessage": "試用期結束後將無法使用",
|
||||
"lastDaySubscriptionStatus": "訂閱即將到期",
|
||||
"lastDaySubscriptionMessage": "即將到期",
|
||||
"subscriptionEndMessage": "訂閱到期後將無法使用",
|
||||
"trialTimeWithDays": "{days}天 {hours}時 {minutes}分 {seconds}秒",
|
||||
"trialTimeWithHours": "{hours}時 {minutes}分 {seconds}秒",
|
||||
"trialTimeWithMinutes": "{minutes}分 {seconds}秒",
|
||||
"refreshLatency": "刷新延遲",
|
||||
"testLatency": "測試延遲",
|
||||
"testing": "正在測試延遲",
|
||||
"refreshLatencyDesc": "刷新所有節點的延遲",
|
||||
"testAllNodesLatency": "測試所有節點的網絡延遲",
|
||||
"autoSelect": "自動選擇",
|
||||
"selected": "已選擇"
|
||||
},
|
||||
"dialog": {
|
||||
"confirm": "確認",
|
||||
"cancel": "取消",
|
||||
"ok": "我知道了"
|
||||
},
|
||||
"update": {
|
||||
"title": "發現新版本",
|
||||
"content": "是否立即更新?",
|
||||
"updateNow": "立即更新",
|
||||
"updateLater": "稍後",
|
||||
"defaultContent": "1. 優化應用性能\n2. 修復已知問題\n3. 改進用戶體驗"
|
||||
},
|
||||
"orderStatus": {
|
||||
"title": "訂單狀態",
|
||||
"pending": {
|
||||
"title": "待支付",
|
||||
"description": "請完成支付"
|
||||
},
|
||||
"paid": {
|
||||
"title": "已支付",
|
||||
"description": "正在處理您的訂單"
|
||||
},
|
||||
"success": {
|
||||
"title": "恭喜你!支付成功",
|
||||
"description": "您的套餐已經購買成功了"
|
||||
},
|
||||
"closed": {
|
||||
"title": "訂單已關閉",
|
||||
"description": "請重新下單"
|
||||
},
|
||||
"failed": {
|
||||
"title": "支付失敗",
|
||||
"description": "請重新嘗試支付"
|
||||
},
|
||||
"unknown": {
|
||||
"title": "未知狀態",
|
||||
"description": "請聯繫客服"
|
||||
},
|
||||
"checkFailed": {
|
||||
"title": "檢查失敗",
|
||||
"description": "請稍後重試"
|
||||
},
|
||||
"initial": {
|
||||
"title": "支付中",
|
||||
"description": "請稍候,正在處理您的支付"
|
||||
}
|
||||
},
|
||||
"country": {
|
||||
"cn": "中國",
|
||||
"ir": "伊朗",
|
||||
"af": "阿富汗",
|
||||
"ru": "俄羅斯",
|
||||
"id": "印尼",
|
||||
"tr": "土耳其",
|
||||
"br": "巴西"
|
||||
},
|
||||
"error": {
|
||||
"200": "成功",
|
||||
"500": "服務器內部錯誤",
|
||||
"10001": "數據庫查詢錯誤",
|
||||
"10002": "數據庫更新錯誤",
|
||||
"10003": "數據庫插入錯誤",
|
||||
"10004": "數據庫刪除錯誤",
|
||||
"20001": "用戶已存在",
|
||||
"20002": "用戶不存在",
|
||||
"20003": "用戶密碼錯誤",
|
||||
"20004": "用戶已被禁用",
|
||||
"20005": "餘額不足",
|
||||
"20006": "註冊已暫停",
|
||||
"20007": "未綁定 Telegram",
|
||||
"20008": "用戶未綁定 OAuth",
|
||||
"20009": "邀請碼錯誤",
|
||||
"30001": "節點已存在",
|
||||
"30002": "節點不存在",
|
||||
"30003": "節點群組已存在",
|
||||
"30004": "節點群組不存在",
|
||||
"30005": "節點群組不為空",
|
||||
"400": "參數錯誤",
|
||||
"40002": "用戶令牌為空",
|
||||
"40003": "用戶令牌無效",
|
||||
"40004": "用戶令牌已過期",
|
||||
"40005": "未登錄",
|
||||
"401": "請求過多",
|
||||
"50001": "優惠券不存在",
|
||||
"50002": "優惠券已使用",
|
||||
"50003": "優惠券不匹配",
|
||||
"60001": "訂閱已過期",
|
||||
"60002": "訂閱不可用",
|
||||
"60003": "用戶已有訂閱",
|
||||
"60004": "訂閱已使用",
|
||||
"60005": "單次訂閱模式超出限制",
|
||||
"60006": "訂閱配額限制",
|
||||
"70001": "驗證碼錯誤",
|
||||
"80001": "加入隊列錯誤",
|
||||
"90001": "調試模式已啟用",
|
||||
"90002": "發送短信錯誤",
|
||||
"90003": "短信功能未啟用",
|
||||
"90004": "郵件功能未啟用",
|
||||
"90005": "不支持的登錄方式",
|
||||
"90006": "驗證器不支持此方式",
|
||||
"90007": "電話國家代碼為空",
|
||||
"90008": "密碼為空",
|
||||
"90009": "國家代碼為空",
|
||||
"90010": "需要密碼或驗證碼",
|
||||
"90011": "郵箱已存在",
|
||||
"90012": "手機號已存在",
|
||||
"90013": "設備已存在",
|
||||
"90014": "手機號錯誤",
|
||||
"90015": "該賬號今日已達到發送限制",
|
||||
"90017": "設備不存在",
|
||||
"90018": "用戶 ID 不匹配",
|
||||
"61001": "訂閱不存在",
|
||||
"61002": "未找到支付方式",
|
||||
"61003": "訂閱狀態錯誤",
|
||||
"61004": "重置期不足",
|
||||
"61005": "存在未使用流量"
|
||||
},
|
||||
"tray": {
|
||||
"open_dashboard": "打開面板",
|
||||
"copy_to_terminal": "複製到終端",
|
||||
"exit_app": "退出應用"
|
||||
},
|
||||
"splash": {
|
||||
"appName": "Hi快VPN",
|
||||
"slogan": "暢享全球高速網絡",
|
||||
"initializing": "正在初始化...",
|
||||
"networkConnectionFailure": "網絡連接失敗,請檢查並重試",
|
||||
"retry": "重試",
|
||||
"networkPermissionFailed": "獲取網絡權限失敗",
|
||||
"initializationFailed": "初始化失敗"
|
||||
},
|
||||
"network": {
|
||||
"status": {
|
||||
"connected": "已連接",
|
||||
"disconnected": "已斷開連接",
|
||||
"connecting": "正在連接...",
|
||||
"disconnecting": "正在斷開連接...",
|
||||
"reconnecting": "正在重新連接...",
|
||||
"failed": "連接失敗"
|
||||
},
|
||||
"permission": {
|
||||
"title": "網絡權限",
|
||||
"description": "需要網絡權限以提供 VPN 服務",
|
||||
"goToSettings": "前往設置",
|
||||
"cancel": "取消"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,427 +0,0 @@
|
||||
{
|
||||
"login": {
|
||||
"welcome": "歡迎使用 Hi快VPN!",
|
||||
"verifyPhone": "驗證您的手機號",
|
||||
"verifyEmail": "驗證您的郵箱",
|
||||
"codeSent": "已向 {account} 發送6位數代碼。請在接下來的 30 分鐘內輸入。",
|
||||
"back": "返回",
|
||||
"enterEmailOrPhone": "輸入郵箱或者手機號",
|
||||
"enterEmail": "Please enter email address",
|
||||
"enterCode": "請輸入驗證碼",
|
||||
"enterPassword": "請輸入密碼",
|
||||
"reenterPassword": "請再次輸入密碼",
|
||||
"forgotPassword": "忘記密碼",
|
||||
"codeLogin": "驗證碼登錄",
|
||||
"passwordLogin": "密碼登錄",
|
||||
"agreeTerms": "登錄/創建賬戶,即表示我同意",
|
||||
"termsOfService": "服務條款",
|
||||
"privacyPolicy": "隱私政策",
|
||||
"next": "下一步",
|
||||
"registerNow": "立即註冊",
|
||||
"setAndLogin": "設置並登錄",
|
||||
"enterAccount": "請輸入賬戶",
|
||||
"passwordMismatch": "兩次密碼輸入不一致",
|
||||
"sendCode": "發送驗證碼",
|
||||
"codeSentCountdown": "驗證碼已發送 {seconds}s",
|
||||
"and": "和",
|
||||
"enterInviteCode": "請輸入邀請碼(選填)",
|
||||
"registerSuccess": "註冊成功"
|
||||
},
|
||||
"failure": {
|
||||
"unexpected": "意外錯誤",
|
||||
"clash": {
|
||||
"unexpected": "意外錯誤",
|
||||
"core": "Clash 錯誤 ${reason}"
|
||||
},
|
||||
"singbox": {
|
||||
"unexpected": "意外服務錯誤",
|
||||
"serviceNotRunning": "服務未運行",
|
||||
"missingPrivilege": "缺少權限",
|
||||
"missingPrivilegeMsg": "VPN 模式需要管理員權限。以管理員身份重新啟動應用程序或更改服務模式",
|
||||
"missingGeoAssets": "缺失 GEO 資源文件",
|
||||
"missingGeoAssetsMsg": "缺失 GEO 資源文件。請考慮更改激活的資源文件或在設置中下載所選資源文件。",
|
||||
"invalidConfigOptions": "配置選項無效",
|
||||
"invalidConfig": "無效配置",
|
||||
"create": "服務創建錯誤",
|
||||
"start": "服務啟動錯誤"
|
||||
},
|
||||
"connectivity": {
|
||||
"unexpected": "意外失敗",
|
||||
"missingVpnPermission": "缺少 VPN 權限",
|
||||
"missingNotificationPermission": "缺少通知權限",
|
||||
"core": "核心錯誤"
|
||||
},
|
||||
"profiles": {
|
||||
"unexpected": "意外錯誤",
|
||||
"notFound": "未找到配置文件",
|
||||
"invalidConfig": "無效配置",
|
||||
"invalidUrl": "網址無效"
|
||||
},
|
||||
"connection": {
|
||||
"unexpected": "意外連接錯誤",
|
||||
"timeout": "連接超時",
|
||||
"badResponse": "錯誤響應",
|
||||
"connectionError": "連接錯誤",
|
||||
"badCertificate": "證書無效"
|
||||
},
|
||||
"geoAssets": {
|
||||
"unexpected": "意外錯誤",
|
||||
"notUpdate": "無可用更新",
|
||||
"activeNotFound": "未找到激活的 GEO 資源文件"
|
||||
}
|
||||
},
|
||||
"userInfo": {
|
||||
"title": "我的信息",
|
||||
"bindingTip": "未綁定郵箱/手機號",
|
||||
"myAccount": "我的賬號",
|
||||
"balance": "餘額",
|
||||
"noValidSubscription": "您沒有有效的訂閱",
|
||||
"subscribeNow": "立即訂閱",
|
||||
"shortcuts": "快捷鍵",
|
||||
"adBlock": "廣告攔截",
|
||||
"dnsUnlock": "DNS 解鎖",
|
||||
"contactUs": "聯繫我們",
|
||||
"others": "其他",
|
||||
"logout": "退出登錄",
|
||||
"logoutConfirmTitle": "退出登錄",
|
||||
"logoutConfirmMessage": "確定要退出登錄嗎?",
|
||||
"logoutCancel": "取消",
|
||||
"vpnWebsite": "VPN 官網",
|
||||
"telegram": "Telegram",
|
||||
"mail": "郵箱",
|
||||
"phone": "電話",
|
||||
"customerService": "人工客服",
|
||||
"workOrder": "填寫工單",
|
||||
"pleaseLogin": "請先登錄賬號",
|
||||
"subscriptionValid": "訂閱有效",
|
||||
"startTime": "開始時間:",
|
||||
"expireTime": "到期時間:",
|
||||
"loginNow": "立即登錄",
|
||||
"trialPeriod": "歡迎使用高級試用版",
|
||||
"remainingTime": "剩餘時間",
|
||||
"trialExpired": "試用期已結束,連接已斷開",
|
||||
"subscriptionExpired": "訂閱已過期,連接已斷開",
|
||||
"copySuccess": "複製成功",
|
||||
"notAvailable": "暫無",
|
||||
"willBeDeleted": "將被刪除",
|
||||
"deleteAccountWarning": "賬號刪除是永久性的。一旦您的賬號被刪除,您將無法使用任何功能。是否繼續?",
|
||||
"requestDelete": "請求刪除",
|
||||
"switchSubscription": "切換訂閱",
|
||||
"resetTrafficTitle": "重置流量",
|
||||
"resetTrafficMessage": "月付套餐流量重置示例:將下一個週期的流量按月重置,訂閱有效期將從{currentTime}提前至{newTime}",
|
||||
"reset": "重置",
|
||||
"trafficUsage": "已用: {used} / {total}",
|
||||
"trafficProgress": {
|
||||
"title": "流量使用情況",
|
||||
"unlimited": "不限流量",
|
||||
"limited": "已用流量"
|
||||
},
|
||||
"deviceLimit": "設備限制: {count}"
|
||||
},
|
||||
"setting": {
|
||||
"title": "設置",
|
||||
"vpnConnection": "VPN連接",
|
||||
"general": "通用",
|
||||
"autoConnect": "自動連接",
|
||||
"routeRule": "路由規則",
|
||||
"countrySelector": "選擇國家",
|
||||
"appearance": "外觀",
|
||||
"notifications": "通知",
|
||||
"helpImprove": "幫助我們改進",
|
||||
"helpImproveSubtitle": "幫助我們改進的副標題",
|
||||
"requestDeleteAccount": "請求刪除賬號",
|
||||
"goToDelete": "去刪除",
|
||||
"rateUs": "在 App Store 上為我們評分",
|
||||
"iosRating": "iOS評分",
|
||||
"version": "版本",
|
||||
"switchLanguage": "切換語言",
|
||||
"system": "系統",
|
||||
"light": "淺色",
|
||||
"dark": "深色",
|
||||
"vpnModeSmart": "智能模式",
|
||||
"mode": "出站模式",
|
||||
"connectionTypeGlobal": "全域代理",
|
||||
"connectionTypeGlobalRemark": "啟用後,所有流量均通過代理伺服器轉發",
|
||||
"connectionTypeRule": "智能代理",
|
||||
"connectionTypeRuleRemark": "當[出站模式]設置為[智能代理]時,根據所選國家,系統自動分流:國內IP/域名直連,境外請求透過代理訪問",
|
||||
"connectionTypeDirect": "直連",
|
||||
"connectionTypeDirectRemark": "啟用後,所有流量均不經代理直接訪問",
|
||||
"smartMode": "智能模式",
|
||||
"secureMode": "安全模式",
|
||||
"deviceLimit": "設備限制: {count}"
|
||||
},
|
||||
"statistics": {
|
||||
"title": "統計",
|
||||
"vpnStatus": "VPN 狀態",
|
||||
"ipAddress": "IP地址",
|
||||
"connectionTime": "連接時間",
|
||||
"protocol": "協議",
|
||||
"weeklyProtectionTime": "每週保護時間",
|
||||
"currentStreak": "當前連續記錄",
|
||||
"highestStreak": "最高記錄",
|
||||
"longestConnection": "最長連接時間",
|
||||
"days": "{days}天",
|
||||
"daysOfWeek": {
|
||||
"monday": "週\n一",
|
||||
"tuesday": "週\n二",
|
||||
"wednesday": "週\n三",
|
||||
"thursday": "週\n四",
|
||||
"friday": "週\n五",
|
||||
"saturday": "週\n六",
|
||||
"sunday": "週\n日"
|
||||
}
|
||||
},
|
||||
"message": {
|
||||
"title": "通知",
|
||||
"system": "系統消息",
|
||||
"promotion": "促銷消息"
|
||||
},
|
||||
"invite": {
|
||||
"title": "邀請好友",
|
||||
"progress": "邀請進度",
|
||||
"inviteStats": "邀請統計",
|
||||
"registers": "已註冊",
|
||||
"totalCommission": "總佣金",
|
||||
"rewardDetails": "獎勵明細 >",
|
||||
"steps": "邀請步驟",
|
||||
"inviteFriend": "邀請好友",
|
||||
"acceptInvite": "好友接受邀請\n下單並註冊",
|
||||
"getReward": "獲得獎勵",
|
||||
"shareLink": "分享連結",
|
||||
"shareQR": "分享二維碼",
|
||||
"rules": "邀請規則",
|
||||
"rule1": "1、您可以通過分享專屬邀請連結或邀請碼給好友,邀請他們加入我們。",
|
||||
"rule2": "2、好友完成註冊並登錄後,邀請獎勵將自動發放至您的賬戶。",
|
||||
"pending": "待下載",
|
||||
"processing": "在路上",
|
||||
"success": "已成功",
|
||||
"expired": "已失效",
|
||||
"myInviteCode": "我的邀請碼",
|
||||
"inviteCodeCopied": "邀請碼已複製到剪貼板",
|
||||
"close": "關閉",
|
||||
"saveQRCode": "保存二維碼",
|
||||
"qrCodeSaved": "二維碼已保存",
|
||||
"copiedToClipboard": "已複製到剪貼板",
|
||||
"getInviteCodeFailed": "獲取邀請碼失敗,請稍後重試",
|
||||
"generateQRCodeFailed": "生成二維碼失敗,請稍後重試",
|
||||
"generateShareLinkFailed": "生成分享連結失敗,請稍後重試"
|
||||
},
|
||||
"purchaseMembership": {
|
||||
"purchasePackage": "購買套餐",
|
||||
"noData": "暫無可用套餐",
|
||||
"myAccount": "我的賬號",
|
||||
"selectPackage": "選擇套餐",
|
||||
"packageDescription": "套餐描述",
|
||||
"paymentMethod": "支付方式",
|
||||
"cancelAnytime": "您可以隨時在APP上取消",
|
||||
"startSubscription": "開始訂閱",
|
||||
"renewNow": "立即續訂",
|
||||
"month": "{months}個月",
|
||||
"year": "{years}年",
|
||||
"day": "{days}天",
|
||||
"unlimitedTraffic": "不限流量",
|
||||
"unlimitedDevices": "不限設備",
|
||||
"devices": "{count}台",
|
||||
"trafficLimit": "流量限制",
|
||||
"deviceLimit": "設備限制",
|
||||
"features": "套餐特性",
|
||||
"expand": "展開",
|
||||
"collapse": "收起",
|
||||
"confirmPurchase": "確認購買",
|
||||
"confirmPurchaseDesc": "您確定要購買此套餐嗎?"
|
||||
},
|
||||
"home": {
|
||||
"welcome": "歡迎使用 Hi快VPN",
|
||||
"disconnected": "已斷開連接",
|
||||
"connecting": "正在連接",
|
||||
"connected": "已連接",
|
||||
"disconnecting": "正在斷開連接",
|
||||
"currentConnectionTitle": "當前連接",
|
||||
"switchNode": "切換節點",
|
||||
"timeout": "超時",
|
||||
"loading": "載入中...",
|
||||
"error": "載入失敗",
|
||||
"checkNetwork": "請檢查網絡連接並重試",
|
||||
"retry": "重試",
|
||||
"connectionSectionTitle": "連接方式",
|
||||
"dedicatedServers": "專用伺服器",
|
||||
"countryRegion": "國家/地區",
|
||||
"serverListTitle": "專用伺服器群組",
|
||||
"nodeListTitle": "所有節點",
|
||||
"countryListTitle": "國家/地區列表",
|
||||
"noServers": "暫無可用伺服器",
|
||||
"noNodes": "暫無可用節點",
|
||||
"noRegions": "暫無可用地區",
|
||||
"subscriptionDescription": "訂閱會員,暢享全球高速網絡",
|
||||
"subscribe": "立即訂閱",
|
||||
"trialPeriod": "歡迎使用 Premium 試用版",
|
||||
"remainingTime": "剩餘時間",
|
||||
"trialExpired": "試用期已結束,已斷開連接",
|
||||
"subscriptionExpired": "訂閱已過期,已斷開連接",
|
||||
"subscriptionUpdated": "訂閱已更新",
|
||||
"subscriptionUpdatedMessage": "您的訂閱信息已更新,請刷新頁面查看最新狀態",
|
||||
"trialStatus": "試用狀態",
|
||||
"trialing": "試用中",
|
||||
"trialEndMessage": "試用期結束後將無法使用",
|
||||
"lastDaySubscriptionStatus": "訂閱即將到期",
|
||||
"lastDaySubscriptionMessage": "即將到期",
|
||||
"subscriptionEndMessage": "訂閱到期後將無法使用",
|
||||
"trialTimeWithDays": "{days}天 {hours}時 {minutes}分 {seconds}秒",
|
||||
"trialTimeWithHours": "{hours}時 {minutes}分 {seconds}秒",
|
||||
"trialTimeWithMinutes": "{minutes}分 {seconds}秒",
|
||||
"refreshLatency": "刷新延遲",
|
||||
"testLatency": "測試延遲",
|
||||
"testing": "正在測試延遲",
|
||||
"refreshLatencyDesc": "刷新所有節點的延遲",
|
||||
"testAllNodesLatency": "測試所有節點的網絡延遲",
|
||||
"autoSelect": "自動選擇",
|
||||
"selected": "已選擇"
|
||||
},
|
||||
"dialog": {
|
||||
"confirm": "確認",
|
||||
"cancel": "取消",
|
||||
"ok": "我知道了"
|
||||
},
|
||||
"update": {
|
||||
"title": "發現新版本",
|
||||
"content": "是否立即更新?",
|
||||
"updateNow": "立即更新",
|
||||
"updateLater": "稍後",
|
||||
"defaultContent": "1. 優化應用性能\n2. 修復已知問題\n3. 改進用戶體驗"
|
||||
},
|
||||
"orderStatus": {
|
||||
"title": "訂單狀態",
|
||||
"pending": {
|
||||
"title": "待支付",
|
||||
"description": "請完成支付"
|
||||
},
|
||||
"paid": {
|
||||
"title": "已支付",
|
||||
"description": "正在處理您的訂單"
|
||||
},
|
||||
"success": {
|
||||
"title": "恭喜你!支付成功",
|
||||
"description": "您的套餐已經購買成功了"
|
||||
},
|
||||
"closed": {
|
||||
"title": "訂單已關閉",
|
||||
"description": "請重新下單"
|
||||
},
|
||||
"failed": {
|
||||
"title": "支付失敗",
|
||||
"description": "請重新嘗試支付"
|
||||
},
|
||||
"unknown": {
|
||||
"title": "未知狀態",
|
||||
"description": "請聯繫客服"
|
||||
},
|
||||
"checkFailed": {
|
||||
"title": "檢查失敗",
|
||||
"description": "請稍後重試"
|
||||
},
|
||||
"initial": {
|
||||
"title": "支付中",
|
||||
"description": "請稍候,正在處理您的支付"
|
||||
}
|
||||
},
|
||||
"country": {
|
||||
"cn": "中國",
|
||||
"ir": "伊朗",
|
||||
"af": "阿富汗",
|
||||
"ru": "俄羅斯",
|
||||
"id": "印尼",
|
||||
"tr": "土耳其",
|
||||
"br": "巴西"
|
||||
},
|
||||
"error": {
|
||||
"200": "成功",
|
||||
"500": "服務器內部錯誤",
|
||||
"10001": "數據庫查詢錯誤",
|
||||
"10002": "數據庫更新錯誤",
|
||||
"10003": "數據庫插入錯誤",
|
||||
"10004": "數據庫刪除錯誤",
|
||||
"20001": "用戶已存在",
|
||||
"20002": "用戶不存在",
|
||||
"20003": "用戶密碼錯誤",
|
||||
"20004": "用戶已被禁用",
|
||||
"20005": "餘額不足",
|
||||
"20006": "註冊已暫停",
|
||||
"20007": "未綁定 Telegram",
|
||||
"20008": "用戶未綁定 OAuth",
|
||||
"20009": "邀請碼錯誤",
|
||||
"30001": "節點已存在",
|
||||
"30002": "節點不存在",
|
||||
"30003": "節點群組已存在",
|
||||
"30004": "節點群組不存在",
|
||||
"30005": "節點群組不為空",
|
||||
"400": "參數錯誤",
|
||||
"40002": "用戶令牌為空",
|
||||
"40003": "用戶令牌無效",
|
||||
"40004": "用戶令牌已過期",
|
||||
"40005": "未登錄",
|
||||
"401": "請求過多",
|
||||
"50001": "優惠券不存在",
|
||||
"50002": "優惠券已使用",
|
||||
"50003": "優惠券不匹配",
|
||||
"60001": "訂閱已過期",
|
||||
"60002": "訂閱不可用",
|
||||
"60003": "用戶已有訂閱",
|
||||
"60004": "訂閱已使用",
|
||||
"60005": "單次訂閱模式超出限制",
|
||||
"60006": "訂閱配額限制",
|
||||
"70001": "驗證碼錯誤",
|
||||
"80001": "加入隊列錯誤",
|
||||
"90001": "調試模式已啟用",
|
||||
"90002": "發送短信錯誤",
|
||||
"90003": "短信功能未啟用",
|
||||
"90004": "郵件功能未啟用",
|
||||
"90005": "不支持的登錄方式",
|
||||
"90006": "驗證器不支持此方式",
|
||||
"90007": "電話國家代碼為空",
|
||||
"90008": "密碼為空",
|
||||
"90009": "國家代碼為空",
|
||||
"90010": "需要密碼或驗證碼",
|
||||
"90011": "郵箱已存在",
|
||||
"90012": "手機號已存在",
|
||||
"90013": "設備已存在",
|
||||
"90014": "手機號錯誤",
|
||||
"90015": "該賬號今日已達到發送限制",
|
||||
"90017": "設備不存在",
|
||||
"90018": "用戶 ID 不匹配",
|
||||
"61001": "訂閱不存在",
|
||||
"61002": "未找到支付方式",
|
||||
"61003": "訂閱狀態錯誤",
|
||||
"61004": "重置期不足",
|
||||
"61005": "存在未使用流量"
|
||||
},
|
||||
"tray": {
|
||||
"open_dashboard": "打開面板",
|
||||
"copy_to_terminal": "複製到終端",
|
||||
"exit_app": "退出應用"
|
||||
},
|
||||
"splash": {
|
||||
"appName": "Hi快VPN",
|
||||
"slogan": "暢享全球高速網絡",
|
||||
"initializing": "正在初始化...",
|
||||
"networkConnectionFailure": "網絡連接失敗,請檢查並重試",
|
||||
"retry": "重試",
|
||||
"networkPermissionFailed": "獲取網絡權限失敗",
|
||||
"initializationFailed": "初始化失敗"
|
||||
},
|
||||
"network": {
|
||||
"status": {
|
||||
"connected": "已連接",
|
||||
"disconnected": "已斷開連接",
|
||||
"connecting": "正在連接...",
|
||||
"disconnecting": "正在斷開連接...",
|
||||
"reconnecting": "正在重新連接...",
|
||||
"failed": "連接失敗"
|
||||
},
|
||||
"permission": {
|
||||
"title": "網絡權限",
|
||||
"description": "需要網絡權限以提供 VPN 服務",
|
||||
"goToSettings": "前往設置",
|
||||
"cancel": "取消"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -78,7 +78,7 @@ SPEC CHECKSUMS:
|
||||
connectivity_plus: 481668c94744c30c53b8895afb39159d1e619bdf
|
||||
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
||||
EasyPermissionX: ff4c438f6ee80488f873b4cb921e32d982523067
|
||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
|
||||
flutter_udid: f7c3884e6ec2951efe4f9de082257fc77c4d15e9
|
||||
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
|
||||
|
||||
@ -1081,21 +1081,141 @@ class KRDomain {
|
||||
);
|
||||
}
|
||||
|
||||
/// 从本地加载域名
|
||||
/// 从本地加载域名(方案1:加载后验证可用性)
|
||||
static Future<void> kr_loadBaseDomain() async {
|
||||
KRLogUtil.kr_i('📂 开始从 Hive 加载域名配置', tag: 'KRDomain');
|
||||
final startTime = DateTime.now();
|
||||
|
||||
try {
|
||||
// 🔧 Android 15 关键修复:添加总体超时保护(15秒)
|
||||
await Future.any([
|
||||
_executeLoadAndVerify(),
|
||||
Future.delayed(const Duration(seconds: 15), () {
|
||||
throw TimeoutException('域名加载和验证总超时(15秒)');
|
||||
}),
|
||||
]);
|
||||
} on TimeoutException catch (e) {
|
||||
final duration = DateTime.now().difference(startTime).inSeconds;
|
||||
KRLogUtil.kr_e('⏱️ 域名加载验证超时($duration 秒): $e', tag: 'KRDomain');
|
||||
// 超时时使用默认域名
|
||||
if (kr_baseDomains.isNotEmpty) {
|
||||
kr_currentDomain = kr_baseDomains[0];
|
||||
KRLogUtil.kr_w('⚠️ 超时后使用默认域名: $kr_currentDomain', tag: 'KRDomain');
|
||||
}
|
||||
} catch (e) {
|
||||
final duration = DateTime.now().difference(startTime).inSeconds;
|
||||
KRLogUtil.kr_e('❌ 域名加载异常($duration 秒): $e', tag: 'KRDomain');
|
||||
// 异常时使用默认域名
|
||||
if (kr_baseDomains.isNotEmpty) {
|
||||
kr_currentDomain = kr_baseDomains[0];
|
||||
}
|
||||
}
|
||||
|
||||
final totalDuration = DateTime.now().difference(startTime).inMilliseconds;
|
||||
KRLogUtil.kr_i('✅ 域名加载完成,耗时: ${totalDuration}ms,当前域名: $kr_currentDomain', tag: 'KRDomain');
|
||||
}
|
||||
|
||||
/// 执行域名加载和验证
|
||||
static Future<void> _executeLoadAndVerify() async {
|
||||
// 加载域名列表
|
||||
String? savedDomains = await _storage.kr_readData(key: kr_domainsKey);
|
||||
if (savedDomains != null) {
|
||||
kr_baseDomains = savedDomains.split(',');
|
||||
if (savedDomains != null && savedDomains.isNotEmpty) {
|
||||
kr_baseDomains = savedDomains.split(',').where((d) => d.isNotEmpty).toList();
|
||||
KRLogUtil.kr_i('📋 从 Hive 加载的域名列表: $kr_baseDomains', tag: 'KRDomain');
|
||||
} else {
|
||||
KRLogUtil.kr_w('⚠️ Hive 中没有保存的域名列表,使用默认配置', tag: 'KRDomain');
|
||||
}
|
||||
|
||||
// 加载当前域名
|
||||
String? savedDomain = await _storage.kr_readData(key: kr_domainKey);
|
||||
if (savedDomain != null && kr_baseDomains.contains(savedDomain)) {
|
||||
kr_currentDomain = savedDomain;
|
||||
KRLogUtil.kr_i('📌 从 Hive 加载的当前域名: $savedDomain', tag: 'KRDomain');
|
||||
|
||||
if (savedDomain != null && savedDomain.isNotEmpty && kr_baseDomains.contains(savedDomain)) {
|
||||
// 🔧 Android 15 关键修复:验证 Hive 缓存的域名是否仍然可用
|
||||
KRLogUtil.kr_i('🔍 验证 Hive 缓存域名的可用性: $savedDomain', tag: 'KRDomain');
|
||||
|
||||
try {
|
||||
// 快速验证(3 秒超时)
|
||||
bool isAvailable = await kr_fastCheckDomainAvailability(savedDomain).timeout(
|
||||
const Duration(seconds: 3),
|
||||
onTimeout: () {
|
||||
KRLogUtil.kr_w('⏱️ 域名验证超时(3秒),视为不可用', tag: 'KRDomain');
|
||||
return false;
|
||||
},
|
||||
);
|
||||
|
||||
if (isAvailable) {
|
||||
kr_currentDomain = savedDomain;
|
||||
KRLogUtil.kr_i('✅ Hive 缓存域名验证通过,继续使用: $kr_currentDomain', tag: 'KRDomain');
|
||||
return;
|
||||
} else {
|
||||
KRLogUtil.kr_w('❌ Hive 缓存域名不可用: $savedDomain,需要切换', tag: 'KRDomain');
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('❌ 域名验证异常: $e,需要切换', tag: 'KRDomain');
|
||||
}
|
||||
} else {
|
||||
if (savedDomain == null || savedDomain.isEmpty) {
|
||||
KRLogUtil.kr_w('⚠️ Hive 中没有保存的当前域名', tag: 'KRDomain');
|
||||
} else {
|
||||
KRLogUtil.kr_w('⚠️ Hive 缓存的域名 $savedDomain 不在域名列表中', tag: 'KRDomain');
|
||||
}
|
||||
}
|
||||
|
||||
// 🔧 Android 15 增强:Hive 域名验证失败,自动选择可用域名
|
||||
KRLogUtil.kr_i('🔄 Hive 域名不可用,开始选择可用域名...', tag: 'KRDomain');
|
||||
|
||||
// 尝试从域名列表中找到可用的域名(每个最多 3 秒)
|
||||
for (String domain in kr_baseDomains) {
|
||||
try {
|
||||
KRLogUtil.kr_i('🧪 测试域名: $domain', tag: 'KRDomain');
|
||||
bool isAvailable = await kr_fastCheckDomainAvailability(domain).timeout(
|
||||
const Duration(seconds: 3),
|
||||
onTimeout: () {
|
||||
KRLogUtil.kr_w('⏱️ 域名 $domain 验证超时(3秒)', tag: 'KRDomain');
|
||||
return false;
|
||||
},
|
||||
);
|
||||
|
||||
if (isAvailable) {
|
||||
kr_currentDomain = domain;
|
||||
await kr_saveCurrentDomain();
|
||||
KRLogUtil.kr_i('✅ 找到可用域名: $kr_currentDomain,已保存到 Hive', tag: 'KRDomain');
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_w('⚠️ 域名 $domain 验证异常: $e', tag: 'KRDomain');
|
||||
}
|
||||
}
|
||||
|
||||
// 如果所有主域名都不可用,快速尝试备用域名(5秒总超时)
|
||||
KRLogUtil.kr_w('⚠️ 所有主域名都不可用,尝试备用域名...', tag: 'KRDomain');
|
||||
try {
|
||||
String? backupDomain = await kr_tryLocalBackupDomains().timeout(
|
||||
const Duration(seconds: 5),
|
||||
onTimeout: () {
|
||||
KRLogUtil.kr_w('⏱️ 备用域名获取超时', tag: 'KRDomain');
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
if (backupDomain != null) {
|
||||
kr_currentDomain = backupDomain;
|
||||
await kr_saveCurrentDomain();
|
||||
KRLogUtil.kr_i('✅ 使用备用域名: $kr_currentDomain', tag: 'KRDomain');
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_w('⚠️ 备用域名获取异常: $e', tag: 'KRDomain');
|
||||
}
|
||||
|
||||
// 最后的兜底:使用列表中的第一个域名(即使不可用)
|
||||
if (kr_baseDomains.isNotEmpty) {
|
||||
kr_currentDomain = kr_baseDomains[0];
|
||||
await kr_saveCurrentDomain();
|
||||
KRLogUtil.kr_w('⚠️ 无法验证任何域名,使用默认域名: $kr_currentDomain', tag: 'KRDomain');
|
||||
} else {
|
||||
KRLogUtil.kr_e('❌ 没有可用的域名配置!', tag: 'KRDomain');
|
||||
}
|
||||
}
|
||||
|
||||
@ -1123,6 +1243,19 @@ class KRDomain {
|
||||
}
|
||||
|
||||
class AppConfig {
|
||||
/// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
/// 初始化日志收集开关
|
||||
/// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
/// 🔧 全局开关:是否启用初始化日志收集
|
||||
/// true:将所有初始化日志写入文件,方便问题诊断
|
||||
/// false:关闭日志收集,减少 I/O 操作
|
||||
///
|
||||
/// 建议:
|
||||
/// - 测试版本、Beta 版本:设置为 true
|
||||
/// - 正式生产版本:根据需要设置为 false(或在遇到问题时临时开启)
|
||||
static const bool enableInitLogCollection = true;
|
||||
|
||||
/// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
/// 加密密钥配置
|
||||
/// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
@ -2,6 +2,7 @@ import 'dart:convert';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../response/kr_node_list.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
/// 表示出站项的模型类
|
||||
class KROutboundItem {
|
||||
@ -50,14 +51,18 @@ class KROutboundItem {
|
||||
// 🔧 优先使用直接字段构建配置(新API格式)
|
||||
// 判断条件:如果有 port 和 serverAddr,说明是新格式
|
||||
if (nodeListItem.port > 0 && nodeListItem.serverAddr.isNotEmpty) {
|
||||
print('ℹ️ 节点 ${nodeListItem.name} 使用直接字段构建配置');
|
||||
if (kDebugMode) {
|
||||
print('ℹ️ 节点 ${nodeListItem.name} 使用直接字段构建配置');
|
||||
}
|
||||
_buildConfigFromFields(nodeListItem);
|
||||
return;
|
||||
}
|
||||
|
||||
// 兜底:尝试解析 config 字段(旧API格式)
|
||||
if (nodeListItem.config.isEmpty) {
|
||||
print('❌ 节点 ${nodeListItem.name} 缺少配置信息(无port或config)');
|
||||
if (kDebugMode) {
|
||||
print('❌ 节点 ${nodeListItem.name} 缺少配置信息(无port或config)');
|
||||
}
|
||||
config = {};
|
||||
return;
|
||||
}
|
||||
@ -66,8 +71,12 @@ class KROutboundItem {
|
||||
try {
|
||||
json = jsonDecode(nodeListItem.config) as Map<String, dynamic>;
|
||||
} catch (e) {
|
||||
print('❌ 节点 ${nodeListItem.name} 的 config 解析失败: $e,尝试使用直接字段');
|
||||
print('📄 Config 内容: ${nodeListItem.config}');
|
||||
if (kDebugMode) {
|
||||
print('❌ 节点 ${nodeListItem.name} 的 config 解析失败: $e,尝试使用直接字段');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('📄 Config 内容: ${nodeListItem.config}');
|
||||
}
|
||||
_buildConfigFromFields(nodeListItem);
|
||||
return;
|
||||
}
|
||||
@ -237,14 +246,30 @@ class KROutboundItem {
|
||||
|
||||
/// 直接从节点字段构建配置(新API格式)
|
||||
void _buildConfigFromFields(KrNodeListItem nodeListItem) {
|
||||
print('🔧 开始构建节点配置 - 协议: ${nodeListItem.protocol}, 名称: ${nodeListItem.name}');
|
||||
print('📋 节点详细信息:');
|
||||
print(' - serverAddr: ${nodeListItem.serverAddr}');
|
||||
print(' - port: ${nodeListItem.port}');
|
||||
print(' - uuid: ${nodeListItem.uuid}');
|
||||
print(' - method: ${nodeListItem.method}');
|
||||
print(' - config: ${nodeListItem.config}');
|
||||
print(' - protocols: ${nodeListItem.protocols}');
|
||||
if (kDebugMode) {
|
||||
print('🔧 开始构建节点配置 - 协议: ${nodeListItem.protocol}, 名称: ${nodeListItem.name}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('📋 节点详细信息:');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' - serverAddr: ${nodeListItem.serverAddr}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' - port: ${nodeListItem.port}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' - uuid: ${nodeListItem.uuid}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' - method: ${nodeListItem.method}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' - config: ${nodeListItem.config}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' - protocols: ${nodeListItem.protocols}');
|
||||
}
|
||||
|
||||
// 🔧 尝试从 config 字段解析 transport 配置
|
||||
Map<String, dynamic>? transportConfig;
|
||||
@ -254,7 +279,9 @@ class KROutboundItem {
|
||||
if (nodeListItem.protocols.isNotEmpty) {
|
||||
try {
|
||||
final protocolsList = jsonDecode(nodeListItem.protocols) as List;
|
||||
print('📄 解析到 protocols 数组,共 ${protocolsList.length} 个协议');
|
||||
if (kDebugMode) {
|
||||
print('📄 解析到 protocols 数组,共 ${protocolsList.length} 个协议');
|
||||
}
|
||||
|
||||
// 查找匹配当前协议类型的配置
|
||||
Map<String, dynamic>? matchedProtocol;
|
||||
@ -263,14 +290,18 @@ class KROutboundItem {
|
||||
final type = protocolMap['type']?.toString().toLowerCase() ?? '';
|
||||
final enable = protocolMap['enable'] ?? true;
|
||||
|
||||
print(' 📋 协议: type=$type, enable=$enable');
|
||||
if (kDebugMode) {
|
||||
print(' 📋 协议: type=$type, enable=$enable');
|
||||
}
|
||||
|
||||
// 匹配协议类型(注意 hysteria 和 hysteria2 都匹配 hysteria)
|
||||
if (type == nodeListItem.protocol.toLowerCase() ||
|
||||
(nodeListItem.protocol == 'hysteria' && type == 'hysteria2') ||
|
||||
(nodeListItem.protocol == 'hysteria2' && type == 'hysteria')) {
|
||||
matchedProtocol = protocolMap;
|
||||
print(' ✅ 找到匹配的协议配置: $type');
|
||||
if (kDebugMode) {
|
||||
print(' ✅ 找到匹配的协议配置: $type');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -279,7 +310,9 @@ class KROutboundItem {
|
||||
// 提取 transport 配置
|
||||
if (matchedProtocol['network'] != null || matchedProtocol['transport'] != null) {
|
||||
final network = matchedProtocol['network'] ?? matchedProtocol['transport'];
|
||||
print(' 📡 传输协议: $network');
|
||||
if (kDebugMode) {
|
||||
print(' 📡 传输协议: $network');
|
||||
}
|
||||
|
||||
if (network == 'ws' || network == 'websocket') {
|
||||
transportConfig = {
|
||||
@ -290,20 +323,26 @@ class KROutboundItem {
|
||||
if (host != null && host.toString().isNotEmpty) {
|
||||
transportConfig['headers'] = {'Host': host.toString()};
|
||||
}
|
||||
print(' ✅ WebSocket transport: $transportConfig');
|
||||
if (kDebugMode) {
|
||||
print(' ✅ WebSocket transport: $transportConfig');
|
||||
}
|
||||
} else if (network == 'grpc') {
|
||||
transportConfig = {
|
||||
'type': 'grpc',
|
||||
'service_name': matchedProtocol['grpc_service_name'] ?? matchedProtocol['service_name'] ?? '',
|
||||
};
|
||||
print(' ✅ gRPC transport: $transportConfig');
|
||||
if (kDebugMode) {
|
||||
print(' ✅ gRPC transport: $transportConfig');
|
||||
}
|
||||
} else if (network == 'http' || network == 'h2') {
|
||||
transportConfig = {
|
||||
'type': 'http',
|
||||
'host': [matchedProtocol['http_host'] ?? matchedProtocol['host'] ?? ''],
|
||||
'path': matchedProtocol['http_path'] ?? matchedProtocol['path'] ?? '/',
|
||||
};
|
||||
print(' ✅ HTTP transport: $transportConfig');
|
||||
if (kDebugMode) {
|
||||
print(' ✅ HTTP transport: $transportConfig');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -319,11 +358,15 @@ class KROutboundItem {
|
||||
'allow_insecure': matchedProtocol['allow_insecure'] ?? matchedProtocol['insecure'] ?? true,
|
||||
'fingerprint': matchedProtocol['fingerprint'] ?? 'chrome',
|
||||
};
|
||||
print(' ✅ Security config: security=$security, tls_enabled=$tlsEnabled, config=$securityConfig');
|
||||
if (kDebugMode) {
|
||||
print(' ✅ Security config: security=$security, tls_enabled=$tlsEnabled, config=$securityConfig');
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
print('⚠️ 解析 protocols 字段失败: $e');
|
||||
if (kDebugMode) {
|
||||
print('⚠️ 解析 protocols 字段失败: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -331,21 +374,29 @@ class KROutboundItem {
|
||||
if (transportConfig == null && nodeListItem.config.isNotEmpty) {
|
||||
try {
|
||||
final configJson = jsonDecode(nodeListItem.config) as Map<String, dynamic>;
|
||||
print('📄 解析到 config JSON: $configJson');
|
||||
if (kDebugMode) {
|
||||
print('📄 解析到 config JSON: $configJson');
|
||||
}
|
||||
|
||||
// 提取 transport 配置
|
||||
if (configJson['transport'] != null && configJson['transport'] != 'tcp') {
|
||||
transportConfig = _buildTransport(configJson);
|
||||
print('✅ 从 config 找到 transport 配置: $transportConfig');
|
||||
if (kDebugMode) {
|
||||
print('✅ 从 config 找到 transport 配置: $transportConfig');
|
||||
}
|
||||
}
|
||||
|
||||
// 提取 security_config
|
||||
if (configJson['security_config'] != null) {
|
||||
securityConfig = configJson['security_config'] as Map<String, dynamic>;
|
||||
print('✅ 从 config 找到 security_config: $securityConfig');
|
||||
if (kDebugMode) {
|
||||
print('✅ 从 config 找到 security_config: $securityConfig');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
print('⚠️ 解析 config 字段失败: $e');
|
||||
if (kDebugMode) {
|
||||
print('⚠️ 解析 config 字段失败: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -364,9 +415,15 @@ class KROutboundItem {
|
||||
"method": finalMethod,
|
||||
"password": nodeListItem.uuid
|
||||
};
|
||||
print('✅ Shadowsocks 节点配置构建成功: ${nodeListItem.name}');
|
||||
print('📄 使用加密方法: $finalMethod');
|
||||
print('📄 完整配置: $config');
|
||||
if (kDebugMode) {
|
||||
print('✅ Shadowsocks 节点配置构建成功: ${nodeListItem.name}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('📄 使用加密方法: $finalMethod');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('📄 完整配置: $config');
|
||||
}
|
||||
break;
|
||||
case "vless":
|
||||
// 判断是否为域名(非IP地址)
|
||||
@ -381,7 +438,9 @@ class KROutboundItem {
|
||||
|
||||
// 🔧 关键修复:根据 security_config 判断是否启用 TLS
|
||||
final bool vlessTlsEnabled = securityConfig?['tls_enabled'] ?? false;
|
||||
print('🔐 VLESS TLS 状态: enabled=$vlessTlsEnabled');
|
||||
if (kDebugMode) {
|
||||
print('🔐 VLESS TLS 状态: enabled=$vlessTlsEnabled');
|
||||
}
|
||||
|
||||
config = {
|
||||
"type": "vless",
|
||||
@ -400,8 +459,12 @@ class KROutboundItem {
|
||||
}
|
||||
}
|
||||
};
|
||||
print('✅ VLESS 节点配置构建成功: ${nodeListItem.name}');
|
||||
print('📄 完整配置: $config');
|
||||
if (kDebugMode) {
|
||||
print('✅ VLESS 节点配置构建成功: ${nodeListItem.name}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('📄 完整配置: $config');
|
||||
}
|
||||
break;
|
||||
case "vmess":
|
||||
// 判断是否为域名(非IP地址)
|
||||
@ -416,7 +479,9 @@ class KROutboundItem {
|
||||
|
||||
// 🔧 关键修复:根据 security_config 判断是否启用 TLS
|
||||
final bool tlsEnabled = securityConfig?['tls_enabled'] ?? false;
|
||||
print('🔐 TLS 状态: enabled=$tlsEnabled');
|
||||
if (kDebugMode) {
|
||||
print('🔐 TLS 状态: enabled=$tlsEnabled');
|
||||
}
|
||||
|
||||
config = {
|
||||
"type": "vmess",
|
||||
@ -437,8 +502,12 @@ class KROutboundItem {
|
||||
}
|
||||
}
|
||||
};
|
||||
print('✅ VMess 节点配置构建成功: ${nodeListItem.name}');
|
||||
print('📄 完整配置: $config');
|
||||
if (kDebugMode) {
|
||||
print('✅ VMess 节点配置构建成功: ${nodeListItem.name}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('📄 完整配置: $config');
|
||||
}
|
||||
break;
|
||||
case "trojan":
|
||||
// 判断是否为域名(非IP地址)
|
||||
@ -468,16 +537,28 @@ class KROutboundItem {
|
||||
}
|
||||
}
|
||||
};
|
||||
print('✅ Trojan 节点配置构建成功: ${nodeListItem.name}');
|
||||
print('📄 完整配置: $config');
|
||||
if (kDebugMode) {
|
||||
print('✅ Trojan 节点配置构建成功: ${nodeListItem.name}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('📄 完整配置: $config');
|
||||
}
|
||||
break;
|
||||
case "hysteria":
|
||||
case "hysteria2":
|
||||
// 后端的 "hysteria" 实际上是 Hysteria2 协议
|
||||
print('🔍 构建 Hysteria2 节点: ${nodeListItem.name}');
|
||||
print(' - serverAddr: ${nodeListItem.serverAddr}');
|
||||
print(' - port: ${nodeListItem.port}');
|
||||
print(' - uuid: ${nodeListItem.uuid}');
|
||||
if (kDebugMode) {
|
||||
print('🔍 构建 Hysteria2 节点: ${nodeListItem.name}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' - serverAddr: ${nodeListItem.serverAddr}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' - port: ${nodeListItem.port}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' - uuid: ${nodeListItem.uuid}');
|
||||
}
|
||||
|
||||
//判断是否为域名
|
||||
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
|
||||
@ -494,11 +575,17 @@ class KROutboundItem {
|
||||
if (isDomain) "server_name": nodeListItem.serverAddr,
|
||||
}
|
||||
};
|
||||
print('✅ Hysteria2 节点配置构建成功');
|
||||
print('📄 完整配置: ${jsonEncode(config)}');
|
||||
if (kDebugMode) {
|
||||
print('✅ Hysteria2 节点配置构建成功');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('📄 完整配置: ${jsonEncode(config)}');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
print('⚠️ 不支持的协议类型: ${nodeListItem.protocol}');
|
||||
if (kDebugMode) {
|
||||
print('⚠️ 不支持的协议类型: ${nodeListItem.protocol}');
|
||||
}
|
||||
config = {};
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ import 'kr_group_outbound_list.dart';
|
||||
|
||||
import '../response/kr_node_list.dart';
|
||||
import 'kr_outbound_item.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
/// 表示出站项列表的模型类
|
||||
class KrOutboundsList {
|
||||
@ -47,14 +48,24 @@ class KrOutboundsList {
|
||||
final KROutboundItem item = KROutboundItem(element);
|
||||
|
||||
// 🔍 调试日志:验证 country 字段传递
|
||||
print('🗺️ 构建节点: name="${element.name}", tag="${item.tag}", country="${item.country}"');
|
||||
print(' - element.country: "${element.country}"');
|
||||
print(' - item.country: "${item.country}"');
|
||||
print(' - country.isEmpty: ${item.country.isEmpty}');
|
||||
if (kDebugMode) {
|
||||
print('🗺️ 构建节点: name="${element.name}", tag="${item.tag}", country="${item.country}"');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' - element.country: "${element.country}"');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' - item.country: "${item.country}"');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' - country.isEmpty: ${item.country.isEmpty}');
|
||||
}
|
||||
|
||||
// 检查节点配置是否有效(必须包含 type 字段)
|
||||
if (item.config.isEmpty || !item.config.containsKey('type')) {
|
||||
print('⚠️ 跳过无效节点: ${element.name},配置为空或缺少 type 字段');
|
||||
if (kDebugMode) {
|
||||
print('⚠️ 跳过无效节点: ${element.name},配置为空或缺少 type 字段');
|
||||
}
|
||||
continue; // 跳过无效节点
|
||||
}
|
||||
|
||||
@ -72,7 +83,9 @@ class KrOutboundsList {
|
||||
|
||||
configJsonList.add(item.config);
|
||||
keyList[item.tag] = item;
|
||||
print('✅ keyList["${item.tag}"] 已设置,country="${item.country}"');
|
||||
if (kDebugMode) {
|
||||
print('✅ keyList["${item.tag}"] 已设置,country="${item.country}"');
|
||||
}
|
||||
}
|
||||
|
||||
// 将标签分组转换为 KRGroupOutboundList 并添加到 groupOutboundList
|
||||
|
||||
@ -8,6 +8,7 @@ import 'package:kaer_with_panels/app/services/kr_site_config_service.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import '../../../services/kr_device_info_service.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
/// Crisp 聊天控制器
|
||||
class KRCrispController extends GetxController {
|
||||
@ -47,7 +48,9 @@ class KRCrispController extends GetxController {
|
||||
kr_isInitialized.value = true;
|
||||
}
|
||||
} catch (e) {
|
||||
print('初始化 Crisp 时出错: $e');
|
||||
if (kDebugMode) {
|
||||
print('初始化 Crisp 时出错: $e');
|
||||
}
|
||||
if (!_kr_isDisposed) {
|
||||
kr_isInitialized.value = false;
|
||||
}
|
||||
@ -125,9 +128,13 @@ class KRCrispController extends GetxController {
|
||||
'device_id': deviceId,
|
||||
});
|
||||
|
||||
print('Crisp 初始化完成');
|
||||
if (kDebugMode) {
|
||||
print('Crisp 初始化完成');
|
||||
}
|
||||
} catch (e) {
|
||||
print('初始化 Crisp 时出错: $e');
|
||||
if (kDebugMode) {
|
||||
print('初始化 Crisp 时出错: $e');
|
||||
}
|
||||
crispController = null;
|
||||
rethrow;
|
||||
}
|
||||
@ -155,7 +162,9 @@ class KRCrispController extends GetxController {
|
||||
kr_isLoading.value = false;
|
||||
}
|
||||
} catch (e) {
|
||||
print('清理 Crisp 资源时出错: $e');
|
||||
if (kDebugMode) {
|
||||
print('清理 Crisp 资源时出错: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -50,7 +50,7 @@ class KRDeleteAccountController extends GetxController {
|
||||
Future<void> kr_sendCode() async {
|
||||
final account = KRAppRunData.getInstance().kr_account.value;
|
||||
if (account == null || account.isEmpty) {
|
||||
KRCommonUtil.kr_showToast('账号不能为空');
|
||||
KRCommonUtil.kr_showToast('account.accountRequired'.tr);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -98,7 +98,7 @@ class KRDeleteAccountController extends GetxController {
|
||||
KRCommonUtil.kr_showToast(error.msg);
|
||||
},
|
||||
(success) {
|
||||
KRCommonUtil.kr_showToast('删除账号成功');
|
||||
KRCommonUtil.kr_showToast('account.deleteSuccess'.tr);
|
||||
KRAppRunData.getInstance().kr_loginOut();
|
||||
},
|
||||
);
|
||||
|
||||
@ -67,7 +67,7 @@ class KRDeviceManagementView extends GetView<KRDeviceManagementController> {
|
||||
size: 64.w,
|
||||
color: Theme.of(context).textTheme.bodySmall?.color,
|
||||
),
|
||||
SizedBox(height: 16.w),
|
||||
SizedBox(height: 16.h),
|
||||
Text(
|
||||
'暂无登录设备',
|
||||
style: KrAppTextStyle(
|
||||
@ -172,7 +172,7 @@ class KRDeviceManagementView extends GetView<KRDeviceManagementController> {
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 8.w,
|
||||
vertical: 2.w,
|
||||
vertical: 2.h,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green.withOpacity(0.1),
|
||||
@ -189,7 +189,7 @@ class KRDeviceManagementView extends GetView<KRDeviceManagementController> {
|
||||
],
|
||||
],
|
||||
),
|
||||
SizedBox(height: 4.w),
|
||||
SizedBox(height: 4.h),
|
||||
Text(
|
||||
'ID: ${identifier.substring(0, identifier.length > 12 ? 12 : identifier.length)}...',
|
||||
style: KrAppTextStyle(
|
||||
@ -205,7 +205,7 @@ class KRDeviceManagementView extends GetView<KRDeviceManagementController> {
|
||||
onPressed: () => controller.deleteDevice(id),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Theme.of(context).colorScheme.error,
|
||||
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.w),
|
||||
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h),
|
||||
),
|
||||
child: Text(
|
||||
'删除',
|
||||
@ -219,9 +219,9 @@ class KRDeviceManagementView extends GetView<KRDeviceManagementController> {
|
||||
),
|
||||
// 分隔线
|
||||
if (ip.isNotEmpty || lastLogin.isNotEmpty) ...[
|
||||
SizedBox(height: 12.w),
|
||||
SizedBox(height: 12.h),
|
||||
Divider(height: 1, color: Theme.of(context).dividerColor),
|
||||
SizedBox(height: 12.w),
|
||||
SizedBox(height: 12.h),
|
||||
],
|
||||
// 详细信息
|
||||
if (ip.isNotEmpty)
|
||||
@ -230,7 +230,7 @@ class KRDeviceManagementView extends GetView<KRDeviceManagementController> {
|
||||
'IP地址',
|
||||
ip,
|
||||
),
|
||||
if (ip.isNotEmpty && lastLogin.isNotEmpty) SizedBox(height: 8.w),
|
||||
if (ip.isNotEmpty && lastLogin.isNotEmpty) SizedBox(height: 8.h),
|
||||
if (lastLogin.isNotEmpty)
|
||||
_buildInfoRow(
|
||||
context,
|
||||
|
||||
@ -29,6 +29,7 @@ import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
|
||||
import 'package:kaer_with_panels/app/utils/kr_common_util.dart';
|
||||
import 'package:kaer_with_panels/app/utils/kr_secure_storage.dart';
|
||||
import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
/// 订阅服务
|
||||
@ -264,6 +265,14 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
kr_forceSyncConnectionStatus();
|
||||
_checkQuickConnectAutoStart();
|
||||
});
|
||||
|
||||
// 🔧 Android 15 新增:5秒后再次强制更新高度,兜底保护
|
||||
Future.delayed(const Duration(seconds: 5), () {
|
||||
if (kr_bottomPanelHeight.value < 100) {
|
||||
KRLogUtil.kr_w('检测到底部面板高度异常(${kr_bottomPanelHeight.value}),强制修正', tag: 'HomeController');
|
||||
kr_updateBottomPanelHeight();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -271,11 +280,22 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
void _kr_initLoginStatus() {
|
||||
KRLogUtil.kr_i('初始化登录状态', tag: 'HomeController');
|
||||
|
||||
// 设置超时处理
|
||||
Timer(const Duration(seconds: 10), () {
|
||||
// 🔧 Android 15 紧急修复:8秒超时,更快响应移动网络慢的情况
|
||||
Timer(const Duration(seconds: 8), () {
|
||||
if (kr_currentListStatus.value == KRHomeViewsListStatus.kr_loading) {
|
||||
KRLogUtil.kr_w('订阅服务初始化超时,设置为错误状态', tag: 'HomeController');
|
||||
kr_currentListStatus.value = KRHomeViewsListStatus.kr_error;
|
||||
KRLogUtil.kr_w('⏱️ 订阅服务初始化超时(8秒),强制设置为无数据状态', tag: 'HomeController');
|
||||
// 🔧 Android 15 优化:超时设置为 none 而非 error,避免底部面板显示错误界面
|
||||
kr_currentListStatus.value = KRHomeViewsListStatus.kr_none;
|
||||
// 强制更新底部面板高度,确保显示正常
|
||||
kr_updateBottomPanelHeight();
|
||||
KRLogUtil.kr_i('✅ 已强制切换到默认视图', tag: 'HomeController');
|
||||
}
|
||||
});
|
||||
|
||||
// 🔧 Android 15 新增:3秒警告,帮助诊断
|
||||
Timer(const Duration(seconds: 3), () {
|
||||
if (kr_currentListStatus.value == KRHomeViewsListStatus.kr_loading) {
|
||||
KRLogUtil.kr_w('⚠️ 订阅服务初始化已耗时3秒,可能网络较慢', tag: 'HomeController');
|
||||
}
|
||||
});
|
||||
|
||||
@ -498,7 +518,9 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
void _bindConnectionStatus() {
|
||||
// 添加更详细的状态监听
|
||||
ever(KRSingBoxImp.instance.kr_status, (status) {
|
||||
print('🔵 Controller 收到状态变化: ${status.runtimeType}');
|
||||
if (kDebugMode) {
|
||||
print('🔵 Controller 收到状态变化: ${status.runtimeType}');
|
||||
}
|
||||
KRLogUtil.kr_i('🔄 连接状态变化: $status', tag: 'HomeController');
|
||||
KRLogUtil.kr_i('📊 当前状态类型: ${status.runtimeType}', tag: 'HomeController');
|
||||
|
||||
@ -526,7 +548,9 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
break;
|
||||
case SingboxStarted():
|
||||
KRLogUtil.kr_i('🟢 状态: 已启动', tag: 'HomeController');
|
||||
print('🔵 状态变为 Started, 当前延迟=${kr_currentNodeLatency.value}');
|
||||
if (kDebugMode) {
|
||||
print('🔵 状态变为 Started, 当前延迟=${kr_currentNodeLatency.value}');
|
||||
}
|
||||
|
||||
// 取消连接超时处理
|
||||
_cancelConnectionTimeout();
|
||||
@ -538,10 +562,14 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
|
||||
// 🔧 关键修复:如果延迟还是-1(连接中状态),立即设置为0(已连接但延迟未知)
|
||||
if (kr_currentNodeLatency.value == -1) {
|
||||
print('🔵 强制将延迟从 -1 更新为 0');
|
||||
if (kDebugMode) {
|
||||
print('🔵 强制将延迟从 -1 更新为 0');
|
||||
}
|
||||
kr_currentNodeLatency.value = 0;
|
||||
kr_currentNodeLatency.refresh(); // 强制刷新延迟值
|
||||
print('🔵 延迟值已刷新');
|
||||
if (kDebugMode) {
|
||||
print('🔵 延迟值已刷新');
|
||||
}
|
||||
}
|
||||
|
||||
// 🔧 修复:立即尝试更新延迟值
|
||||
@ -551,7 +579,9 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
kr_isConnected.refresh();
|
||||
// 强制更新UI
|
||||
update();
|
||||
print('🔵 状态更新完成,当前延迟=${kr_currentNodeLatency.value}');
|
||||
if (kDebugMode) {
|
||||
print('🔵 状态更新完成,当前延迟=${kr_currentNodeLatency.value}');
|
||||
}
|
||||
break;
|
||||
case SingboxStopping():
|
||||
KRLogUtil.kr_i('🟠 状态: 正在停止', tag: 'HomeController');
|
||||
@ -637,12 +667,16 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
final currentStatus = KRSingBoxImp.instance.kr_status.value;
|
||||
|
||||
KRLogUtil.kr_i('🔵 toggleSwitch 被调用: value=$value, currentStatus=$currentStatus', tag: 'HomeController');
|
||||
print('🔵 toggleSwitch: value=$value, currentStatus=$currentStatus');
|
||||
if (kDebugMode) {
|
||||
print('🔵 toggleSwitch: value=$value, currentStatus=$currentStatus');
|
||||
}
|
||||
|
||||
// 🔧 关键: 如果正在切换中,直接忽略(参考 hiddify-app 的 "switching status, debounce")
|
||||
if (currentStatus is SingboxStarting || currentStatus is SingboxStopping) {
|
||||
KRLogUtil.kr_i('🔄 正在切换中,忽略本次操作 (当前状态: $currentStatus)', tag: 'HomeController');
|
||||
print('🔵 忽略操作:正在切换中');
|
||||
if (kDebugMode) {
|
||||
print('🔵 忽略操作:正在切换中');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -650,17 +684,23 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
if (value) {
|
||||
// 开启连接
|
||||
KRLogUtil.kr_i('🔄 开始连接...', tag: 'HomeController');
|
||||
print('🔵 执行 kr_start()');
|
||||
if (kDebugMode) {
|
||||
print('🔵 执行 kr_start()');
|
||||
}
|
||||
await KRSingBoxImp.instance.kr_start();
|
||||
KRLogUtil.kr_i('✅ 连接命令已发送', tag: 'HomeController');
|
||||
print('🔵 kr_start() 完成');
|
||||
if (kDebugMode) {
|
||||
print('🔵 kr_start() 完成');
|
||||
}
|
||||
|
||||
// 🔧 修复: 等待状态更新,最多3秒
|
||||
await _waitForStatus(SingboxStarted, maxSeconds: 3);
|
||||
} else {
|
||||
// 关闭连接
|
||||
KRLogUtil.kr_i('🛑 开始断开连接...', tag: 'HomeController');
|
||||
print('🔵 执行 kr_stop()');
|
||||
if (kDebugMode) {
|
||||
print('🔵 执行 kr_stop()');
|
||||
}
|
||||
await KRSingBoxImp.instance.kr_stop().timeout(
|
||||
const Duration(seconds: 10),
|
||||
onTimeout: () {
|
||||
@ -669,32 +709,44 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
},
|
||||
);
|
||||
KRLogUtil.kr_i('✅ 断开命令已发送', tag: 'HomeController');
|
||||
print('🔵 kr_stop() 完成');
|
||||
if (kDebugMode) {
|
||||
print('🔵 kr_stop() 完成');
|
||||
}
|
||||
|
||||
// 🔧 修复: 等待状态更新,最多2秒
|
||||
await _waitForStatus(SingboxStopped, maxSeconds: 2);
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('❌ 切换失败: $e', tag: 'HomeController');
|
||||
print('🔵 切换失败: $e');
|
||||
if (kDebugMode) {
|
||||
print('🔵 切换失败: $e');
|
||||
}
|
||||
// 发生错误时强制同步状态
|
||||
kr_forceSyncConnectionStatus();
|
||||
}
|
||||
|
||||
print('🔵 toggleSwitch 完成,当前 kr_isConnected=${kr_isConnected.value}');
|
||||
if (kDebugMode) {
|
||||
print('🔵 toggleSwitch 完成,当前 kr_isConnected=${kr_isConnected.value}');
|
||||
}
|
||||
}
|
||||
|
||||
/// 🔧 等待状态达到预期值
|
||||
Future<void> _waitForStatus(Type expectedType, {int maxSeconds = 3}) async {
|
||||
print('🔵 等待状态变为: $expectedType');
|
||||
if (kDebugMode) {
|
||||
print('🔵 等待状态变为: $expectedType');
|
||||
}
|
||||
final startTime = DateTime.now();
|
||||
|
||||
while (DateTime.now().difference(startTime).inSeconds < maxSeconds) {
|
||||
final currentStatus = KRSingBoxImp.instance.kr_status.value;
|
||||
print('🔵 当前状态: ${currentStatus.runtimeType}');
|
||||
if (kDebugMode) {
|
||||
print('🔵 当前状态: ${currentStatus.runtimeType}');
|
||||
}
|
||||
|
||||
if (currentStatus.runtimeType == expectedType) {
|
||||
print('🔵 状态已达到: $expectedType');
|
||||
if (kDebugMode) {
|
||||
print('🔵 状态已达到: $expectedType');
|
||||
}
|
||||
// 强制同步确保 kr_isConnected 正确
|
||||
kr_forceSyncConnectionStatus();
|
||||
return;
|
||||
@ -703,7 +755,9 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
}
|
||||
|
||||
print('🔵 等待超时,强制同步状态');
|
||||
if (kDebugMode) {
|
||||
print('🔵 等待超时,强制同步状态');
|
||||
}
|
||||
kr_forceSyncConnectionStatus();
|
||||
}
|
||||
|
||||
@ -988,9 +1042,9 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 4. VPN已连接,需要通知后台进行节点切换
|
||||
// 4. VPN已连接,需要重启VPN以断开现有连接并应用新节点
|
||||
try {
|
||||
KRLogUtil.kr_i('🔌 VPN已连接,开始切换后台节点: $tag', tag: 'HomeController');
|
||||
KRLogUtil.kr_i('🔌 VPN已连接,将重启VPN以断开现有连接: $tag', tag: 'HomeController');
|
||||
|
||||
// 🔧 诊断:打印当前活动组信息
|
||||
KRLogUtil.kr_i('📊 当前活动组数量: ${KRSingBoxImp.instance.kr_activeGroups.length}', tag: 'HomeController');
|
||||
@ -1007,22 +1061,61 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
kr_currentNodeLatency.value = -1;
|
||||
kr_isLatency.value = true; // 显示加载动画
|
||||
|
||||
// 等待后台节点切换完成(关键!)
|
||||
KRLogUtil.kr_i('⏳ 调用 kr_selectOutbound($tag),等待完成...', tag: 'HomeController');
|
||||
await KRSingBoxImp.instance.kr_selectOutbound(tag);
|
||||
KRLogUtil.kr_i('✅ kr_selectOutbound 完成,开始更新 UI', tag: 'HomeController');
|
||||
// 🔧 关键修复:保存新节点选择
|
||||
KRLogUtil.kr_i('💾 保存新节点选择: $tag', tag: 'HomeController');
|
||||
await KRSecureStorage().kr_saveData(key: 'SELECTED_NODE_TAG', value: tag);
|
||||
|
||||
// 🔧 方案A增强:重启VPN连接以断开所有现有长连接
|
||||
KRLogUtil.kr_i('🔄 [增强] 停止VPN连接以断开现有连接...', tag: 'HomeController');
|
||||
await KRSingBoxImp.instance.kr_stop(); // 先停止VPN
|
||||
|
||||
// 🚀 方案A增强:增加等待时间,确保所有连接完全释放
|
||||
KRLogUtil.kr_i('⏳ [增强] 等待VPN完全停止(1500ms,确保旧连接全部断开)...', tag: 'HomeController');
|
||||
await Future.delayed(const Duration(milliseconds: 1500)); // 从800ms增加到1500ms
|
||||
|
||||
KRLogUtil.kr_i('🔄 [增强] 启动VPN并应用新节点: $tag', tag: 'HomeController');
|
||||
await KRSingBoxImp.instance.kr_start(); // 重新启动VPN,会自动使用新保存的节点
|
||||
|
||||
// 🚀 方案A增强:增加启动等待时间,确保新VPN完全建立
|
||||
KRLogUtil.kr_i('⏳ [增强] 等待VPN完全启动(2500ms,确保新连接完全建立)...', tag: 'HomeController');
|
||||
await Future.delayed(const Duration(milliseconds: 2500)); // 从1500ms增加到2500ms
|
||||
|
||||
// 后台切换成功,更新UI
|
||||
kr_cutSeletedTag.value = tag;
|
||||
kr_updateConnectionInfo();
|
||||
kr_moveToSelectedNode();
|
||||
|
||||
// 🔧 关键修复:节点切换完成后,立即获取新节点的延迟值
|
||||
// 这样可以避免UI继续显示"正在连接"状态
|
||||
await Future.delayed(const Duration(milliseconds: 200)); // 等待活动组更新
|
||||
// 🚀 方案A增强:增加验证前等待时间,确保活动组完全更新
|
||||
KRLogUtil.kr_i('⏳ [增强] 等待活动组更新(500ms)...', tag: 'HomeController');
|
||||
await Future.delayed(const Duration(milliseconds: 500)); // 从200ms增加到500ms
|
||||
|
||||
// 🚀 方案A增强:验证节点是否真正切换成功
|
||||
KRLogUtil.kr_i('🔍 [增强] 验证节点切换是否成功...', tag: 'HomeController');
|
||||
try {
|
||||
// 刷新活动组信息
|
||||
final activeGroups = KRSingBoxImp.instance.kr_activeGroups;
|
||||
final selectGroup = activeGroups.firstWhere(
|
||||
(group) => group.tag == 'select',
|
||||
orElse: () => throw Exception('未找到 select 组'),
|
||||
);
|
||||
|
||||
KRLogUtil.kr_i('📊 [增强] Select组当前选中: ${selectGroup.selected}', tag: 'HomeController');
|
||||
KRLogUtil.kr_i('📊 [增强] 目标节点: $tag', tag: 'HomeController');
|
||||
|
||||
if (selectGroup.selected != tag) {
|
||||
KRLogUtil.kr_w('⚠️ [增强] 节点选择验证失败,实际选中: ${selectGroup.selected}', tag: 'HomeController');
|
||||
// 不抛出异常,但记录警告
|
||||
} else {
|
||||
KRLogUtil.kr_i('✅ [增强] 节点选择验证成功!', tag: 'HomeController');
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_w('⚠️ [增强] 节点验证过程出错: $e', tag: 'HomeController');
|
||||
}
|
||||
|
||||
// 更新延迟信息
|
||||
_kr_updateLatencyOnConnected();
|
||||
|
||||
KRLogUtil.kr_i('✅ 节点切换成功: $tag', tag: 'HomeController');
|
||||
KRLogUtil.kr_i('✅ 节点切换成功(已重启VPN断开旧连接): $tag', tag: 'HomeController');
|
||||
return true;
|
||||
|
||||
} catch (switchError) {
|
||||
@ -1034,6 +1127,13 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
kr_currentNodeName.value = originalTag; // 🔧 修复:同时恢复节点名称显示
|
||||
kr_currentNodeLatency.value = -2; // 恢复为未连接状态
|
||||
|
||||
// 恢复原节点选择
|
||||
try {
|
||||
await KRSecureStorage().kr_saveData(key: 'SELECTED_NODE_TAG', value: originalTag);
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('❌ 恢复节点选择失败: $e', tag: 'HomeController');
|
||||
}
|
||||
|
||||
// 显示错误提示给用户
|
||||
KRCommonUtil.kr_showToast('节点切换失败,已恢复为: $originalTag');
|
||||
|
||||
@ -1333,8 +1433,10 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
|
||||
// 更新底部面板高度
|
||||
void kr_updateBottomPanelHeight() {
|
||||
// 🔧 Android 15 优化:加载状态时也允许更新高度,显示加载指示器
|
||||
if (kr_subscribeService.kr_currentStatus ==
|
||||
KRHomeViewsListStatus.kr_loading) {
|
||||
KRHomeViewsListStatus.kr_loading &&
|
||||
kr_currentListStatus.value != KRHomeViewsListStatus.kr_loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -50,81 +50,76 @@ class KRHomeBottomPanel extends GetView<KRHomeController> {
|
||||
}
|
||||
|
||||
Widget _kr_buildDefaultView(BuildContext context) {
|
||||
// 使用 GetX 的 .obs 变量来避免重复访问
|
||||
final hasValidSubscription =
|
||||
controller.kr_subscribeService.kr_currentSubscribe.value != null;
|
||||
final isTrial = controller.kr_subscribeService.kr_isTrial;
|
||||
final isLastDay = controller.kr_subscribeService.kr_isLastDayOfSubscription;
|
||||
// 🔧 Android 15 增强:增加防御性检查,避免空指针
|
||||
bool hasValidSubscription = false;
|
||||
bool isTrial = false;
|
||||
bool isLastDay = false;
|
||||
|
||||
try {
|
||||
hasValidSubscription = controller.kr_subscribeService.kr_currentSubscribe.value != null;
|
||||
isTrial = controller.kr_subscribeService.kr_isTrial.value;
|
||||
isLastDay = controller.kr_subscribeService.kr_isLastDayOfSubscription.value;
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('获取订阅数据异常: $e', tag: 'HomeBottomPanel');
|
||||
}
|
||||
|
||||
final isNotLoggedIn = controller.kr_currentViewStatus.value ==
|
||||
KRHomeViewsStatus.kr_notLoggedIn;
|
||||
|
||||
KRLogUtil.kr_i('构建默认视图', tag: 'HomeBottomPanel');
|
||||
KRLogUtil.kr_i('是否未登录: $isNotLoggedIn', tag: 'HomeBottomPanel');
|
||||
KRLogUtil.kr_i('是否有有效订阅: $hasValidSubscription', tag: 'HomeBottomPanel');
|
||||
KRLogUtil.kr_i('是否试用: ${isTrial.value}', tag: 'HomeBottomPanel');
|
||||
KRLogUtil.kr_i('是否试用: $isTrial', tag: 'HomeBottomPanel');
|
||||
KRLogUtil.kr_i('当前高度: ${controller.kr_bottomPanelHeight.value}',
|
||||
tag: 'HomeBottomPanel');
|
||||
|
||||
// 🔧 关键修复:统一布局逻辑,确保无论登录状态如何都显示完整UI
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 主要内容区域
|
||||
if (isNotLoggedIn)
|
||||
// 未登录状态下,使用 SingleChildScrollView 让内容自然撑开
|
||||
SingleChildScrollView(
|
||||
// 主要内容区域 - 始终使用 Expanded + ScrollView 确保内容可见
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 🔧 核心修复:无论登录状态,都显示核心卡片(订阅或连接信息)
|
||||
if (hasValidSubscription)
|
||||
// 已订阅:显示连接信息卡片
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 12.h),
|
||||
child: const KRHomeConnectionInfoView(),
|
||||
)
|
||||
else
|
||||
// 未订阅(包括未登录):始终显示订阅卡片
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 12.h, left: 12.w, right: 12.w),
|
||||
child: const KRSubscriptionCard(),
|
||||
),
|
||||
|
||||
// 2. 如果已订阅且是试用,展示试用卡片
|
||||
if (hasValidSubscription && isTrial)
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 12.h),
|
||||
child: const KRHomeTrialCard(),
|
||||
),
|
||||
|
||||
// 3. 如果已订阅且是最后一天,展示最后一天卡片
|
||||
if (hasValidSubscription && isLastDay && !isTrial)
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 12.h),
|
||||
child: const KRHomeLastDayCard(),
|
||||
),
|
||||
|
||||
// 4. 连接选项(分组和国家入口)- 始终显示
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.w),
|
||||
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h),
|
||||
child: const KRHomeConnectionOptionsView(),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
else
|
||||
// 已登录状态下,使用固定高度
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 1. 如果已订阅,展示当前连接卡片
|
||||
if (hasValidSubscription)
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 12.h),
|
||||
child: const KRHomeConnectionInfoView())
|
||||
else
|
||||
Container(
|
||||
margin:
|
||||
EdgeInsets.only(top: 12.h, left: 12.w, right: 12.w),
|
||||
child: const KRSubscriptionCard()),
|
||||
|
||||
// 2. 如果已订阅且是试用,展示试用卡片
|
||||
if (hasValidSubscription && isTrial.value)
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 12.h),
|
||||
child: const KRHomeTrialCard(),
|
||||
),
|
||||
|
||||
// 3. 如果已订阅且是最后一天,展示最后一天卡片
|
||||
if (hasValidSubscription && isLastDay.value && !isTrial.value)
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 12.h),
|
||||
child: const KRHomeLastDayCard(),
|
||||
),
|
||||
|
||||
// 4. 连接选项(分组和国家入口)
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.w),
|
||||
child: const KRHomeConnectionOptionsView(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -134,17 +129,56 @@ class KRHomeBottomPanel extends GetView<KRHomeController> {
|
||||
KRLogUtil.kr_i('当前高度: ${controller.kr_bottomPanelHeight.value}',
|
||||
tag: 'HomeBottomPanel');
|
||||
|
||||
return Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.green,
|
||||
strokeWidth: 2.0,
|
||||
),
|
||||
// 🔧 Android 15 紧急修复:加载时显示完整的默认内容 + 加载指示器
|
||||
// 而不是只显示一个转圈圈,避免用户看到空白面板
|
||||
return Stack(
|
||||
children: [
|
||||
// 底层:显示默认内容(半透明)
|
||||
Opacity(
|
||||
opacity: 0.5,
|
||||
child: _kr_buildDefaultView(Get.context!),
|
||||
),
|
||||
// 顶层:加载指示器
|
||||
Center(
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(16.w),
|
||||
decoration: BoxDecoration(
|
||||
color: Get.context!.theme.cardColor,
|
||||
borderRadius: BorderRadius.circular(12.r),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
color: Colors.green,
|
||||
strokeWidth: 3.0,
|
||||
),
|
||||
SizedBox(height: 12.h),
|
||||
Text(
|
||||
'正在加载...',
|
||||
style: TextStyle(
|
||||
fontSize: 14.sp,
|
||||
color: Get.context!.theme.textTheme.bodyMedium?.color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _kr_buildErrorView(BuildContext context) {
|
||||
return Container(
|
||||
height: 200.w,
|
||||
height: 200.h,
|
||||
padding: EdgeInsets.all(16.w),
|
||||
child: Center(
|
||||
child: Column(
|
||||
@ -155,7 +189,7 @@ class KRHomeBottomPanel extends GetView<KRHomeController> {
|
||||
size: 48.w,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
SizedBox(height: 16.w),
|
||||
SizedBox(height: 16.h),
|
||||
Text(
|
||||
AppTranslations.kr_home.error,
|
||||
style: KrAppTextStyle(
|
||||
@ -164,7 +198,7 @@ class KRHomeBottomPanel extends GetView<KRHomeController> {
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8.w),
|
||||
SizedBox(height: 8.h),
|
||||
Text(
|
||||
AppTranslations.kr_home.checkNetwork,
|
||||
style: KrAppTextStyle(
|
||||
@ -172,7 +206,7 @@ class KRHomeBottomPanel extends GetView<KRHomeController> {
|
||||
color: Theme.of(context).textTheme.bodySmall?.color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24.w),
|
||||
SizedBox(height: 24.h),
|
||||
SizedBox(
|
||||
width: 200.w,
|
||||
height: 44.h,
|
||||
|
||||
@ -10,6 +10,7 @@ import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart';
|
||||
import 'package:kaer_with_panels/singbox/model/singbox_status.dart';
|
||||
import '../controllers/kr_home_controller.dart';
|
||||
import '../models/kr_home_views_status.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class KRHomeConnectionInfoView extends GetView<KRHomeController> {
|
||||
const KRHomeConnectionInfoView({super.key});
|
||||
@ -26,7 +27,7 @@ class KRHomeConnectionInfoView extends GetView<KRHomeController> {
|
||||
return Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 16.w),
|
||||
width: double.infinity,
|
||||
height: 116.w,
|
||||
height: 116.h,
|
||||
decoration: ShapeDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
@ -73,7 +74,7 @@ class KRHomeConnectionInfoView extends GetView<KRHomeController> {
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 10.w),
|
||||
SizedBox(height: 10.h),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
@ -82,7 +83,9 @@ class KRHomeConnectionInfoView extends GetView<KRHomeController> {
|
||||
// 🔧 修复:使用 Obx 包裹确保国旗响应式更新
|
||||
Obx(() {
|
||||
final countryCode = controller.kr_getCurrentNodeCountry();
|
||||
print('🌍 ConnectionInfo 更新,国家代码: $countryCode');
|
||||
if (kDebugMode) {
|
||||
print('🌍 ConnectionInfo 更新,国家代码: $countryCode');
|
||||
}
|
||||
return KRCountryFlag(
|
||||
countryCode: countryCode,
|
||||
);
|
||||
@ -99,12 +102,14 @@ class KRHomeConnectionInfoView extends GetView<KRHomeController> {
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 6.w),
|
||||
SizedBox(height: 6.h),
|
||||
Row(
|
||||
children: [
|
||||
Obx(() {
|
||||
final delay = controller.kr_currentNodeLatency.value;
|
||||
print('🔵 UI延迟显示更新: delay=$delay');
|
||||
if (kDebugMode) {
|
||||
print('🔵 UI延迟显示更新: delay=$delay');
|
||||
}
|
||||
|
||||
// 获取延迟颜色
|
||||
Color getLatencyColor(int delay) {
|
||||
@ -227,7 +232,9 @@ class KRHomeConnectionInfoView extends GetView<KRHomeController> {
|
||||
final isSwitching = status is SingboxStarting || status is SingboxStopping;
|
||||
|
||||
// 🔧 调试日志
|
||||
print('🔵 Switch UI 更新: status=${status.runtimeType}, isConnected=$isConnected, isSwitching=$isSwitching');
|
||||
if (kDebugMode) {
|
||||
print('🔵 Switch UI 更新: status=${status.runtimeType}, isConnected=$isConnected, isSwitching=$isSwitching');
|
||||
}
|
||||
|
||||
return CupertinoSwitch(
|
||||
value: isConnected,
|
||||
@ -235,7 +242,9 @@ class KRHomeConnectionInfoView extends GetView<KRHomeController> {
|
||||
onChanged: isSwitching
|
||||
? null
|
||||
: (bool value) {
|
||||
print('🔵 Switch onChanged 触发: 请求=$value, 当前状态=$status');
|
||||
if (kDebugMode) {
|
||||
print('🔵 Switch onChanged 触发: 请求=$value, 当前状态=$status');
|
||||
}
|
||||
controller.kr_toggleSwitch(value);
|
||||
},
|
||||
activeColor: Colors.blue,
|
||||
|
||||
@ -26,7 +26,7 @@ class KRHomeConnectionOptionsView extends GetView<KRHomeController> {
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8.w),
|
||||
SizedBox(height: 8.h),
|
||||
_buildConnectionOption(
|
||||
"home_ct",
|
||||
AppTranslations.kr_home.countryRegion,
|
||||
@ -66,7 +66,7 @@ class KRHomeConnectionOptionsView extends GetView<KRHomeController> {
|
||||
height: 32.w,
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
),
|
||||
SizedBox(height: 12.w),
|
||||
SizedBox(height: 12.h),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
|
||||
@ -64,7 +64,7 @@ class KRHomeLastDayCard extends GetView<KRHomeController> {
|
||||
),
|
||||
|
||||
// 倒计时显示
|
||||
SizedBox(height: 10.w),
|
||||
SizedBox(height: 10.h),
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
@ -123,7 +123,7 @@ class KRHomeLastDayCard extends GetView<KRHomeController> {
|
||||
?.color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4.w),
|
||||
SizedBox(height: 4.h),
|
||||
Text(
|
||||
AppTranslations.kr_home.subscriptionEndMessage,
|
||||
style: KrAppTextStyle(
|
||||
|
||||
@ -16,6 +16,7 @@ import 'package:kaer_with_panels/app/widgets/kr_network_image.dart';
|
||||
import '../../../widgets/kr_simple_loading.dart';
|
||||
|
||||
import '../../../../singbox/model/singbox_proxy_type.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
/// 节点列表视图组件
|
||||
/// 用于展示所有节点相关的列表视图
|
||||
@ -71,7 +72,7 @@ class KRHomeNodeListView extends GetView<KRHomeController> {
|
||||
Widget _buildServerList(BuildContext context) {
|
||||
return Container(
|
||||
width: ScreenUtil().screenWidth,
|
||||
height: 360.w, // 减小高度比例
|
||||
height: 360.h, // 减小高度比例
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).primaryColor,
|
||||
borderRadius: BorderRadius.only(
|
||||
@ -83,7 +84,7 @@ class KRHomeNodeListView extends GetView<KRHomeController> {
|
||||
children: [
|
||||
// 标题栏
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.w),
|
||||
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
@ -221,7 +222,7 @@ class KRHomeNodeListView extends GetView<KRHomeController> {
|
||||
country.isExpand.value = !country.isExpand.value;
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 12.w),
|
||||
padding: EdgeInsets.symmetric(vertical: 12.h),
|
||||
child: Row(
|
||||
children: [
|
||||
KRCountryFlag(
|
||||
@ -281,7 +282,9 @@ class KRHomeNodeListView extends GetView<KRHomeController> {
|
||||
// 🔧 修复:改为 async,等待节点切换完成后再关闭列表
|
||||
onTap: () async {
|
||||
try {
|
||||
print('🔄 用户点击节点: ${server.tag}');
|
||||
if (kDebugMode) {
|
||||
print('🔄 用户点击节点: ${server.tag}');
|
||||
}
|
||||
// 使用统一的节点切换方法,等待完成
|
||||
final success = await controller
|
||||
.kr_performNodeSwitch(server.tag);
|
||||
@ -290,19 +293,25 @@ class KRHomeNodeListView extends GetView<KRHomeController> {
|
||||
if (success) {
|
||||
controller.kr_currentListStatus.value =
|
||||
KRHomeViewsListStatus.kr_none;
|
||||
print('✅ 节点切换成功,关闭列表');
|
||||
if (kDebugMode) {
|
||||
print('✅ 节点切换成功,关闭列表');
|
||||
}
|
||||
} else {
|
||||
print('❌ 节点切换失败,列表保持打开');
|
||||
if (kDebugMode) {
|
||||
print('❌ 节点切换失败,列表保持打开');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
print('❌ 节点切换异常: $e');
|
||||
if (kDebugMode) {
|
||||
print('❌ 节点切换异常: $e');
|
||||
}
|
||||
KRLogUtil.kr_e('节点切换异常: $e',
|
||||
tag: 'NodeListView');
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 8.w,
|
||||
vertical: 8.h,
|
||||
horizontal: 16.w,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
@ -397,7 +406,7 @@ class KRHomeNodeListView extends GetView<KRHomeController> {
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 4.w),
|
||||
padding: EdgeInsets.symmetric(vertical: 4.h),
|
||||
child: _kr_buildNodeListItem(
|
||||
context,
|
||||
item: server,
|
||||
@ -444,13 +453,13 @@ class KRHomeNodeListView extends GetView<KRHomeController> {
|
||||
onClose: () =>
|
||||
controller.kr_currentListStatus.value = KRHomeViewsListStatus.kr_none,
|
||||
),
|
||||
SizedBox(height: 16.w),
|
||||
SizedBox(height: 16.h),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(child: listContent),
|
||||
// 添加底部间距
|
||||
SizedBox(height: 12.w),
|
||||
SizedBox(height: 12.h),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -544,7 +553,7 @@ class KRHomeNodeListView extends GetView<KRHomeController> {
|
||||
|
||||
return Container(
|
||||
key: ValueKey(item.id),
|
||||
padding: EdgeInsets.symmetric(vertical: 8.w),
|
||||
padding: EdgeInsets.symmetric(vertical: 8.h),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
@ -575,7 +584,7 @@ class KRHomeNodeListView extends GetView<KRHomeController> {
|
||||
? Container(
|
||||
margin: EdgeInsets.only(left: 4.w),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 4.w, vertical: 1.w),
|
||||
horizontal: 4.w, vertical: 1.h),
|
||||
decoration: BoxDecoration(
|
||||
color: krModernGreenLight.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(4.w),
|
||||
@ -593,7 +602,7 @@ class KRHomeNodeListView extends GetView<KRHomeController> {
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 2.w),
|
||||
SizedBox(height: 2.h),
|
||||
Text(
|
||||
item.city,
|
||||
style: KrAppTextStyle(
|
||||
@ -666,7 +675,7 @@ class KRHomeNodeListView extends GetView<KRHomeController> {
|
||||
InkWell(
|
||||
onTap: () => controller.kr_urlTest(),
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 8.w),
|
||||
padding: EdgeInsets.symmetric(vertical: 8.h),
|
||||
margin: EdgeInsets.only(top: 8.w), // 添加上方间距
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
@ -719,7 +728,7 @@ class KRHomeNodeListView extends GetView<KRHomeController> {
|
||||
),
|
||||
),
|
||||
if (!controller.kr_isLatency.value) ...[
|
||||
SizedBox(height: 2.w),
|
||||
SizedBox(height: 2.h),
|
||||
Text(
|
||||
AppTranslations.kr_home.refreshLatencyDesc,
|
||||
style: KrAppTextStyle(
|
||||
@ -766,7 +775,7 @@ class KRHomeNodeListView extends GetView<KRHomeController> {
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 8.w),
|
||||
padding: EdgeInsets.symmetric(vertical: 8.h),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
@ -801,7 +810,7 @@ class KRHomeNodeListView extends GetView<KRHomeController> {
|
||||
Container(
|
||||
margin: EdgeInsets.only(left: 4.w),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 4.w, vertical: 1.w),
|
||||
horizontal: 4.w, vertical: 1.h),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
krModernGreenLight.withOpacity(0.1),
|
||||
@ -818,7 +827,7 @@ class KRHomeNodeListView extends GetView<KRHomeController> {
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 2.w),
|
||||
SizedBox(height: 2.h),
|
||||
Obx(() {
|
||||
// 获取当前自动选择的节点
|
||||
String selectedNode =
|
||||
@ -920,7 +929,7 @@ class KRHomeNodeListView extends GetView<KRHomeController> {
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 4.w),
|
||||
padding: EdgeInsets.symmetric(vertical: 4.h),
|
||||
child: _kr_buildNodeListItem(
|
||||
context,
|
||||
item: node,
|
||||
|
||||
@ -65,7 +65,7 @@ class KRHomeTrialCard extends GetView<KRHomeController> {
|
||||
),
|
||||
|
||||
// 倒计时显示
|
||||
SizedBox(height: 10.w),
|
||||
SizedBox(height: 10.h),
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
@ -133,7 +133,7 @@ class KRHomeTrialCard extends GetView<KRHomeController> {
|
||||
: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4.w),
|
||||
SizedBox(height: 4.h),
|
||||
Text(
|
||||
AppTranslations.kr_home.trialEndMessage,
|
||||
style: KrAppTextStyle(
|
||||
|
||||
@ -5,6 +5,7 @@ import 'package:latlong2/latlong.dart';
|
||||
import '../../../utils/kr_fm_tc.dart';
|
||||
import '../controllers/kr_home_controller.dart';
|
||||
import '../../../utils/kr_log_util.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
/// 首页地图视图组件
|
||||
class KRHomeMapView extends GetView<KRHomeController> {
|
||||
@ -187,12 +188,16 @@ return GetBuilder<KRHomeController>(
|
||||
|
||||
/// 构建样式化的标记
|
||||
Marker _buildStyledMarker(dynamic node) {
|
||||
print("原始Marker:${node}");
|
||||
if (kDebugMode) {
|
||||
print("原始Marker:${node}");
|
||||
}
|
||||
int type = 0;
|
||||
MaterialColor markerColor = Colors.grey;
|
||||
//如果没订阅过,就是灰色
|
||||
bool status = controller.kr_isConnected.value;
|
||||
print("连接状态:$status");
|
||||
if (kDebugMode) {
|
||||
print("连接状态:$status");
|
||||
}
|
||||
if(status){
|
||||
if (node.urlTestDelay.value == 0) {
|
||||
// 延迟为0时使用默认颜色
|
||||
|
||||
@ -37,7 +37,7 @@ class KRSubscribeSelectorView extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.w),
|
||||
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.withOpacity(0.05),
|
||||
borderRadius: BorderRadius.only(
|
||||
@ -78,7 +78,7 @@ class KRSubscribeSelectorView extends StatelessWidget {
|
||||
final subscribes = homeController.kr_subscribeService.kr_availableSubscribes;
|
||||
if (subscribes.isEmpty) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 16.w, horizontal: 12.w),
|
||||
padding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 12.w),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
@ -86,7 +86,7 @@ class KRSubscribeSelectorView extends StatelessWidget {
|
||||
size: 48.w,
|
||||
color: Theme.of(context).textTheme.bodyLarge?.color?.withOpacity(0.3),
|
||||
),
|
||||
SizedBox(height: 12.w),
|
||||
SizedBox(height: 12.h),
|
||||
Text(
|
||||
AppTranslations.kr_purchaseMembership.noData,
|
||||
style: KrAppTextStyle(
|
||||
@ -105,7 +105,7 @@ class KRSubscribeSelectorView extends StatelessWidget {
|
||||
),
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.symmetric(vertical: 4.w, horizontal: 4.w),
|
||||
padding: EdgeInsets.symmetric(vertical: 4.h, horizontal: 4.w),
|
||||
itemCount: subscribes.length,
|
||||
itemBuilder: (context, index) {
|
||||
final subscribe = subscribes[index];
|
||||
@ -123,7 +123,7 @@ class KRSubscribeSelectorView extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}),
|
||||
SizedBox(height: 8.w),
|
||||
SizedBox(height: 8.h),
|
||||
],
|
||||
),
|
||||
);
|
||||
@ -159,7 +159,7 @@ class _SubscribeItem extends StatelessWidget {
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 2.w),
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 2.h),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
@ -199,7 +199,7 @@ class _SubscribeItem extends StatelessWidget {
|
||||
),
|
||||
if (isCurrent)
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 2.w),
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 2.h),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue,
|
||||
borderRadius: BorderRadius.circular(16.r),
|
||||
@ -222,7 +222,7 @@ class _SubscribeItem extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 8.w),
|
||||
SizedBox(height: 8.h),
|
||||
Text(
|
||||
isUnlimited
|
||||
? AppTranslations.kr_purchaseMembership.unlimitedTraffic
|
||||
@ -234,7 +234,7 @@ class _SubscribeItem extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
if (!isUnlimited) ...[
|
||||
SizedBox(height: 8.w),
|
||||
SizedBox(height: 8.h),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4.r),
|
||||
child: LinearProgressIndicator(
|
||||
|
||||
@ -158,7 +158,7 @@ class KRInviteController extends GetxController {
|
||||
borderRadius: BorderRadius.circular(16.r),
|
||||
),
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 24.w),
|
||||
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 24.h),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@ -170,7 +170,7 @@ class KRInviteController extends GetxController {
|
||||
color: Theme.of(Get.context!).textTheme.titleMedium?.color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16.w),
|
||||
SizedBox(height: 16.h),
|
||||
Container(
|
||||
padding: EdgeInsets.all(16.w),
|
||||
decoration: BoxDecoration(
|
||||
@ -195,7 +195,7 @@ class KRInviteController extends GetxController {
|
||||
foregroundColor: Colors.black,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20.w),
|
||||
SizedBox(height: 20.h),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: 44.w,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:kaer_with_panels/app/common/app_config.dart';
|
||||
import 'package:kaer_with_panels/app/localization/app_translations.dart';
|
||||
import 'package:kaer_with_panels/app/widgets/kr_local_image.dart';
|
||||
import '../controllers/kr_invite_controller.dart';
|
||||
|
||||
@ -75,6 +75,9 @@ class KRLoginController extends GetxController
|
||||
/// 登陆进度状态
|
||||
var kr_loginStatus = KRLoginProgressStatus.kr_registerSendCode.obs;
|
||||
|
||||
/// 是否使用密码登录(手机号模式下有效:true=密码登录,false=验证码登录)
|
||||
var kr_isPasswordLogin = true.obs;
|
||||
|
||||
/// 验证码倒计时
|
||||
var _countdown = 60; // 倒计时初始值
|
||||
late Timer _timer;
|
||||
@ -172,8 +175,8 @@ class KRLoginController extends GetxController
|
||||
}
|
||||
// 如果没有匹配的 entry 值,则保持默认的 kr_loginByPsd 状态
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 初始化计时器
|
||||
_timer = Timer(Duration.zero, () {});
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@ import 'package:kaer_with_panels/app/widgets/hi_help_entrance.dart';
|
||||
import 'package:kaer_with_panels/app/common/app_run_data.dart';
|
||||
import 'package:kaer_with_panels/app/modules/kr_home/controllers/kr_home_controller.dart';
|
||||
import 'package:kaer_with_panels/app/services/kr_site_config_service.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class KRLoginView extends GetView<KRLoginController> {
|
||||
const KRLoginView({super.key});
|
||||
|
||||
@ -35,7 +35,7 @@ class KRSearchAreaView extends GetView<KRSearchAreaController> {
|
||||
onTap: () {}, // 阻止点击事件传递到背景
|
||||
child: Container(
|
||||
width: 300.w,
|
||||
height: 450.w,
|
||||
height: 450.h,
|
||||
padding: EdgeInsets.all(16.w),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.primaryColor,
|
||||
@ -46,18 +46,18 @@ class KRSearchAreaView extends GetView<KRSearchAreaController> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'选择其他地区',
|
||||
'login.selectOtherRegion'.tr,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 15.w,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: theme.textTheme.titleMedium?.color),
|
||||
),
|
||||
SizedBox(height: 10.w),
|
||||
SizedBox(height: 10.h),
|
||||
TextField(
|
||||
onChanged: (value) => controller.searchQuery.value = value,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: Icon(Icons.search, color: Colors.grey),
|
||||
hintText: '搜索',
|
||||
hintText: 'login.search'.tr,
|
||||
hintStyle: TextStyle(color: Colors.grey),
|
||||
filled: true,
|
||||
// fillColor: Colors.grey.shade200,
|
||||
@ -65,11 +65,11 @@ class KRSearchAreaView extends GetView<KRSearchAreaController> {
|
||||
borderRadius: BorderRadius.circular(8.w),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 10.w),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 10.h),
|
||||
),
|
||||
style: theme.textTheme.bodyMedium?.copyWith(fontSize: 14.sp, fontFamily: 'AlibabaPuHuiTi-Regular',),
|
||||
),
|
||||
// SizedBox(height: 5.w),
|
||||
// SizedBox(height: 5.h),
|
||||
Obx(() => Expanded(
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
@ -84,7 +84,7 @@ class KRSearchAreaView extends GetView<KRSearchAreaController> {
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 10.w, horizontal: 0),
|
||||
vertical: 10.h, horizontal: 0),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
|
||||
@ -6,6 +6,10 @@ import 'package:kaer_with_panels/app/modules/kr_statistics/views/kr_statistics_v
|
||||
import 'package:kaer_with_panels/app/modules/kr_user_info/views/kr_user_info_view.dart';
|
||||
import 'package:kaer_with_panels/app/modules/kr_user_info/controllers/kr_user_info_controller.dart';
|
||||
import 'package:kaer_with_panels/app/widgets/kr_keep_alive_wrapper.dart';
|
||||
import 'package:kaer_with_panels/app/common/app_run_data.dart';
|
||||
import 'package:kaer_with_panels/app/localization/app_translations.dart';
|
||||
import 'package:kaer_with_panels/app/widgets/dialogs/kr_dialog.dart';
|
||||
import 'package:kaer_with_panels/app/routes/app_pages.dart';
|
||||
|
||||
import '../../../widgets/kr_language_switch_dialog.dart';
|
||||
|
||||
@ -56,9 +60,38 @@ class KRMainController extends GetxController {
|
||||
|
||||
/// 到哪个页面,具体传值查看MainRoutes的枚举类
|
||||
kr_setPage(int index) {
|
||||
// 🔒 检查邀请页面(index=1)是否需要登录
|
||||
if (index == 1) {
|
||||
final appRunData = KRAppRunData.getInstance();
|
||||
final isDeviceLogin = appRunData.isDeviceLogin();
|
||||
final isLoggedIn = appRunData.kr_isLogin.value && !isDeviceLogin;
|
||||
|
||||
// 如果是游客模式,弹出登录提示对话框,不切换页面
|
||||
if (!isLoggedIn) {
|
||||
KRDialog.show(
|
||||
title: AppTranslations.kr_dialog.deviceLoginBindingTitle,
|
||||
message: AppTranslations.kr_dialog.deviceLoginBindingMessage,
|
||||
confirmText: AppTranslations.kr_dialog.kr_ok,
|
||||
cancelText: AppTranslations.kr_dialog.kr_cancel,
|
||||
onConfirm: () {
|
||||
Get.back(); // 关闭对话框
|
||||
// 延迟后跳转到登录页面
|
||||
Future.delayed(const Duration(milliseconds: 300), () {
|
||||
Get.toNamed(Routes.MR_LOGIN);
|
||||
});
|
||||
},
|
||||
onCancel: () {
|
||||
Get.back(); // 关闭对话框
|
||||
},
|
||||
);
|
||||
return; // 不切换页面
|
||||
}
|
||||
}
|
||||
|
||||
// 正常切换页面
|
||||
kr_currentIndex.value = index;
|
||||
pageController.jumpToPage(index);
|
||||
|
||||
|
||||
// 监控页面进入
|
||||
if (index == MainRoutes.USER_CENTER.i) {
|
||||
final userInfoController = Get.find<KRUserInfoController>();
|
||||
|
||||
@ -10,6 +10,7 @@ import '../../../utils/kr_event_bus.dart';
|
||||
import '../../../utils/kr_common_util.dart';
|
||||
import '../../../localization/app_translations.dart';
|
||||
import '../../../utils/kr_log_util.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
/// 订单状态控制器(参考 Tauri 项目实现)
|
||||
class KROrderStatusController extends GetxController {
|
||||
@ -71,12 +72,24 @@ class KROrderStatusController extends GetxController {
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
print('═══════════════════════════════════════');
|
||||
print('📊 订单状态页面初始化');
|
||||
print(' 订单号: $kr_order');
|
||||
print(' 支付方式: $kr_paymentType');
|
||||
print(' Checkout类型: $kr_checkoutType');
|
||||
print('═══════════════════════════════════════');
|
||||
if (kDebugMode) {
|
||||
print('═══════════════════════════════════════');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('📊 订单状态页面初始化');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' 订单号: $kr_order');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' 支付方式: $kr_paymentType');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' Checkout类型: $kr_checkoutType');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('═══════════════════════════════════════');
|
||||
}
|
||||
|
||||
// 立即查询一次订单状态,获取创建时间
|
||||
kr_checkPaymentStatus();
|
||||
@ -92,7 +105,9 @@ class KROrderStatusController extends GetxController {
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
print('🔚 订单状态页面关闭,清理定时器');
|
||||
if (kDebugMode) {
|
||||
print('🔚 订单状态页面关闭,清理定时器');
|
||||
}
|
||||
kr_timer?.cancel();
|
||||
kr_countdownTimer?.cancel();
|
||||
super.onClose();
|
||||
@ -100,7 +115,9 @@ class KROrderStatusController extends GetxController {
|
||||
|
||||
/// 开始检查支付状态(参考 Tauri 项目:5秒轮询一次)
|
||||
void kr_startCheckingPaymentStatus() {
|
||||
print('🔄 启动支付状态轮询(每5秒检查一次)');
|
||||
if (kDebugMode) {
|
||||
print('🔄 启动支付状态轮询(每5秒检查一次)');
|
||||
}
|
||||
|
||||
// 状态轮询:每5秒检查一次订单状态
|
||||
kr_timer = Timer.periodic(const Duration(seconds: 5), (timer) {
|
||||
@ -118,7 +135,9 @@ class KROrderStatusController extends GetxController {
|
||||
if (kr_orderCreatedAt == null) {
|
||||
// 订单创建时间还未获取,显示默认倒计时
|
||||
kr_formattedCountdown.value = '15:00';
|
||||
print('⏱️ 倒计时更新: 等待订单创建时间...');
|
||||
if (kDebugMode) {
|
||||
print('⏱️ 倒计时更新: 等待订单创建时间...');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -128,12 +147,24 @@ class KROrderStatusController extends GetxController {
|
||||
|
||||
final timeLeft = targetTime - now;
|
||||
|
||||
print('⏱️ 倒计时调试信息:');
|
||||
print(' 当前时间(ms): $now');
|
||||
print(' 创建时间(ms): $createdAt');
|
||||
print(' 目标时间(ms): $targetTime');
|
||||
print(' 剩余时间(ms): $timeLeft');
|
||||
print(' 剩余时间(秒): ${(timeLeft / 1000).floor()}');
|
||||
if (kDebugMode) {
|
||||
print('⏱️ 倒计时调试信息:');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' 当前时间(ms): $now');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' 创建时间(ms): $createdAt');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' 目标时间(ms): $targetTime');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' 剩余时间(ms): $timeLeft');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' 剩余时间(秒): ${(timeLeft / 1000).floor()}');
|
||||
}
|
||||
|
||||
if (timeLeft > 0) {
|
||||
kr_countdown.value = timeLeft;
|
||||
@ -141,7 +172,9 @@ class KROrderStatusController extends GetxController {
|
||||
final minutes = (timeLeft / 60000).floor();
|
||||
final seconds = ((timeLeft % 60000) / 1000).floor();
|
||||
kr_formattedCountdown.value = '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
|
||||
print(' 格式化倒计时: ${kr_formattedCountdown.value}');
|
||||
if (kDebugMode) {
|
||||
print(' 格式化倒计时: ${kr_formattedCountdown.value}');
|
||||
}
|
||||
} else {
|
||||
// 倒计时结束,订单超时
|
||||
kr_countdown.value = 0;
|
||||
@ -149,7 +182,9 @@ class KROrderStatusController extends GetxController {
|
||||
kr_countdownTimer?.cancel();
|
||||
kr_timer?.cancel();
|
||||
|
||||
print('⏱️ 订单支付超时(15分钟)');
|
||||
if (kDebugMode) {
|
||||
print('⏱️ 订单支付超时(15分钟)');
|
||||
}
|
||||
kr_statusTitle.value = AppTranslations.kr_orderStatus.closedTitle;
|
||||
kr_statusDescription.value = AppTranslations.kr_orderStatus.timeoutMessage;
|
||||
kr_isLoading.value = false;
|
||||
@ -161,14 +196,18 @@ class KROrderStatusController extends GetxController {
|
||||
final now = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
try {
|
||||
print('🔍 检查订单状态 [${kr_order}]');
|
||||
if (kDebugMode) {
|
||||
print('🔍 检查订单状态 [${kr_order}]');
|
||||
}
|
||||
|
||||
// 使用公开接口查询订单状态
|
||||
final result = await kr_subscribeApi.kr_queryOrderStatus(kr_order);
|
||||
|
||||
result.fold(
|
||||
(error) {
|
||||
print('❌ 查询失败: ${error.msg}');
|
||||
if (kDebugMode) {
|
||||
print('❌ 查询失败: ${error.msg}');
|
||||
}
|
||||
KRLogUtil.kr_e('检查支付状态失败: ${error.msg}', tag: 'OrderStatusController');
|
||||
kr_isLoading.value = false;
|
||||
kr_statusTitle.value = AppTranslations.kr_orderStatus.checkFailedTitle;
|
||||
@ -186,13 +225,23 @@ class KROrderStatusController extends GetxController {
|
||||
isMilliseconds ? timestamp : timestamp * 1000
|
||||
);
|
||||
|
||||
print('📅 订单创建时间: ${kr_orderCreatedAt}');
|
||||
print('📅 原始时间戳: $timestamp');
|
||||
print('📅 时间戳类型: ${isMilliseconds ? "毫秒级" : "秒级"}');
|
||||
print('📅 转换后时间戳(ms): ${kr_orderCreatedAt!.millisecondsSinceEpoch}');
|
||||
if (kDebugMode) {
|
||||
print('📅 订单创建时间: ${kr_orderCreatedAt}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('📅 原始时间戳: $timestamp');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('📅 时间戳类型: ${isMilliseconds ? "毫秒级" : "秒级"}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('📅 转换后时间戳(ms): ${kr_orderCreatedAt!.millisecondsSinceEpoch}');
|
||||
}
|
||||
}
|
||||
|
||||
print('📊 订单状态: ${kr_orderStatus.kr_status} (${_getStatusName(kr_orderStatus.kr_status)})');
|
||||
if (kDebugMode) {
|
||||
print('📊 订单状态: ${kr_orderStatus.kr_status} (${_getStatusName(kr_orderStatus.kr_status)})');
|
||||
}
|
||||
|
||||
switch (kr_orderStatus.kr_status) {
|
||||
case kr_statusPending:
|
||||
@ -204,7 +253,9 @@ class KROrderStatusController extends GetxController {
|
||||
|
||||
case kr_statusPaid:
|
||||
// 已支付状态,继续轮询直到完成
|
||||
print('✅ 订单已支付,等待确认...');
|
||||
if (kDebugMode) {
|
||||
print('✅ 订单已支付,等待确认...');
|
||||
}
|
||||
kr_statusTitle.value = AppTranslations.kr_orderStatus.paidTitle;
|
||||
kr_statusDescription.value = AppTranslations.kr_orderStatus.paidDescription;
|
||||
kr_statusIcon.value = 'payment_success';
|
||||
@ -212,7 +263,9 @@ class KROrderStatusController extends GetxController {
|
||||
|
||||
case kr_statusFinished:
|
||||
// 订单完成
|
||||
print('🎉 订单完成!停止轮询');
|
||||
if (kDebugMode) {
|
||||
print('🎉 订单完成!停止轮询');
|
||||
}
|
||||
kr_isPaymentSuccess.value = true;
|
||||
kr_isLoading.value = false;
|
||||
kr_timer?.cancel();
|
||||
@ -226,7 +279,9 @@ class KROrderStatusController extends GetxController {
|
||||
|
||||
case kr_statusClose:
|
||||
// 订单已关闭
|
||||
print('❌ 订单已关闭');
|
||||
if (kDebugMode) {
|
||||
print('❌ 订单已关闭');
|
||||
}
|
||||
kr_isLoading.value = false;
|
||||
kr_timer?.cancel();
|
||||
kr_countdownTimer?.cancel();
|
||||
@ -237,7 +292,9 @@ class KROrderStatusController extends GetxController {
|
||||
|
||||
case kr_statusFailed:
|
||||
// 支付失败
|
||||
print('❌ 支付失败');
|
||||
if (kDebugMode) {
|
||||
print('❌ 支付失败');
|
||||
}
|
||||
kr_isLoading.value = false;
|
||||
kr_timer?.cancel();
|
||||
kr_countdownTimer?.cancel();
|
||||
@ -248,7 +305,9 @@ class KROrderStatusController extends GetxController {
|
||||
|
||||
default:
|
||||
// 未知状态
|
||||
print('⚠️ 未知状态: ${kr_orderStatus.kr_status}');
|
||||
if (kDebugMode) {
|
||||
print('⚠️ 未知状态: ${kr_orderStatus.kr_status}');
|
||||
}
|
||||
kr_isLoading.value = false;
|
||||
kr_timer?.cancel();
|
||||
kr_countdownTimer?.cancel();
|
||||
@ -262,7 +321,9 @@ class KROrderStatusController extends GetxController {
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
print('❌ 异常: $error');
|
||||
if (kDebugMode) {
|
||||
print('❌ 异常: $error');
|
||||
}
|
||||
KRLogUtil.kr_e('检查支付状态失败: $error', tag: 'OrderStatusController');
|
||||
kr_isLoading.value = false;
|
||||
kr_statusTitle.value = AppTranslations.kr_orderStatus.checkFailedTitle;
|
||||
|
||||
@ -13,6 +13,7 @@ import '../../../model/response/kr_payment_methods.dart';
|
||||
import '../../../routes/app_pages.dart';
|
||||
import '../../../services/api_service/kr_api.user.dart';
|
||||
import '../../../utils/kr_event_bus.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
/// 会员购买控制器
|
||||
/// 负责处理会员套餐选择、支付方式选择和订阅流程
|
||||
|
||||
@ -248,7 +248,7 @@ class KRSettingView extends GetView<KRSettingController> {
|
||||
// 设备登录(游客模式),显示"点击这里登录/注册"
|
||||
return _kr_buildActionTile(
|
||||
context,
|
||||
title: "登录/注册",
|
||||
title: 'userInfo.loginRegister'.tr,
|
||||
trailing: "",
|
||||
onTap: () => Get.toNamed(Routes.MR_LOGIN),
|
||||
);
|
||||
|
||||
@ -27,8 +27,12 @@ import 'package:kaer_with_panels/app/localization/app_translations.dart';
|
||||
import 'package:kaer_with_panels/app/modules/kr_home/controllers/kr_home_controller.dart';
|
||||
import 'package:kaer_with_panels/app/modules/kr_home/models/kr_home_views_status.dart';
|
||||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:kaer_with_panels/app/utils/kr_init_log_collector.dart';
|
||||
|
||||
class KRSplashController extends GetxController {
|
||||
// 🔧 新增:初始化日志收集器
|
||||
final _initLog = KRInitLogCollector();
|
||||
// 加载状态
|
||||
final RxBool kr_isLoading = true.obs;
|
||||
|
||||
@ -86,9 +90,35 @@ class KRSplashController extends GetxController {
|
||||
KRLogUtil.kr_i('🌐 后台任务:网站配置和设备登录', tag: 'SplashController');
|
||||
_kr_initSiteConfig(); // 不等待完成,在后台执行
|
||||
|
||||
// 立即开始主初始化流程
|
||||
print('🔧 立即开始主初始化流程...');
|
||||
_kr_initialize();
|
||||
// 🔧 关键修复:先初始化日志收集器,再开始主流程
|
||||
if (kDebugMode) {
|
||||
print('🔧 初始化日志收集器...');
|
||||
}
|
||||
_initializeAndStart();
|
||||
}
|
||||
|
||||
/// 🔧 新增:初始化日志收集器并启动主流程
|
||||
Future<void> _initializeAndStart() async {
|
||||
try {
|
||||
// 等待日志收集器初始化完成
|
||||
await _initLog.initialize();
|
||||
_initLog.logPhaseStart('应用启动');
|
||||
_initLog.log('KRSplashController.onInit 被调用', tag: 'Splash');
|
||||
|
||||
if (kDebugMode) {
|
||||
print('✅ 日志收集器初始化完成');
|
||||
print('🔧 立即开始主初始化流程...');
|
||||
}
|
||||
|
||||
// 开始主初始化流程
|
||||
_kr_initialize();
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('❌ 日志收集器初始化失败: $e,继续执行主流程');
|
||||
}
|
||||
// 即使日志收集器失败,也要继续主流程
|
||||
_kr_initialize();
|
||||
}
|
||||
}
|
||||
|
||||
// 记录步骤耗时的辅助方法
|
||||
@ -116,11 +146,11 @@ class KRSplashController extends GetxController {
|
||||
KRLogUtil.kr_i('[SPLASH_TIMING] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'SplashController');
|
||||
|
||||
try {
|
||||
// 🔧 P2优化:为后台任务添加10秒超时保护
|
||||
// 🔧 Android 15 优化:延长超时保护到25秒,匹配网络请求超时(20秒 + 5秒缓冲)
|
||||
await Future.any([
|
||||
_executeSiteConfigInit(),
|
||||
Future.delayed(const Duration(seconds: 10), () {
|
||||
throw TimeoutException('网站配置加载超时(10秒)');
|
||||
Future.delayed(const Duration(seconds: 25), () {
|
||||
throw TimeoutException('网站配置加载超时(25秒)');
|
||||
}),
|
||||
]);
|
||||
} on TimeoutException catch (e) {
|
||||
@ -178,11 +208,11 @@ class KRSplashController extends GetxController {
|
||||
|
||||
print('✅ 设备登录已启用,开始初始化设备信息...');
|
||||
|
||||
// 🔧 P2优化:为设备登录添加8秒超时保护
|
||||
// 🔧 Android 15 优化:延长设备登录超时到15秒,匹配网络请求超时
|
||||
await Future.any([
|
||||
_executeDeviceLogin(),
|
||||
Future.delayed(const Duration(seconds: 8), () {
|
||||
throw TimeoutException('设备登录超时(8秒)');
|
||||
Future.delayed(const Duration(seconds: 15), () {
|
||||
throw TimeoutException('设备登录超时(15秒)');
|
||||
}),
|
||||
]);
|
||||
|
||||
@ -237,15 +267,19 @@ class KRSplashController extends GetxController {
|
||||
|
||||
Future<void> _kr_initialize() async {
|
||||
try {
|
||||
_initLog.logPhaseStart('主初始化流程');
|
||||
_initLog.log('开始执行 _kr_initialize', tag: 'Splash');
|
||||
KRLogUtil.kr_i('🔧 开始执行 _kr_initialize', tag: 'SplashController');
|
||||
|
||||
// 🔧 P0修复:添加总体超时保护(30秒)
|
||||
// 🔧 Android 15 优化:添加总体超时保护(15秒),更快响应
|
||||
await Future.any([
|
||||
_executeInitialization(),
|
||||
Future.delayed(const Duration(seconds: 30), () {
|
||||
throw TimeoutException('初始化超时:30秒内未完成');
|
||||
Future.delayed(const Duration(seconds: 15), () {
|
||||
throw TimeoutException('初始化超时:15秒内未完成');
|
||||
}),
|
||||
]);
|
||||
|
||||
_initLog.logPhaseEnd('主初始化流程', success: true);
|
||||
} on TimeoutException catch (e) {
|
||||
// 🔧 P2优化:超时错误提供更友好的提示
|
||||
KRLogUtil.kr_e('⏱️ 初始化超时: $e', tag: 'SplashController');
|
||||
@ -311,24 +345,51 @@ class KRSplashController extends GetxController {
|
||||
|
||||
// 🔧 P0修复:将原来的初始化逻辑提取到单独方法
|
||||
Future<void> _executeInitialization() async {
|
||||
_initLog.log('🔍 开始执行核心初始化流程', tag: 'Init');
|
||||
|
||||
// 只在手机端检查网络权限
|
||||
if (Platform.isIOS || Platform.isAndroid) {
|
||||
_initLog.log('📱 检测到移动平台,检查网络权限', tag: 'Init');
|
||||
KRLogUtil.kr_i('📱 移动平台,检查网络权限...', tag: 'SplashController');
|
||||
final bool hasNetworkPermission = await KRNetworkCheck.kr_initialize(
|
||||
Get.context!,
|
||||
onPermissionGranted: () async {
|
||||
await _kr_continueInitialization();
|
||||
},
|
||||
);
|
||||
|
||||
if (!hasNetworkPermission) {
|
||||
kr_hasError.value = true;
|
||||
kr_errorMessage.value = AppTranslations.kr_splash.kr_networkPermissionFailed;
|
||||
KRLogUtil.kr_e('❌ 网络权限检查失败', tag: 'SplashController');
|
||||
try {
|
||||
_initLog.log('开始网络权限检查(超时8秒)', tag: 'Init');
|
||||
// 🔧 Android 15 优化:网络权限检查添加 8 秒超时
|
||||
final bool hasNetworkPermission = await KRNetworkCheck.kr_initialize(
|
||||
Get.context!,
|
||||
onPermissionGranted: () async {
|
||||
_initLog.logSuccess('网络权限已授予', tag: 'Init');
|
||||
await _kr_continueInitialization();
|
||||
},
|
||||
).timeout(
|
||||
const Duration(seconds: 8),
|
||||
onTimeout: () {
|
||||
_initLog.logWarning('网络权限检查超时(8秒)', tag: 'Init');
|
||||
KRLogUtil.kr_w('⏱️ 网络权限检查超时,执行降级初始化', tag: 'SplashController');
|
||||
return false;
|
||||
},
|
||||
);
|
||||
|
||||
if (!hasNetworkPermission) {
|
||||
// 🔧 Android 15 优化:权限检查失败时执行降级策略而非显示错误
|
||||
_initLog.logWarning('网络权限检查失败或超时,执行降级初始化', tag: 'Init');
|
||||
KRLogUtil.kr_w('⚠️ 网络权限检查失败或超时,执行降级初始化', tag: 'SplashController');
|
||||
await _executeMinimalInitialization();
|
||||
Get.offAllNamed(Routes.KR_MAIN);
|
||||
return;
|
||||
}
|
||||
|
||||
_initLog.logSuccess('网络权限检查通过', tag: 'Init');
|
||||
} catch (e) {
|
||||
_initLog.logError('网络权限检查异常,执行降级初始化', tag: 'Init', error: e);
|
||||
KRLogUtil.kr_w('⚠️ 网络权限检查异常: $e,执行降级初始化', tag: 'SplashController');
|
||||
await _executeMinimalInitialization();
|
||||
Get.offAllNamed(Routes.KR_MAIN);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// 非手机端直接继续初始化
|
||||
_initLog.log('💻 检测到桌面平台,直接执行初始化', tag: 'Init');
|
||||
KRLogUtil.kr_i('💻 桌面平台,直接执行初始化', tag: 'SplashController');
|
||||
await _kr_continueInitialization();
|
||||
}
|
||||
@ -336,22 +397,44 @@ class KRSplashController extends GetxController {
|
||||
|
||||
Future<void> _kr_continueInitialization() async {
|
||||
try {
|
||||
_initLog.logSeparator();
|
||||
_initLog.log('🚀 启动页主流程开始', tag: 'Continue');
|
||||
_initLog.logSeparator();
|
||||
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'SplashController');
|
||||
KRLogUtil.kr_i('🚀 启动页主流程开始...', tag: 'SplashController');
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'SplashController');
|
||||
|
||||
// 只在手机端检查网络连接
|
||||
// 🔧 Android 15 优化:网络连接检查改为非阻塞
|
||||
if (Platform.isIOS || Platform.isAndroid) {
|
||||
_initLog.log('📱 检查网络连接状态(超时5秒)', tag: 'Continue');
|
||||
KRLogUtil.kr_i('📱 检查网络连接...', tag: 'SplashController');
|
||||
final bool isConnected = await KRNetworkCheck.kr_checkNetworkConnection();
|
||||
if (!isConnected) {
|
||||
kr_hasError.value = true;
|
||||
kr_errorMessage.value = AppTranslations.kr_splash.kr_networkConnectionFailed;
|
||||
KRLogUtil.kr_e('❌ 网络连接失败', tag: 'SplashController');
|
||||
return;
|
||||
try {
|
||||
// 添加5秒超时保护
|
||||
final bool isConnected = await KRNetworkCheck.kr_checkNetworkConnection().timeout(
|
||||
const Duration(seconds: 5),
|
||||
onTimeout: () {
|
||||
_initLog.logWarning('网络连接检查超时(5秒),继续初始化', tag: 'Continue');
|
||||
KRLogUtil.kr_w('⏱️ 网络连接检查超时,继续初始化', tag: 'SplashController');
|
||||
return true; // 超时时允许继续
|
||||
},
|
||||
);
|
||||
|
||||
if (isConnected) {
|
||||
_initLog.logSuccess('网络连接正常', tag: 'Continue');
|
||||
KRLogUtil.kr_i('✅ 网络连接正常', tag: 'SplashController');
|
||||
} else {
|
||||
// 🔧 Android 15 优化:网络未连接也允许继续,改为警告而非错误
|
||||
_initLog.logWarning('网络未连接,应用将以离线模式启动', tag: 'Continue');
|
||||
KRLogUtil.kr_w('⚠️ 网络未连接,应用将以离线模式启动', tag: 'SplashController');
|
||||
}
|
||||
} catch (e) {
|
||||
// 🔧 Android 15 优化:网络检查异常不阻塞启动
|
||||
_initLog.logError('网络检查异常,继续初始化', tag: 'Continue', error: e);
|
||||
KRLogUtil.kr_w('⚠️ 网络检查异常: $e,继续初始化', tag: 'SplashController');
|
||||
}
|
||||
KRLogUtil.kr_i('✅ 网络连接正常', tag: 'SplashController');
|
||||
} else {
|
||||
_initLog.log('💻 桌面平台,跳过网络连接检查', tag: 'Continue');
|
||||
KRLogUtil.kr_i('💻 桌面平台,跳过网络连接检查', tag: 'SplashController');
|
||||
}
|
||||
|
||||
@ -375,19 +458,22 @@ class KRSplashController extends GetxController {
|
||||
_logStepTiming('设备登录检查完成');
|
||||
|
||||
// 初始化配置
|
||||
_initLog.log('⚙️ 开始初始化应用配置(域名加载等)', tag: 'Continue');
|
||||
KRLogUtil.kr_i('⚙️ 开始初始化应用配置...', tag: 'SplashController');
|
||||
await AppConfig().initConfig(
|
||||
onSuccess: () async {
|
||||
// 配置初始化成功,继续后续步骤
|
||||
_initLog.logSuccess('应用配置初始化成功', tag: 'Continue');
|
||||
KRLogUtil.kr_i('✅ 应用配置初始化成功,继续后续步骤', tag: 'SplashController');
|
||||
await _kr_continueAfterConfig();
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
// 配置初始化失败,显示错误信息
|
||||
KRLogUtil.kr_e('❌ 启动页初始化异常: $e', tag: 'SplashController');
|
||||
kr_hasError.value = true;
|
||||
kr_errorMessage.value = '${AppTranslations.kr_splash.kr_initializationFailed}$e';
|
||||
// 🔧 Android 15 优化:配置初始化失败时执行降级策略
|
||||
_initLog.logError('启动页初始化异常,执行降级策略', tag: 'Continue', error: e);
|
||||
KRLogUtil.kr_w('⚠️ 启动页初始化异常,执行降级策略: $e', tag: 'SplashController');
|
||||
await _executeMinimalInitialization();
|
||||
Get.offAllNamed(Routes.KR_MAIN);
|
||||
}
|
||||
}
|
||||
|
||||
@ -424,6 +510,19 @@ class KRSplashController extends GetxController {
|
||||
KRLogUtil.kr_i('🎫 Token存在: $hasToken', tag: 'SplashController');
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'SplashController');
|
||||
|
||||
// 🔧 记录最终状态到日志
|
||||
_initLog.logSeparator();
|
||||
_initLog.log('准备进入主页', tag: 'Splash');
|
||||
_initLog.log('最终登录状态: $loginStatus', tag: 'Splash');
|
||||
_initLog.log('Token存在: $hasToken', tag: 'Splash');
|
||||
_initLog.logSuccess('正常初始化流程完成', tag: 'Splash');
|
||||
|
||||
// 完成日志收集
|
||||
await _initLog.finalize();
|
||||
if (kDebugMode && _initLog.getLogFilePath() != null) {
|
||||
print('📁 初始化日志文件: ${_initLog.getLogFilePath()}');
|
||||
}
|
||||
|
||||
// 直接导航到主页(无论是否登录,主页会根据登录状态显示不同内容)
|
||||
_logStepTiming('开始页面导航');
|
||||
if(loginStatus) {
|
||||
@ -444,10 +543,14 @@ class KRSplashController extends GetxController {
|
||||
print('🕐 启动失败总耗时: ${totalMs}ms (${totalDuration.inSeconds}.${(totalMs % 1000).toString().padLeft(3, '0')}s)');
|
||||
|
||||
// 后续步骤失败,显示错误信息
|
||||
_initLog.logError('启动初始化失败', tag: 'Splash', error: e);
|
||||
KRLogUtil.kr_e('启动初始化失败: $e', tag: 'SplashController');
|
||||
KRLogUtil.kr_e('⏰ 启动失败总耗时: ${totalMs}ms', tag: 'SplashController');
|
||||
kr_hasError.value = true;
|
||||
kr_errorMessage.value = '${AppTranslations.kr_splash.kr_initializationFailed}$e';
|
||||
|
||||
// 完成日志收集
|
||||
await _initLog.finalize();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -101,7 +101,7 @@ class KRSplashView extends GetView<KRSplashController> {
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
'跳过',
|
||||
'splash.skip'.tr,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey,
|
||||
|
||||
@ -154,7 +154,7 @@ class KRStatisticsController extends GetxController {
|
||||
}
|
||||
kr_weeklyData.value = weeklyHours;
|
||||
} catch (e) {
|
||||
KRCommonUtil.kr_showToast('处理流量日志数据失败');
|
||||
KRCommonUtil.kr_showToast('statistics.processTrafficFailed'.tr);
|
||||
KRLogUtil.kr_e('处理流量日志数据失败: $e', tag: 'Statistics');
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,7 +152,7 @@ class KRStatisticsView extends GetView<KRStatisticsController> {
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 8.w),
|
||||
SizedBox(height: 8.h),
|
||||
Text(
|
||||
value,
|
||||
style: KrAppTextStyle(
|
||||
@ -188,9 +188,9 @@ class KRStatisticsView extends GetView<KRStatisticsController> {
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16.w),
|
||||
SizedBox(height: 16.h),
|
||||
SizedBox(
|
||||
height: 200.w,
|
||||
height: 200.h,
|
||||
child: Obx(() => LineChart(
|
||||
LineChartData(
|
||||
gridData: FlGridData(show: false),
|
||||
@ -297,7 +297,7 @@ class KRStatisticsView extends GetView<KRStatisticsController> {
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16.w),
|
||||
SizedBox(height: 16.h),
|
||||
Obx(() => _kr_buildLongestConnection(context)),
|
||||
],
|
||||
),
|
||||
@ -322,7 +322,7 @@ class KRStatisticsView extends GetView<KRStatisticsController> {
|
||||
color: Theme.of(context).textTheme.bodySmall?.color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8.w),
|
||||
SizedBox(height: 8.h),
|
||||
Text(
|
||||
value,
|
||||
style: KrAppTextStyle(
|
||||
|
||||
@ -85,7 +85,7 @@ class KRUserInfoView extends GetView<KRUserInfoController> {
|
||||
_kr_buildShortcutSection(context),
|
||||
_kr_buildOtherSection(context),
|
||||
_kr_buildLogoutButton(context),
|
||||
SizedBox(height: 30.w),
|
||||
SizedBox(height: 30.h),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -142,7 +142,7 @@ class KRUserInfoView extends GetView<KRUserInfoController> {
|
||||
}
|
||||
: null,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.w),
|
||||
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h),
|
||||
decoration: shouldShowLoginPrompt
|
||||
? BoxDecoration(
|
||||
color: const Color(0xFF1797FF).withOpacity(0.05),
|
||||
@ -282,7 +282,7 @@ class KRUserInfoView extends GetView<KRUserInfoController> {
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.w),
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12.w),
|
||||
@ -309,7 +309,7 @@ class KRUserInfoView extends GetView<KRUserInfoController> {
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16.w),
|
||||
SizedBox(height: 16.h),
|
||||
// 过期时间
|
||||
Row(
|
||||
children: [
|
||||
@ -332,10 +332,10 @@ class KRUserInfoView extends GetView<KRUserInfoController> {
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16.w),
|
||||
SizedBox(height: 16.h),
|
||||
// 流量进度条
|
||||
_kr_buildTrafficProgress(context, subscribe),
|
||||
SizedBox(height: 16.w),
|
||||
SizedBox(height: 16.h),
|
||||
// 操作按钮
|
||||
_kr_buildSubscriptionActions(context, subscribe),
|
||||
],
|
||||
@ -443,7 +443,7 @@ class KRUserInfoView extends GetView<KRUserInfoController> {
|
||||
},
|
||||
child: Container(
|
||||
padding:
|
||||
EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.w),
|
||||
EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
@ -477,7 +477,7 @@ class KRUserInfoView extends GetView<KRUserInfoController> {
|
||||
],
|
||||
),
|
||||
if (totalTraffic > 0) ...[
|
||||
SizedBox(height: 6.w),
|
||||
SizedBox(height: 6.h),
|
||||
Stack(
|
||||
children: [
|
||||
Container(
|
||||
@ -539,7 +539,7 @@ class KRUserInfoView extends GetView<KRUserInfoController> {
|
||||
backgroundColor: const Color(0xFF1797FF),
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
padding: EdgeInsets.symmetric(vertical: 12.w),
|
||||
padding: EdgeInsets.symmetric(vertical: 12.h),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20.w),
|
||||
),
|
||||
@ -603,7 +603,7 @@ class KRUserInfoView extends GetView<KRUserInfoController> {
|
||||
backgroundColor: const Color(0xFF1797FF),
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 8.w),
|
||||
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 8.h),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20.w),
|
||||
),
|
||||
@ -647,7 +647,7 @@ class KRUserInfoView extends GetView<KRUserInfoController> {
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 12.w),
|
||||
SizedBox(height: 12.h),
|
||||
Column(
|
||||
children: [
|
||||
_kr_buildShortcutContainer(
|
||||
@ -702,7 +702,7 @@ class KRUserInfoView extends GetView<KRUserInfoController> {
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
height: 62.w,
|
||||
margin: EdgeInsets.symmetric(vertical: 6.w),
|
||||
margin: EdgeInsets.symmetric(vertical: 6.h),
|
||||
padding: EdgeInsets.only(left: 12.w, right: 12.w),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
@ -763,7 +763,7 @@ class KRUserInfoView extends GetView<KRUserInfoController> {
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 12.w),
|
||||
SizedBox(height: 12.h),
|
||||
Obx(() => Wrap(
|
||||
spacing: 12.w,
|
||||
runSpacing: 12.w,
|
||||
@ -812,7 +812,7 @@ class KRUserInfoView extends GetView<KRUserInfoController> {
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 8.w),
|
||||
SizedBox(height: 8.h),
|
||||
Text(
|
||||
controller.getTitle(item.type),
|
||||
style: KrAppTextStyle(
|
||||
@ -823,7 +823,7 @@ class KRUserInfoView extends GetView<KRUserInfoController> {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
),
|
||||
SizedBox(height: 4.w),
|
||||
SizedBox(height: 4.h),
|
||||
Text(
|
||||
item.subtitle,
|
||||
style: KrAppTextStyle(
|
||||
@ -841,8 +841,13 @@ class KRUserInfoView extends GetView<KRUserInfoController> {
|
||||
|
||||
// 构建退出登录按钮
|
||||
Widget _kr_buildLogoutButton(BuildContext context) {
|
||||
return Obx(() => Visibility(
|
||||
visible: KRAppRunData.getInstance().kr_isLogin.value,
|
||||
return Obx(() {
|
||||
final appRunData = KRAppRunData.getInstance();
|
||||
// 只有账号密码登录才显示退出按钮,设备登录(游客模式)不显示
|
||||
final isAccountLogin = appRunData.kr_isLogin.value && !appRunData.isDeviceLogin();
|
||||
|
||||
return Visibility(
|
||||
visible: isAccountLogin,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
margin: EdgeInsets.all(16.w),
|
||||
@ -858,7 +863,7 @@ class KRUserInfoView extends GetView<KRUserInfoController> {
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).cardColor,
|
||||
padding: EdgeInsets.symmetric(vertical: 12.w),
|
||||
padding: EdgeInsets.symmetric(vertical: 12.h),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.w),
|
||||
),
|
||||
@ -872,7 +877,8 @@ class KRUserInfoView extends GetView<KRUserInfoController> {
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// 处理网格项点击
|
||||
@ -910,17 +916,17 @@ class KRUserInfoView extends GetView<KRUserInfoController> {
|
||||
}
|
||||
|
||||
if (!launched) {
|
||||
KRCommonUtil.kr_showToast("尝试使用浏览器打开");
|
||||
KRCommonUtil.kr_showToast("common.tryBrowser".tr);
|
||||
// 降级使用浏览器打开
|
||||
try {
|
||||
final Uri webUrl = Uri.parse(tgUrl);
|
||||
if (await canLaunchUrl(webUrl)) {
|
||||
await launchUrl(webUrl, mode: LaunchMode.externalApplication);
|
||||
} else {
|
||||
KRCommonUtil.kr_showToast("无法打开浏览器");
|
||||
KRCommonUtil.kr_showToast("common.cannotOpenBrowser".tr);
|
||||
}
|
||||
} catch (e) {
|
||||
KRCommonUtil.kr_showToast("打开链接失败,请稍后重试");
|
||||
KRCommonUtil.kr_showToast("common.openLinkFailed".tr);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -930,10 +936,10 @@ class KRUserInfoView extends GetView<KRUserInfoController> {
|
||||
if (await canLaunchUrl(webUrl)) {
|
||||
await launchUrl(webUrl);
|
||||
} else {
|
||||
KRCommonUtil.kr_showToast("无法打开Telegram链接");
|
||||
KRCommonUtil.kr_showToast("common.cannotOpenTelegram".tr);
|
||||
}
|
||||
} catch (e) {
|
||||
KRCommonUtil.kr_showToast("打开链接失败,请稍后重试");
|
||||
KRCommonUtil.kr_showToast("common.openLinkFailed".tr);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:kaer_with_panels/app/common/app_run_data.dart';
|
||||
import 'package:kaer_with_panels/app/common/app_config.dart';
|
||||
@ -30,27 +31,33 @@ class BaseResponse<T> {
|
||||
|
||||
if (shouldDecrypt && cipherText.isNotEmpty && nonce.isNotEmpty) {
|
||||
try {
|
||||
print('═══════════════════════════════════════');
|
||||
print('🔐 检测到加密响应,开始解密...');
|
||||
print('📥 加密数据长度: ${cipherText.length} 字符');
|
||||
print('⏰ 时间戳: $nonce');
|
||||
if (kDebugMode) {
|
||||
print('═══════════════════════════════════════');
|
||||
print('🔐 检测到加密响应,开始解密...');
|
||||
print('📥 加密数据长度: ${cipherText.length} 字符');
|
||||
print('⏰ 时间戳: $nonce');
|
||||
}
|
||||
|
||||
final decrypted = KRAesUtil.decryptData(cipherText, nonce, AppConfig.kr_encryptionKey);
|
||||
body = jsonDecode(decrypted);
|
||||
|
||||
print('✅ 解密成功');
|
||||
print('');
|
||||
print('📦 解密后的完整数据:');
|
||||
// 格式化打印 JSON,方便调试
|
||||
final bodyStr = JsonEncoder.withIndent(' ').convert(body);
|
||||
print(bodyStr);
|
||||
print('═══════════════════════════════════════');
|
||||
if (kDebugMode) {
|
||||
print('✅ 解密成功');
|
||||
print('');
|
||||
print('📦 解密后的完整数据:');
|
||||
// 格式化打印 JSON,方便调试
|
||||
final bodyStr = JsonEncoder.withIndent(' ').convert(body);
|
||||
print(bodyStr);
|
||||
print('═══════════════════════════════════════');
|
||||
}
|
||||
|
||||
KRLogUtil.kr_i('🔓 解密成功', tag: 'BaseResponse');
|
||||
} catch (e) {
|
||||
print('❌ 解密失败: $e');
|
||||
print('⚠️ 将使用原始数据');
|
||||
print('═══════════════════════════════════════');
|
||||
if (kDebugMode) {
|
||||
print('❌ 解密失败: $e');
|
||||
print('⚠️ 将使用原始数据');
|
||||
print('═══════════════════════════════════════');
|
||||
}
|
||||
KRLogUtil.kr_e('❌ 解密失败: $e', tag: 'BaseResponse');
|
||||
body = dataMap;
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@ import 'package:loggy/loggy.dart';
|
||||
|
||||
import '../utils/kr_aes_util.dart';
|
||||
import '../utils/kr_log_util.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
// import 'package:video/app/utils/common_util.dart';
|
||||
// import 'package:video/app/utils/log_util.dart';
|
||||
@ -316,31 +317,47 @@ class HttpUtil {
|
||||
class MyInterceptor extends Interceptor {
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
print('>>> Request │ ${options.method} │ ${options.uri}');
|
||||
if (kDebugMode) {
|
||||
print('>>> Request │ ${options.method} │ ${options.uri}');
|
||||
}
|
||||
if (options.data != null) {
|
||||
print('Body: ${options.data}');
|
||||
if (kDebugMode) {
|
||||
print('Body: ${options.data}');
|
||||
}
|
||||
}
|
||||
handler.next(options);
|
||||
}
|
||||
|
||||
@override
|
||||
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||||
print('<<< Response │ ${response.requestOptions.method} │ ${response.statusCode} ${response.statusMessage} │ ${response.requestOptions.uri}');
|
||||
if (kDebugMode) {
|
||||
print('<<< Response │ ${response.requestOptions.method} │ ${response.statusCode} ${response.statusMessage} │ ${response.requestOptions.uri}');
|
||||
}
|
||||
if (response.data != null) {
|
||||
print('Body: ${response.data}');
|
||||
if (kDebugMode) {
|
||||
print('Body: ${response.data}');
|
||||
}
|
||||
}
|
||||
handler.next(response);
|
||||
}
|
||||
|
||||
@override
|
||||
void onError(DioException err, ErrorInterceptorHandler handler) {
|
||||
print('<<< Error │ ${err.requestOptions.method} │ ${err.requestOptions.uri}');
|
||||
print('Error Type: ${err.type}');
|
||||
if (kDebugMode) {
|
||||
print('<<< Error │ ${err.requestOptions.method} │ ${err.requestOptions.uri}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('Error Type: ${err.type}');
|
||||
}
|
||||
if (err.message != null) {
|
||||
print('Error Message: ${err.message}');
|
||||
if (kDebugMode) {
|
||||
print('Error Message: ${err.message}');
|
||||
}
|
||||
}
|
||||
if (err.response?.data != null) {
|
||||
print('Response Data: ${err.response?.data}');
|
||||
if (kDebugMode) {
|
||||
print('Response Data: ${err.response?.data}');
|
||||
}
|
||||
}
|
||||
handler.next(err);
|
||||
}
|
||||
@ -350,17 +367,25 @@ class MyInterceptor extends Interceptor {
|
||||
class _KRSimpleHttpInterceptor extends Interceptor {
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
print('>>> Request │ ${options.method} │ ${options.uri}');
|
||||
if (kDebugMode) {
|
||||
print('>>> Request │ ${options.method} │ ${options.uri}');
|
||||
}
|
||||
if (options.data != null) {
|
||||
print('Body: ${options.data}');
|
||||
if (kDebugMode) {
|
||||
print('Body: ${options.data}');
|
||||
}
|
||||
|
||||
// 检查是否是加密数据(包含 data 和 time 字段)
|
||||
if (options.data is Map<String, dynamic>) {
|
||||
final data = options.data as Map<String, dynamic>;
|
||||
if (data.containsKey('data') && data.containsKey('time')) {
|
||||
try {
|
||||
print('');
|
||||
print('🔐 检测到加密请求,正在解密...');
|
||||
if (kDebugMode) {
|
||||
print('');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('🔐 检测到加密请求,正在解密...');
|
||||
}
|
||||
// 尝试解密并打印原始数据
|
||||
final encryptedData = data['data'] as String;
|
||||
final nonce = data['time'] as String;
|
||||
@ -369,17 +394,25 @@ class _KRSimpleHttpInterceptor extends Interceptor {
|
||||
nonce,
|
||||
AppConfig.kr_encryptionKey,
|
||||
);
|
||||
print('🔓 解密后的原始请求数据:');
|
||||
if (kDebugMode) {
|
||||
print('🔓 解密后的原始请求数据:');
|
||||
}
|
||||
// 尝试格式化 JSON
|
||||
try {
|
||||
final jsonData = jsonDecode(decrypted);
|
||||
final prettyJson = JsonEncoder.withIndent(' ').convert(jsonData);
|
||||
print(prettyJson);
|
||||
if (kDebugMode) {
|
||||
print(prettyJson);
|
||||
}
|
||||
} catch (_) {
|
||||
print(decrypted);
|
||||
if (kDebugMode) {
|
||||
print(decrypted);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
print('⚠️ 请求解密失败: $e');
|
||||
if (kDebugMode) {
|
||||
print('⚠️ 请求解密失败: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -389,9 +422,13 @@ class _KRSimpleHttpInterceptor extends Interceptor {
|
||||
|
||||
@override
|
||||
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||||
print('<<< Response │ ${response.requestOptions.method} │ ${response.statusCode} ${response.statusMessage} │ ${response.requestOptions.uri}');
|
||||
if (kDebugMode) {
|
||||
print('<<< Response │ ${response.requestOptions.method} │ ${response.statusCode} ${response.statusMessage} │ ${response.requestOptions.uri}');
|
||||
}
|
||||
if (response.data != null) {
|
||||
print('Body: ${response.data}');
|
||||
if (kDebugMode) {
|
||||
print('Body: ${response.data}');
|
||||
}
|
||||
|
||||
// 检查响应是否是加密数据(包含 data 和 time 字段)
|
||||
if (response.data is Map<String, dynamic>) {
|
||||
@ -403,8 +440,12 @@ class _KRSimpleHttpInterceptor extends Interceptor {
|
||||
nestedData.containsKey('data') &&
|
||||
nestedData.containsKey('time')) {
|
||||
try {
|
||||
print('');
|
||||
print('🔐 检测到加密响应,正在解密...');
|
||||
if (kDebugMode) {
|
||||
print('');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('🔐 检测到加密响应,正在解密...');
|
||||
}
|
||||
// 尝试解密并打印原始响应数据
|
||||
final encryptedData = nestedData['data'] as String;
|
||||
final nonce = nestedData['time'] as String;
|
||||
@ -413,17 +454,25 @@ class _KRSimpleHttpInterceptor extends Interceptor {
|
||||
nonce,
|
||||
AppConfig.kr_encryptionKey,
|
||||
);
|
||||
print('🔓 解密后的原始响应数据: ${response.requestOptions.uri}');
|
||||
if (kDebugMode) {
|
||||
print('🔓 解密后的原始响应数据:');
|
||||
}
|
||||
// 尝试格式化 JSON
|
||||
try {
|
||||
final jsonData = jsonDecode(decrypted);
|
||||
final prettyJson = JsonEncoder.withIndent(' ').convert(jsonData);
|
||||
print(prettyJson);
|
||||
if (kDebugMode) {
|
||||
print(prettyJson);
|
||||
}
|
||||
} catch (_) {
|
||||
print(decrypted);
|
||||
if (kDebugMode) {
|
||||
print(decrypted);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
print('⚠️ 响应解密失败: $e');
|
||||
if (kDebugMode) {
|
||||
print('⚠️ 响应解密失败: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -433,13 +482,21 @@ class _KRSimpleHttpInterceptor extends Interceptor {
|
||||
|
||||
@override
|
||||
void onError(DioException err, ErrorInterceptorHandler handler) {
|
||||
print('<<< Error │ ${err.requestOptions.method} │ ${err.requestOptions.uri}');
|
||||
print('Error Type: ${err.type}');
|
||||
if (kDebugMode) {
|
||||
print('<<< Error │ ${err.requestOptions.method} │ ${err.requestOptions.uri}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('Error Type: ${err.type}');
|
||||
}
|
||||
if (err.message != null) {
|
||||
print('Error Message: ${err.message}');
|
||||
if (kDebugMode) {
|
||||
print('Error Message: ${err.message}');
|
||||
}
|
||||
}
|
||||
if (err.response?.data != null) {
|
||||
print('Response Data: ${err.response?.data}');
|
||||
if (kDebugMode) {
|
||||
print('Response Data: ${err.response?.data}');
|
||||
}
|
||||
}
|
||||
handler.next(err);
|
||||
}
|
||||
|
||||
@ -25,8 +25,11 @@ abstract class Api {
|
||||
/// 删除账号
|
||||
static const String kr_deleteAccount = "/v1/public/user/delete_account";
|
||||
|
||||
/// 忘记密码-设置新密码
|
||||
static const String kr_setNewPsdByForgetPsd = "/v1/app/auth/reset_password";
|
||||
/// 忘记密码-邮箱重置密码
|
||||
static const String kr_resetPassword = "/v1/auth/reset";
|
||||
|
||||
/// 忘记密码-手机号重置密码
|
||||
static const String kr_resetPasswordByTelephone = "/v1/auth/reset/telephone";
|
||||
|
||||
/// 节点信息(包含试用/付费标志)
|
||||
static const String kr_nodeList = "/v1/public/subscribe/node/list";
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import 'dart:io';
|
||||
import 'dart:io' as io;
|
||||
import 'dart:math';
|
||||
import 'dart:convert';
|
||||
|
||||
@ -21,6 +21,8 @@ import '../kr_device_info_service.dart';
|
||||
import '../kr_site_config_service.dart';
|
||||
import '../../common/app_config.dart';
|
||||
import 'package:dio/dio.dart' as dio;
|
||||
import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class KRAuthApi {
|
||||
/// 检查账号是否已注册(仅支持邮箱)
|
||||
@ -107,6 +109,39 @@ class KRAuthApi {
|
||||
return right(baseResponse.model.kr_token.toString());
|
||||
}
|
||||
|
||||
/// 手机号注册(手机号+区号+密码+验证码,邀请码可选)
|
||||
Future<Either<HttpError, String>> kr_telephoneRegister(
|
||||
String telephone,
|
||||
String areaCode,
|
||||
String password,
|
||||
String code,
|
||||
{String? inviteCode}) async {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['telephone'] = telephone;
|
||||
data['telephone_area_code'] = areaCode;
|
||||
data['password'] = password;
|
||||
data['code'] = code;
|
||||
data["identifier"] = KRDeviceInfoService().deviceId ?? 'unknown';
|
||||
|
||||
// 邀请码是可选的
|
||||
if (inviteCode != null && inviteCode.isNotEmpty) {
|
||||
data["invite"] = inviteCode;
|
||||
}
|
||||
|
||||
// cf_token 为空字符串(如果后续需要可以添加)
|
||||
data["cf_token"] = "";
|
||||
|
||||
BaseResponse<KRLoginData> baseResponse = await HttpUtil.getInstance()
|
||||
.request<KRLoginData>('/v1/auth/register/telephone', data,
|
||||
method: HttpMethod.POST, isShowLoading: true);
|
||||
if (!baseResponse.isSuccess) {
|
||||
return left(
|
||||
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
|
||||
}
|
||||
|
||||
return right(baseResponse.model.kr_token.toString());
|
||||
}
|
||||
|
||||
/// 验证验证码(仅支持邮箱)
|
||||
Future<Either<HttpError, bool>> kr_checkVerificationCode(
|
||||
String email, String code, int type) async {
|
||||
@ -151,6 +186,52 @@ class KRAuthApi {
|
||||
return right(baseResponse.model.kr_token.toString());
|
||||
}
|
||||
|
||||
/// 手机号密码登录(手机号+区号+密码)
|
||||
Future<Either<HttpError, String>> kr_telephoneLogin(
|
||||
String telephone, String areaCode, String password) async {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['telephone'] = telephone;
|
||||
data['telephone_area_code'] = areaCode;
|
||||
data['password'] = password;
|
||||
|
||||
final deviceId = KRDeviceInfoService().deviceId ?? 'unknown';
|
||||
KRLogUtil.kr_i('设备ID: $deviceId', tag: 'KRAuthApi');
|
||||
data["identifier"] = deviceId;
|
||||
|
||||
BaseResponse<KRLoginData> baseResponse = await HttpUtil.getInstance()
|
||||
.request<KRLoginData>('/v1/auth/login/telephone', data,
|
||||
method: HttpMethod.POST, isShowLoading: true);
|
||||
if (!baseResponse.isSuccess) {
|
||||
return left(
|
||||
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
|
||||
}
|
||||
|
||||
return right(baseResponse.model.kr_token.toString());
|
||||
}
|
||||
|
||||
/// 手机号验证码登录(手机号+区号+验证码)
|
||||
Future<Either<HttpError, String>> kr_telephoneCodeLogin(
|
||||
String telephone, String areaCode, String code) async {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['telephone'] = telephone;
|
||||
data['telephone_area_code'] = areaCode;
|
||||
data['telephone_code'] = code;
|
||||
|
||||
final deviceId = KRDeviceInfoService().deviceId ?? 'unknown';
|
||||
KRLogUtil.kr_i('设备ID: $deviceId', tag: 'KRAuthApi');
|
||||
data["identifier"] = deviceId;
|
||||
|
||||
BaseResponse<KRLoginData> baseResponse = await HttpUtil.getInstance()
|
||||
.request<KRLoginData>('/v1/auth/login/telephone', data,
|
||||
method: HttpMethod.POST, isShowLoading: true);
|
||||
if (!baseResponse.isSuccess) {
|
||||
return left(
|
||||
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
|
||||
}
|
||||
|
||||
return right(baseResponse.model.kr_token.toString());
|
||||
}
|
||||
|
||||
/// 发送验证码(仅支持邮箱)
|
||||
/// type: 1=登录, 2=注册, 3=重置密码
|
||||
Future<Either<HttpError, bool>> kr_sendCode(String email, int type) async {
|
||||
@ -169,6 +250,26 @@ class KRAuthApi {
|
||||
return right(true);
|
||||
}
|
||||
|
||||
/// 发送短信验证码(支持手机号)
|
||||
/// type: 1=注册, 2=验证登录
|
||||
Future<Either<HttpError, bool>> kr_sendSmsCode(
|
||||
String telephone, String areaCode, int type) async {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['telephone'] = telephone;
|
||||
data['telephone_area_code'] = areaCode;
|
||||
data['type'] = type;
|
||||
|
||||
BaseResponse<dynamic> baseResponse = await HttpUtil.getInstance()
|
||||
.request<dynamic>('/v1/common/send_sms_code', data,
|
||||
method: HttpMethod.POST, isShowLoading: true);
|
||||
if (!baseResponse.isSuccess) {
|
||||
return left(
|
||||
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
|
||||
}
|
||||
|
||||
return right(true);
|
||||
}
|
||||
|
||||
/// 删除账号
|
||||
Future<Either<HttpError, String>> kr_deleteAccount(String email, String code) async {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
@ -186,17 +287,40 @@ class KRAuthApi {
|
||||
return right("");
|
||||
}
|
||||
|
||||
/// 忘记密码-设置新密码(仅支持邮箱)
|
||||
Future<Either<HttpError, String>> kr_setNewPsdByForgetPsd(
|
||||
/// 忘记密码-邮箱重置密码
|
||||
Future<Either<HttpError, String>> kr_resetPassword(
|
||||
String email, String code, String password) async {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['identifier'] = KRDeviceInfoService().deviceId ?? 'unknown';
|
||||
data['email'] = email;
|
||||
data['password'] = password;
|
||||
data["code"] = code;
|
||||
data["identifier"] = KRDeviceInfoService().deviceId ?? 'unknown';
|
||||
data['code'] = code;
|
||||
data['cf_token'] = ""; // Cloudflare token,暂时留空
|
||||
|
||||
BaseResponse<KRLoginData> baseResponse = await HttpUtil.getInstance()
|
||||
.request<KRLoginData>(Api.kr_setNewPsdByForgetPsd, data,
|
||||
.request<KRLoginData>(Api.kr_resetPassword, data,
|
||||
method: HttpMethod.POST, isShowLoading: true);
|
||||
if (!baseResponse.isSuccess) {
|
||||
return left(
|
||||
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
|
||||
}
|
||||
|
||||
return right(baseResponse.model.kr_token.toString());
|
||||
}
|
||||
|
||||
/// 忘记密码-手机号重置密码
|
||||
Future<Either<HttpError, String>> kr_resetPasswordByTelephone(
|
||||
String telephone, String areaCode, String code, String password) async {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['identifier'] = KRDeviceInfoService().deviceId ?? 'unknown';
|
||||
data['telephone'] = telephone;
|
||||
data['telephone_area_code'] = areaCode;
|
||||
data['password'] = password;
|
||||
data['code'] = code;
|
||||
data['cf_token'] = ""; // Cloudflare token,暂时留空
|
||||
|
||||
BaseResponse<KRLoginData> baseResponse = await HttpUtil.getInstance()
|
||||
.request<KRLoginData>(Api.kr_resetPasswordByTelephone, data,
|
||||
method: HttpMethod.POST, isShowLoading: true);
|
||||
if (!baseResponse.isSuccess) {
|
||||
return left(
|
||||
@ -343,17 +467,17 @@ class KRAuthApi {
|
||||
}
|
||||
|
||||
String _kr_getUserAgent() {
|
||||
if (Platform.isAndroid) {
|
||||
if (io.Platform.isAndroid) {
|
||||
return 'android';
|
||||
} else if (Platform.isIOS) {
|
||||
} else if (io.Platform.isIOS) {
|
||||
return 'ios';
|
||||
} else if (Platform.isMacOS) {
|
||||
} else if (io.Platform.isMacOS) {
|
||||
return 'mac';
|
||||
} else if (Platform.isWindows) {
|
||||
} else if (io.Platform.isWindows) {
|
||||
return 'windows';
|
||||
} else if (Platform.isLinux) {
|
||||
} else if (io.Platform.isLinux) {
|
||||
return 'linux';
|
||||
} else if (Platform.isFuchsia) {
|
||||
} else if (io.Platform.isFuchsia) {
|
||||
return 'harmony';
|
||||
} else {
|
||||
return 'unknown';
|
||||
|
||||
@ -27,26 +27,32 @@ class KRDeviceInfoService {
|
||||
/// 初始化设备信息
|
||||
Future<void> initialize() async {
|
||||
try {
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
print('📱 开始初始化设备信息服务');
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
if (kDebugMode) {
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
print('📱 开始初始化设备信息服务');
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
}
|
||||
KRLogUtil.kr_i('📱 开始初始化设备信息', tag: 'KRDeviceInfoService');
|
||||
|
||||
_deviceId = await _getDeviceId();
|
||||
_deviceDetails = await _getDeviceDetails();
|
||||
|
||||
print('✅ 设备信息初始化成功');
|
||||
print('📱 设备ID: $_deviceId');
|
||||
print('📱 设备平台: ${getPlatformName()}');
|
||||
print('📱 设备型号: ${getDeviceModel()}');
|
||||
print('📱 系统版本: ${getOSVersion()}');
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
if (kDebugMode) {
|
||||
print('✅ 设备信息初始化成功');
|
||||
print('📱 设备ID: $_deviceId');
|
||||
print('📱 设备平台: ${getPlatformName()}');
|
||||
print('📱 设备型号: ${getDeviceModel()}');
|
||||
print('📱 系统版本: ${getOSVersion()}');
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
}
|
||||
|
||||
KRLogUtil.kr_i('✅ 设备信息初始化成功', tag: 'KRDeviceInfoService');
|
||||
KRLogUtil.kr_i('📱 设备ID - $_deviceId', tag: 'KRDeviceInfoService');
|
||||
KRLogUtil.kr_i('📱 设备详情 - $_deviceDetails', tag: 'KRDeviceInfoService');
|
||||
} catch (e) {
|
||||
print('❌ 设备信息初始化失败: $e');
|
||||
if (kDebugMode) {
|
||||
print('❌ 设备信息初始化失败: $e');
|
||||
}
|
||||
KRLogUtil.kr_e('❌ 设备信息初始化失败 - $e', tag: 'KRDeviceInfoService');
|
||||
}
|
||||
}
|
||||
@ -79,7 +85,9 @@ class KRDeviceInfoService {
|
||||
|
||||
return identifier;
|
||||
} catch (e) {
|
||||
print('❌ 获取设备ID失败: $e');
|
||||
if (kDebugMode) {
|
||||
print('❌ 获取设备ID失败: $e');
|
||||
}
|
||||
KRLogUtil.kr_e('❌ 获取设备ID失败 - $e', tag: 'KRDeviceInfoService');
|
||||
// 如果获取失败,返回存储的或生成新的ID
|
||||
return await _getOrCreateStoredDeviceId();
|
||||
@ -119,12 +127,16 @@ class KRDeviceInfoService {
|
||||
final bytes = utf8.encode(combined);
|
||||
final hash = sha256.convert(bytes);
|
||||
|
||||
print('📱 Android多因子ID生成 - 因子数: ${factors.where((f) => f != null && f.isNotEmpty).length}');
|
||||
if (kDebugMode) {
|
||||
print('📱 Android多因子ID生成 - 因子数: ${factors.where((f) => f != null && f.isNotEmpty).length}');
|
||||
}
|
||||
KRLogUtil.kr_i('📱 Android多因子ID - $hash', tag: 'KRDeviceInfoService');
|
||||
|
||||
return hash.toString();
|
||||
} catch (e) {
|
||||
print('❌ Android设备ID获取失败: $e');
|
||||
if (kDebugMode) {
|
||||
print('❌ Android设备ID获取失败: $e');
|
||||
}
|
||||
KRLogUtil.kr_e('❌ Android设备ID获取失败 - $e', tag: 'KRDeviceInfoService');
|
||||
return '';
|
||||
}
|
||||
@ -157,12 +169,16 @@ class KRDeviceInfoService {
|
||||
final bytes = utf8.encode(combined);
|
||||
final hash = sha256.convert(bytes);
|
||||
|
||||
print('📱 iOS多因子ID生成 - 因子数: ${factors.where((f) => f.isNotEmpty).length}');
|
||||
if (kDebugMode) {
|
||||
print('📱 iOS多因子ID生成 - 因子数: ${factors.where((f) => f.isNotEmpty).length}');
|
||||
}
|
||||
KRLogUtil.kr_i('📱 iOS多因子ID - $hash', tag: 'KRDeviceInfoService');
|
||||
|
||||
return hash.toString();
|
||||
} catch (e) {
|
||||
print('❌ iOS设备ID获取失败: $e');
|
||||
if (kDebugMode) {
|
||||
print('❌ iOS设备ID获取失败: $e');
|
||||
}
|
||||
KRLogUtil.kr_e('❌ iOS设备ID获取失败 - $e', tag: 'KRDeviceInfoService');
|
||||
return '';
|
||||
}
|
||||
@ -193,12 +209,16 @@ class KRDeviceInfoService {
|
||||
final bytes = utf8.encode(combined);
|
||||
final hash = sha256.convert(bytes);
|
||||
|
||||
print('📱 macOS多因子ID生成 - 因子数: ${factors.where((f) => f.isNotEmpty).length}');
|
||||
if (kDebugMode) {
|
||||
print('📱 macOS多因子ID生成 - 因子数: ${factors.where((f) => f.isNotEmpty).length}');
|
||||
}
|
||||
KRLogUtil.kr_i('📱 macOS多因子ID - $hash', tag: 'KRDeviceInfoService');
|
||||
|
||||
return hash.toString();
|
||||
} catch (e) {
|
||||
print('❌ macOS设备ID获取失败: $e');
|
||||
if (kDebugMode) {
|
||||
print('❌ macOS设备ID获取失败: $e');
|
||||
}
|
||||
KRLogUtil.kr_e('❌ macOS设备ID获取失败 - $e', tag: 'KRDeviceInfoService');
|
||||
return '';
|
||||
}
|
||||
@ -229,12 +249,16 @@ class KRDeviceInfoService {
|
||||
final bytes = utf8.encode(combined);
|
||||
final hash = sha256.convert(bytes);
|
||||
|
||||
print('📱 Windows多因子ID生成 - 因子数: ${factors.where((f) => f.isNotEmpty).length}');
|
||||
if (kDebugMode) {
|
||||
print('📱 Windows多因子ID生成 - 因子数: ${factors.where((f) => f.isNotEmpty).length}');
|
||||
}
|
||||
KRLogUtil.kr_i('📱 Windows多因子ID - $hash', tag: 'KRDeviceInfoService');
|
||||
|
||||
return hash.toString();
|
||||
} catch (e) {
|
||||
print('❌ Windows设备ID获取失败: $e');
|
||||
if (kDebugMode) {
|
||||
print('❌ Windows设备ID获取失败: $e');
|
||||
}
|
||||
KRLogUtil.kr_e('❌ Windows设备ID获取失败 - $e', tag: 'KRDeviceInfoService');
|
||||
return '';
|
||||
}
|
||||
@ -265,12 +289,16 @@ class KRDeviceInfoService {
|
||||
final bytes = utf8.encode(combined);
|
||||
final hash = sha256.convert(bytes);
|
||||
|
||||
print('📱 Linux多因子ID生成 - 因子数: ${factors.where((f) => f.isNotEmpty).length}');
|
||||
if (kDebugMode) {
|
||||
print('📱 Linux多因子ID生成 - 因子数: ${factors.where((f) => f.isNotEmpty).length}');
|
||||
}
|
||||
KRLogUtil.kr_i('📱 Linux多因子ID - $hash', tag: 'KRDeviceInfoService');
|
||||
|
||||
return hash.toString();
|
||||
} catch (e) {
|
||||
print('❌ Linux设备ID获取失败: $e');
|
||||
if (kDebugMode) {
|
||||
print('❌ Linux设备ID获取失败: $e');
|
||||
}
|
||||
KRLogUtil.kr_e('❌ Linux设备ID获取失败 - $e', tag: 'KRDeviceInfoService');
|
||||
return '';
|
||||
}
|
||||
@ -288,16 +316,22 @@ class KRDeviceInfoService {
|
||||
// 生成新的UUID
|
||||
storedId = _generateUniqueId();
|
||||
await storage.kr_saveData(key: key, value: storedId);
|
||||
print('📱 生成新的设备ID: $storedId');
|
||||
if (kDebugMode) {
|
||||
print('📱 生成新的设备ID: $storedId');
|
||||
}
|
||||
KRLogUtil.kr_i('📱 生成新的设备ID - $storedId', tag: 'KRDeviceInfoService');
|
||||
} else {
|
||||
print('📱 使用存储的设备ID: $storedId');
|
||||
if (kDebugMode) {
|
||||
print('📱 使用存储的设备ID: $storedId');
|
||||
}
|
||||
KRLogUtil.kr_i('📱 使用存储的设备ID - $storedId', tag: 'KRDeviceInfoService');
|
||||
}
|
||||
|
||||
return storedId;
|
||||
} catch (e) {
|
||||
print('❌ 获取存储的设备ID失败: $e');
|
||||
if (kDebugMode) {
|
||||
print('❌ 获取存储的设备ID失败: $e');
|
||||
}
|
||||
KRLogUtil.kr_e('❌ 获取存储的设备ID失败 - $e', tag: 'KRDeviceInfoService');
|
||||
return _generateUniqueId();
|
||||
}
|
||||
@ -383,7 +417,9 @@ class KRDeviceInfoService {
|
||||
'platform': 'unknown',
|
||||
};
|
||||
} catch (e) {
|
||||
print('❌ 获取设备详情失败: $e');
|
||||
if (kDebugMode) {
|
||||
print('❌ 获取设备详情失败: $e');
|
||||
}
|
||||
KRLogUtil.kr_e('❌ 获取设备详情失败 - $e', tag: 'KRDeviceInfoService');
|
||||
return {
|
||||
'platform': 'unknown',
|
||||
|
||||
@ -12,10 +12,10 @@ class KRSiteConfigService extends ChangeNotifier {
|
||||
static final KRSiteConfigService _instance = KRSiteConfigService._internal();
|
||||
factory KRSiteConfigService() => _instance;
|
||||
KRSiteConfigService._internal() {
|
||||
// 🔧 P1修复:减少超时时间 10秒 → 5秒
|
||||
_dio.options.connectTimeout = const Duration(seconds: 5);
|
||||
_dio.options.sendTimeout = const Duration(seconds: 5);
|
||||
_dio.options.receiveTimeout = const Duration(seconds: 5);
|
||||
// 🔧 Android 15 优化:增加超时时间以适应移动网络(5秒 → 20秒)
|
||||
_dio.options.connectTimeout = const Duration(seconds: 20);
|
||||
_dio.options.sendTimeout = const Duration(seconds: 20);
|
||||
_dio.options.receiveTimeout = const Duration(seconds: 20);
|
||||
|
||||
// 🔧 配置HttpClientAdapter使用sing-box的mixed代理
|
||||
_dio.httpClientAdapter = IOHttpClientAdapter(
|
||||
@ -47,33 +47,54 @@ class KRSiteConfigService extends ChangeNotifier {
|
||||
/// 初始化站点配置
|
||||
Future<bool> initialize() async {
|
||||
try {
|
||||
print('🔧 KRSiteConfigService.initialize() 开始执行');
|
||||
if (kDebugMode) {
|
||||
print('🔧 KRSiteConfigService.initialize() 开始执行');
|
||||
}
|
||||
KRLogUtil.kr_i('🔧 开始初始化网站配置', tag: 'KRSiteConfigService');
|
||||
|
||||
// Debug 模式下使用固定地址
|
||||
final baseUrl = AppConfig().baseUrl;
|
||||
print('📍 baseUrl = $baseUrl');
|
||||
if (kDebugMode) {
|
||||
print('📍 baseUrl = $baseUrl');
|
||||
}
|
||||
final url = '$baseUrl/v1/common/site/config';
|
||||
print('📍 完整URL = $url');
|
||||
if (kDebugMode) {
|
||||
print('📍 完整URL = $url');
|
||||
}
|
||||
|
||||
KRLogUtil.kr_i('📤 请求网站配置 - $url', tag: 'KRSiteConfigService');
|
||||
print('📤 准备发送 GET 请求到: $url');
|
||||
print('⏱️ 超时配置: connectTimeout=10s, sendTimeout=10s, receiveTimeout=10s');
|
||||
if (kDebugMode) {
|
||||
print('📤 准备发送 GET 请求到: $url');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
// 🔧 修正日志,显示真实的超时配置
|
||||
print('⏱️ 超时配置: connectTimeout=${_dio.options.connectTimeout}, sendTimeout=${_dio.options.sendTimeout}, receiveTimeout=${_dio.options.receiveTimeout}');
|
||||
}
|
||||
|
||||
print('⏳ 开始发送请求...');
|
||||
if (kDebugMode) {
|
||||
print('⏳ 开始发送请求...');
|
||||
}
|
||||
final startTime = DateTime.now();
|
||||
final response = await _dio.get(url);
|
||||
final endTime = DateTime.now();
|
||||
final duration = endTime.difference(startTime).inMilliseconds;
|
||||
print('⏱️ 请求耗时: ${duration}ms');
|
||||
if (kDebugMode) {
|
||||
print('⏱️ 请求耗时: ${duration}ms');
|
||||
}
|
||||
|
||||
print('✅ 请求完成,状态码: ${response.statusCode}');
|
||||
if (kDebugMode) {
|
||||
print('✅ 请求完成,状态码: ${response.statusCode}');
|
||||
}
|
||||
KRLogUtil.kr_i('📥 响应状态码 - ${response.statusCode}', tag: 'KRSiteConfigService');
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final responseData = response.data;
|
||||
print('📥 响应数据类型: ${responseData.runtimeType}');
|
||||
print('📥 响应数据: $responseData');
|
||||
if (kDebugMode) {
|
||||
print('📥 响应数据类型: ${responseData.runtimeType}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('📥 响应数据: $responseData');
|
||||
}
|
||||
KRLogUtil.kr_i('📥 响应数据 - $responseData', tag: 'KRSiteConfigService');
|
||||
|
||||
if (responseData['code'] == 200) {
|
||||
@ -96,24 +117,46 @@ class KRSiteConfigService extends ChangeNotifier {
|
||||
return false;
|
||||
}
|
||||
} on DioException catch (e, stackTrace) {
|
||||
print('❌ Dio请求异常: ${e.type}');
|
||||
print('❌ 错误信息: ${e.message}');
|
||||
print('❌ 请求URL: ${e.requestOptions.uri}');
|
||||
print('❌ 连接超时: ${e.requestOptions.connectTimeout}');
|
||||
print('❌ 发送超时: ${e.requestOptions.sendTimeout}');
|
||||
print('❌ 接收超时: ${e.requestOptions.receiveTimeout}');
|
||||
if (e.response != null) {
|
||||
print('❌ 响应状态码: ${e.response?.statusCode}');
|
||||
print('❌ 响应数据: ${e.response?.data}');
|
||||
if (kDebugMode) {
|
||||
print('❌ Dio请求异常: ${e.type}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('❌ 错误信息: ${e.message}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('❌ 请求URL: ${e.requestOptions.uri}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('❌ 连接超时: ${e.requestOptions.connectTimeout}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('❌ 发送超时: ${e.requestOptions.sendTimeout}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('❌ 接收超时: ${e.requestOptions.receiveTimeout}');
|
||||
}
|
||||
if (e.response != null) {
|
||||
if (kDebugMode) {
|
||||
print('❌ 响应状态码: ${e.response?.statusCode}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('❌ 响应数据: ${e.response?.data}');
|
||||
}
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('📚 堆栈跟踪: $stackTrace');
|
||||
}
|
||||
print('📚 堆栈跟踪: $stackTrace');
|
||||
|
||||
KRLogUtil.kr_e('❌ Dio异常 - ${e.type}: ${e.message}', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_e('📚 堆栈: $stackTrace', tag: 'KRSiteConfigService');
|
||||
return false;
|
||||
} catch (e, stackTrace) {
|
||||
print('❌ 未知异常: $e');
|
||||
print('📚 堆栈跟踪: $stackTrace');
|
||||
if (kDebugMode) {
|
||||
print('❌ 未知异常: $e');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('📚 堆栈跟踪: $stackTrace');
|
||||
}
|
||||
KRLogUtil.kr_e('❌ 初始化失败 - $e', tag: 'KRSiteConfigService');
|
||||
KRLogUtil.kr_e('📚 堆栈: $stackTrace', tag: 'KRSiteConfigService');
|
||||
return false;
|
||||
|
||||
@ -98,7 +98,7 @@ class KRSubscribeService {
|
||||
/// 重置订阅周期
|
||||
Future<void> kr_resetSubscribePeriod() async {
|
||||
if (kr_currentSubscribe.value == null) {
|
||||
KRCommonUtil.kr_showToast('请先选择订阅');
|
||||
KRCommonUtil.kr_showToast('subscribe.pleaseSelectFirst'.tr);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -22,6 +22,7 @@ import '../../utils/kr_country_util.dart';
|
||||
import '../../utils/kr_log_util.dart';
|
||||
import '../../utils/kr_secure_storage.dart';
|
||||
import '../../common/app_run_data.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
enum KRConnectionType {
|
||||
global,
|
||||
@ -549,14 +550,18 @@ class KRSingBoxImp {
|
||||
/// 订阅状态变化流
|
||||
/// 参考 hiddify-app: 监听 libcore 发送的状态事件来自动更新 UI
|
||||
void _kr_subscribeToStatus() {
|
||||
print('🔵 _kr_subscribeToStatus 被调用,重新订阅状态流');
|
||||
if (kDebugMode) {
|
||||
print('🔵 _kr_subscribeToStatus 被调用,重新订阅状态流');
|
||||
}
|
||||
KRLogUtil.kr_i('🔵 _kr_subscribeToStatus 被调用', tag: 'SingBox');
|
||||
|
||||
// 取消之前的状态订阅
|
||||
for (var sub in _kr_subscriptions) {
|
||||
if (sub.hashCode.toString().contains('Status')) {
|
||||
sub.cancel();
|
||||
print('🔵 已取消旧的状态订阅');
|
||||
if (kDebugMode) {
|
||||
print('🔵 已取消旧的状态订阅');
|
||||
}
|
||||
}
|
||||
}
|
||||
_kr_subscriptions
|
||||
@ -565,19 +570,25 @@ class KRSingBoxImp {
|
||||
_kr_subscriptions.add(
|
||||
kr_singBox.watchStatus().listen(
|
||||
(status) {
|
||||
print('🔵 收到 Native 状态更新: ${status.runtimeType}');
|
||||
if (kDebugMode) {
|
||||
print('🔵 收到 Native 状态更新: ${status.runtimeType}');
|
||||
}
|
||||
KRLogUtil.kr_i('📡 收到状态更新: $status', tag: 'SingBox');
|
||||
kr_status.value = status;
|
||||
},
|
||||
onError: (error) {
|
||||
print('🔵 状态流错误: $error');
|
||||
if (kDebugMode) {
|
||||
print('🔵 状态流错误: $error');
|
||||
}
|
||||
KRLogUtil.kr_e('📡 状态流错误: $error', tag: 'SingBox');
|
||||
},
|
||||
cancelOnError: false,
|
||||
),
|
||||
);
|
||||
|
||||
print('🔵 状态流订阅完成');
|
||||
if (kDebugMode) {
|
||||
print('🔵 状态流订阅完成');
|
||||
}
|
||||
}
|
||||
|
||||
/// 订阅统计数据流
|
||||
@ -1141,7 +1152,9 @@ class KRSingBoxImp {
|
||||
final selectedNode = await KRSecureStorage().kr_readData(key: _keySelectedNode);
|
||||
if (selectedNode != null && selectedNode.isNotEmpty) {
|
||||
KRLogUtil.kr_i('🎯 恢复用户选择的节点: $selectedNode', tag: 'SingBox');
|
||||
print('🔵 启动后恢复节点选择: $selectedNode');
|
||||
if (kDebugMode) {
|
||||
print('🔵 启动后恢复节点选择: $selectedNode');
|
||||
}
|
||||
|
||||
// 延迟500ms确保sing-box完全启动
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
@ -1150,18 +1163,26 @@ class KRSingBoxImp {
|
||||
try {
|
||||
await kr_selectOutbound(selectedNode);
|
||||
KRLogUtil.kr_i('✅ 节点已切换到用户选择: $selectedNode', tag: 'SingBox');
|
||||
print('🔵 节点切换成功: $selectedNode');
|
||||
if (kDebugMode) {
|
||||
print('🔵 节点切换成功: $selectedNode');
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('❌ 节点切换失败: $e', tag: 'SingBox');
|
||||
print('🔵 节点切换失败: $e');
|
||||
if (kDebugMode) {
|
||||
print('🔵 节点切换失败: $e');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
KRLogUtil.kr_i('ℹ️ 没有保存的节点选择,使用默认配置', tag: 'SingBox');
|
||||
print('🔵 没有保存的节点选择,使用默认');
|
||||
if (kDebugMode) {
|
||||
print('🔵 没有保存的节点选择,使用默认');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('❌ 恢复节点选择失败: $e', tag: 'SingBox');
|
||||
print('🔵 恢复节点选择失败: $e');
|
||||
if (kDebugMode) {
|
||||
print('🔵 恢复节点选择失败: $e');
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (e, stackTrace) {
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:loggy/loggy.dart';
|
||||
|
||||
/// 日志工具类
|
||||
/// 🔒 Release模式下所有日志都不会输出,确保生产环境的性能和安全
|
||||
class KRLogUtil {
|
||||
static final KRLogUtil _instance = KRLogUtil._internal();
|
||||
factory KRLogUtil() => _instance;
|
||||
@ -8,38 +10,59 @@ class KRLogUtil {
|
||||
|
||||
/// 初始化日志
|
||||
static void kr_init() {
|
||||
Loggy.initLoggy(
|
||||
logPrinter: PrettyPrinter(),
|
||||
);
|
||||
// 只在 Debug 模式下初始化日志
|
||||
if (kDebugMode) {
|
||||
Loggy.initLoggy(
|
||||
logPrinter: PrettyPrinter(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 调试日志
|
||||
/// 🔒 只在 Debug 模式下输出
|
||||
static void kr_d(String message, {String? tag}) {
|
||||
Loggy('${tag ?? 'KRLogUtil'}').debug(message);
|
||||
if (kDebugMode) {
|
||||
Loggy('${tag ?? 'KRLogUtil'}').debug(message);
|
||||
}
|
||||
}
|
||||
|
||||
/// 信息日志
|
||||
/// 🔒 只在 Debug 模式下输出
|
||||
static void kr_i(String message, {String? tag}) {
|
||||
Loggy('${tag ?? 'KRLogUtil'}').info(message);
|
||||
if (kDebugMode) {
|
||||
Loggy('${tag ?? 'KRLogUtil'}').info(message);
|
||||
}
|
||||
}
|
||||
|
||||
/// 警告日志
|
||||
/// 🔒 只在 Debug 模式下输出
|
||||
static void kr_w(String message, {String? tag}) {
|
||||
Loggy('${tag ?? 'KRLogUtil'}').warning(message);
|
||||
if (kDebugMode) {
|
||||
Loggy('${tag ?? 'KRLogUtil'}').warning(message);
|
||||
}
|
||||
}
|
||||
|
||||
/// 错误日志
|
||||
/// 🔒 只在 Debug 模式下输出
|
||||
static void kr_e(String message, {String? tag, Object? error, StackTrace? stackTrace}) {
|
||||
Loggy('${tag ?? 'KRLogUtil'}').error(message, error, stackTrace);
|
||||
if (kDebugMode) {
|
||||
Loggy('${tag ?? 'KRLogUtil'}').error(message, error, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
/// 网络日志
|
||||
/// 🔒 只在 Debug 模式下输出
|
||||
static void kr_network(String message, {String? tag}) {
|
||||
Loggy('${tag ?? 'Network'}').info(message);
|
||||
if (kDebugMode) {
|
||||
Loggy('${tag ?? 'Network'}').info(message);
|
||||
}
|
||||
}
|
||||
|
||||
/// 性能日志
|
||||
/// 🔒 只在 Debug 模式下输出
|
||||
static void kr_performance(String message, {String? tag}) {
|
||||
Loggy('${tag ?? 'Performance'}').info(message);
|
||||
if (kDebugMode) {
|
||||
Loggy('${tag ?? 'Performance'}').info(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -84,7 +84,7 @@ class KRNetworkCheck {
|
||||
if (connectivityResult == ConnectivityResult.none) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// 尝试进行实际的网络请求测试
|
||||
try {
|
||||
final result = await InternetAddress.lookup('www.apple.com');
|
||||
@ -93,13 +93,51 @@ class KRNetworkCheck {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Android 平台保持原有逻辑
|
||||
final connectivityResult = await Connectivity().checkConnectivity();
|
||||
return connectivityResult != ConnectivityResult.none;
|
||||
// 🔧 Android 15 增强:多重验证网络可用性
|
||||
// 1. 检查连接状态(基础检查)
|
||||
final connectivityResult = await Connectivity().checkConnectivity().timeout(
|
||||
const Duration(seconds: 3),
|
||||
onTimeout: () {
|
||||
debugPrint('⚠️ 连接性检查超时');
|
||||
return ConnectivityResult.none;
|
||||
},
|
||||
);
|
||||
|
||||
if (connectivityResult == ConnectivityResult.none) {
|
||||
debugPrint('❌ 网络未连接');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 🔧 Android 15 新增:尝试实际 DNS 解析验证网络可用性
|
||||
// 这在 Android 15 上很重要,因为系统可能报告网络已连接但实际无法访问
|
||||
try {
|
||||
final result = await InternetAddress.lookup('www.google.com').timeout(
|
||||
const Duration(seconds: 5),
|
||||
onTimeout: () {
|
||||
debugPrint('⚠️ DNS 解析超时,网络可能不稳定');
|
||||
return <InternetAddress>[];
|
||||
},
|
||||
);
|
||||
|
||||
if (result.isEmpty) {
|
||||
debugPrint('⚠️ DNS 解析失败,但允许继续(可能是网络延迟)');
|
||||
// 即使 DNS 解析失败,也允许继续,因为可能只是网络延迟
|
||||
return true;
|
||||
}
|
||||
|
||||
debugPrint('✅ 网络权限检查通过(连接性 + DNS 解析)');
|
||||
return true;
|
||||
} catch (e) {
|
||||
debugPrint('⚠️ DNS 解析异常: $e,但允许继续');
|
||||
// DNS 解析异常也允许继续,避免误判
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('网络权限检查错误: $e');
|
||||
return false;
|
||||
debugPrint('❌ 网络权限检查错误: $e');
|
||||
// 🔧 Android 15 优化:出错时返回 true,避免阻塞应用启动
|
||||
// 实际网络问题会在后续步骤中处理
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,7 +150,7 @@ class KRNetworkCheck {
|
||||
if (connectivityResult == ConnectivityResult.none) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
final result = await InternetAddress.lookup('www.apple.com');
|
||||
return result.isNotEmpty && result[0].rawAddress.isNotEmpty;
|
||||
@ -120,12 +158,29 @@ class KRNetworkCheck {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
final connectivityResult = await Connectivity().checkConnectivity();
|
||||
return connectivityResult != ConnectivityResult.none;
|
||||
// 🔧 Android 15 增强:添加超时保护
|
||||
final connectivityResult = await Connectivity().checkConnectivity().timeout(
|
||||
const Duration(seconds: 3),
|
||||
onTimeout: () {
|
||||
debugPrint('⚠️ 网络连接检查超时');
|
||||
return ConnectivityResult.none;
|
||||
},
|
||||
);
|
||||
|
||||
if (connectivityResult == ConnectivityResult.none) {
|
||||
debugPrint('❌ 网络未连接');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 🔧 Android 15 优化:对于已连接的网络,快速返回 true
|
||||
// 避免不必要的 DNS 查询延迟应用启动
|
||||
debugPrint('✅ 网络连接检查通过(${connectivityResult.name})');
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('网络连接检查错误: $e');
|
||||
return false;
|
||||
debugPrint('❌ 网络连接检查错误: $e');
|
||||
// 🔧 Android 15 优化:出错时返回 true,避免误判
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -107,7 +107,7 @@ class KRDialog extends StatelessWidget {
|
||||
children: [
|
||||
if (icon != null) ...[
|
||||
icon!,
|
||||
SizedBox(height: 20.w),
|
||||
SizedBox(height: 20.h),
|
||||
],
|
||||
if (title != null) ...[
|
||||
Text(
|
||||
@ -121,7 +121,7 @@ class KRDialog extends StatelessWidget {
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 12.w),
|
||||
SizedBox(height: 12.h),
|
||||
],
|
||||
if (message != null || customMessageWidget != null) ...[
|
||||
Container(
|
||||
@ -139,7 +139,7 @@ class KRDialog extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 28.w),
|
||||
SizedBox(height: 28.h),
|
||||
],
|
||||
if (cancelText != null) ...[
|
||||
Row(
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:country_flags/country_flags.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class KRCountryFlag extends StatelessWidget {
|
||||
final String countryCode;
|
||||
@ -38,17 +39,29 @@ class KRCountryFlag extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 🔍 调试日志
|
||||
print('🏳️ KRCountryFlag.build 被调用');
|
||||
print(' - 原始 countryCode: "$countryCode"');
|
||||
print(' - countryCode.isEmpty: ${countryCode.isEmpty}');
|
||||
print(' - countryCode.length: ${countryCode.length}');
|
||||
if (kDebugMode) {
|
||||
print('🏳️ KRCountryFlag.build 被调用');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' - 原始 countryCode: "$countryCode"');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' - countryCode.isEmpty: ${countryCode.isEmpty}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' - countryCode.length: ${countryCode.length}');
|
||||
}
|
||||
|
||||
final processedCode = _getCountryCode(countryCode);
|
||||
print(' - 处理后 code: "$processedCode"');
|
||||
if (kDebugMode) {
|
||||
print(' - 处理后 code: "$processedCode"');
|
||||
}
|
||||
|
||||
// 如果国家代码为空,显示占位符
|
||||
if (countryCode.isEmpty) {
|
||||
print(' ❌ 国家代码为空,显示占位符');
|
||||
if (kDebugMode) {
|
||||
print(' ❌ 国家代码为空,显示占位符');
|
||||
}
|
||||
return Container(
|
||||
width: width ?? 50.w,
|
||||
height: height ?? 50.w,
|
||||
@ -65,7 +78,9 @@ class KRCountryFlag extends StatelessWidget {
|
||||
final double actualWidth = width ?? 50.w;
|
||||
final double actualHeight = maintainSize ? actualWidth : (height ?? 50.w);
|
||||
|
||||
print(' ✅ 尝试加载国旗: $processedCode');
|
||||
if (kDebugMode) {
|
||||
print(' ✅ 尝试加载国旗: $processedCode');
|
||||
}
|
||||
|
||||
Widget flagWidget = CountryFlag.fromCountryCode(
|
||||
processedCode,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user