bug fix
750
assets/l10n/intl_es.json
Normal file
@ -0,0 +1,750 @@
|
|||||||
|
{
|
||||||
|
"signInWithGoogle": "Iniciar sesión con Google",
|
||||||
|
"or": "O",
|
||||||
|
"signInWithYourAccount": "Inicia sesión con tu cuenta",
|
||||||
|
"signInWithApple": "Iniciar sesión con Apple",
|
||||||
|
"loginRepresentsAgreementTo": "Iniciar sesión representa un acuerdo para",
|
||||||
|
"termsofService": "Términos de servicio",
|
||||||
|
"privaceyPolicy": "política de privacidad",
|
||||||
|
"tips": "Consejos",
|
||||||
|
"searchNoDataTips": "Ingrese la sala o ID de usuario que desea buscar.",
|
||||||
|
"games": "Juegos",
|
||||||
|
"mine": "Mío",
|
||||||
|
"official": "Oficial",
|
||||||
|
"party": "Fiesta",
|
||||||
|
"other": "Otro",
|
||||||
|
"yes": "Sí",
|
||||||
|
"bag": "Bolsa",
|
||||||
|
"sound2": "Sonido",
|
||||||
|
"startYourBrandNewJourney": "Comience su nuevo viaje",
|
||||||
|
"maliciousHarassment": "Acoso malicioso",
|
||||||
|
"event": "Evento",
|
||||||
|
"roomEdit": "Editar habitación",
|
||||||
|
"cancelRoomPassword": "¿Estás seguro de que quieres eliminar la contraseña de la sala?",
|
||||||
|
"roomMemberFee": "Tarifa de miembro de la sala",
|
||||||
|
"roomTheme2": "Tema de la habitación",
|
||||||
|
"background": "Fondo",
|
||||||
|
"blockedList2": "Lista de bloqueados",
|
||||||
|
"roomPassword": "Contraseña de la habitación",
|
||||||
|
"numberOfMic": "Número de micrófono",
|
||||||
|
"pleaseEnterContent": "Por favor ingresa el contenido",
|
||||||
|
"profilePhoto": "Foto de perfil",
|
||||||
|
"aboutMe": "Acerca de mí",
|
||||||
|
"myRoom": "mi cuarto",
|
||||||
|
"noHistoricalRecordsAvailable": "No hay registros históricos disponibles.",
|
||||||
|
"inviteNewUsersToEarnCoins": "Invita a nuevos usuarios a ganar monedas.",
|
||||||
|
"crateMyRoom": "CREA TU PROPIA HABITACIÓN.",
|
||||||
|
"casualInteraction": "Interacción casual",
|
||||||
|
"haveGamePlayingTips": "Tienes un juego en progreso. Por favor sal del juego actual. ¿Estás seguro de que quieres salir?",
|
||||||
|
"historicalTour": "Tour Histórico",
|
||||||
|
"gameCenter": "Centro de juegos",
|
||||||
|
"returnToVoiceChat": "¿Volver al chat de voz?",
|
||||||
|
"exitGameMode": "Salir del modo de juego",
|
||||||
|
"enterTheRoom": "Entra a la habitación",
|
||||||
|
"invite": "Invitar",
|
||||||
|
"recent": "Reciente",
|
||||||
|
"popularEvents": "Eventos populares",
|
||||||
|
"inviteGoRoomTips": "Siempre aquí para ti, llueva o haga sol. ¡Entra y saluda!",
|
||||||
|
"confirmInviteThisUserToTheRoom": "¿Confirmar la invitación de este usuario (ID:{1}) a la sala?",
|
||||||
|
"clearCacheSuccessfully": "Borrar caché exitosamente",
|
||||||
|
"sent": "Enviado",
|
||||||
|
"keep": "Mantener",
|
||||||
|
"open": "Abierto",
|
||||||
|
"deleteAccountTips2": "*Si cambia de opinión, puede volver a iniciar sesión en su cuenta actual dentro de los siete días y la restauraremos automáticamente. Si no se produce ninguna restauración dentro de los siete días, la cuenta se eliminará permanentemente.",
|
||||||
|
"deleteAccountTips": "Tiene todos los derechos administrativos sobre esta cuenta. Si tiene la intención de eliminar la cuenta, tenga en cuenta los siguientes riesgos asociados con esta operación:\n1. Una vez que la cuenta se elimine correctamente, ya no podrá iniciar sesión en su cuenta actual. La eliminación de la cuenta es una acción permanente.\n2. Una vez que la cuenta se elimine correctamente, no podrá recuperar ningún dato de la cuenta. Toda la información (incluidas habitaciones y amigos), moneda virtual, regalos y artículos virtuales se eliminarán permanentemente y no se podrán restaurar.\n3. Período de reflexión: si no restaura la cuenta, no podrá acceder a la página de compra, a la página de retiro ni a ninguna otra página de la aplicación.\n4. Durante el período de reflexión o después de que la cuenta haya sido eliminada, la página de perfil indicará que se ha eliminado. Para proteger su cuenta de que otros busquen o accedan a ella, su información personal se eliminará de los sistemas relacionados con las funciones diarias. Cuando la eliminación de la cuenta implique seguridad nacional, procedimientos civiles o penales, o la protección de derechos e intereses legítimos de terceros, el equipo oficial se reserva el derecho de rechazar la solicitud de eliminación de la cuenta del usuario.\nSi está seguro de que desea eliminar todos los datos personales de su cuenta actual, haga clic en \"Eliminar cuenta\".",
|
||||||
|
"accountDeletionNotice": "Aviso de eliminación de cuenta:",
|
||||||
|
"thisUserHasBeenBlacklisted": "Este usuario ha sido incluido en la lista negra.",
|
||||||
|
"trend": "Tendencia",
|
||||||
|
"like": "Como",
|
||||||
|
"more": "Más",
|
||||||
|
"discard": "Desechar",
|
||||||
|
"catchFirstComment": "Captura el primer comentario.",
|
||||||
|
"reply": "Responder",
|
||||||
|
"posting": "Destino",
|
||||||
|
"multiple": "Múltiple",
|
||||||
|
"successfullyRemovedFromTheBlacklist": "¡Eliminado con éxito de la lista negra!",
|
||||||
|
"successfullyAddedToTheBlacklist": "¡Agregado exitosamente a la lista negra!",
|
||||||
|
"youAreCurrentlyCPRelationshipPleaseDissolve": "Actualmente estás en una relación CP.\nPor favor disuélvalo primero.",
|
||||||
|
"areYouSureToCancelBlacklist": "¿Estás seguro de cancelar la lista negra?",
|
||||||
|
"areYouSureYouWantToBlockThisUser": "¿Estás seguro de que quieres bloquear a este usuario?",
|
||||||
|
"removeFromBlacklist": "Eliminar de la lista negra",
|
||||||
|
"moveToBlacklist": "Mover a la lista negra",
|
||||||
|
"userBlacklist": "Lista negra de usuarios",
|
||||||
|
"specialEffectsManagement": "Manejo de efectos especiales",
|
||||||
|
"wishingYouHappinessEveryDay": "Deseándote felicidad todos los días.",
|
||||||
|
"newMessage": "Nuevo mensaje",
|
||||||
|
"createRoomSuccsess": "¡La habitación se creó correctamente!",
|
||||||
|
"contactUs": "Contáctenos",
|
||||||
|
"systemAnnouncementTips1": "Protéjase contra el fraude:",
|
||||||
|
"systemAnnouncementTips": "Verificar información únicamente a través de canales oficiales. Nunca descargue software de terceros, no comparta datos personales ni transfiera dinero en función de solicitudes externas. Las identificaciones oficiales del personal son solo 10000, 10003 y 10086. Ante cualquier sospecha, deténgase e informe a través de",
|
||||||
|
"systemAnnouncement": "Anuncio del sistema",
|
||||||
|
"doNotClickUnfamiliarTips": "No haga clic en enlaces desconocidos, ya que pueden exponer su información personal. Nunca compartas tu DNI o los datos de tu tarjeta bancaria con nadie.",
|
||||||
|
"atTag": "@Etiqueta",
|
||||||
|
"sayHi2": "Di hola",
|
||||||
|
"roomBottomGreeting": "Hola...",
|
||||||
|
"canSendMsgTips": "Ambas partes deben seguirse antes de poder enviar mensajes privados.",
|
||||||
|
"msgSendRedEnvelopeTips": "*Se cobrará una tarifa de servicio del 10 % en los sobres rojos y los destinatarios solo recibirán el 90 % del valor del sobre rojo. El nivel de riqueza del remitente debe ser superior al nivel 10.",
|
||||||
|
"reapply": "Aplicar de nuevo",
|
||||||
|
"cancelRequest": "Cancelar solicitud",
|
||||||
|
"pending": "Pendiente",
|
||||||
|
"supporter": "Seguidor",
|
||||||
|
"coinsReceived": "Monedas recibidas",
|
||||||
|
"numberOfSign": "Número de signo: {1}",
|
||||||
|
"hostWeeklyRank": "Clasificación semanal de anfitrión",
|
||||||
|
"supporterWeeklyRank": "Clasificación semanal de partidario",
|
||||||
|
"memberList": "Lista de miembros",
|
||||||
|
"treasureChest": "Cofre del tesoro",
|
||||||
|
"applicationRecord": "Registro de solicitud",
|
||||||
|
"appUpdateTip": "La aplicación tiene una nueva versión ({1}). ¿Quieres descargarlo ahora?",
|
||||||
|
"ownerIncomeCoins": "Ingresos del propietario: {1} monedas",
|
||||||
|
"game": "Juego",
|
||||||
|
"skip2": "Saltar",
|
||||||
|
"skipCountdown": "Saltar {1}",
|
||||||
|
"coins4": "monedas",
|
||||||
|
"weekStart": "Inicio de semana",
|
||||||
|
"forMoreRewardsPleaseCheckTheTaskCenter": "Para obtener más recompensas, consulte el centro de tareas.",
|
||||||
|
"kingQuuen": "Rey-Reina",
|
||||||
|
"ramadan": "Ramadán",
|
||||||
|
"updateNow": "Actualizar ahora",
|
||||||
|
"allGames": "Todos los juegos",
|
||||||
|
"fishClass": "Clase de pescado",
|
||||||
|
"greedyClass": "Clase codiciosa",
|
||||||
|
"raceSeries": "Serie de carreras",
|
||||||
|
"slotsClass": "Clase de tragamonedas",
|
||||||
|
"others": "Otros",
|
||||||
|
"hotGames": "Juegos calientes",
|
||||||
|
"chatBox": "cuadro de chat",
|
||||||
|
"termsOfServicePrivacyPolicyTips": "Al continuar, aceptas los Términos de servicio y la Política de privacidad.",
|
||||||
|
"and": " y ",
|
||||||
|
"pleaseSelectTheTypeContent": "Seleccione el tipo de contenido ofensivo.",
|
||||||
|
"illegalInformation": "Información ilegal",
|
||||||
|
"inappropriateContent": "Contenido inapropiado",
|
||||||
|
"personalAttack": "Ataque personal",
|
||||||
|
"confirm": "Confirmar",
|
||||||
|
"spam": "Correo basura",
|
||||||
|
"countdownMinutes": "Minutos de cuenta regresiva:",
|
||||||
|
"number2": "Número:",
|
||||||
|
"fraud": "Fraude",
|
||||||
|
"received": "Recibió",
|
||||||
|
"currentProgress": "Progreso actual",
|
||||||
|
"currentStage": "Etapa actual:{1}",
|
||||||
|
"roomReward2": "Recompensa de habitación:{1}",
|
||||||
|
"roomReward": "Recompensa de habitación",
|
||||||
|
"expirationTime": "tiempo de vencimiento",
|
||||||
|
"ownerSendTheRedEnvelope": "El propietario envió monedas de recompensa.",
|
||||||
|
"rewardCoins": "Monedas de recompensa:{1} monedas",
|
||||||
|
"signInRewardReceived": "Iniciaste sesión exitosamente. Recompensa: {1}",
|
||||||
|
"registerRewardReceived": "Recompensa de registro recibida: {1}",
|
||||||
|
"lastWeekProgress": "El progreso de la semana pasada.",
|
||||||
|
"redEnvelopeTips2": "*Si el sobre rojo no se reclama dentro del plazo, las monedas restantes serán devueltas al usuario que envió el sobre rojo.",
|
||||||
|
"goToRecharge": "ir a recargar",
|
||||||
|
"deleteAccount2": " Eliminar cuenta ({1}s)",
|
||||||
|
"areYouSureYouWantToDeleteYourAccount": " ¿Estás seguro de que quieres eliminar tu cuenta?",
|
||||||
|
"insufhcientGoldsGoToRecharge": "Oro insuficiente. ¡Recarga ahora!",
|
||||||
|
"coins2": "{1} monedas",
|
||||||
|
"remainingNumberTips": "Disponible restante: ({1}/{2})",
|
||||||
|
"collectionTimeTips": "Hora de recogida:{1}({2}/{3})",
|
||||||
|
"sendARedEnvelope": "enviar un sobre rojo",
|
||||||
|
"sendRedPackConfirmTips": "¿Estás seguro de que quieres enviar el paquete rojo?",
|
||||||
|
"redEnvelopeSendingRecords": "Registros de envío de sobre rojo:",
|
||||||
|
"redEnvelope": "Sobre rojo",
|
||||||
|
"roomRedPacketGoldCoins": "Monedas de oro",
|
||||||
|
"roomRedPacketQuantity": "Cantidad",
|
||||||
|
"roomRedPacketPickupTime": "Hora de recogida",
|
||||||
|
"roomRedPacketInMinutes": "En {1} minutos",
|
||||||
|
"roomRedPacketClaimNow": "Reclamar ahora",
|
||||||
|
"roomRedPacketSendLuckyPack": "Envía un paquete de la suerte",
|
||||||
|
"roomRedPacketSending": "Envío...",
|
||||||
|
"roomRedPacketSendSuccess": "Paquete de la suerte enviado",
|
||||||
|
"roomRedPacketComingSoon": "Muy pronto",
|
||||||
|
"roomRedPacketRulesTitle": "Normas",
|
||||||
|
"roomRedPacketRulesContent": "1. Puedes gastar monedas para enviar un Lucky Pack. Si otras personas abren tu Lucky Pack, pueden recibir una cantidad aleatoria de monedas.\n\n2. Dos horas después de enviar un Lucky Pack, las monedas no reclamadas se devolverán a su billetera.\n\n3. Después de enviar un Lucky Pack a todo el servidor, se activará un banner de Lucky Pack para todo el servidor, lo que le brindará más atención.\n\n4. Puedes enviar Lucky Packs de habitación a %1$s por día.\n\n5. Los Lucky Packs enviados por usuarios fuera de su región no se pueden reclamar.",
|
||||||
|
"roomRedPacketHistoryTitle": "Historial de entrega del paquete de la suerte",
|
||||||
|
"roomRedPacketHistoryItemTitle": "Bolsa de la suerte ({1} sobres rojos)",
|
||||||
|
"roomRedPacketHistoryStatusSent": "Enviado",
|
||||||
|
"roomRedPacketHistoryStatusFinished": "Totalmente reclamado",
|
||||||
|
"roomRedPacketHistoryStatusPending": "Pendiente",
|
||||||
|
"roomRedPacketHistoryStatusReturned": "Devuelto",
|
||||||
|
"roomRedPacketHistoryRefund": "Reembolso: {1}",
|
||||||
|
"roomRedPacketClaimRecordsTitle": "Registros de reclamaciones",
|
||||||
|
"roomRedPacketChatMessage": "Te envié un sobre rojo, ¡ábrelo ahora mismo!",
|
||||||
|
"roomRedPacketReceivedMessage": "Reclama {1} monedas del {2} Lucky Pack",
|
||||||
|
"roomRedPacketOpenSubtitle": "El paquete de la suerte ha llegado",
|
||||||
|
"roomRedPacketOpenYouGot": "obtienes",
|
||||||
|
"roomRedPacketClaimSuccessTips": "Las monedas han sido enviadas a tu billetera.",
|
||||||
|
"roomRedPacketOpenSoldOut": "El Lucky Pack ya se ha agotado.",
|
||||||
|
"roomRedPacketOpenExpired": "El paquete de la suerte ha caducado.",
|
||||||
|
"roomRedPacketRegionNotMatch": "Solo los usuarios de la misma región lingüística pueden reclamar este paquete de la suerte.",
|
||||||
|
"roomRedPacketNotStarted": "El paquete de la suerte aún no está listo.",
|
||||||
|
"roomRedPacketDailyLimitExceeded": "Se ha alcanzado el límite de envío de hoy.",
|
||||||
|
"roomRedPacketInsufficientBalance": "Monedas de oro insuficientes.",
|
||||||
|
"roomRedPacketPresenceRequired": "Quédese en la habitación para usar paquetes de la suerte.",
|
||||||
|
"roomRedPacketNetworkError": "No se puede procesar el paquete de la suerte en este momento.",
|
||||||
|
"roomRedPacketAlreadyClaimed": "Ya has reclamado este paquete de la suerte.",
|
||||||
|
"roomRedPacketOpenWaiting": "Se puede abrir en {1} minutos.",
|
||||||
|
"roomRedPacketOpenWaitingCountdown": "Disponible para recogida después del {1}",
|
||||||
|
"roomRedPacketOkay": "Bueno",
|
||||||
|
"roomRedPacketFloatingTitle": "{1} envió un sobre rojo",
|
||||||
|
"roomRedPacketFloatingSubtitle": "Envió un sobre rojo",
|
||||||
|
"redEnvelopeRecTips2": "Todos los sobres rojos han sido reclamados.",
|
||||||
|
"redEnvelopeRecTips3": "¡El tiempo de recogida del sobre rojo ha expirado!",
|
||||||
|
"openTheTreasureChest": "Abre el cofre del tesoro",
|
||||||
|
"redEnvelopeRecTips1": "Las monedas ganadas se han depositado en su billetera.",
|
||||||
|
"redEnvelopeTips1": "Monedas:",
|
||||||
|
"roomTools": "Herramientas de la habitación:",
|
||||||
|
"entertainment": "Entretenimiento:",
|
||||||
|
"reportSucc": "Informe exitoso",
|
||||||
|
"pornography": "Pornografía",
|
||||||
|
"reportInputTips": "Describa el problema con el mayor detalle posible para que podamos comprenderlo y resolverlo.",
|
||||||
|
"cancel": "Cancelar",
|
||||||
|
"join": "Unirse",
|
||||||
|
"items": "Elementos",
|
||||||
|
"vistors": "Visitantes",
|
||||||
|
"fans": "aficionados",
|
||||||
|
"balanceNotEnough": "Saldo insuficiente de monedas de oro. ¿Quieres ir a recargar?",
|
||||||
|
"skip": "Saltar {1}",
|
||||||
|
"letGoToWatch": "¡Vamos a mirar!",
|
||||||
|
"launchedARocket": "lanzó un cohete",
|
||||||
|
"sendUserId": "Enviar ID de usuario:{1}",
|
||||||
|
"giveUpIdentity": "renunciar a la identidad",
|
||||||
|
"leaveRoomIdentityTips": "¿Estás seguro de que quieres ceder la identidad de la habitación?",
|
||||||
|
"joinMemberTips2": "¿Quieres confirmar unirte a la sala como miembro?",
|
||||||
|
"sureUnfollowThisRoom": "¿Seguro que dejarás de seguir esta sala?",
|
||||||
|
"welcomeMessage": "¡Bienvenido a nuestra aplicación, {nombre}!",
|
||||||
|
"settings": "Ajustes",
|
||||||
|
"account": "Cuenta",
|
||||||
|
"common": "Común",
|
||||||
|
"delete": "Borrar",
|
||||||
|
"copy": "Copiar",
|
||||||
|
"bio": "Biografía",
|
||||||
|
"introduction": "Introducción",
|
||||||
|
"useCoupontips": "¿Estás seguro de que quieres utilizar el cupón?",
|
||||||
|
"searchUserId": "Buscar ID de usuario",
|
||||||
|
"sendUser": "Enviar usuario",
|
||||||
|
"hobby": "Pasatiempo",
|
||||||
|
"sendCoupontips": "¿Estás seguro de que quieres enviar este cupón a este usuario?",
|
||||||
|
"youDontHaveAnyCouponsYet": "Aún no tienes ningún cupón.",
|
||||||
|
"recall": "Recordar",
|
||||||
|
"youHaventFollowed": "No has seguido ninguna habitación",
|
||||||
|
"deleteFromMyDevice": "Eliminar de mi dispositivo",
|
||||||
|
"deleteOnAllDevices": "Eliminar en todos los dispositivos",
|
||||||
|
"messageHasBeenRecalled": "Este mensaje ha sido recordado.",
|
||||||
|
"recallThisMessage": "¿Recuerdas este mensaje?",
|
||||||
|
"language": "Idioma",
|
||||||
|
"feedback": "Comentario",
|
||||||
|
"signedin": "Iniciado sesión",
|
||||||
|
"receiveSucc": "Reclamado con éxito",
|
||||||
|
"about": "Acerca de",
|
||||||
|
"aboutUs": "Sobre nosotros",
|
||||||
|
"theme": "Tema",
|
||||||
|
"wealthLevel": "Nivel rico",
|
||||||
|
"userLevel": "Nivel de encanto",
|
||||||
|
"goToUpload": "ir a subir",
|
||||||
|
"logout": "Cerrar sesión",
|
||||||
|
"luck": "Suerte",
|
||||||
|
"level": "Nivel",
|
||||||
|
"themeGoToUploadTips": "1. La revisión se completará dentro de las 24 horas posteriores a la carga exitosa.\n2. Todas las monedas serán devueltas si la revisión falla.",
|
||||||
|
"pleaseUploadAccordingToExample": "Cárguelo de acuerdo con el ejemplo proporcionado.",
|
||||||
|
"home": "Hogar",
|
||||||
|
"explore": "Explorar",
|
||||||
|
"me": "A mí",
|
||||||
|
"socialPrivilege": "privilegio social",
|
||||||
|
"information": "Información",
|
||||||
|
"myPhoto": "mi foto",
|
||||||
|
"areYouSureYouWantToSpend3": "*Si la otra parte rechaza la invitación de CP, sus monedas serán devueltas a su billetera.",
|
||||||
|
"areYouSureYouWantToSpend": "¿Estás seguro de que quieres gastar?",
|
||||||
|
"areYouSureYouWantToSpend2": "enviar una invitación CP a este usuario?",
|
||||||
|
"underReview": "En revisión",
|
||||||
|
"doYouWantToDeleteIt": "¿Quieres eliminarlo?",
|
||||||
|
"chooseFromAblum": "Elige del álbum",
|
||||||
|
"spaceBackground": "Fondo espacial",
|
||||||
|
"editProfile": "Editar perfil",
|
||||||
|
"sendTheCpRequest": "Enviar la solicitud de CP",
|
||||||
|
"addCp": "Agregar CP",
|
||||||
|
"partWays": "Caminos parciales",
|
||||||
|
"reconcile": "Conciliar",
|
||||||
|
"separated": "Apartado",
|
||||||
|
"areYouSureYouWantToSpend5": "{1} te confesó el sentimiento; si aceptáis, os convertiréis en pareja.",
|
||||||
|
"areYouSureYouWantToSpend6": "{1} quiere volver contigo. Si decide conciliar, se restaurarán todos sus datos anteriores.",
|
||||||
|
"reconcileInvitationTips": "*Si la otra parte rechaza la invitación de CP, sus monedas serán devueltas a su billetera.",
|
||||||
|
"reconcileInvitation": "Conciliar invitación",
|
||||||
|
"areYouSureYouWantToSpend4": "enviar una invitación de reconciliación a este usuario?",
|
||||||
|
"partWaysTips": "*Si un miembro de una pareja decide separarse, habrá un período de reflexión de 7 días. Durante este tiempo, ambas partes pueden optar por reconciliarse y todos los datos se restaurarán tras la reconciliación. Si no se elige ninguna reconciliación al final del período de 7 días, se borrarán los datos de la pareja.",
|
||||||
|
"areYouSureYouWantToPartWaysWithYourCP": "¿Estás seguro de que quieres separarte de tu CP?",
|
||||||
|
"timeSpentTogether": "Tiempo que pasamos juntos: {1} días",
|
||||||
|
"firstDay": "Primer día:{1}",
|
||||||
|
"numberOfMyCPs": "Número de mis CP:({1}/{2})",
|
||||||
|
"props": "Accesorios",
|
||||||
|
"win": "Ganar",
|
||||||
|
"dice": "Dados",
|
||||||
|
"rps": "RPS",
|
||||||
|
"operationFail": "La operación fracasó.",
|
||||||
|
"enterRoomFailedRetry": "No se pudo entrar a la habitación. Por favor inténtalo de nuevo.",
|
||||||
|
"voiceConnectionFailedRetry": "La conexión de voz falló. Por favor inténtalo de nuevo.",
|
||||||
|
"likedYourComment": "Me gustó tu comentario.",
|
||||||
|
"doYouWantToKeepTheDraft": "¿Quieres conservar el borrador?",
|
||||||
|
"operationsAreTooFrequent": "Las operaciones son demasiado frecuentes.",
|
||||||
|
"luckNumber": "Número de suerte",
|
||||||
|
"relationShip": "Relación",
|
||||||
|
"couple": "Pareja {1}:",
|
||||||
|
"couple2": "Pareja",
|
||||||
|
"reject": "Rechazar",
|
||||||
|
"accept": "Aceptar",
|
||||||
|
"noMatchedCP": "No hay CP coincidente",
|
||||||
|
"inviteYouToBecomeBD": "Te invitamos a convertirte en BD.",
|
||||||
|
"adminInviteRechargeAgent": "Te invitamos a convertirte en agente de Recarga.",
|
||||||
|
"confirmAcceptTheInvitation": "¿Confirmar para aceptar la invitación?",
|
||||||
|
"confirmDeclineTheInvitation": "¿Confirmar que rechazas la invitación?",
|
||||||
|
"cpInviteRejectedByOther": "La otra parte se negó a convertirse en {1}.",
|
||||||
|
"closeFriendInvitationTitle": "Invitación de un amigo cercano",
|
||||||
|
"closeFriendInvitationSubtitle": "Haga clic para ver la invitación",
|
||||||
|
"closeFriendInvitationExpired": "La invitación para agregar un amigo cercano ha caducado. Esta acción no se puede realizar.",
|
||||||
|
"closeFriendRequestDeclined": "Solicitud de amistad rechazada",
|
||||||
|
"closeFriendsHaveBecome": "Se han hecho amigos cercanos",
|
||||||
|
"otherAcceptedCloseFriendInvite": "La otra parte aceptó su invitación de amigo cercano.",
|
||||||
|
"otherDeclinedCloseFriendInvite": "La otra parte rechazó su invitación de amigo cercano.",
|
||||||
|
"youAcceptedCloseFriendInvite": "Aceptaste la invitación de amigo cercano de la otra parte.",
|
||||||
|
"youDeclinedCloseFriendInvite": "Rechazaste la invitación de amigo cercano de la otra parte.",
|
||||||
|
"closeFriendInviteTimedOut": "Se agotó el tiempo de espera para la invitación de amigo cercano.",
|
||||||
|
"cpDismissConfirmMessage": "Eliminar esta relación costará {1} monedas. ¿Confirmar la eliminación?",
|
||||||
|
"cpRelationCp": "CP",
|
||||||
|
"cpRelationBrother": "Hermano",
|
||||||
|
"cpRelationSister": "Hermana",
|
||||||
|
"host": "Anfitrión",
|
||||||
|
"following": "Siguiente",
|
||||||
|
"agent": "Agencia",
|
||||||
|
"approved": "Aprobado",
|
||||||
|
"approvedWithinOneMinute": "Aprobado y mostrado dentro de 1 minuto después del envío",
|
||||||
|
"onlineUsers": "Usuarios en línea ({1}/{2}):",
|
||||||
|
"applyToJoin": "Aplicar para unirse",
|
||||||
|
"supporterList": "Lista de seguidores",
|
||||||
|
"hostList": "Lista de anfitriones",
|
||||||
|
"upToAdmins": "Hasta {1} administradores",
|
||||||
|
"upToMembers": "Hasta {1} miembros",
|
||||||
|
"levelPrivileges": "Privilegios de nivel",
|
||||||
|
"ra": "REAL ACADEMIA DE BELLAS ARTES",
|
||||||
|
"roomAnnouncement": "Anuncio de la habitación",
|
||||||
|
"help": "Ayuda",
|
||||||
|
"rejected": "Rechazado",
|
||||||
|
"boxContributeTips": "La inversión ya se ha realizado hoy. Por favor no vuelvas a invertir.",
|
||||||
|
"bd": "BD",
|
||||||
|
"coupon": "Cupón",
|
||||||
|
"search": "Buscar",
|
||||||
|
"get": "Conseguir",
|
||||||
|
"inRocket": "en el cohete",
|
||||||
|
"roomRocketHelpTips": "1. Enviar regalos en la habitación aumenta la energía del cohete. *1 moneda de oro de regalo = 1 punto de energía de cohete; Los obsequios de la suerte aumentan la energía del cohete en un 4% del valor de la moneda de oro del obsequio.\n2. Una vez que la energía del cohete esté completamente cargada, la sala puede lanzar el cohete. Las recompensas se distribuirán automáticamente después del lanzamiento.\n3. Los diferentes niveles de cohetes ofrecen diferentes recompensas.\n4. Cuando se lanza el cohete, todos los usuarios en la sala pueden reclamar la recompensa del cohete.\n5. La energía del cohete se reinicia a las 00:00 todos los días.",
|
||||||
|
"couponRecord": "Registro de uso de cupones",
|
||||||
|
"inRoom": "en la habitación",
|
||||||
|
"searchCouponHint": "Buscar cupón",
|
||||||
|
"giftCounter": "Mostrador de regalos",
|
||||||
|
"bDLeaderInviteYouToBecomeBDLeader": "Te invitamos a convertirte en BDLeader",
|
||||||
|
"wins": "gana",
|
||||||
|
"inviteYouToBecomeHost": "Te invitamos a convertirte en anfitrión.",
|
||||||
|
"friends": "Amigos",
|
||||||
|
"deleteConversationTips": "¿Estás seguro de que deseas eliminar el historial de chat con este usuario?",
|
||||||
|
"propMessagePrompt": "Mensaje de apoyo",
|
||||||
|
"inputUserId": "Introduzca el ID de usuario",
|
||||||
|
"fromLuckyGifts": "De regalos mágicos/afortunados",
|
||||||
|
"receive": "Recibir",
|
||||||
|
"checkInSuccessful": "Registro exitoso",
|
||||||
|
"sginTips": "Recibirás una recompensa la primera vez que inicies sesión cada día. Si interrumpes tu racha de inicios de sesión, la recompensa se volverá a calcular desde el primer día cuando vuelvas a iniciar sesión.",
|
||||||
|
"popular": "Popular",
|
||||||
|
"recommend": "Recomendar",
|
||||||
|
"follow": "Seguir",
|
||||||
|
"history": "Historia",
|
||||||
|
"hotRooms": "Habitaciones Calientes",
|
||||||
|
"viewMore": "Ver más",
|
||||||
|
"noData": "Sin datos",
|
||||||
|
"users": "Usuarios",
|
||||||
|
"rooms": "Alojamiento",
|
||||||
|
"coins": "monedas",
|
||||||
|
"unread": "No leído",
|
||||||
|
"read": "Leer",
|
||||||
|
"image": "[Imagen]",
|
||||||
|
"video": "[Video]",
|
||||||
|
"sound": "[Sonido]",
|
||||||
|
"gift2": "[Regalo]",
|
||||||
|
"clickHereToStartChatting": "Di algo.....",
|
||||||
|
"receivedAMessage": "[Recibido un mensaje]",
|
||||||
|
"confirmSwitchMicModelTips": "¿Confirmas el cambio en modo asiento?",
|
||||||
|
"number": "Número",
|
||||||
|
"album": "Álbum",
|
||||||
|
"camera": "Cámara",
|
||||||
|
"system": "Sistema",
|
||||||
|
"notifcation": "Notificación",
|
||||||
|
"inviteYouToBecomeAgent": "Te invitamos a convertirte en agencia.",
|
||||||
|
"myMusic": "mi musica",
|
||||||
|
"add": "Agregar",
|
||||||
|
"pullToLoadMore": "Tira para cargar más",
|
||||||
|
"loadingFailedClickToRetry": "Error al cargar, haga clic para volver a intentarlo",
|
||||||
|
"releaseToLoadMore": "Suelta para cargar más",
|
||||||
|
"haveMyLimits": "---Tengo mis límites.---",
|
||||||
|
"music": "Música",
|
||||||
|
"free": "Gratis",
|
||||||
|
"charm": "Encanto de regalo",
|
||||||
|
"start": "Comenzar",
|
||||||
|
"stop": "Detener",
|
||||||
|
"chats": "Charlas",
|
||||||
|
"refuse": "Rechazar",
|
||||||
|
"agree": "Aceptar",
|
||||||
|
"thisFeatureIsCurrentlyUnavailable": "Esta característica no está disponible actualmente.",
|
||||||
|
"pleaseSelectTheRecipient": "Por favor seleccione el destinatario.",
|
||||||
|
"searchMemberIdHint": "Por favor ingrese el ID del miembro",
|
||||||
|
"unclaimedRedEnvelopes": "Los sobres rojos no reclamados se reembolsan en 24 horas.",
|
||||||
|
"redEnvelopeNotYetClaimed": "Sobre rojo aún no reclamado.",
|
||||||
|
"redEnvelopeAmount": "Cantidad del sobre rojo: {1} monedas",
|
||||||
|
"sentARedEnvelope": "Envió un sobre rojo.",
|
||||||
|
"theRedEnvelopeHasExpired": "El sobre rojo ha caducado.",
|
||||||
|
"joinRequest": "Solicitud de unión",
|
||||||
|
"scrollToTheBottom": "Desplácese hasta el final",
|
||||||
|
"gameRules": "Reglas del juego:",
|
||||||
|
"charmGameRulesTips": "Encienda el panel para mostrar las monedas de regalo recibidas de todos los usuarios en el micrófono. 1 moneda = 1 puntuación (Regalo de la suerte: 1 moneda = 0,04 puntuación).",
|
||||||
|
"inputYourOldPassword": "Ingrese su antigua contraseña",
|
||||||
|
"enterYourOldPassword": "Ingrese su antigua contraseña",
|
||||||
|
"setYourPassword": "Establece tu contraseña",
|
||||||
|
"enterYourNewPassword": "Ingresa tu nueva contraseña",
|
||||||
|
"confirmYourPassword": "Confirma tu contraseña",
|
||||||
|
"theTwoPasswordsDoNotMatch": "Las dos contraseñas no coinciden.",
|
||||||
|
"resetLoginPasswordtTips2": "La contraseña debe tener entre 8 y 16 caracteres y debe ser una combinación de letras inglesas mayúsculas y minúsculas y números (no solo números).",
|
||||||
|
"resetLoginPassword": "Restablecer contraseña de inicio de sesión",
|
||||||
|
"resetLoginPasswordtTips1": "Inicie sesión con su ID de usuario o ID personalizado. Es más seguro iniciar sesión con una cuenta yumi.",
|
||||||
|
"localMusic": "Música Local",
|
||||||
|
"setLoginPassword": "Establecer contraseña de inicio de sesión",
|
||||||
|
"confirmSwitchMicThemeTips": "¿Confirmas el cambio de estilo de asiento?",
|
||||||
|
"micTheme": "Tema de micrófono",
|
||||||
|
"classicMic": "Micrófono clásico {1}",
|
||||||
|
"yesterday": "Ayer {1}",
|
||||||
|
"monday": "lunes {1}",
|
||||||
|
"tuesday": "martes {1}",
|
||||||
|
"wednesday": "miércoles {1}",
|
||||||
|
"thursday": "jueves {1}",
|
||||||
|
"friday": "viernes {1}",
|
||||||
|
"saturday": "sábado {1}",
|
||||||
|
"sunday": "domingo {1}",
|
||||||
|
"acceptedYour": "{1} aceptó tu ",
|
||||||
|
"youAccepted": "Aceptaste {1} ",
|
||||||
|
"openRedPackDialogTip": "El sobre rojo",
|
||||||
|
"micManagement": "Gestión de micrófonos",
|
||||||
|
"bdCenter": "Centro BD",
|
||||||
|
"rechargeAgency": "Agencia de recarga",
|
||||||
|
"adminCenter": "Centro de administración",
|
||||||
|
"managerCenter": "Centro de gerentes",
|
||||||
|
"goldList": "Lista de oro",
|
||||||
|
"followList": "Lista de seguimiento",
|
||||||
|
"fansList": "Lista de fans",
|
||||||
|
"daily": "A diario",
|
||||||
|
"female": "Femenino",
|
||||||
|
"male": "Masculino",
|
||||||
|
"identity": "Identidad",
|
||||||
|
"adjust": "Ajustar",
|
||||||
|
"warning": "Advertencia",
|
||||||
|
"screenshotTips": "Captura de pantalla (hasta 3)",
|
||||||
|
"roomNotice": "Aviso de habitación",
|
||||||
|
"roomTheme": "Tema de la habitación",
|
||||||
|
"description": "Descripción:",
|
||||||
|
"inputDesHint": "Describa el problema con el mayor detalle posible para que podamos comprenderlo y resolverlo.",
|
||||||
|
"roomProfilePicture": "Foto de perfil de la habitación",
|
||||||
|
"userProfilePicture": "Foto de perfil de usuario",
|
||||||
|
"userName": "Nombre de usuario",
|
||||||
|
"pleaseSelectTheTypeToProcess": "Por favor seleccione el tipo a procesar:",
|
||||||
|
"roomEditing": "Edición de sala",
|
||||||
|
"setAccount": "Establecer cuenta",
|
||||||
|
"userEditing": "Edición de usuario",
|
||||||
|
"enterTheUserId": "Introduzca el ID de usuario",
|
||||||
|
"enterTheRoomId": "Ingrese el ID de la habitación",
|
||||||
|
"deleteAccount": "Eliminar cuenta",
|
||||||
|
"becomeAgent": "Conviértete en agente",
|
||||||
|
"enterNickname": "Introduzca el apodo",
|
||||||
|
"enterInvitationCode": "ingrese el código de invitación",
|
||||||
|
"selectYourCountry": "Selecciona tu país",
|
||||||
|
"inviteCode": "Código de invitación",
|
||||||
|
"magic": "Magia",
|
||||||
|
"luckGiftSpecialEffects": "Efectos de animación de regalo de la suerte.",
|
||||||
|
"theVideoSizeCannotExceed": "El tamaño del vídeo no puede exceder los 50M.",
|
||||||
|
"weekly": "Semanalmente",
|
||||||
|
"customizedGiftRulesContent": "Cómo conseguir mi regalo personalizado:\n1.Determine si el usuario es elegible para recibir un obsequio personalizado según su nivel de riqueza actual.\n(1) Cuando el nivel de riqueza del usuario es ≥35: el usuario puede cargar un vídeo para el regalo personalizado una vez. Usaremos la imagen de perfil actual del usuario como imagen de regalo y el video proporcionado como efecto de regalo en \"Personalizado\". La producción llevará algún tiempo y te avisaremos tan pronto como esté disponible en la tienda.\n(2) Cuando el nivel de riqueza del usuario es ≥45: el usuario puede cargar un vídeo para el regalo personalizado una vez. Usaremos la imagen de perfil actual del usuario como imagen de regalo y el video proporcionado como efecto de regalo en \"Personalizado\". La producción llevará algún tiempo y te avisaremos tan pronto como esté disponible en la tienda.\n2.Puedes participar en actividades específicas en la aplicación y, luego de cumplir con los criterios, contactarnos a través de la sección \"Mensaje\" → \"Contáctanos\". Proporcione capturas de pantalla de la actividad y el vídeo que desea utilizar para el regalo personalizado. Usaremos su foto de perfil actual como imagen de regalo y el video proporcionado como efecto de regalo, luego lo incluiremos en \"Personalizado\". La producción llevará algún tiempo y te avisaremos tan pronto como esté disponible en la tienda.\nPeríodo de validez del regalo personalizado y cómo ampliarlo:\nLos obsequios personalizados exclusivos estarán disponibles en las estanterías durante un período de validez de 30 días. Para permitir que los usuarios influyentes e inspiradores sigan disfrutando de esta nueva experiencia y muestren su estilo personal, los usuarios que poseen obsequios personalizados pueden extender el tiempo de almacenamiento de todos sus obsequios personalizados en 30 días recargando $500 en cualquier mes determinado.",
|
||||||
|
"customizedGiftRules": "Reglas de regalo personalizadas",
|
||||||
|
"rulesUpload": "Reglas y carga",
|
||||||
|
"monthly": "Mensual",
|
||||||
|
"message": "Mensaje",
|
||||||
|
"clearCache": "Borrar caché",
|
||||||
|
"customized": "Personalizado",
|
||||||
|
"searchInputHint": "Ingrese el número de cuenta/habitación",
|
||||||
|
"kickRoomTips": "Te han echado de la habitación.",
|
||||||
|
"roomKickedEntryBlockedTips": "Un administrador lo ha eliminado de la sala y no puede ingresar por ahora.",
|
||||||
|
"joinRoomTips": "¡Se unió a la sala!",
|
||||||
|
"roomSetting": "Configuración de la habitación",
|
||||||
|
"roomDetails": "Detalles de la habitación",
|
||||||
|
"systemRoomTips": "Sea cortés y respetuoso. Cualquier contenido pornográfico o vulgar está estrictamente prohibido en yumi. Una vez descubierta, la cuenta será prohibida permanentemente. Cumpla conscientemente con las regulaciones de la plataforma yumi.",
|
||||||
|
"copiedToClipboard": "Copiado al portapapeles",
|
||||||
|
"recharge": "Recargar",
|
||||||
|
"receivedFromALuckyGift": "Recibido de un regalo mágico/afortunado.",
|
||||||
|
"followedYou": "te seguí",
|
||||||
|
"agentCenter": "Centro de agencia",
|
||||||
|
"areYouSureYouWantToClearLocalCache": "¿Está seguro de que desea borrar el caché local?",
|
||||||
|
"clearMessage": "Borrar mensajes en pantalla",
|
||||||
|
"report": "Informe",
|
||||||
|
"coins3": "monedas",
|
||||||
|
"giftSpecialEffects": "efectos especiales de regalo",
|
||||||
|
"basicFeatures": "Funciones básicas",
|
||||||
|
"task": "Tarea",
|
||||||
|
"importantReminder": "Recordatorio importante",
|
||||||
|
"entryVehicleAnimation": "Animación del vehículo de entrada.",
|
||||||
|
"floatingAnimationInGlobal": "Animación flotante en global.",
|
||||||
|
"broadcast": "Transmisión",
|
||||||
|
"broadcastDisplay": "Pantalla de transmisión",
|
||||||
|
"broadcastAllPlaces": "Todos los lugares",
|
||||||
|
"broadcastRoomOnly": "en la habitación",
|
||||||
|
"broadcastOff": "Cerrar transmisión",
|
||||||
|
"entryVehicleAnimation2": "Los usuarios con privilegios VIP4 o superiores pueden utilizar esta función para desactivar las animaciones del vehículo.",
|
||||||
|
"dailyTasks": "Tareas Diarias",
|
||||||
|
"exclusiveForNewcomers": "Exclusivo para recién llegados",
|
||||||
|
"resetsDailyAtMidnight": "Se reinicia diariamente a medianoche",
|
||||||
|
"limitedToOneTime": "Limitado a una vez",
|
||||||
|
"useMicrophoneForOneMin": "Usa el micrófono durante 1 min.",
|
||||||
|
"taskRequirement": "Requisito",
|
||||||
|
"taskProgress": "Progreso",
|
||||||
|
"rewardClaimedSuccessfully": "Recompensa reclamada exitosamente",
|
||||||
|
"rewardClaimFailed": "Reclamación de recompensa fallida",
|
||||||
|
"enterRoomConfirmTips": "¿Estás seguro de que quieres entrar a la habitación?",
|
||||||
|
"followSucc": "Seguido con éxito",
|
||||||
|
"goldListort": "Lista de oro",
|
||||||
|
"rechargeList": "Lista de recarga",
|
||||||
|
"edit": "Editar",
|
||||||
|
"swipeLeftOnTheFloatingScreenAreaToQuicklyCloseIt": "*Consejo: deslízate hacia la izquierda en el área de la pantalla flotante para cerrarla rápidamente.",
|
||||||
|
"enterThisVoiceChatRoom": "¿Ingresar a esta sala de chat de voz?",
|
||||||
|
"go": "Ir",
|
||||||
|
"done": "Hecho",
|
||||||
|
"improvementTasks": "Tareas de mejora",
|
||||||
|
"save": "Ahorrar",
|
||||||
|
"kickPrevention": "Prevención de patadas",
|
||||||
|
"freeChatSpeak": "Chatear y hablar gratis",
|
||||||
|
"nickName": "Apodo",
|
||||||
|
"gender": "Género",
|
||||||
|
"unFollow": "Dejar de seguir",
|
||||||
|
"days": "Días",
|
||||||
|
"permanent": "Permanente",
|
||||||
|
"country": "País",
|
||||||
|
"birthday": "Cumpleaños",
|
||||||
|
"man": "Hombre",
|
||||||
|
"woman": "Mujer",
|
||||||
|
"apple": "Manzana",
|
||||||
|
"google": "Google",
|
||||||
|
"startVoiceParty": "¡Comienza una fiesta de voz!",
|
||||||
|
"enterRoomTips": "{1} Entra en la habitación",
|
||||||
|
"roomName": "Nombre de la habitación",
|
||||||
|
"roomMember": "Miembro de la sala",
|
||||||
|
"pleaseSelectYourCountry": "Por favor seleccione su país.",
|
||||||
|
"pleaseSelectYourGender": "Por favor seleccione su género.",
|
||||||
|
"pleaseEnterNickname": "Por favor ingrese un apodo.",
|
||||||
|
"countryRegion": "País y región",
|
||||||
|
"dateOfBirth": "Fecha de nacimiento",
|
||||||
|
"mute": "Silenciar",
|
||||||
|
"exit": "Salida",
|
||||||
|
"mysteriousInvisibility": "Invisibilidad misteriosa",
|
||||||
|
"antiBlock": "Antibloqueo",
|
||||||
|
"privateChat": "Chat Privado",
|
||||||
|
"storeDiscount": "Descuento en tienda {1} de descuento",
|
||||||
|
"membershipFreeChatSpeak": "Chat & Speak sin membresía",
|
||||||
|
"priorityRoomSorting": "Clasificación de habitaciones prioritarias",
|
||||||
|
"userColoredID": "ID de color del usuario",
|
||||||
|
"pleaseSelectaItem": "Por favor seleccione un artículo",
|
||||||
|
"areYouRureRoRecharge": "¿Estás seguro de que quieres recargar?",
|
||||||
|
"mInimize": "Mantener",
|
||||||
|
"everyone": "Todos",
|
||||||
|
"shop": "Comercio",
|
||||||
|
"roomOwner": "Dueño de la habitación",
|
||||||
|
"dailyTaskRewardBonus": "Bonificación de recompensa de tarea diaria ({1} XP)",
|
||||||
|
"userLevelXPBoost": "Aumento de XP de nivel de usuario ({1} XP)",
|
||||||
|
"andAboveUsers": "{1} y usuarios superiores",
|
||||||
|
"privileges": "{1} Privilegios",
|
||||||
|
"basicPermissions": "Permisos básicos",
|
||||||
|
"hostCenter": "Centro anfitrión",
|
||||||
|
"allOnMicrophone": "Todo en micrófono",
|
||||||
|
"usersOnMicrophone": "Usuarios en el micrófono",
|
||||||
|
"allInTheRoom": "todo en la habitación",
|
||||||
|
"send": "Enviar",
|
||||||
|
"crop": "Cultivo",
|
||||||
|
"goToUpgrade": "Ir a actualizar",
|
||||||
|
"exclusiveEmojiWillBeReleasedAfterBecoming": "Se lanzarán emoji exclusivos después de convertirse",
|
||||||
|
"preventBeingBlocked": "Evitar ser bloqueado",
|
||||||
|
"enableRankIncognitoMode": "Habilitar el modo de incógnito de clasificación",
|
||||||
|
"avoidBeingKicked": "Evite ser pateado",
|
||||||
|
"finish": "Finalizar",
|
||||||
|
"takeTheMic": "Toma el micrófono",
|
||||||
|
"openTheMic": "Abre el micrófono",
|
||||||
|
"muteTheMic": "Silenciar el micrófono",
|
||||||
|
"unlockTheMic": "Desbloquea el micrófono",
|
||||||
|
"leavelTheMic": "dejar micrófono",
|
||||||
|
"lockTheMic": "Bloquear el micrófono",
|
||||||
|
"removeTheMic": "quitar el micrófono",
|
||||||
|
"inviteToTheMicrophone": "Invitar al micrófono",
|
||||||
|
"openUserProfleCard": "Abrir tarjeta de perfil de usuario",
|
||||||
|
"obtain": "obtener",
|
||||||
|
"win2": "Gana {1}",
|
||||||
|
"backTheRoom": "cuarto trasero",
|
||||||
|
"toConsume": "consumir",
|
||||||
|
"howToUpgrade": "¿Cómo actualizar?",
|
||||||
|
"spendCoinsToGainExperiencePoints": "Gasta monedas para ganar puntos de experiencia.",
|
||||||
|
"richLevelUpgradeAnswer": "Deja que tu riqueza lo conquiste todo",
|
||||||
|
"charmLevelUpgradeAnswer": "Haz que los demás se sometan a tu encanto",
|
||||||
|
"higherLevelFancierAvatarFrame": "Insignias/marco de avatar de nivel superior y más elegantes",
|
||||||
|
"all": "Todo",
|
||||||
|
"gift": "Regalo",
|
||||||
|
"chat": "Charlar",
|
||||||
|
"owner": "Dueño",
|
||||||
|
"store": "Almacenar",
|
||||||
|
"admin": "Administración",
|
||||||
|
"member": "Miembro",
|
||||||
|
"guest": "Invitado",
|
||||||
|
"submit": "Entregar",
|
||||||
|
"claim": "Afirmar",
|
||||||
|
"claimed": "Reclamado",
|
||||||
|
"complete": "Completo",
|
||||||
|
"shareTo": "Compartir con",
|
||||||
|
"copyLink": "Copiar enlace",
|
||||||
|
"faceBook": "Facebook",
|
||||||
|
"whatsApp": "Whatsapp",
|
||||||
|
"snapChat": "Snapchat",
|
||||||
|
"taskNamePersonalGameConsume": "Gasto en juegos",
|
||||||
|
"taskNameRoomNewMember": "Nuevos miembros de la sala",
|
||||||
|
"taskNameRoomOwnerSendRedPacket": "El propietario de la habitación envía un paquete rojo",
|
||||||
|
"taskNameRoomOwnerSendGiftUser": "El propietario de la habitación envía un regalo",
|
||||||
|
"taskNameRoomMicUser120Min": "Miembros con más de 120 minutos en el micrófono",
|
||||||
|
"taskNameRoomMicUser60Min": "Miembros con más de 60 minutos en el micrófono",
|
||||||
|
"taskNameRoomMicUser30Min": "Miembros con más de 30 minutos en el micrófono",
|
||||||
|
"taskNamePersonalSendGift": "Enviar regalo al usuario",
|
||||||
|
"taskNameRoomOnlineUserCount": "Miembros de la sala en línea",
|
||||||
|
"taskNameRoomOwnerInviteMic": "Invitar miembro al micrófono",
|
||||||
|
"taskNameRoomUserSendGiftGold": "Monedas regaladas por los miembros",
|
||||||
|
"taskNameRoomOwnerSendGiftGold": "Monedas regaladas por el dueño de la habitación",
|
||||||
|
"taskNamePersonalMagicGiftGold": "Monedas ganadas enviando regalos mágicos",
|
||||||
|
"taskNamePersonalLuckyGiftGold": "Monedas ganadas enviando regalos de la suerte",
|
||||||
|
"taskNameRoomOwnerMicTime": "El propietario de la sala utiliza el micrófono en la sala",
|
||||||
|
"taskNamePersonalActiveInRoom": "Sea activo en las habitaciones de los demás",
|
||||||
|
"taskNamePersonalMicInRoom": "Ir al micrófono",
|
||||||
|
"dailyCoinBonanzaRulesDetail": "1. Las tareas personales diarias y las tareas diarias del propietario de la sala se pueden completar una vez por usuario al día. Las tareas se reinician a las 00:00:00 hora de Arabia Saudita.\n2. Las tareas personales diarias sólo se pueden completar en las habitaciones de otras personas; Las tareas del propietario de la sala solo se pueden completar en su propia sala.\n3. Las tareas de entrega de regalos solo cuentan los regalos enviados en las salas, no los regalos enviados en los feeds.\n4. Si se crean varias cuentas usando el mismo dispositivo o la misma tarjeta SIM de alguna manera, las recompensas por todas las tareas solo se pueden reclamar una vez.",
|
||||||
|
"dailyCoinBonanzaRules": "Reglas diarias de bonanza de monedas",
|
||||||
|
"roomOwnerTasks": "Tareas del propietario de la sala",
|
||||||
|
"personalTasks": "Tareas personales",
|
||||||
|
"noPromptsToday": "No hay tareas hoy.",
|
||||||
|
"getPaidToRefer": "Recibe dinero por recomendar",
|
||||||
|
"membershipFee": "Cuota de membresía",
|
||||||
|
"membershipFeeTips1": "Establezca la tarifa de membresía para su habitación. Los usuarios pueden unirse a su sala pagando la tarifa.",
|
||||||
|
"membershipFeeTips2": "Los oros necesarios para que el usuario se convierta en miembro de la sala. El propietario de la sala obtendrá el 50% de los oros.",
|
||||||
|
"freePrice": "Tarifa: 0-10000",
|
||||||
|
"touristsSendText": "Turista envía texto",
|
||||||
|
"touristsTakeToTheMic": "Los turistas toman el micrófono",
|
||||||
|
"theMembershipFee": "La cuota de membresía",
|
||||||
|
"theModificationsMade": "Las modificaciones realizadas esta vez no se guardarán después de salir.",
|
||||||
|
"viewFrame": "Ver marco",
|
||||||
|
"enterRoomName": "Introduce el nombre de la habitación",
|
||||||
|
"headdress": "Marcos",
|
||||||
|
"mountains": "Vehículos",
|
||||||
|
"purchaseIsSuccessful": "La compra es exitosa",
|
||||||
|
"buy": "Comprar",
|
||||||
|
"followed": "Seguido",
|
||||||
|
"follow2": "Seguir:{1}",
|
||||||
|
"fans2": "Fanáticos:{1}",
|
||||||
|
"vistors2": "Visitantes:{1}",
|
||||||
|
"personal2": "Personal:",
|
||||||
|
"conntinue": "Continuar",
|
||||||
|
"confirmBuyTips": "¿Estás seguro de que quieres comprar?",
|
||||||
|
"purchase": "Compra",
|
||||||
|
"setRoomPassword": "Establecer contraseña de sala",
|
||||||
|
"inputRoomPassword": "Introduzca la contraseña de la sala",
|
||||||
|
"enter": "Ingresar",
|
||||||
|
"deleteCommentTips": "¿Estás seguro de que quieres eliminar este comentario?",
|
||||||
|
"deleteSuccessful": "Eliminación exitosa!",
|
||||||
|
"itemsLeft": "Artículos restantes",
|
||||||
|
"password": "Contraseña",
|
||||||
|
"replySucc": "Responder exitosamente",
|
||||||
|
"comment": "Comentario",
|
||||||
|
"showMore": "Mostrar más",
|
||||||
|
"showLess": "Mostrar menos",
|
||||||
|
"enterPassword": "Ingrese la contraseña",
|
||||||
|
"enterAccount": "Ingresar cuenta",
|
||||||
|
"logIn": "Acceso",
|
||||||
|
"saySomething": "Di algo...",
|
||||||
|
"sayHi": "Di hola..",
|
||||||
|
"pleaseChatFfriendly": "Por favor charla amablemente",
|
||||||
|
"unLockTheRoom": "Desbloquear la habitación",
|
||||||
|
"operationSuccessful": "La operación fue exitosa.",
|
||||||
|
"adminByHomeowner": "es establecido como administrador por el propietario de la vivienda.",
|
||||||
|
"memberByHomeowner": "es establecido como miembros por el propietario de la vivienda.",
|
||||||
|
"touristByHomeowner": "es catalogado como turista por el propietario de la vivienda.",
|
||||||
|
"becomeHost": "Solicite convertirse en anfitrión",
|
||||||
|
"superFans": "Súper fanáticos:",
|
||||||
|
"setUpAnIdentity": "Configurar una identidad",
|
||||||
|
"kickedOutOfRoom": "expulsado de la habitación",
|
||||||
|
"knapsack": "Mochila",
|
||||||
|
"bdLeader": "Líder BD",
|
||||||
|
"picture": "Imagen",
|
||||||
|
"theImageSizeCannotExceed": "El tamaño de la imagen no puede exceder los 4M.",
|
||||||
|
"activity": "Actividad",
|
||||||
|
"alreadyAnAdministrator": "Ya soy administrador",
|
||||||
|
"alreadyAnMember": "Ya eres miembro",
|
||||||
|
"alreadyAnTourist": "ya un turista",
|
||||||
|
"touristsCannotSendMessages": "Los turistas no pueden enviar mensajes.",
|
||||||
|
"touristsAreNotAllowedToGoOnTheMic": "Los turistas no pueden usar el micrófono",
|
||||||
|
"lockTheRoom": "cerrar la habitación",
|
||||||
|
"special": "Especial",
|
||||||
|
"visitorList": "Lista de visitantes",
|
||||||
|
"successfulWear": "Desgaste exitoso",
|
||||||
|
"confirmUnUseTips": "¿Confirmas eliminarlo?",
|
||||||
|
"custom": "Costumbre",
|
||||||
|
"customBackground": "Fondo personalizado",
|
||||||
|
"example": "Ejemplo",
|
||||||
|
"endPreview": "Vista previa final",
|
||||||
|
"selectAgain": "Seleccionar de nuevo",
|
||||||
|
"myItems": "Mis artículos",
|
||||||
|
"use": "Usar",
|
||||||
|
"unUse": "Desequipar",
|
||||||
|
"renewal": "Renovación",
|
||||||
|
"wallet": "Billetera",
|
||||||
|
"profile": "Perfil",
|
||||||
|
"giftwall": "Muro de regalos",
|
||||||
|
"announcement": "Anuncio",
|
||||||
|
"blockedList": "Lista de bloqueados",
|
||||||
|
"country2": "País:",
|
||||||
|
"sendTo": "Enviar a",
|
||||||
|
"credits": "Créditos: {1}",
|
||||||
|
"successfullyUnloaded": "descargado exitosamente",
|
||||||
|
"expired": "Venció",
|
||||||
|
"day": "Día",
|
||||||
|
"inUse": "En uso",
|
||||||
|
"staticOrGifImage": "Imagen estática / GIF",
|
||||||
|
"confirmUseTips": "¿Quieres confirmar su uso?",
|
||||||
|
"pleaseUploadUserAvatar": "Por favor sube un avatar.",
|
||||||
|
"joinMemberTips": "Si es visitante en la sala, no podrá tomar el micrófono.",
|
||||||
|
"giftGivingSuccessful": "Entrega de regalos exitosa.",
|
||||||
|
"theAccountPasswordCannotBeEmpty": "La cuenta o contraseña no puede estar vacía.",
|
||||||
|
"invitesYouToTheMicrophone": "{1} te invita al micrófono",
|
||||||
|
"english": "Inglés",
|
||||||
|
"chinese": "Chino",
|
||||||
|
"arabic": "árabe",
|
||||||
|
"darkMode": "Modo oscuro",
|
||||||
|
"lightMode": "Modo de luz",
|
||||||
|
"systemDefault": "Valor predeterminado del sistema",
|
||||||
|
"pleaseGetOnTheMicFirst": "Por favor, sube al micrófono primero.",
|
||||||
|
"roomMusicAddMusic": "Agregar música",
|
||||||
|
"roomMusicSelectAll": "Seleccionar todo",
|
||||||
|
"roomMusicAddedCount": "Se agregaron {1} canciones",
|
||||||
|
"roomMusicEmpty": "sin musica",
|
||||||
|
"roomMusicScanFromPhone": "Escanear desde el teléfono",
|
||||||
|
"roomMusicTotalSongs": "Total: {1} canciones",
|
||||||
|
"roomMusicNotFound": "No se encontró música",
|
||||||
|
"roomMusicDeleteConfirm": "¿Eliminar esta música?",
|
||||||
|
"roomMusicDeleted": "Música eliminada",
|
||||||
|
"roomMusicListMode": "jugar en orden",
|
||||||
|
"roomMusicShuffleMode": "Juego aleatorio",
|
||||||
|
"roomMusicSingleMode": "Bucle único",
|
||||||
|
"duration2": "Duración:{1}",
|
||||||
|
"roomRocketNoteTitle": "Nota",
|
||||||
|
"roomRocketNoteSectionTitle": "Cada vez que un VIP",
|
||||||
|
"roomRocketNoteSectionBody": "1. Cada vez que se compra un nivel VIP, es válido por 30 días y se puede renovar manualmente una vez vencido.",
|
||||||
|
"roomRocketRewardEmptyText": "Sin recompensa",
|
||||||
|
"roomRocketRecordEmptyText": "Sin registros de recompensa",
|
||||||
|
"launchRocket": "Lanzamiento de cohete",
|
||||||
|
"roomRocketFuelFullNotice": "¡El combustible para cohetes está lleno! ¡Lanzamiento en {1} s!"
|
||||||
|
}
|
||||||
750
assets/l10n/intl_id.json
Normal file
@ -0,0 +1,750 @@
|
|||||||
|
{
|
||||||
|
"signInWithGoogle": "Masuk dengan Google",
|
||||||
|
"or": "Atau",
|
||||||
|
"signInWithYourAccount": "Masuk dengan akun Anda",
|
||||||
|
"signInWithApple": "Masuk dengan Apple",
|
||||||
|
"loginRepresentsAgreementTo": "Login mewakili persetujuan untuk",
|
||||||
|
"termsofService": "Persyaratan layanan",
|
||||||
|
"privaceyPolicy": "Kebijakan Privasi",
|
||||||
|
"tips": "Kiat",
|
||||||
|
"searchNoDataTips": "Masukkan ruangan atau ID pengguna yang ingin Anda cari.",
|
||||||
|
"games": "Pertandingan",
|
||||||
|
"mine": "Milikku",
|
||||||
|
"official": "Resmi",
|
||||||
|
"party": "Berpesta",
|
||||||
|
"other": "Lainnya",
|
||||||
|
"yes": "Ya",
|
||||||
|
"bag": "Tas",
|
||||||
|
"sound2": "Suara",
|
||||||
|
"startYourBrandNewJourney": "Mulailah Perjalanan Baru Anda",
|
||||||
|
"maliciousHarassment": "Pelecehan yang berbahaya",
|
||||||
|
"event": "Peristiwa",
|
||||||
|
"roomEdit": "Pengeditan Ruangan",
|
||||||
|
"cancelRoomPassword": "Apakah Anda yakin ingin menghapus kata sandi ruangan?",
|
||||||
|
"roomMemberFee": "Biaya Anggota Kamar",
|
||||||
|
"roomTheme2": "Tema Kamar",
|
||||||
|
"background": "Latar belakang",
|
||||||
|
"blockedList2": "Daftar yang Diblokir",
|
||||||
|
"roomPassword": "Kata Sandi Kamar",
|
||||||
|
"numberOfMic": "Jumlah Mikrofon",
|
||||||
|
"pleaseEnterContent": "Silakan masukkan konten",
|
||||||
|
"profilePhoto": "Foto Profil",
|
||||||
|
"aboutMe": "Tentang saya",
|
||||||
|
"myRoom": "Kamarku",
|
||||||
|
"noHistoricalRecordsAvailable": "Tidak ada catatan sejarah yang tersedia.",
|
||||||
|
"inviteNewUsersToEarnCoins": "Undang pengguna baru untuk mendapatkan koin",
|
||||||
|
"crateMyRoom": "BUAT RUANG ANDA SENDIRI.",
|
||||||
|
"casualInteraction": "Interaksi Santai",
|
||||||
|
"haveGamePlayingTips": "Anda memiliki permainan yang sedang berlangsung. Silakan keluar dari permainan saat ini. Apakah Anda yakin ingin keluar?",
|
||||||
|
"historicalTour": "Wisata Sejarah",
|
||||||
|
"gameCenter": "Pusat Permainan",
|
||||||
|
"returnToVoiceChat": "Kembali ke obrolan suara?",
|
||||||
|
"exitGameMode": "Keluar dari Mode Permainan",
|
||||||
|
"enterTheRoom": "Masuki ruangan",
|
||||||
|
"invite": "Mengundang",
|
||||||
|
"recent": "Terkini",
|
||||||
|
"popularEvents": "Acara Populer",
|
||||||
|
"inviteGoRoomTips": "Selalu di sini untukmu, hujan atau cerah. Mampirlah dan sapa!",
|
||||||
|
"confirmInviteThisUserToTheRoom": "Konfirmasikan undangan pengguna ini (ID:{1}) ke ruang?",
|
||||||
|
"clearCacheSuccessfully": "Hapus cache berhasil",
|
||||||
|
"sent": "Terkirim",
|
||||||
|
"keep": "Menyimpan",
|
||||||
|
"open": "Membuka",
|
||||||
|
"deleteAccountTips2": "*Jika Anda berubah pikiran, Anda dapat masuk kembali ke akun Anda saat ini dalam waktu tujuh hari, dan kami akan memulihkan akun Anda secara otomatis. Jika tidak ada pemulihan yang terjadi dalam waktu tujuh hari, akun akan dihapus secara permanen.",
|
||||||
|
"deleteAccountTips": "Anda memiliki hak administratif penuh atas akun ini. Jika Anda bermaksud menghapus akun, harap perhatikan risiko berikut yang terkait dengan operasi ini:\n1. Setelah akun berhasil dihapus, Anda tidak dapat lagi masuk ke akun Anda saat ini. Penghapusan akun merupakan tindakan permanen.\n2. Setelah akun berhasil dihapus, Anda tidak akan dapat memulihkan data akun apa pun. Semua informasi (termasuk kamar dan teman), mata uang virtual, hadiah, dan barang virtual akan dihapus secara permanen dan tidak dapat dipulihkan.\n3. Masa tunggu: Jika Anda tidak memulihkan akun, Anda tidak akan dapat mengakses halaman pembelian, halaman penarikan, atau halaman lainnya di aplikasi.\n4. Selama masa tunggu atau setelah akun dihapus, halaman profil akan menunjukkan bahwa akun tersebut telah dihapus. Untuk melindungi akun Anda agar tidak dicari atau diakses oleh orang lain, informasi pribadi Anda akan dihapus dari sistem yang terkait dengan fungsi sehari-hari. Jika penghapusan akun melibatkan keamanan nasional, proses perdata atau pidana, atau perlindungan hak dan kepentingan sah pihak ketiga, tim resmi berhak menolak permintaan penghapusan akun pengguna.\nJika Anda yakin ingin menghapus semua data pribadi dari akun Anda saat ini, silakan klik \"Hapus Akun\".",
|
||||||
|
"accountDeletionNotice": "Pemberitahuan Penghapusan Akun:",
|
||||||
|
"thisUserHasBeenBlacklisted": "Pengguna ini telah masuk daftar hitam.",
|
||||||
|
"trend": "Kecenderungan",
|
||||||
|
"like": "Menyukai",
|
||||||
|
"more": "Lagi",
|
||||||
|
"discard": "Membuang",
|
||||||
|
"catchFirstComment": "Tangkap komentar pertama",
|
||||||
|
"reply": "Membalas",
|
||||||
|
"posting": "Posting",
|
||||||
|
"multiple": "Banyak",
|
||||||
|
"successfullyRemovedFromTheBlacklist": "Berhasil dihapus dari daftar hitam!",
|
||||||
|
"successfullyAddedToTheBlacklist": "Berhasil ditambahkan ke daftar hitam!",
|
||||||
|
"youAreCurrentlyCPRelationshipPleaseDissolve": "Anda sedang menjalin hubungan CP.\nSilakan dilarutkan terlebih dahulu.",
|
||||||
|
"areYouSureToCancelBlacklist": "Apakah Anda yakin untuk membatalkan daftar hitam?",
|
||||||
|
"areYouSureYouWantToBlockThisUser": "Apakah Anda yakin ingin memblokir pengguna ini?",
|
||||||
|
"removeFromBlacklist": "Hapus dari daftar hitam",
|
||||||
|
"moveToBlacklist": "Pindah ke daftar hitam",
|
||||||
|
"userBlacklist": "Daftar hitam pengguna",
|
||||||
|
"specialEffectsManagement": "Manajemen efek khusus",
|
||||||
|
"wishingYouHappinessEveryDay": "Semoga Anda bahagia setiap hari.",
|
||||||
|
"newMessage": "Pesan baru",
|
||||||
|
"createRoomSuccsess": "Ruangan berhasil dibuat!",
|
||||||
|
"contactUs": "Hubungi kami",
|
||||||
|
"systemAnnouncementTips1": "Penjagaan terhadap penipuan:",
|
||||||
|
"systemAnnouncementTips": "Verifikasi informasi hanya melalui saluran resmi. Jangan pernah mengunduh perangkat lunak pihak ketiga, membagikan data pribadi, atau mentransfer uang berdasarkan permintaan eksternal. ID staf resmi hanya 10000, 10003, dan 10086. Jika ada kecurigaan, hentikan dan laporkan melalui",
|
||||||
|
"systemAnnouncement": "Pengumuman Sistem",
|
||||||
|
"doNotClickUnfamiliarTips": "Jangan klik tautan yang tidak dikenal, karena dapat mengungkap informasi pribadi Anda. Jangan pernah membagikan detail ID atau kartu bank Anda kepada siapa pun.",
|
||||||
|
"atTag": "@Menandai",
|
||||||
|
"sayHi2": "Katakan Hai",
|
||||||
|
"roomBottomGreeting": "Hai...",
|
||||||
|
"canSendMsgTips": "Kedua belah pihak harus saling mengikuti sebelum mereka dapat mengirim pesan pribadi.",
|
||||||
|
"msgSendRedEnvelopeTips": "*Biaya layanan sebesar 10% akan dikenakan pada amplop merah, dan penerima hanya akan menerima 90% dari nilai amplop merah. Tingkat kekayaan pengirim harus lebih tinggi dari Level 10.",
|
||||||
|
"reapply": "Melamar lagi",
|
||||||
|
"cancelRequest": "Batalkan Permintaan",
|
||||||
|
"pending": "Tertunda",
|
||||||
|
"supporter": "Pendukung",
|
||||||
|
"coinsReceived": "Koin Diterima",
|
||||||
|
"numberOfSign": "Jumlah tanda: {1}",
|
||||||
|
"hostWeeklyRank": "Peringkat Mingguan Tuan Rumah",
|
||||||
|
"supporterWeeklyRank": "Peringkat Mingguan Suporter",
|
||||||
|
"memberList": "Daftar Anggota",
|
||||||
|
"treasureChest": "Peti Harta Karun",
|
||||||
|
"applicationRecord": "Catatan Aplikasi",
|
||||||
|
"appUpdateTip": "Aplikasi ini memiliki versi baru ({1}). Apakah Anda ingin mengunduhnya sekarang?",
|
||||||
|
"ownerIncomeCoins": "Pendapatan pemilik: {1} koin",
|
||||||
|
"game": "Permainan",
|
||||||
|
"skip2": "Melewati",
|
||||||
|
"skipCountdown": "Lewati {1} detik",
|
||||||
|
"coins4": "Koin",
|
||||||
|
"weekStart": "Awal Minggu",
|
||||||
|
"forMoreRewardsPleaseCheckTheTaskCenter": "Untuk hadiah lebih lanjut, silakan periksa pusat tugas",
|
||||||
|
"kingQuuen": "Raja-Ratu",
|
||||||
|
"ramadan": "Ramadan",
|
||||||
|
"updateNow": "Perbarui Sekarang",
|
||||||
|
"allGames": "Semua Permainan",
|
||||||
|
"fishClass": "Kelas Ikan",
|
||||||
|
"greedyClass": "Kelas serakah",
|
||||||
|
"raceSeries": "Seri Balapan",
|
||||||
|
"slotsClass": "Kelas Slot",
|
||||||
|
"others": "Yang lain",
|
||||||
|
"hotGames": "Permainan Panas",
|
||||||
|
"chatBox": "Kotak obrolan",
|
||||||
|
"termsOfServicePrivacyPolicyTips": "Dengan melanjutkan, Anda menyetujui Ketentuan Layanan & Kebijakan Privasi",
|
||||||
|
"and": " Dan ",
|
||||||
|
"pleaseSelectTheTypeContent": "Silakan pilih jenis konten yang menyinggung.",
|
||||||
|
"illegalInformation": "Informasi ilegal",
|
||||||
|
"inappropriateContent": "Konten yang tidak pantas",
|
||||||
|
"personalAttack": "Serangan pribadi",
|
||||||
|
"confirm": "Mengonfirmasi",
|
||||||
|
"spam": "Spam",
|
||||||
|
"countdownMinutes": "Menit Hitung Mundur:",
|
||||||
|
"number2": "Nomor:",
|
||||||
|
"fraud": "Tipuan",
|
||||||
|
"received": "Diterima",
|
||||||
|
"currentProgress": "Kemajuan saat ini",
|
||||||
|
"currentStage": "Tahap saat ini:{1}",
|
||||||
|
"roomReward2": "Hadiah Kamar:{1}",
|
||||||
|
"roomReward": "Hadiah Kamar",
|
||||||
|
"expirationTime": "Waktu kedaluwarsa",
|
||||||
|
"ownerSendTheRedEnvelope": "Pemiliknya mengirimkan koin hadiah.",
|
||||||
|
"rewardCoins": "Koin hadiah:{1} koin",
|
||||||
|
"signInRewardReceived": "Berhasil masuk. Hadiah: {1}",
|
||||||
|
"registerRewardReceived": "Hadiah pendaftaran diterima: {1}",
|
||||||
|
"lastWeekProgress": "Kemajuan minggu lalu",
|
||||||
|
"redEnvelopeTips2": "*Jika amplop merah tidak diklaim dalam batas waktu, sisa koin akan dikembalikan kepada pengguna yang mengirimkan amplop merah.",
|
||||||
|
"goToRecharge": "Pergi untuk mengisi ulang",
|
||||||
|
"deleteAccount2": " Hapus Akun ({1}s)",
|
||||||
|
"areYouSureYouWantToDeleteYourAccount": " Apakah Anda yakin ingin menghapus akun Anda?",
|
||||||
|
"insufhcientGoldsGoToRecharge": "Emas tidak mencukupi. Isi ulang sekarang!",
|
||||||
|
"coins2": "{1} Koin",
|
||||||
|
"remainingNumberTips": "Sisa tersedia: ({1}/{2})",
|
||||||
|
"collectionTimeTips": "Waktu pengambilan:{1}({2}/{3})",
|
||||||
|
"sendARedEnvelope": "Kirim amplop merah",
|
||||||
|
"sendRedPackConfirmTips": "Apakah Anda yakin ingin mengirim paket merah?",
|
||||||
|
"redEnvelopeSendingRecords": "Catatan pengiriman amplop merah:",
|
||||||
|
"redEnvelope": "Amplop Merah",
|
||||||
|
"roomRedPacketGoldCoins": "Koin Emas",
|
||||||
|
"roomRedPacketQuantity": "Kuantitas",
|
||||||
|
"roomRedPacketPickupTime": "Waktu Penjemputan",
|
||||||
|
"roomRedPacketInMinutes": "Dalam {1} Menit",
|
||||||
|
"roomRedPacketClaimNow": "Klaim Sekarang",
|
||||||
|
"roomRedPacketSendLuckyPack": "Kirim Paket Keberuntungan",
|
||||||
|
"roomRedPacketSending": "Mengirim...",
|
||||||
|
"roomRedPacketSendSuccess": "Paket keberuntungan terkirim",
|
||||||
|
"roomRedPacketComingSoon": "Segera hadir",
|
||||||
|
"roomRedPacketRulesTitle": "Aturan",
|
||||||
|
"roomRedPacketRulesContent": "1. Anda dapat menggunakan koin untuk mengirim Paket Keberuntungan. Jika orang lain membuka Paket Keberuntungan Anda, mereka dapat menerima sejumlah koin secara acak.\n\n2. Dua jam setelah Paket Keberuntungan dikirim, koin yang tidak diklaim akan dikembalikan ke dompet Anda.\n\n3. Setelah Anda mengirimkan Paket Keberuntungan di seluruh server, spanduk Paket Keberuntungan di seluruh server akan terpicu, sehingga memberi Anda lebih banyak perhatian.\n\n4. Anda dapat mengirimkan Paket Keberuntungan kamar sebesar %1$s per hari.\n\n5. Paket Keberuntungan yang dikirim oleh pengguna di luar wilayah Anda tidak dapat diklaim.",
|
||||||
|
"roomRedPacketHistoryTitle": "Riwayat Pengiriman Paket Keberuntungan",
|
||||||
|
"roomRedPacketHistoryItemTitle": "Tas Keberuntungan ({1} amplop merah)",
|
||||||
|
"roomRedPacketHistoryStatusSent": "Terkirim",
|
||||||
|
"roomRedPacketHistoryStatusFinished": "Diklaim sepenuhnya",
|
||||||
|
"roomRedPacketHistoryStatusPending": "Tertunda",
|
||||||
|
"roomRedPacketHistoryStatusReturned": "Kembali",
|
||||||
|
"roomRedPacketHistoryRefund": "Pengembalian dana: {1}",
|
||||||
|
"roomRedPacketClaimRecordsTitle": "Catatan Klaim",
|
||||||
|
"roomRedPacketChatMessage": "Saya mengirimi Anda amplop merah, segera buka!",
|
||||||
|
"roomRedPacketReceivedMessage": "Klaim {1} koin dari {2} Paket Keberuntungan",
|
||||||
|
"roomRedPacketOpenSubtitle": "Paket Keberuntungan Telah Tiba",
|
||||||
|
"roomRedPacketOpenYouGot": "Anda Dapatkan",
|
||||||
|
"roomRedPacketClaimSuccessTips": "Koin telah dikirim ke dompet Anda.",
|
||||||
|
"roomRedPacketOpenSoldOut": "Paket Keberuntungan Sudah Terjual Habis.",
|
||||||
|
"roomRedPacketOpenExpired": "Paket Keberuntungan Telah Kedaluwarsa.",
|
||||||
|
"roomRedPacketRegionNotMatch": "Hanya pengguna di wilayah bahasa yang sama yang dapat mengklaim paket keberuntungan ini.",
|
||||||
|
"roomRedPacketNotStarted": "Paket keberuntungan belum siap.",
|
||||||
|
"roomRedPacketDailyLimitExceeded": "Batas pengiriman hari ini telah tercapai.",
|
||||||
|
"roomRedPacketInsufficientBalance": "Koin emas tidak mencukupi.",
|
||||||
|
"roomRedPacketPresenceRequired": "Harap tetap di kamar untuk menggunakan paket keberuntungan.",
|
||||||
|
"roomRedPacketNetworkError": "Tidak dapat memproses paket keberuntungan saat ini.",
|
||||||
|
"roomRedPacketAlreadyClaimed": "Anda sudah mengklaim paket keberuntungan ini.",
|
||||||
|
"roomRedPacketOpenWaiting": "Dapat dibuka dalam {1} menit.",
|
||||||
|
"roomRedPacketOpenWaitingCountdown": "Tersedia Untuk Pengambilan Setelah {1}",
|
||||||
|
"roomRedPacketOkay": "Oke",
|
||||||
|
"roomRedPacketFloatingTitle": "{1} mengirimkan amplop merah",
|
||||||
|
"roomRedPacketFloatingSubtitle": "Mengirim amplop merah",
|
||||||
|
"redEnvelopeRecTips2": "Semua amplop merah telah diklaim.",
|
||||||
|
"redEnvelopeRecTips3": "Waktu pengambilan amplop merah telah habis!",
|
||||||
|
"openTheTreasureChest": "Buka peti harta karun",
|
||||||
|
"redEnvelopeRecTips1": "Koin yang diperoleh telah disimpan ke dompet Anda.",
|
||||||
|
"redEnvelopeTips1": "Koin:",
|
||||||
|
"roomTools": "Peralatan Ruangan:",
|
||||||
|
"entertainment": "Hiburan:",
|
||||||
|
"reportSucc": "Laporkan berhasil",
|
||||||
|
"pornography": "Pornografi",
|
||||||
|
"reportInputTips": "Mohon jelaskan masalahnya sedetail mungkin agar kami dapat memahami dan menyelesaikannya.",
|
||||||
|
"cancel": "Membatalkan",
|
||||||
|
"join": "Bergabung",
|
||||||
|
"items": "Barang",
|
||||||
|
"vistors": "Pengunjung",
|
||||||
|
"fans": "Penggemar",
|
||||||
|
"balanceNotEnough": "Saldo koin emas tidak mencukupi. Apakah Anda ingin pergi ke top up?",
|
||||||
|
"skip": "Lewati {1}",
|
||||||
|
"letGoToWatch": "Ayo pergi menonton!",
|
||||||
|
"launchedARocket": "meluncurkan roket",
|
||||||
|
"sendUserId": "Kirim ID Pengguna:{1}",
|
||||||
|
"giveUpIdentity": "Menyerahkan identitas",
|
||||||
|
"leaveRoomIdentityTips": "Apakah Anda yakin ingin melepaskan identitas kamar?",
|
||||||
|
"joinMemberTips2": "Apakah Anda ingin mengonfirmasi bergabung dengan ruang sebagai anggota?",
|
||||||
|
"sureUnfollowThisRoom": "Yakin untuk berhenti mengikuti ruangan ini?",
|
||||||
|
"welcomeMessage": "Selamat datang di aplikasi kami, {name}!",
|
||||||
|
"settings": "Pengaturan",
|
||||||
|
"account": "Akun",
|
||||||
|
"common": "Umum",
|
||||||
|
"delete": "Menghapus",
|
||||||
|
"copy": "Menyalin",
|
||||||
|
"bio": "Biografi",
|
||||||
|
"introduction": "Perkenalan",
|
||||||
|
"useCoupontips": "Apakah Anda yakin ingin menggunakan kupon tersebut?",
|
||||||
|
"searchUserId": "Cari ID pengguna",
|
||||||
|
"sendUser": "Kirim Pengguna",
|
||||||
|
"hobby": "Hobi",
|
||||||
|
"sendCoupontips": "Apakah Anda yakin ingin mengirimkan kupon ini kepada pengguna ini?",
|
||||||
|
"youDontHaveAnyCouponsYet": "Anda belum memiliki kupon apa pun.",
|
||||||
|
"recall": "Mengingat",
|
||||||
|
"youHaventFollowed": "Anda belum mengikuti ruangan mana pun",
|
||||||
|
"deleteFromMyDevice": "Hapus dari perangkat saya",
|
||||||
|
"deleteOnAllDevices": "Hapus di semua perangkat",
|
||||||
|
"messageHasBeenRecalled": "Pesan ini telah ditarik kembali",
|
||||||
|
"recallThisMessage": "Ingat pesan ini?",
|
||||||
|
"language": "Bahasa",
|
||||||
|
"feedback": "Masukan",
|
||||||
|
"signedin": "Masuk",
|
||||||
|
"receiveSucc": "Berhasil diklaim",
|
||||||
|
"about": "Tentang",
|
||||||
|
"aboutUs": "Tentang Kami",
|
||||||
|
"theme": "Tema",
|
||||||
|
"wealthLevel": "Tingkat Kaya",
|
||||||
|
"userLevel": "Tingkat Pesona",
|
||||||
|
"goToUpload": "Pergi untuk mengunggah",
|
||||||
|
"logout": "Keluar",
|
||||||
|
"luck": "Keberuntungan",
|
||||||
|
"level": "Tingkat",
|
||||||
|
"themeGoToUploadTips": "1. Review akan selesai dalam waktu 24 jam setelah upload berhasil.\n2. Semua koin akan dikembalikan jika peninjauan gagal.",
|
||||||
|
"pleaseUploadAccordingToExample": "Silakan upload sesuai contoh yang diberikan.",
|
||||||
|
"home": "Rumah",
|
||||||
|
"explore": "Mengeksplorasi",
|
||||||
|
"me": "Aku",
|
||||||
|
"socialPrivilege": "Hak istimewa sosial",
|
||||||
|
"information": "Informasi",
|
||||||
|
"myPhoto": "Foto saya",
|
||||||
|
"areYouSureYouWantToSpend3": "*Jika pihak lain menolak undangan CP, koin Anda akan dikembalikan ke dompet Anda.",
|
||||||
|
"areYouSureYouWantToSpend": "Apakah Anda yakin ingin membelanjakannya",
|
||||||
|
"areYouSureYouWantToSpend2": "untuk mengirim undangan CP ke pengguna ini?",
|
||||||
|
"underReview": "Sedang ditinjau",
|
||||||
|
"doYouWantToDeleteIt": "Apakah Anda ingin menghapusnya?",
|
||||||
|
"chooseFromAblum": "Pilih dari Album",
|
||||||
|
"spaceBackground": "Latar Belakang Luar Angkasa",
|
||||||
|
"editProfile": "Sunting Profil",
|
||||||
|
"sendTheCpRequest": "Kirim permintaan CP",
|
||||||
|
"addCp": "Tambahkan CP",
|
||||||
|
"partWays": "Bagian Cara",
|
||||||
|
"reconcile": "Mendamaikan",
|
||||||
|
"separated": "Terpisah",
|
||||||
|
"areYouSureYouWantToSpend5": "{1} menyatakan perasaannya padamu; jika kamu menerimanya, kamu akan menjadi pasangan.",
|
||||||
|
"areYouSureYouWantToSpend6": "{1} ingin kembali bersama Anda. Jika Anda memutuskan untuk melakukan rekonsiliasi, semua data Anda sebelumnya akan dipulihkan.",
|
||||||
|
"reconcileInvitationTips": "*Jika pihak lain menolak undangan CP, koin Anda akan dikembalikan ke dompet Anda.",
|
||||||
|
"reconcileInvitation": "Rekonsiliasi Undangan",
|
||||||
|
"areYouSureYouWantToSpend4": "mengirim undangan rekonsiliasi kepada pengguna ini?",
|
||||||
|
"partWaysTips": "*Jika salah satu pasangan memilih untuk berpisah, akan ada periode jeda selama 7 hari. Selama jangka waktu tersebut, kedua belah pihak dapat memilih untuk melakukan rekonsiliasi, dan semua data akan dipulihkan setelah rekonsiliasi. Jika tidak ada rekonsiliasi yang dipilih pada akhir periode 7 hari, data pasangan tersebut akan dihapus.",
|
||||||
|
"areYouSureYouWantToPartWaysWithYourCP": "Apakah Anda yakin ingin berpisah dengan CP Anda?",
|
||||||
|
"timeSpentTogether": "Waktu yang dihabiskan bersama: {1} hari",
|
||||||
|
"firstDay": "Hari pertama:{1}",
|
||||||
|
"numberOfMyCPs": "Jumlah CP saya:({1}/{2})",
|
||||||
|
"props": "Alat peraga",
|
||||||
|
"win": "Menang",
|
||||||
|
"dice": "Dadu",
|
||||||
|
"rps": "RPS",
|
||||||
|
"operationFail": "Operasi gagal.",
|
||||||
|
"enterRoomFailedRetry": "Gagal masuk ruangan. Silakan coba lagi.",
|
||||||
|
"voiceConnectionFailedRetry": "Sambungan suara gagal. Silakan coba lagi.",
|
||||||
|
"likedYourComment": "Menyukai komentar Anda.",
|
||||||
|
"doYouWantToKeepTheDraft": "Apakah Anda ingin menyimpan drafnya?",
|
||||||
|
"operationsAreTooFrequent": "Operasi terlalu sering",
|
||||||
|
"luckNumber": "Nomor Keberuntungan",
|
||||||
|
"relationShip": "Hubungan",
|
||||||
|
"couple": "Pasangan {1}:",
|
||||||
|
"couple2": "Pasangan",
|
||||||
|
"reject": "Menolak",
|
||||||
|
"accept": "Menerima",
|
||||||
|
"noMatchedCP": "Tidak ada CP yang cocok",
|
||||||
|
"inviteYouToBecomeBD": "Mengundang Anda untuk menjadi BD.",
|
||||||
|
"adminInviteRechargeAgent": "Mengundang Anda untuk menjadi agen Isi Ulang.",
|
||||||
|
"confirmAcceptTheInvitation": "Konfirmasikan untuk menerima undangan?",
|
||||||
|
"confirmDeclineTheInvitation": "Konfirmasi penolakan undangan?",
|
||||||
|
"cpInviteRejectedByOther": "Pihak lain menolak menjadi {1}.",
|
||||||
|
"closeFriendInvitationTitle": "Undangan dari Teman Dekat",
|
||||||
|
"closeFriendInvitationSubtitle": "Klik untuk melihat undangan",
|
||||||
|
"closeFriendInvitationExpired": "Undangan untuk menambah teman dekat telah kedaluwarsa. Tindakan ini tidak dapat dilakukan.",
|
||||||
|
"closeFriendRequestDeclined": "Permintaan pertemanan ditolak",
|
||||||
|
"closeFriendsHaveBecome": "Telah menjadi teman dekat",
|
||||||
|
"otherAcceptedCloseFriendInvite": "Pihak lain menerima undangan teman dekat Anda.",
|
||||||
|
"otherDeclinedCloseFriendInvite": "Pihak lain menolak undangan teman dekat Anda.",
|
||||||
|
"youAcceptedCloseFriendInvite": "Anda menerima undangan teman dekat pihak lain.",
|
||||||
|
"youDeclinedCloseFriendInvite": "Anda menolak undangan teman dekat pihak lain.",
|
||||||
|
"closeFriendInviteTimedOut": "Batas waktu undangan teman dekat telah habis.",
|
||||||
|
"cpDismissConfirmMessage": "Menghapus hubungan ini akan dikenakan biaya {1} koin. Konfirmasikan penghapusan?",
|
||||||
|
"cpRelationCp": "CP",
|
||||||
|
"cpRelationBrother": "Saudara laki-laki",
|
||||||
|
"cpRelationSister": "Saudari",
|
||||||
|
"host": "Tuan rumah",
|
||||||
|
"following": "Mengikuti",
|
||||||
|
"agent": "Agen",
|
||||||
|
"approved": "Disetujui",
|
||||||
|
"approvedWithinOneMinute": "Disetujui dan ditampilkan dalam waktu 1 menit setelah penyerahan",
|
||||||
|
"onlineUsers": "Pengguna Daring ({1}/{2}):",
|
||||||
|
"applyToJoin": "Ajukan permohonan untuk bergabung",
|
||||||
|
"supporterList": "Daftar Pendukung",
|
||||||
|
"hostList": "Daftar Tuan Rumah",
|
||||||
|
"upToAdmins": "Hingga {1} Admin",
|
||||||
|
"upToMembers": "Hingga {1} Anggota",
|
||||||
|
"levelPrivileges": "Hak istimewa tingkat",
|
||||||
|
"ra": "RA",
|
||||||
|
"roomAnnouncement": "Pengumuman Kamar",
|
||||||
|
"help": "Membantu",
|
||||||
|
"rejected": "Ditolak",
|
||||||
|
"boxContributeTips": "Investasi telah dilakukan hari ini. Tolong jangan berinvestasi lagi.",
|
||||||
|
"bd": "BD",
|
||||||
|
"coupon": "Kupon",
|
||||||
|
"search": "Mencari",
|
||||||
|
"get": "Mendapatkan",
|
||||||
|
"inRocket": "Di dalam roket",
|
||||||
|
"roomRocketHelpTips": "1. Mengirim hadiah di dalam ruangan meningkatkan energi roket. *1 hadiah koin emas = 1 titik energi roket; hadiah keberuntungan meningkatkan energi roket sebesar 4% dari nilai koin emas hadiah.\n2. Setelah energi roket terisi penuh, ruangan dapat meluncurkan roket. Hadiah akan didistribusikan secara otomatis setelah peluncuran.\n3. Level roket yang berbeda menawarkan hadiah yang berbeda.\n4. Saat roket diluncurkan, semua pengguna di dalam ruangan dapat mengklaim hadiah roket.\n5. Energi roket disetel ulang pada pukul 00:00 setiap hari.",
|
||||||
|
"couponRecord": "Catatan penggunaan kupon",
|
||||||
|
"inRoom": "Di Kamar",
|
||||||
|
"searchCouponHint": "Cari Kupon",
|
||||||
|
"giftCounter": "Penghitung hadiah",
|
||||||
|
"bDLeaderInviteYouToBecomeBDLeader": "Mengundang Anda untuk menjadi BDLeader",
|
||||||
|
"wins": "menang",
|
||||||
|
"inviteYouToBecomeHost": "Mengundang Anda untuk menjadi tuan rumah.",
|
||||||
|
"friends": "Teman-teman",
|
||||||
|
"deleteConversationTips": "Apakah Anda yakin ingin menghapus riwayat obrolan dengan pengguna ini?",
|
||||||
|
"propMessagePrompt": "Prompt pesan prop",
|
||||||
|
"inputUserId": "Masukkan ID Pengguna",
|
||||||
|
"fromLuckyGifts": "Dari hadiah keberuntungan/ajaib",
|
||||||
|
"receive": "Menerima",
|
||||||
|
"checkInSuccessful": "Check-in berhasil",
|
||||||
|
"sginTips": "Anda akan mendapatkan hadiah saat pertama kali login setiap hari. Jika Anda menghentikan proses login berturut-turut, hadiah akan dihitung ulang dari hari pertama saat Anda login lagi.",
|
||||||
|
"popular": "Populer",
|
||||||
|
"recommend": "Menyarankan",
|
||||||
|
"follow": "Mengikuti",
|
||||||
|
"history": "Sejarah",
|
||||||
|
"hotRooms": "Kamar Panas",
|
||||||
|
"viewMore": "Lihat lebih banyak",
|
||||||
|
"noData": "Tidak ada data",
|
||||||
|
"users": "Pengguna",
|
||||||
|
"rooms": "Kamar",
|
||||||
|
"coins": "koin",
|
||||||
|
"unread": "Belum dibaca",
|
||||||
|
"read": "Membaca",
|
||||||
|
"image": "[Gambar]",
|
||||||
|
"video": "[Video]",
|
||||||
|
"sound": "[Suara]",
|
||||||
|
"gift2": "[Hadiah]",
|
||||||
|
"clickHereToStartChatting": "Katakan sesuatu.....",
|
||||||
|
"receivedAMessage": "[Menerima pesan]",
|
||||||
|
"confirmSwitchMicModelTips": "Apakah Anda mengonfirmasi peralihan dalam mode tempat duduk?",
|
||||||
|
"number": "Nomor",
|
||||||
|
"album": "Album",
|
||||||
|
"camera": "Kamera",
|
||||||
|
"system": "Sistem",
|
||||||
|
"notifcation": "Pemberitahuan",
|
||||||
|
"inviteYouToBecomeAgent": "Mengundang Anda untuk menjadi agensi.",
|
||||||
|
"myMusic": "Musik Saya",
|
||||||
|
"add": "Menambahkan",
|
||||||
|
"pullToLoadMore": "Tarik untuk memuat lebih banyak",
|
||||||
|
"loadingFailedClickToRetry": "Gagal memuat, klik untuk mencoba lagi",
|
||||||
|
"releaseToLoadMore": "Lepaskan untuk memuat lebih banyak",
|
||||||
|
"haveMyLimits": "---Aku punya batasku.---",
|
||||||
|
"music": "Musik",
|
||||||
|
"free": "Bebas",
|
||||||
|
"charm": "Pesona hadiah",
|
||||||
|
"start": "Awal",
|
||||||
|
"stop": "Berhenti",
|
||||||
|
"chats": "Obrolan",
|
||||||
|
"refuse": "Menolak",
|
||||||
|
"agree": "Setuju",
|
||||||
|
"thisFeatureIsCurrentlyUnavailable": "Fitur ini saat ini tidak tersedia.",
|
||||||
|
"pleaseSelectTheRecipient": "Silakan pilih penerima.",
|
||||||
|
"searchMemberIdHint": "Silakan masukkan ID anggota",
|
||||||
|
"unclaimedRedEnvelopes": "Amplop merah yang tidak diambil akan dikembalikan dalam 24 jam.",
|
||||||
|
"redEnvelopeNotYetClaimed": "Amplop merah belum diklaim.",
|
||||||
|
"redEnvelopeAmount": "Jumlah amplop merah: {1} koin",
|
||||||
|
"sentARedEnvelope": "mengirim amplop merah.",
|
||||||
|
"theRedEnvelopeHasExpired": "Amplop merah sudah habis masa berlakunya.",
|
||||||
|
"joinRequest": "Permintaan Gabung",
|
||||||
|
"scrollToTheBottom": "Gulir ke bawah",
|
||||||
|
"gameRules": "Aturan Permainan:",
|
||||||
|
"charmGameRulesTips": "Aktifkan dasbor untuk menampilkan koin hadiah yang diterima semua pengguna di mikrofon. 1 koin = 1 skor (Hadiah keberuntungan: 1 koin = skor 0,04).",
|
||||||
|
"inputYourOldPassword": "Masukkan kata sandi lama Anda",
|
||||||
|
"enterYourOldPassword": "Masukkan kata sandi lama Anda",
|
||||||
|
"setYourPassword": "Tetapkan kata sandi Anda",
|
||||||
|
"enterYourNewPassword": "Masukkan kata sandi baru Anda",
|
||||||
|
"confirmYourPassword": "Konfirmasikan kata sandi Anda",
|
||||||
|
"theTwoPasswordsDoNotMatch": "Kedua kata sandi tidak cocok.",
|
||||||
|
"resetLoginPasswordtTips2": "Kata sandi harus terdiri dari 8-16 karakter dan harus berupa kombinasi huruf besar dan kecil bahasa Inggris serta angka (bukan hanya angka).",
|
||||||
|
"resetLoginPassword": "Atur Ulang Kata Sandi Masuk",
|
||||||
|
"resetLoginPasswordtTips1": "Masuk dengan ID pengguna atau ID cantik Anda. Lebih aman masuk dengan akun yumi.",
|
||||||
|
"localMusic": "Musik Lokal",
|
||||||
|
"setLoginPassword": "Tetapkan Kata Sandi Masuk",
|
||||||
|
"confirmSwitchMicThemeTips": "Apakah Anda mengonfirmasi peralihan gaya kursi?",
|
||||||
|
"micTheme": "Tema Mikrofon",
|
||||||
|
"classicMic": "Klasik {1} Mikrofon",
|
||||||
|
"yesterday": "Kemarin {1}",
|
||||||
|
"monday": "Senin {1}",
|
||||||
|
"tuesday": "Selasa {1}",
|
||||||
|
"wednesday": "Rabu {1}",
|
||||||
|
"thursday": "Kamis {1}",
|
||||||
|
"friday": "Jumat {1}",
|
||||||
|
"saturday": "Sabtu {1}",
|
||||||
|
"sunday": "Minggu {1}",
|
||||||
|
"acceptedYour": "{1} menerima Anda ",
|
||||||
|
"youAccepted": "Anda menyetujui {1} ",
|
||||||
|
"openRedPackDialogTip": "Amplop merah",
|
||||||
|
"micManagement": "Manajemen Mikrofon",
|
||||||
|
"bdCenter": "Pusat BD",
|
||||||
|
"rechargeAgency": "Badan Isi Ulang",
|
||||||
|
"adminCenter": "Pusat Admin",
|
||||||
|
"managerCenter": "Pusat Manajer",
|
||||||
|
"goldList": "Daftar Emas",
|
||||||
|
"followList": "Ikuti Daftar",
|
||||||
|
"fansList": "Daftar Penggemar",
|
||||||
|
"daily": "Sehari-hari",
|
||||||
|
"female": "Perempuan",
|
||||||
|
"male": "Pria",
|
||||||
|
"identity": "Identitas",
|
||||||
|
"adjust": "Menyesuaikan",
|
||||||
|
"warning": "Peringatan",
|
||||||
|
"screenshotTips": "Tangkapan Layar (Hingga 3)",
|
||||||
|
"roomNotice": "Pemberitahuan kamar",
|
||||||
|
"roomTheme": "Tema kamar",
|
||||||
|
"description": "Keterangan:",
|
||||||
|
"inputDesHint": "Mohon jelaskan masalahnya sedetail mungkin agar kami dapat memahami dan menyelesaikannya.",
|
||||||
|
"roomProfilePicture": "Gambar profil kamar",
|
||||||
|
"userProfilePicture": "Gambar profil pengguna",
|
||||||
|
"userName": "Nama belakang",
|
||||||
|
"pleaseSelectTheTypeToProcess": "Silakan pilih jenis yang akan diproses:",
|
||||||
|
"roomEditing": "Pengeditan Ruangan",
|
||||||
|
"setAccount": "Tetapkan Akun",
|
||||||
|
"userEditing": "Pengeditan Pengguna",
|
||||||
|
"enterTheUserId": "Masukkan ID pengguna",
|
||||||
|
"enterTheRoomId": "Masukkan ID ruangan",
|
||||||
|
"deleteAccount": "Hapus Akun",
|
||||||
|
"becomeAgent": "Menjadi agen",
|
||||||
|
"enterNickname": "Masukkan Nama Panggilan",
|
||||||
|
"enterInvitationCode": "masukkan kode undangan",
|
||||||
|
"selectYourCountry": "Pilih negara Anda",
|
||||||
|
"inviteCode": "Kode Undangan",
|
||||||
|
"magic": "Sihir",
|
||||||
|
"luckGiftSpecialEffects": "Efek animasi hadiah keberuntungan",
|
||||||
|
"theVideoSizeCannotExceed": "Ukuran video tidak boleh melebihi 50M",
|
||||||
|
"weekly": "Mingguan",
|
||||||
|
"customizedGiftRulesContent": "Cara mendapatkan hadiah khusus saya:\n1.Tentukan apakah pengguna berhak menerima hadiah khusus berdasarkan tingkat kekayaan mereka saat ini.\n(1) Ketika tingkat kekayaan pengguna ≥35: Pengguna dapat mengunggah video untuk hadiah khusus satu kali. Kami akan menggunakan gambar profil pengguna saat ini sebagai gambar hadiah dan video yang disediakan sebagai efek hadiah pada \"Disesuaikan\". Produksi akan memakan waktu beberapa saat, dan kami akan memberi tahu Anda segera setelah tersedia di toko.\n(2) Ketika tingkat kekayaan pengguna ≥45: Pengguna dapat mengunggah video untuk hadiah khusus satu kali. Kami akan menggunakan gambar profil pengguna saat ini sebagai gambar hadiah dan video yang disediakan sebagai efek hadiah pada \"Disesuaikan\". Produksi akan memakan waktu beberapa saat, dan kami akan memberi tahu Anda segera setelah tersedia di toko.\n2.Anda dapat berpartisipasi dalam aktivitas tertentu di aplikasi dan, setelah memenuhi kriteria, menghubungi kami melalui bagian \"Pesan\" → \"Hubungi kami\". Berikan tangkapan layar aktivitas dan video yang ingin Anda gunakan untuk hadiah khusus. Kami akan menggunakan gambar profil Anda saat ini sebagai gambar hadiah dan video yang disediakan sebagai efek hadiah, lalu mencantumkannya di bawah \"Disesuaikan\". Produksi akan memakan waktu beberapa saat, dan kami akan memberi tahu Anda segera setelah tersedia di toko.\nPeriode Validitas Hadiah yang Disesuaikan & Cara Memperpanjangnya:\nHadiah eksklusif yang disesuaikan akan tersedia di rak untuk masa berlaku 30 hari. Untuk memungkinkan pengguna yang berpengaruh dan menginspirasi untuk terus menikmati pengalaman baru ini dan menampilkan gaya pribadi mereka, pengguna yang memiliki hadiah khusus dapat memperpanjang waktu penyimpanan semua hadiah khusus mereka selama 30 hari dengan mengisi ulang $500 pada bulan tertentu.",
|
||||||
|
"customizedGiftRules": "Aturan Hadiah yang Disesuaikan",
|
||||||
|
"rulesUpload": "Aturan & Unggah",
|
||||||
|
"monthly": "Bulanan",
|
||||||
|
"message": "Pesan",
|
||||||
|
"clearCache": "Hapus Tembolok",
|
||||||
|
"customized": "Disesuaikan",
|
||||||
|
"searchInputHint": "Masukkan nomor rekening/kamar",
|
||||||
|
"kickRoomTips": "Anda telah diusir dari ruangan.",
|
||||||
|
"roomKickedEntryBlockedTips": "Anda telah dikeluarkan dari ruangan oleh admin dan tidak dapat masuk untuk saat ini.",
|
||||||
|
"joinRoomTips": "Bergabung dengan ruangan!",
|
||||||
|
"roomSetting": "Pengaturan Ruangan",
|
||||||
|
"roomDetails": "Detail Kamar",
|
||||||
|
"systemRoomTips": "Bersikaplah sopan dan hormat. Konten pornografi atau vulgar apa pun dilarang keras di yumi. Setelah ditemukan, akun tersebut akan diblokir secara permanen. Harap secara sadar mematuhi peraturan platform yumi.",
|
||||||
|
"copiedToClipboard": "Disalin ke papan klip",
|
||||||
|
"recharge": "Isi ulang",
|
||||||
|
"receivedFromALuckyGift": "Diterima dari hadiah keberuntungan/ajaib.",
|
||||||
|
"followedYou": "Mengikutimu",
|
||||||
|
"agentCenter": "Pusat Agensi",
|
||||||
|
"areYouSureYouWantToClearLocalCache": "Apakah Anda yakin ingin menghapus cache lokal?",
|
||||||
|
"clearMessage": "Hapus pesan layar",
|
||||||
|
"report": "Laporan",
|
||||||
|
"coins3": "Koin",
|
||||||
|
"giftSpecialEffects": "Hadiahkan efek khusus",
|
||||||
|
"basicFeatures": "Fitur Dasar",
|
||||||
|
"task": "Tugas",
|
||||||
|
"importantReminder": "Pengingat Penting",
|
||||||
|
"entryVehicleAnimation": "Animasi kendaraan masuk",
|
||||||
|
"floatingAnimationInGlobal": "Animasi mengambang secara global",
|
||||||
|
"broadcast": "Siaran",
|
||||||
|
"broadcastDisplay": "Tampilan siaran",
|
||||||
|
"broadcastAllPlaces": "Semua tempat",
|
||||||
|
"broadcastRoomOnly": "Di kamar",
|
||||||
|
"broadcastOff": "Tutup siaran",
|
||||||
|
"entryVehicleAnimation2": "Pengguna dengan hak istimewa VIP4 atau lebih tinggi dapat menggunakan fungsi ini untuk menonaktifkan animasi kendaraan.",
|
||||||
|
"dailyTasks": "Tugas Harian",
|
||||||
|
"exclusiveForNewcomers": "Eksklusif Untuk Pendatang Baru",
|
||||||
|
"resetsDailyAtMidnight": "Direset setiap hari pada tengah malam",
|
||||||
|
"limitedToOneTime": "Terbatas satu kali saja",
|
||||||
|
"useMicrophoneForOneMin": "Gunakan mikrofon selama 1 menit",
|
||||||
|
"taskRequirement": "Persyaratan",
|
||||||
|
"taskProgress": "Kemajuan",
|
||||||
|
"rewardClaimedSuccessfully": "Hadiah berhasil diklaim",
|
||||||
|
"rewardClaimFailed": "Klaim hadiah gagal",
|
||||||
|
"enterRoomConfirmTips": "Apakah Anda yakin ingin memasuki ruangan?",
|
||||||
|
"followSucc": "Diikuti dengan sukses",
|
||||||
|
"goldListort": "Daftar Emas",
|
||||||
|
"rechargeList": "Daftar Isi Ulang",
|
||||||
|
"edit": "Sunting",
|
||||||
|
"swipeLeftOnTheFloatingScreenAreaToQuicklyCloseIt": "*Tips: Gesek ke kiri pada area layar mengambang untuk menutupnya dengan cepat.",
|
||||||
|
"enterThisVoiceChatRoom": "Masuk ke ruang obrolan suara ini?",
|
||||||
|
"go": "Pergi",
|
||||||
|
"done": "Selesai",
|
||||||
|
"improvementTasks": "Tugas Peningkatan",
|
||||||
|
"save": "Menyimpan",
|
||||||
|
"kickPrevention": "Pencegahan tendangan",
|
||||||
|
"freeChatSpeak": "Obrolan & Bicara Gratis",
|
||||||
|
"nickName": "Nama panggilan",
|
||||||
|
"gender": "Jenis kelamin",
|
||||||
|
"unFollow": "Berhenti mengikuti",
|
||||||
|
"days": "hari",
|
||||||
|
"permanent": "Permanen",
|
||||||
|
"country": "Negara",
|
||||||
|
"birthday": "Hari ulang tahun",
|
||||||
|
"man": "Pria",
|
||||||
|
"woman": "Wanita",
|
||||||
|
"apple": "Apel",
|
||||||
|
"google": "Google",
|
||||||
|
"startVoiceParty": "Mulai pesta suara!",
|
||||||
|
"enterRoomTips": "{1} Masuk ke ruangan",
|
||||||
|
"roomName": "Nama Kamar",
|
||||||
|
"roomMember": "Anggota Kamar",
|
||||||
|
"pleaseSelectYourCountry": "Silakan pilih negara Anda.",
|
||||||
|
"pleaseSelectYourGender": "Silakan pilih jenis kelamin Anda.",
|
||||||
|
"pleaseEnterNickname": "Silakan masukkan nama panggilan.",
|
||||||
|
"countryRegion": "Negara & Wilayah",
|
||||||
|
"dateOfBirth": "Tanggal lahir",
|
||||||
|
"mute": "Bisu",
|
||||||
|
"exit": "KELUAR",
|
||||||
|
"mysteriousInvisibility": "Gaib yang misterius",
|
||||||
|
"antiBlock": "Anti-Blok",
|
||||||
|
"privateChat": "Obrolan Pribadi",
|
||||||
|
"storeDiscount": "Diskon Toko Diskon {1}",
|
||||||
|
"membershipFreeChatSpeak": "Obrolan & Bicara bebas keanggotaan",
|
||||||
|
"priorityRoomSorting": "Penyortiran Kamar Prioritas",
|
||||||
|
"userColoredID": "ID Berwarna Pengguna",
|
||||||
|
"pleaseSelectaItem": "Silakan pilih item",
|
||||||
|
"areYouRureRoRecharge": "Apakah Anda yakin ingin mengisi ulang?",
|
||||||
|
"mInimize": "Menyimpan",
|
||||||
|
"everyone": "Setiap orang",
|
||||||
|
"shop": "Toko",
|
||||||
|
"roomOwner": "Pemilik Kamar",
|
||||||
|
"dailyTaskRewardBonus": "Bonus Hadiah Tugas Harian ({1} XP)",
|
||||||
|
"userLevelXPBoost": "Peningkatan XP Tingkat Pengguna ({1} XP)",
|
||||||
|
"andAboveUsers": "{1} Dan Pengguna Di Atas",
|
||||||
|
"privileges": "{1} Hak istimewa",
|
||||||
|
"basicPermissions": "Izin dasar",
|
||||||
|
"hostCenter": "Pusat Tuan Rumah",
|
||||||
|
"allOnMicrophone": "Semuanya menggunakan mikrofon",
|
||||||
|
"usersOnMicrophone": "Pengguna di mikrofon",
|
||||||
|
"allInTheRoom": "Semua di kamar",
|
||||||
|
"send": "Mengirim",
|
||||||
|
"crop": "Tanaman",
|
||||||
|
"goToUpgrade": "Pergi untuk meningkatkan",
|
||||||
|
"exclusiveEmojiWillBeReleasedAfterBecoming": "Emoji eksklusif akan dirilis setelah menjadi",
|
||||||
|
"preventBeingBlocked": "Mencegah Diblokir",
|
||||||
|
"enableRankIncognitoMode": "Aktifkan Mode Penyamaran Peringkat",
|
||||||
|
"avoidBeingKicked": "Hindari Ditendang",
|
||||||
|
"finish": "Menyelesaikan",
|
||||||
|
"takeTheMic": "Ambil mikrofonnya",
|
||||||
|
"openTheMic": "Buka mikrofon",
|
||||||
|
"muteTheMic": "Matikan mikrofon",
|
||||||
|
"unlockTheMic": "Buka kunci mikrofon",
|
||||||
|
"leavelTheMic": "Tinggalkan mikrofon",
|
||||||
|
"lockTheMic": "Kunci mikrofon",
|
||||||
|
"removeTheMic": "Hapus mikrofon",
|
||||||
|
"inviteToTheMicrophone": "Undang ke mikrofon",
|
||||||
|
"openUserProfleCard": "Buka kartu profil pengguna",
|
||||||
|
"obtain": "memperoleh",
|
||||||
|
"win2": "Menangkan {1}",
|
||||||
|
"backTheRoom": "Ruang belakang",
|
||||||
|
"toConsume": "Untuk mengkonsumsi",
|
||||||
|
"howToUpgrade": "Bagaimana cara meningkatkannya?",
|
||||||
|
"spendCoinsToGainExperiencePoints": "Habiskan Koin untuk mendapatkan poin pengalaman",
|
||||||
|
"richLevelUpgradeAnswer": "Biarkan kekayaan Anda mengalahkan segalanya",
|
||||||
|
"charmLevelUpgradeAnswer": "Buat orang lain tunduk pada pesona Anda",
|
||||||
|
"higherLevelFancierAvatarFrame": "Tingkat yang lebih tinggi, lencana/bingkai avatar yang lebih menarik",
|
||||||
|
"all": "Semua",
|
||||||
|
"gift": "Hadiah",
|
||||||
|
"chat": "Mengobrol",
|
||||||
|
"owner": "Pemilik",
|
||||||
|
"store": "Toko",
|
||||||
|
"admin": "Admin",
|
||||||
|
"member": "Anggota",
|
||||||
|
"guest": "Tamu",
|
||||||
|
"submit": "Kirim",
|
||||||
|
"claim": "Mengeklaim",
|
||||||
|
"claimed": "Diklaim",
|
||||||
|
"complete": "Menyelesaikan",
|
||||||
|
"shareTo": "Bagikan ke",
|
||||||
|
"copyLink": "Salin Tautan",
|
||||||
|
"faceBook": "Facebook",
|
||||||
|
"whatsApp": "ada apa",
|
||||||
|
"snapChat": "Snapchat",
|
||||||
|
"taskNamePersonalGameConsume": "Pengeluaran Game",
|
||||||
|
"taskNameRoomNewMember": "Anggota Ruang Baru",
|
||||||
|
"taskNameRoomOwnerSendRedPacket": "Pemilik Kamar mengirimkan satu paket merah",
|
||||||
|
"taskNameRoomOwnerSendGiftUser": "Pemilik Kamar Mengirim Hadiah",
|
||||||
|
"taskNameRoomMicUser120Min": "Anggota dengan 120+ menit menggunakan mikrofon",
|
||||||
|
"taskNameRoomMicUser60Min": "Anggota dengan 60+ menit menggunakan mikrofon",
|
||||||
|
"taskNameRoomMicUser30Min": "Anggota dengan 30+ menit menggunakan mikrofon",
|
||||||
|
"taskNamePersonalSendGift": "Kirim Hadiah ke Pengguna",
|
||||||
|
"taskNameRoomOnlineUserCount": "Anggota ruang online",
|
||||||
|
"taskNameRoomOwnerInviteMic": "Undang anggota ke Mic",
|
||||||
|
"taskNameRoomUserSendGiftGold": "Koin yang diberikan oleh anggota",
|
||||||
|
"taskNameRoomOwnerSendGiftGold": "Koin yang diberikan oleh pemilik kamar",
|
||||||
|
"taskNamePersonalMagicGiftGold": "Koin Dimenangkan dengan Mengirim Hadiah Ajaib",
|
||||||
|
"taskNamePersonalLuckyGiftGold": "Koin Dimenangkan dengan Mengirim Hadiah Keberuntungan",
|
||||||
|
"taskNameRoomOwnerMicTime": "Pemilik Kamar menyalakan mikrofon di dalam kamar",
|
||||||
|
"taskNamePersonalActiveInRoom": "Jadilah Aktif di Ruangan Orang Lain",
|
||||||
|
"taskNamePersonalMicInRoom": "Ayo Mikrofon",
|
||||||
|
"dailyCoinBonanzaRulesDetail": "1. Tugas Pribadi Harian dan Tugas Pemilik Ruangan Harian dapat diselesaikan satu kali per pengguna per hari. Tugas diatur ulang pada 00:00:00 waktu Saudi.\n2. Tugas pribadi sehari-hari hanya dapat diselesaikan di ruangan orang lain; tugas pemilik ruangan hanya dapat diselesaikan di ruangan Anda sendiri.\n3. Tugas pemberian hadiah hanya menghitung hadiah yang dikirim di ruangan, bukan hadiah yang dikirim di feed.\n4. Jika beberapa akun dibuat menggunakan perangkat yang sama atau kartu SIM yang sama dengan cara apa pun, hadiah untuk semua tugas hanya dapat diklaim satu kali.",
|
||||||
|
"dailyCoinBonanzaRules": "Aturan Bonanza Koin Harian",
|
||||||
|
"roomOwnerTasks": "Tugas Pemilik Ruangan",
|
||||||
|
"personalTasks": "Tugas Pribadi",
|
||||||
|
"noPromptsToday": "Tidak ada tugas hari ini.",
|
||||||
|
"getPaidToRefer": "Dapatkan Bayaran untuk Merujuk",
|
||||||
|
"membershipFee": "Biaya Keanggotaan",
|
||||||
|
"membershipFeeTips1": "Silakan tentukan biaya keanggotaan untuk kamar Anda. Pengguna dapat bergabung dengan kamar Anda dengan membayar biayanya.",
|
||||||
|
"membershipFeeTips2": "Emas yang dibutuhkan pengguna untuk menjadi anggota ruangan. Pemilik ruangan akan mendapatkan 50% emas.",
|
||||||
|
"freePrice": "Biaya: 0-10.000",
|
||||||
|
"touristsSendText": "Turis mengirim SMS",
|
||||||
|
"touristsTakeToTheMic": "Wisatawan mendengarkan mikrofon",
|
||||||
|
"theMembershipFee": "Biaya keanggotaan",
|
||||||
|
"theModificationsMade": "Modifikasi yang dilakukan kali ini tidak akan disimpan setelah keluar",
|
||||||
|
"viewFrame": "Lihat Bingkai",
|
||||||
|
"enterRoomName": "Masukkan nama ruangan",
|
||||||
|
"headdress": "Bingkai",
|
||||||
|
"mountains": "Kendaraan",
|
||||||
|
"purchaseIsSuccessful": "Pembelian berhasil",
|
||||||
|
"buy": "Membeli",
|
||||||
|
"followed": "Diikuti",
|
||||||
|
"follow2": "Ikuti:{1}",
|
||||||
|
"fans2": "Penggemar:{1}",
|
||||||
|
"vistors2": "Pengunjung:{1}",
|
||||||
|
"personal2": "Pribadi:",
|
||||||
|
"conntinue": "Melanjutkan",
|
||||||
|
"confirmBuyTips": "Apakah Anda yakin ingin membeli?",
|
||||||
|
"purchase": "Pembelian",
|
||||||
|
"setRoomPassword": "Tetapkan kata sandi ruangan",
|
||||||
|
"inputRoomPassword": "Masukkan kata sandi ruangan",
|
||||||
|
"enter": "Memasuki",
|
||||||
|
"deleteCommentTips": "Apakah Anda yakin ingin menghapus komentar ini?",
|
||||||
|
"deleteSuccessful": "Penghapusan berhasil!",
|
||||||
|
"itemsLeft": "Barang tersisa",
|
||||||
|
"password": "Kata sandi",
|
||||||
|
"replySucc": "Balasan berhasil",
|
||||||
|
"comment": "Komentar",
|
||||||
|
"showMore": "Tampilkan lebih banyak",
|
||||||
|
"showLess": "Tampilkan lebih sedikit",
|
||||||
|
"enterPassword": "Masukkan Kata Sandi",
|
||||||
|
"enterAccount": "Masuk ke Akun",
|
||||||
|
"logIn": "Masuk",
|
||||||
|
"saySomething": "Katakan sesuatu...",
|
||||||
|
"sayHi": "Ucapkan salam..",
|
||||||
|
"pleaseChatFfriendly": "Silakan ngobrol dengan ramah",
|
||||||
|
"unLockTheRoom": "Buka Kunci Ruangan",
|
||||||
|
"operationSuccessful": "Operasinya berhasil.",
|
||||||
|
"adminByHomeowner": "ditetapkan sebagai administrator oleh pemilik rumah.",
|
||||||
|
"memberByHomeowner": "ditetapkan sebagai anggota oleh pemilik rumah.",
|
||||||
|
"touristByHomeowner": "ditetapkan sebagai turis oleh pemilik rumah.",
|
||||||
|
"becomeHost": "Lamar Untuk Menjadi Tuan Rumah",
|
||||||
|
"superFans": "Penggemar super:",
|
||||||
|
"setUpAnIdentity": "Siapkan identitas",
|
||||||
|
"kickedOutOfRoom": "Ditendang keluar dari ruangan",
|
||||||
|
"knapsack": "ransel",
|
||||||
|
"bdLeader": "Pemimpin BD",
|
||||||
|
"picture": "Gambar",
|
||||||
|
"theImageSizeCannotExceed": "Ukuran gambar tidak boleh melebihi 4M",
|
||||||
|
"activity": "Aktivitas",
|
||||||
|
"alreadyAnAdministrator": "Sudah menjadi administrator",
|
||||||
|
"alreadyAnMember": "Sudah menjadi anggota",
|
||||||
|
"alreadyAnTourist": "Sudah menjadi turis",
|
||||||
|
"touristsCannotSendMessages": "Wisatawan tidak dapat mengirim pesan",
|
||||||
|
"touristsAreNotAllowedToGoOnTheMic": "Wisatawan tidak diperbolehkan menggunakan mikrofon",
|
||||||
|
"lockTheRoom": "Kunci Ruangan",
|
||||||
|
"special": "Spesial",
|
||||||
|
"visitorList": "Daftar Pengunjung",
|
||||||
|
"successfulWear": "Pakaian yang sukses",
|
||||||
|
"confirmUnUseTips": "Apakah Anda mengonfirmasi untuk menghapusnya?",
|
||||||
|
"custom": "Kebiasaan",
|
||||||
|
"customBackground": "Latar Belakang Kustom",
|
||||||
|
"example": "Contoh",
|
||||||
|
"endPreview": "Akhiri Pratinjau",
|
||||||
|
"selectAgain": "Pilih lagi",
|
||||||
|
"myItems": "Barang saya",
|
||||||
|
"use": "Menggunakan",
|
||||||
|
"unUse": "Buka perlengkapan",
|
||||||
|
"renewal": "Pembaruan",
|
||||||
|
"wallet": "Dompet",
|
||||||
|
"profile": "Profil",
|
||||||
|
"giftwall": "dinding hadiah",
|
||||||
|
"announcement": "Pengumuman",
|
||||||
|
"blockedList": "Daftar yang diblokir",
|
||||||
|
"country2": "Negara:",
|
||||||
|
"sendTo": "Kirim ke",
|
||||||
|
"credits": "Kredit: {1}",
|
||||||
|
"successfullyUnloaded": "Berhasil dibongkar",
|
||||||
|
"expired": "Kedaluwarsa",
|
||||||
|
"day": "Hari",
|
||||||
|
"inUse": "Sedang digunakan",
|
||||||
|
"staticOrGifImage": "Gambar statis / GIF",
|
||||||
|
"confirmUseTips": "Apakah Anda ingin mengonfirmasi penggunaannya?",
|
||||||
|
"pleaseUploadUserAvatar": "Silakan unggah avatar.",
|
||||||
|
"joinMemberTips": "Jika Anda adalah pengunjung ruangan, Anda tidak dapat mengambil mikrofon.",
|
||||||
|
"giftGivingSuccessful": "Pemberian hadiah berhasil.",
|
||||||
|
"theAccountPasswordCannotBeEmpty": "Akun atau kata sandi tidak boleh kosong.",
|
||||||
|
"invitesYouToTheMicrophone": "{1} mengundang Anda ke mikrofon",
|
||||||
|
"english": "Bahasa inggris",
|
||||||
|
"chinese": "Cina",
|
||||||
|
"arabic": "Arab",
|
||||||
|
"darkMode": "Mode Gelap",
|
||||||
|
"lightMode": "Modus Cahaya",
|
||||||
|
"systemDefault": "Bawaan Sistem",
|
||||||
|
"pleaseGetOnTheMicFirst": "Silakan nyalakan mikrofonnya terlebih dahulu.",
|
||||||
|
"roomMusicAddMusic": "Tambahkan Musik",
|
||||||
|
"roomMusicSelectAll": "Pilih semua",
|
||||||
|
"roomMusicAddedCount": "Menambahkan {1} lagu",
|
||||||
|
"roomMusicEmpty": "Tidak ada musik",
|
||||||
|
"roomMusicScanFromPhone": "Pindai dari ponsel",
|
||||||
|
"roomMusicTotalSongs": "Jumlah: {1} lagu",
|
||||||
|
"roomMusicNotFound": "Tidak ada musik yang ditemukan",
|
||||||
|
"roomMusicDeleteConfirm": "Hapus musik ini?",
|
||||||
|
"roomMusicDeleted": "Musik dihapus",
|
||||||
|
"roomMusicListMode": "Mainkan secara berurutan",
|
||||||
|
"roomMusicShuffleMode": "Putar acak",
|
||||||
|
"roomMusicSingleMode": "Lingkaran tunggal",
|
||||||
|
"duration2": "Durasi:{1}",
|
||||||
|
"roomRocketNoteTitle": "Catatan",
|
||||||
|
"roomRocketNoteSectionTitle": "Setiap kali VIP",
|
||||||
|
"roomRocketNoteSectionBody": "1. Setiap kali level VIP dibeli, level tersebut berlaku selama 30 hari, dan dapat diperpanjang secara manual setelah habis masa berlakunya.",
|
||||||
|
"roomRocketRewardEmptyText": "Tidak ada imbalan",
|
||||||
|
"roomRocketRecordEmptyText": "Tidak ada catatan hadiah",
|
||||||
|
"launchRocket": "Luncurkan Roket",
|
||||||
|
"roomRocketFuelFullNotice": "Bahan bakar roket sudah penuh! Diluncurkan dalam waktu {1} detik!"
|
||||||
|
}
|
||||||
@ -16,7 +16,7 @@ class SCVariant1Config implements AppConfig {
|
|||||||
@override
|
@override
|
||||||
String get apiHost => const String.fromEnvironment(
|
String get apiHost => const String.fromEnvironment(
|
||||||
'API_HOST',
|
'API_HOST',
|
||||||
defaultValue: 'https://jvapi.haiyihy.com/',
|
defaultValue: 'http://192.168.110.64:1100/',
|
||||||
); // 默认连本地接口,本地调试可通过 --dart-define=API_HOST 覆盖,线上“https://jvapi.haiyihy.com/”
|
); // 默认连本地接口,本地调试可通过 --dart-define=API_HOST 覆盖,线上“https://jvapi.haiyihy.com/”
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -16,6 +16,8 @@ class SCAppLocalizations {
|
|||||||
'it',
|
'it',
|
||||||
'pt',
|
'pt',
|
||||||
'fr',
|
'fr',
|
||||||
|
'id',
|
||||||
|
'es',
|
||||||
];
|
];
|
||||||
|
|
||||||
static const List<Locale> supportedLocales = [
|
static const List<Locale> supportedLocales = [
|
||||||
@ -26,6 +28,8 @@ class SCAppLocalizations {
|
|||||||
Locale('it', ''),
|
Locale('it', ''),
|
||||||
Locale('pt', ''),
|
Locale('pt', ''),
|
||||||
Locale('fr', ''),
|
Locale('fr', ''),
|
||||||
|
Locale('id', ''),
|
||||||
|
Locale('es', ''),
|
||||||
];
|
];
|
||||||
|
|
||||||
static bool isSupportedLanguageCode(String languageCode) {
|
static bool isSupportedLanguageCode(String languageCode) {
|
||||||
|
|||||||
@ -441,12 +441,12 @@ class _SCIndexPageState extends State<SCIndexPage>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _showDailySignInDialogIfNeeded();
|
await _showEntryPopupIfNeeded();
|
||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _showEntryPopupIfNeeded();
|
await _showDailySignInDialogIfNeeded();
|
||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -619,15 +619,12 @@ class _EmoticonsState extends State<Emoticons> with TickerProviderStateMixin {
|
|||||||
if (path.isEmpty) {
|
if (path.isEmpty) {
|
||||||
return SizedBox(width: width, height: width);
|
return SizedBox(width: width, height: width);
|
||||||
}
|
}
|
||||||
if (path.startsWith("assets/") || path.startsWith("sc_images/")) {
|
return RoomEmojiAssetImage(
|
||||||
return RoomEmojiAssetImage(
|
key: ValueKey(path),
|
||||||
key: ValueKey(path),
|
asset: path,
|
||||||
asset: path,
|
width: width,
|
||||||
width: width,
|
height: width,
|
||||||
height: width,
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
return netImage(url: path, width: width);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _checkStart() {
|
void _checkStart() {
|
||||||
|
|||||||
@ -32,6 +32,8 @@ class _LanguagePageState extends State<LanguagePage> {
|
|||||||
_LanguageOption(code: 'it', label: 'Italiano'),
|
_LanguageOption(code: 'it', label: 'Italiano'),
|
||||||
_LanguageOption(code: 'pt', label: 'Português'),
|
_LanguageOption(code: 'pt', label: 'Português'),
|
||||||
_LanguageOption(code: 'fr', label: 'Français'),
|
_LanguageOption(code: 'fr', label: 'Français'),
|
||||||
|
_LanguageOption(code: 'id', label: 'Bahasa Indonesia'),
|
||||||
|
_LanguageOption(code: 'es', label: 'Español'),
|
||||||
];
|
];
|
||||||
|
|
||||||
String _selectedLanguageCode = 'en';
|
String _selectedLanguageCode = 'en';
|
||||||
|
|||||||
@ -521,6 +521,27 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
return currentUserId.isNotEmpty && currentUserId == userId.trim();
|
return currentUserId.isNotEmpty && currentUserId == userId.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Set<String> _currentUserIdentityKeys() {
|
||||||
|
final currentUser = AccountStorage().getCurrentUser()?.userProfile;
|
||||||
|
return {currentUser?.id?.trim() ?? "", currentUser?.account?.trim() ?? ""}
|
||||||
|
..removeWhere((item) => item.isEmpty);
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> _userProfileIdentityKeys(SocialChatUserProfile? user) {
|
||||||
|
return {user?.id?.trim() ?? "", user?.account?.trim() ?? ""}
|
||||||
|
..removeWhere((item) => item.isEmpty);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _userProfileMatchesAnyIdentity(
|
||||||
|
SocialChatUserProfile? user,
|
||||||
|
Set<String> identityKeys,
|
||||||
|
) {
|
||||||
|
if (identityKeys.isEmpty) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return _userProfileIdentityKeys(user).any(identityKeys.contains);
|
||||||
|
}
|
||||||
|
|
||||||
///房间红包列表
|
///房间红包列表
|
||||||
List<SCRoomRedPacketListRes> redPacketList = [];
|
List<SCRoomRedPacketListRes> redPacketList = [];
|
||||||
|
|
||||||
@ -1269,9 +1290,8 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _shouldSuppressCurrentUserOnSeat(num index, {MicRes? seat}) {
|
bool _shouldSuppressCurrentUserOnSeat(num index, {MicRes? seat}) {
|
||||||
final currentUserId =
|
final currentUserKeys = _currentUserIdentityKeys();
|
||||||
(AccountStorage().getCurrentUser()?.userProfile?.id ?? "").trim();
|
if (currentUserKeys.isEmpty) {
|
||||||
if (currentUserId.isEmpty) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final releaseIndex = _pendingSelfMicReleaseIndex;
|
final releaseIndex = _pendingSelfMicReleaseIndex;
|
||||||
@ -1281,7 +1301,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final resolvedSeat = seat ?? roomWheatMap[index];
|
final resolvedSeat = seat ?? roomWheatMap[index];
|
||||||
return (resolvedSeat?.user?.id ?? "").trim() == currentUserId;
|
return _userProfileMatchesAnyIdentity(resolvedSeat?.user, currentUserKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
MicRes? micAtIndexForDisplay(num index) {
|
MicRes? micAtIndexForDisplay(num index) {
|
||||||
@ -1308,9 +1328,8 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
Map<num, MicRes> nextMap, {
|
Map<num, MicRes> nextMap, {
|
||||||
required Map<num, MicRes> previousMap,
|
required Map<num, MicRes> previousMap,
|
||||||
}) {
|
}) {
|
||||||
final currentUserId =
|
final currentUserKeys = _currentUserIdentityKeys();
|
||||||
(AccountStorage().getCurrentUser()?.userProfile?.id ?? "").trim();
|
if (currentUserKeys.isEmpty) {
|
||||||
if (currentUserId.isEmpty) {
|
|
||||||
return nextMap;
|
return nextMap;
|
||||||
}
|
}
|
||||||
final nowMs = DateTime.now().millisecondsSinceEpoch;
|
final nowMs = DateTime.now().millisecondsSinceEpoch;
|
||||||
@ -1322,12 +1341,20 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
targetIndex != null && sourceIndex != null && guardUntilMs > nowMs;
|
targetIndex != null && sourceIndex != null && guardUntilMs > nowMs;
|
||||||
final selfSeatIndices =
|
final selfSeatIndices =
|
||||||
nextMap.entries
|
nextMap.entries
|
||||||
.where((entry) => entry.value.user?.id == currentUserId)
|
.where(
|
||||||
|
(entry) => _userProfileMatchesAnyIdentity(
|
||||||
|
entry.value.user,
|
||||||
|
currentUserKeys,
|
||||||
|
),
|
||||||
|
)
|
||||||
.map((entry) => entry.key)
|
.map((entry) => entry.key)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
if (targetIndex != null &&
|
if (targetIndex != null &&
|
||||||
nextMap[targetIndex]?.user?.id == currentUserId) {
|
_userProfileMatchesAnyIdentity(
|
||||||
|
nextMap[targetIndex]?.user,
|
||||||
|
currentUserKeys,
|
||||||
|
)) {
|
||||||
_clearSelfMicSwitchGuard();
|
_clearSelfMicSwitchGuard();
|
||||||
_clearSelfMicGoUpGuard();
|
_clearSelfMicGoUpGuard();
|
||||||
return nextMap;
|
return nextMap;
|
||||||
@ -1345,7 +1372,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
previousMap: previousMap,
|
previousMap: previousMap,
|
||||||
selfSeatIndices: selfSeatIndices,
|
selfSeatIndices: selfSeatIndices,
|
||||||
targetIndex: goUpIndex,
|
targetIndex: goUpIndex,
|
||||||
currentUserId: currentUserId,
|
currentUserKeys: currentUserKeys,
|
||||||
);
|
);
|
||||||
if (stabilizedMap != null) {
|
if (stabilizedMap != null) {
|
||||||
return stabilizedMap;
|
return stabilizedMap;
|
||||||
@ -1371,7 +1398,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
previousMap: previousMap,
|
previousMap: previousMap,
|
||||||
selfSeatIndices: selfSeatIndices,
|
selfSeatIndices: selfSeatIndices,
|
||||||
targetIndex: resolvedTargetIndex,
|
targetIndex: resolvedTargetIndex,
|
||||||
currentUserId: currentUserId,
|
currentUserKeys: currentUserKeys,
|
||||||
);
|
);
|
||||||
if (stabilizedMap != null) {
|
if (stabilizedMap != null) {
|
||||||
return stabilizedMap;
|
return stabilizedMap;
|
||||||
@ -1398,7 +1425,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final releaseSeat = nextMap[releaseIndex];
|
final releaseSeat = nextMap[releaseIndex];
|
||||||
if (releaseSeat?.user?.id != currentUserId) {
|
if (!_userProfileMatchesAnyIdentity(releaseSeat?.user, currentUserKeys)) {
|
||||||
_clearSelfMicReleaseGuard();
|
_clearSelfMicReleaseGuard();
|
||||||
return nextMap;
|
return nextMap;
|
||||||
}
|
}
|
||||||
@ -1413,16 +1440,22 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
required Map<num, MicRes> previousMap,
|
required Map<num, MicRes> previousMap,
|
||||||
required Iterable<num> selfSeatIndices,
|
required Iterable<num> selfSeatIndices,
|
||||||
required num targetIndex,
|
required num targetIndex,
|
||||||
required String currentUserId,
|
required Set<String> currentUserKeys,
|
||||||
}) {
|
}) {
|
||||||
final optimisticTargetSeat = previousMap[targetIndex];
|
final optimisticTargetSeat = previousMap[targetIndex];
|
||||||
if (optimisticTargetSeat == null ||
|
if (optimisticTargetSeat == null ||
|
||||||
optimisticTargetSeat.user?.id != currentUserId) {
|
!_userProfileMatchesAnyIdentity(
|
||||||
|
optimisticTargetSeat.user,
|
||||||
|
currentUserKeys,
|
||||||
|
)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final incomingTargetSeat = nextMap[targetIndex];
|
final incomingTargetSeat = nextMap[targetIndex];
|
||||||
if (incomingTargetSeat?.user != null &&
|
if (incomingTargetSeat?.user != null &&
|
||||||
incomingTargetSeat?.user?.id != currentUserId) {
|
!_userProfileMatchesAnyIdentity(
|
||||||
|
incomingTargetSeat?.user,
|
||||||
|
currentUserKeys,
|
||||||
|
)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1518,13 +1551,12 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MicRes? _currentUserMicSeat() {
|
MicRes? _currentUserMicSeat() {
|
||||||
final currentUserId =
|
final currentUserKeys = _currentUserIdentityKeys();
|
||||||
(AccountStorage().getCurrentUser()?.userProfile?.id ?? "").trim();
|
if (currentUserKeys.isEmpty) {
|
||||||
if (currentUserId.isEmpty) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
for (final mic in roomWheatMap.values) {
|
for (final mic in roomWheatMap.values) {
|
||||||
if ((mic.user?.id ?? "").trim() == currentUserId) {
|
if (_userProfileMatchesAnyIdentity(mic.user, currentUserKeys)) {
|
||||||
return mic;
|
return mic;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5420,14 +5452,15 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
|
|
||||||
///自己是否在麦上
|
///自己是否在麦上
|
||||||
bool isOnMai() {
|
bool isOnMai() {
|
||||||
final currentUserId =
|
final currentUserKeys = _currentUserIdentityKeys();
|
||||||
(AccountStorage().getCurrentUser()?.userProfile?.id ?? "").trim();
|
if (currentUserKeys.isEmpty) {
|
||||||
if (currentUserId.isEmpty) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (final entry in roomWheatMap.entries) {
|
for (final entry in roomWheatMap.entries) {
|
||||||
if ((micAtIndexForDisplay(entry.key)?.user?.id ?? "").trim() ==
|
if (_userProfileMatchesAnyIdentity(
|
||||||
currentUserId) {
|
micAtIndexForDisplay(entry.key)?.user,
|
||||||
|
currentUserKeys,
|
||||||
|
)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5436,8 +5469,10 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
|
|
||||||
///自己是否在指定的麦上
|
///自己是否在指定的麦上
|
||||||
bool isOnMaiInIndex(num index) {
|
bool isOnMaiInIndex(num index) {
|
||||||
return (micAtIndexForDisplay(index)?.user?.id ?? "").trim() ==
|
return _userProfileMatchesAnyIdentity(
|
||||||
(AccountStorage().getCurrentUser()?.userProfile?.id ?? "").trim();
|
micAtIndexForDisplay(index)?.user,
|
||||||
|
_currentUserIdentityKeys(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
///点击的用户在哪个麦上
|
///点击的用户在哪个麦上
|
||||||
@ -5447,14 +5482,14 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
num index = -1;
|
num index = -1;
|
||||||
|
final currentUserKeys = _currentUserIdentityKeys();
|
||||||
|
final isCurrentUserQuery = currentUserKeys.contains(normalizedUserId);
|
||||||
|
final lookupKeys =
|
||||||
|
isCurrentUserQuery ? currentUserKeys : <String>{normalizedUserId}
|
||||||
|
..removeWhere((item) => item.isEmpty);
|
||||||
roomWheatMap.forEach((k, value) {
|
roomWheatMap.forEach((k, value) {
|
||||||
final visibleSeat =
|
final visibleSeat = isCurrentUserQuery ? micAtIndexForDisplay(k) : value;
|
||||||
normalizedUserId ==
|
if (_userProfileMatchesAnyIdentity(visibleSeat?.user, lookupKeys)) {
|
||||||
(AccountStorage().getCurrentUser()?.userProfile?.id ?? "")
|
|
||||||
.trim()
|
|
||||||
? micAtIndexForDisplay(k)
|
|
||||||
: value;
|
|
||||||
if ((visibleSeat?.user?.id ?? "").trim() == normalizedUserId) {
|
|
||||||
index = k;
|
index = k;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -464,14 +464,97 @@ class SCAppGeneralManager extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Map<String, List<Emojis>> emojiByTab = {};
|
Map<String, List<Emojis>> emojiByTab = {};
|
||||||
|
List<SCRoomEmojiRes> roomEmojiGroups = [];
|
||||||
|
bool _isFetchingRoomEmojis = false;
|
||||||
|
bool _hasRoomEmojisLoaded = false;
|
||||||
|
bool _roomEmojisLoadedFromAll = false;
|
||||||
|
|
||||||
|
bool get isFetchingRoomEmojis => _isFetchingRoomEmojis;
|
||||||
|
|
||||||
|
bool get hasRoomEmojisLoaded => _hasRoomEmojisLoaded;
|
||||||
|
|
||||||
|
bool get roomEmojisLoadedFromAll => _roomEmojisLoadedFromAll;
|
||||||
|
|
||||||
|
bool get hasRemoteRoomEmojis =>
|
||||||
|
roomEmojiGroups.any((group) => (group.emojis ?? const []).isNotEmpty);
|
||||||
|
|
||||||
///emoji表情
|
///emoji表情
|
||||||
void emojiAll() async {
|
Future<void> emojiAll({bool forceRefresh = false}) async {
|
||||||
var roomEmojis = await SCChatRoomRepository().emojiAll();
|
if (_isFetchingRoomEmojis) {
|
||||||
for (var value in roomEmojis) {
|
_roomEmojiDebugLog('skip load: request already in flight');
|
||||||
emojiByTab[value.groupName!] = value.emojis ?? [];
|
return;
|
||||||
|
}
|
||||||
|
if (_hasRoomEmojisLoaded && !forceRefresh && _roomEmojisLoadedFromAll) {
|
||||||
|
_roomEmojiDebugLog(
|
||||||
|
'skip load: cached groups=${roomEmojiGroups.length} '
|
||||||
|
'tabs=${emojiByTab.length} source=/material/emoji/all',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_hasRoomEmojisLoaded && !forceRefresh && !_roomEmojisLoadedFromAll) {
|
||||||
|
_roomEmojiDebugLog(
|
||||||
|
'reload: cached data was not loaded from temporary /all source',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_isFetchingRoomEmojis = true;
|
||||||
|
_roomEmojiDebugLog('load start forceRefresh=$forceRefresh');
|
||||||
|
try {
|
||||||
|
final roomEmojis = await SCChatRoomRepository().emojiAll();
|
||||||
|
_roomEmojiDebugLog(
|
||||||
|
'load response source=/material/emoji/all '
|
||||||
|
'rawGroups=${roomEmojis.length} '
|
||||||
|
'rawEmojiTotal=${_countRoomEmojiItems(roomEmojis)}',
|
||||||
|
);
|
||||||
|
roomEmojiGroups =
|
||||||
|
roomEmojis
|
||||||
|
.where(
|
||||||
|
(group) =>
|
||||||
|
group.have == true && (group.emojis ?? const []).isNotEmpty,
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
final filteredNoAccess =
|
||||||
|
roomEmojis.where((group) => group.have != true).length;
|
||||||
|
final filteredEmptyGroups =
|
||||||
|
roomEmojis
|
||||||
|
.where(
|
||||||
|
(group) =>
|
||||||
|
group.have == true && (group.emojis ?? const []).isEmpty,
|
||||||
|
)
|
||||||
|
.length;
|
||||||
|
emojiByTab.clear();
|
||||||
|
for (final value in roomEmojiGroups) {
|
||||||
|
final groupName =
|
||||||
|
value.groupName?.trim().isNotEmpty == true
|
||||||
|
? value.groupName!.trim()
|
||||||
|
: (value.groupCode ?? value.id ?? "");
|
||||||
|
if (groupName.isEmpty) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
emojiByTab[groupName] = value.emojis ?? [];
|
||||||
|
}
|
||||||
|
_hasRoomEmojisLoaded = true;
|
||||||
|
_roomEmojisLoadedFromAll = true;
|
||||||
|
_roomEmojiDebugLog(
|
||||||
|
'load success visibleGroups=${roomEmojiGroups.length} '
|
||||||
|
'filteredNoAccess=$filteredNoAccess '
|
||||||
|
'filteredEmptyGroups=$filteredEmptyGroups '
|
||||||
|
'tabs=${emojiByTab.length} '
|
||||||
|
'visibleEmojiTotal=${_countRoomEmojiItems(roomEmojiGroups)} '
|
||||||
|
'sample=${_roomEmojiGroupSample(roomEmojiGroups)}',
|
||||||
|
);
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
_roomEmojiDebugLog('load failed error=$error');
|
||||||
|
debugPrintStack(
|
||||||
|
label: '[RoomEmoji] load stack',
|
||||||
|
stackTrace: stackTrace,
|
||||||
|
maxFrames: 8,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
} finally {
|
||||||
|
_isFetchingRoomEmojis = false;
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
notifyListeners();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///加载等级资源
|
///加载等级资源
|
||||||
@ -491,3 +574,31 @@ class SCAppGeneralManager extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _roomEmojiDebugLog(String message) {
|
||||||
|
debugPrint('[RoomEmoji] $message');
|
||||||
|
}
|
||||||
|
|
||||||
|
int _countRoomEmojiItems(List<SCRoomEmojiRes> groups) {
|
||||||
|
return groups.fold<int>(
|
||||||
|
0,
|
||||||
|
(total, group) => total + (group.emojis?.length ?? 0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _roomEmojiGroupSample(List<SCRoomEmojiRes> groups) {
|
||||||
|
if (groups.isEmpty) {
|
||||||
|
return '[]';
|
||||||
|
}
|
||||||
|
return groups
|
||||||
|
.take(3)
|
||||||
|
.map((group) {
|
||||||
|
final emojis = group.emojis ?? const <Emojis>[];
|
||||||
|
final first = emojis.isNotEmpty ? emojis.first : null;
|
||||||
|
return '{id=${group.id}, code=${group.groupCode}, name=${group.groupName}, '
|
||||||
|
'have=${group.have}, amount=${group.amount}, count=${emojis.length}, '
|
||||||
|
'firstType=${first?.sourceType}, firstSource=${first?.sourceUrl}, '
|
||||||
|
'firstCover=${first?.coverUrl}}';
|
||||||
|
})
|
||||||
|
.join(', ');
|
||||||
|
}
|
||||||
|
|||||||
@ -1,146 +1,197 @@
|
|||||||
/// id : 0
|
// id : 0
|
||||||
/// sysOrigin : ""
|
// sysOrigin : ""
|
||||||
/// groupCode : ""
|
// groupCode : ""
|
||||||
/// groupName : ""
|
// groupName : ""
|
||||||
/// cover : ""
|
// cover : ""
|
||||||
/// have : false
|
// have : false
|
||||||
/// amount : 0.0
|
// amount : 0.0
|
||||||
/// emojis : [{"sourceUrl":"","sourceType":0,"coverUrl":"","type":""}]
|
// emojis : [{"sourceUrl":"","sourceType":0,"coverUrl":"","type":""}]
|
||||||
|
|
||||||
class SCRoomEmojiRes {
|
class SCRoomEmojiRes {
|
||||||
SCRoomEmojiRes({
|
SCRoomEmojiRes({
|
||||||
String? id,
|
String? id,
|
||||||
String? sysOrigin,
|
String? sysOrigin,
|
||||||
String? groupCode,
|
String? groupCode,
|
||||||
String? groupName,
|
String? groupName,
|
||||||
String? cover,
|
String? cover,
|
||||||
bool? have,
|
bool? have,
|
||||||
num? amount,
|
num? amount,
|
||||||
List<Emojis>? emojis,}){
|
List<Emojis>? emojis,
|
||||||
_id = id;
|
}) {
|
||||||
_sysOrigin = sysOrigin;
|
_id = id;
|
||||||
_groupCode = groupCode;
|
_sysOrigin = sysOrigin;
|
||||||
_groupName = groupName;
|
_groupCode = groupCode;
|
||||||
_cover = cover;
|
_groupName = groupName;
|
||||||
_have = have;
|
_cover = cover;
|
||||||
_amount = amount;
|
_have = have;
|
||||||
_emojis = emojis;
|
_amount = amount;
|
||||||
}
|
_emojis = emojis;
|
||||||
|
}
|
||||||
SCRoomEmojiRes.fromJson(dynamic json) {
|
|
||||||
_id = json['id'];
|
SCRoomEmojiRes.fromJson(dynamic json) {
|
||||||
_sysOrigin = json['sysOrigin'];
|
final map = json is Map ? json : const <String, dynamic>{};
|
||||||
_groupCode = json['groupCode'];
|
_id = _stringOrNull(map['id']);
|
||||||
_groupName = json['groupName'];
|
_sysOrigin = _stringOrNull(map['sysOrigin']);
|
||||||
_cover = json['cover'];
|
_groupCode = _stringOrNull(map['groupCode']);
|
||||||
_have = json['have'];
|
_groupName = _stringOrNull(map['groupName']);
|
||||||
_amount = json['amount'];
|
_cover = _stringOrNull(map['cover']);
|
||||||
if (json['emojis'] != null) {
|
_have = _boolOrNull(map['have']);
|
||||||
_emojis = [];
|
_amount = _numOrNull(map['amount']);
|
||||||
json['emojis'].forEach((v) {
|
if (map['emojis'] is List) {
|
||||||
_emojis?.add(Emojis.fromJson(v));
|
_emojis = [];
|
||||||
});
|
map['emojis'].forEach((v) {
|
||||||
}
|
_emojis?.add(Emojis.fromJson(v));
|
||||||
}
|
});
|
||||||
String? _id;
|
}
|
||||||
String? _sysOrigin;
|
}
|
||||||
String? _groupCode;
|
|
||||||
String? _groupName;
|
String? _id;
|
||||||
String? _cover;
|
String? _sysOrigin;
|
||||||
bool? _have;
|
String? _groupCode;
|
||||||
num? _amount;
|
String? _groupName;
|
||||||
List<Emojis>? _emojis;
|
String? _cover;
|
||||||
SCRoomEmojiRes copyWith({ String? id,
|
bool? _have;
|
||||||
String? sysOrigin,
|
num? _amount;
|
||||||
String? groupCode,
|
List<Emojis>? _emojis;
|
||||||
String? groupName,
|
SCRoomEmojiRes copyWith({
|
||||||
String? cover,
|
String? id,
|
||||||
bool? have,
|
String? sysOrigin,
|
||||||
num? amount,
|
String? groupCode,
|
||||||
List<Emojis>? emojis,
|
String? groupName,
|
||||||
}) => SCRoomEmojiRes( id: id ?? _id,
|
String? cover,
|
||||||
sysOrigin: sysOrigin ?? _sysOrigin,
|
bool? have,
|
||||||
groupCode: groupCode ?? _groupCode,
|
num? amount,
|
||||||
groupName: groupName ?? _groupName,
|
List<Emojis>? emojis,
|
||||||
cover: cover ?? _cover,
|
}) => SCRoomEmojiRes(
|
||||||
have: have ?? _have,
|
id: id ?? _id,
|
||||||
amount: amount ?? _amount,
|
sysOrigin: sysOrigin ?? _sysOrigin,
|
||||||
emojis: emojis ?? _emojis,
|
groupCode: groupCode ?? _groupCode,
|
||||||
);
|
groupName: groupName ?? _groupName,
|
||||||
String? get id => _id;
|
cover: cover ?? _cover,
|
||||||
String? get sysOrigin => _sysOrigin;
|
have: have ?? _have,
|
||||||
String? get groupCode => _groupCode;
|
amount: amount ?? _amount,
|
||||||
String? get groupName => _groupName;
|
emojis: emojis ?? _emojis,
|
||||||
String? get cover => _cover;
|
);
|
||||||
bool? get have => _have;
|
|
||||||
num? get amount => _amount;
|
String? get id => _id;
|
||||||
List<Emojis>? get emojis => _emojis;
|
String? get sysOrigin => _sysOrigin;
|
||||||
|
String? get groupCode => _groupCode;
|
||||||
Map<String, dynamic> toJson() {
|
String? get groupName => _groupName;
|
||||||
final map = <String, dynamic>{};
|
String? get cover => _cover;
|
||||||
map['id'] = _id;
|
bool? get have => _have;
|
||||||
map['sysOrigin'] = _sysOrigin;
|
num? get amount => _amount;
|
||||||
map['groupCode'] = _groupCode;
|
List<Emojis>? get emojis => _emojis;
|
||||||
map['groupName'] = _groupName;
|
|
||||||
map['cover'] = _cover;
|
Map<String, dynamic> toJson() {
|
||||||
map['have'] = _have;
|
final map = <String, dynamic>{};
|
||||||
map['amount'] = _amount;
|
map['id'] = _id;
|
||||||
if (_emojis != null) {
|
map['sysOrigin'] = _sysOrigin;
|
||||||
map['emojis'] = _emojis?.map((v) => v.toJson()).toList();
|
map['groupCode'] = _groupCode;
|
||||||
}
|
map['groupName'] = _groupName;
|
||||||
return map;
|
map['cover'] = _cover;
|
||||||
}
|
map['have'] = _have;
|
||||||
|
map['amount'] = _amount;
|
||||||
}
|
if (_emojis != null) {
|
||||||
|
map['emojis'] = _emojis?.map((v) => v.toJson()).toList();
|
||||||
/// sourceUrl : ""
|
}
|
||||||
/// sourceType : 0
|
return map;
|
||||||
/// coverUrl : ""
|
}
|
||||||
/// type : ""
|
}
|
||||||
|
|
||||||
class Emojis {
|
/// sourceUrl : ""
|
||||||
Emojis({
|
/// sourceType : 0
|
||||||
String? sourceUrl,
|
/// coverUrl : ""
|
||||||
num? sourceType,
|
/// type : ""
|
||||||
String? coverUrl,
|
|
||||||
String? type,}){
|
class Emojis {
|
||||||
_sourceUrl = sourceUrl;
|
Emojis({String? sourceUrl, num? sourceType, String? coverUrl, String? type}) {
|
||||||
_sourceType = sourceType;
|
_sourceUrl = sourceUrl;
|
||||||
_coverUrl = coverUrl;
|
_sourceType = sourceType;
|
||||||
_type = type;
|
_coverUrl = coverUrl;
|
||||||
}
|
_type = type;
|
||||||
|
}
|
||||||
Emojis.fromJson(dynamic json) {
|
|
||||||
_sourceUrl = json['sourceUrl'];
|
Emojis.fromJson(dynamic json) {
|
||||||
_sourceType = json['sourceType'];
|
final map = json is Map ? json : const <String, dynamic>{};
|
||||||
_coverUrl = json['coverUrl'];
|
_sourceUrl = _stringOrNull(map['sourceUrl']);
|
||||||
_type = json['type'];
|
_sourceType = _numOrNull(map['sourceType']);
|
||||||
}
|
_coverUrl = _stringOrNull(map['coverUrl']);
|
||||||
String? _sourceUrl;
|
_type = _stringOrNull(map['type']);
|
||||||
num? _sourceType;
|
}
|
||||||
String? _coverUrl;
|
|
||||||
String? _type;
|
String? _sourceUrl;
|
||||||
Emojis copyWith({ String? sourceUrl,
|
num? _sourceType;
|
||||||
num? sourceType,
|
String? _coverUrl;
|
||||||
String? coverUrl,
|
String? _type;
|
||||||
String? type,
|
Emojis copyWith({
|
||||||
}) => Emojis( sourceUrl: sourceUrl ?? _sourceUrl,
|
String? sourceUrl,
|
||||||
sourceType: sourceType ?? _sourceType,
|
num? sourceType,
|
||||||
coverUrl: coverUrl ?? _coverUrl,
|
String? coverUrl,
|
||||||
type: type ?? _type,
|
String? type,
|
||||||
);
|
}) => Emojis(
|
||||||
String? get sourceUrl => _sourceUrl;
|
sourceUrl: sourceUrl ?? _sourceUrl,
|
||||||
num? get sourceType => _sourceType;
|
sourceType: sourceType ?? _sourceType,
|
||||||
String? get coverUrl => _coverUrl;
|
coverUrl: coverUrl ?? _coverUrl,
|
||||||
String? get type => _type;
|
type: type ?? _type,
|
||||||
|
);
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
final map = <String, dynamic>{};
|
String? get sourceUrl => _sourceUrl;
|
||||||
map['sourceUrl'] = _sourceUrl;
|
num? get sourceType => _sourceType;
|
||||||
map['sourceType'] = _sourceType;
|
String? get coverUrl => _coverUrl;
|
||||||
map['coverUrl'] = _coverUrl;
|
String? get type => _type;
|
||||||
map['type'] = _type;
|
String get previewUrl {
|
||||||
return map;
|
final cover = _coverUrl?.trim() ?? "";
|
||||||
}
|
if (cover.isNotEmpty) {
|
||||||
|
return cover;
|
||||||
}
|
}
|
||||||
|
return _sourceUrl?.trim() ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
String get sendUrl => _sourceUrl?.trim() ?? previewUrl;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final map = <String, dynamic>{};
|
||||||
|
map['sourceUrl'] = _sourceUrl;
|
||||||
|
map['sourceType'] = _sourceType;
|
||||||
|
map['coverUrl'] = _coverUrl;
|
||||||
|
map['type'] = _type;
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _stringOrNull(dynamic value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final text = value.toString();
|
||||||
|
return text.isEmpty ? null : text;
|
||||||
|
}
|
||||||
|
|
||||||
|
num? _numOrNull(dynamic value) {
|
||||||
|
if (value is num) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (value is String) {
|
||||||
|
return num.tryParse(value);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool? _boolOrNull(dynamic value) {
|
||||||
|
if (value is bool) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (value is num) {
|
||||||
|
return value != 0;
|
||||||
|
}
|
||||||
|
if (value is String) {
|
||||||
|
final normalized = value.trim().toLowerCase();
|
||||||
|
if (normalized == 'true' || normalized == '1') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (normalized == 'false' || normalized == '0') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|||||||
@ -131,6 +131,12 @@ abstract class SocialChatRoomRepository {
|
|||||||
///麦位表情
|
///麦位表情
|
||||||
Future<List<SCRoomEmojiRes>> emojiAll();
|
Future<List<SCRoomEmojiRes>> emojiAll();
|
||||||
|
|
||||||
|
///当前用户已拥有的麦位表情
|
||||||
|
Future<List<SCRoomEmojiRes>> emojiOwned();
|
||||||
|
|
||||||
|
///购买表情包
|
||||||
|
Future<bool> purchaseEmoji(String id);
|
||||||
|
|
||||||
///设置房间用户身份
|
///设置房间用户身份
|
||||||
Future<bool> changeRoomRole(
|
Future<bool> changeRoomRole(
|
||||||
String roomId,
|
String roomId,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
@ -70,20 +71,38 @@ class MediaCache {
|
|||||||
bool ignoreCache = false,
|
bool ignoreCache = false,
|
||||||
Map<String, String>? headers,
|
Map<String, String>? headers,
|
||||||
}) async {
|
}) async {
|
||||||
if (ignoreCache) {
|
final stopwatch = Stopwatch()..start();
|
||||||
// 强制重新下载
|
_debug('getFile start ignoreCache=$ignoreCache url=$url');
|
||||||
final fileInfo = await _cacheManager.downloadFile(
|
try {
|
||||||
url,
|
if (ignoreCache) {
|
||||||
authHeaders: headers,
|
// 强制重新下载
|
||||||
|
final fileInfo = await _cacheManager.downloadFile(
|
||||||
|
url,
|
||||||
|
authHeaders: headers,
|
||||||
|
);
|
||||||
|
_debug(
|
||||||
|
'getFile success bytes=${await fileInfo.file.length()} '
|
||||||
|
'elapsedMs=${stopwatch.elapsedMilliseconds} path=${fileInfo.file.path}',
|
||||||
|
);
|
||||||
|
return fileInfo.file;
|
||||||
|
} else {
|
||||||
|
// 优先从缓存获取
|
||||||
|
final cacheFile = await _cacheManager.getSingleFile(
|
||||||
|
url,
|
||||||
|
headers: headers,
|
||||||
|
);
|
||||||
|
_debug(
|
||||||
|
'getFile success bytes=${await cacheFile.length()} '
|
||||||
|
'elapsedMs=${stopwatch.elapsedMilliseconds} path=${cacheFile.path}',
|
||||||
|
);
|
||||||
|
return cacheFile;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
_debug(
|
||||||
|
'getFile failed elapsedMs=${stopwatch.elapsedMilliseconds} '
|
||||||
|
'url=$url error=$error',
|
||||||
);
|
);
|
||||||
return fileInfo.file;
|
rethrow;
|
||||||
} else {
|
|
||||||
// 优先从缓存获取
|
|
||||||
final cacheFile = await _cacheManager.getSingleFile(
|
|
||||||
url,
|
|
||||||
headers: headers,
|
|
||||||
);
|
|
||||||
return cacheFile;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,3 +153,10 @@ class MediaCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _debug(String message) {
|
||||||
|
if (!kDebugMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
debugPrint('[FileCacheManager] $message');
|
||||||
|
}
|
||||||
|
|||||||
@ -55,6 +55,45 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
|
|||||||
debugPrint('[GiftFx][repo] $message');
|
debugPrint('[GiftFx][repo] $message');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _roomEmojiRepoDebugLog(String message) {
|
||||||
|
debugPrint('[RoomEmoji][repo] $message');
|
||||||
|
}
|
||||||
|
|
||||||
|
void _roomEmojiRequestLog(
|
||||||
|
String method,
|
||||||
|
String path, {
|
||||||
|
dynamic query,
|
||||||
|
dynamic data,
|
||||||
|
}) {
|
||||||
|
_roomEmojiRepoDebugLog(
|
||||||
|
'request method=$method path=$path '
|
||||||
|
'query=${_roomEmojiDebugJson(query ?? const <String, dynamic>{})} '
|
||||||
|
'body=${_roomEmojiDebugJson(data)}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _roomEmojiResponseLog(String method, String path, dynamic responseBody) {
|
||||||
|
_roomEmojiRepoDebugLog(
|
||||||
|
'response method=$method path=$path '
|
||||||
|
'responseBody(unwrapped)=${_roomEmojiDebugJson(responseBody)}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _roomEmojiDebugJson(dynamic data) {
|
||||||
|
return _clipGiftDebug(_stringifyGiftDebug(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
int _roomEmojiTotal(List<SCRoomEmojiRes> groups) {
|
||||||
|
return groups.fold<int>(
|
||||||
|
0,
|
||||||
|
(total, group) => total + (group.emojis?.length ?? 0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _ownedEmojiGroups(List<SCRoomEmojiRes> groups) {
|
||||||
|
return groups.where((group) => group.have == true).length;
|
||||||
|
}
|
||||||
|
|
||||||
String _giftRepoErrorDetails(Object error) {
|
String _giftRepoErrorDetails(Object error) {
|
||||||
if (error is DioException) {
|
if (error is DioException) {
|
||||||
final request = error.requestOptions;
|
final request = error.requestOptions;
|
||||||
@ -768,13 +807,87 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
|
|||||||
///material/emoji/all
|
///material/emoji/all
|
||||||
@override
|
@override
|
||||||
Future<List<SCRoomEmojiRes>> emojiAll() async {
|
Future<List<SCRoomEmojiRes>> emojiAll() async {
|
||||||
final result = await http.get<List<SCRoomEmojiRes>>(
|
const method = 'GET';
|
||||||
"1d2546ea50639e65f04d029dd0a9655eda4aeb3a4bd182b663d60d87cb3f2938",
|
const path = '/material/emoji/all';
|
||||||
fromJson:
|
_roomEmojiRequestLog(method, path);
|
||||||
(json) =>
|
try {
|
||||||
(json as List).map((e) => SCRoomEmojiRes.fromJson(e)).toList(),
|
final result = await http.get<List<SCRoomEmojiRes>>(
|
||||||
|
"1d2546ea50639e65f04d029dd0a9655eda4aeb3a4bd182b663d60d87cb3f2938",
|
||||||
|
extra: const {BaseNetworkClient.silentErrorToastKey: true},
|
||||||
|
fromJson: (json) {
|
||||||
|
_roomEmojiResponseLog(method, path, json);
|
||||||
|
final list = json is List ? json : const [];
|
||||||
|
_roomEmojiRepoDebugLog(
|
||||||
|
'parse method=$method path=$path bodyType=${json.runtimeType} '
|
||||||
|
'groups=${list.length}',
|
||||||
|
);
|
||||||
|
return list.map((e) => SCRoomEmojiRes.fromJson(e)).toList();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
_roomEmojiRepoDebugLog(
|
||||||
|
'success method=$method path=$path groups=${result.length} '
|
||||||
|
'emojiTotal=${_roomEmojiTotal(result)} ownedGroups=${_ownedEmojiGroups(result)}',
|
||||||
|
);
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
_roomEmojiRepoDebugLog(
|
||||||
|
'failed method=$method path=$path error=${_giftRepoErrorDetails(error)}',
|
||||||
|
);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///material/emoji
|
||||||
|
@override
|
||||||
|
Future<List<SCRoomEmojiRes>> emojiOwned() async {
|
||||||
|
const method = 'GET';
|
||||||
|
const path = '/material/emoji';
|
||||||
|
_roomEmojiRequestLog(method, path);
|
||||||
|
try {
|
||||||
|
final result = await http.get<List<SCRoomEmojiRes>>(
|
||||||
|
path,
|
||||||
|
extra: const {BaseNetworkClient.silentErrorToastKey: true},
|
||||||
|
fromJson: (json) {
|
||||||
|
_roomEmojiResponseLog(method, path, json);
|
||||||
|
final list = json is List ? json : const [];
|
||||||
|
_roomEmojiRepoDebugLog(
|
||||||
|
'parse method=$method path=$path bodyType=${json.runtimeType} '
|
||||||
|
'groups=${list.length}',
|
||||||
|
);
|
||||||
|
return list.map((e) => SCRoomEmojiRes.fromJson(e)).toList();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
_roomEmojiRepoDebugLog(
|
||||||
|
'success method=$method path=$path groups=${result.length} '
|
||||||
|
'emojiTotal=${_roomEmojiTotal(result)}',
|
||||||
|
);
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
_roomEmojiRepoDebugLog(
|
||||||
|
'failed method=$method path=$path error=${_giftRepoErrorDetails(error)}',
|
||||||
|
);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///material/emoji/purchase
|
||||||
|
@override
|
||||||
|
Future<bool> purchaseEmoji(String id) async {
|
||||||
|
const method = 'POST';
|
||||||
|
const path = '/material/emoji/purchase';
|
||||||
|
final params = {"id": id};
|
||||||
|
_roomEmojiRequestLog(method, path, data: params);
|
||||||
|
await http.post<Object?>(
|
||||||
|
path,
|
||||||
|
data: params,
|
||||||
|
allowNullBody: true,
|
||||||
|
fromJson: (json) {
|
||||||
|
_roomEmojiResponseLog(method, path, json);
|
||||||
|
return json;
|
||||||
|
},
|
||||||
);
|
);
|
||||||
return result;
|
_roomEmojiRepoDebugLog('success method=$method path=$path result=true');
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
///room/member/change-role
|
///room/member/change-role
|
||||||
|
|||||||
@ -92,6 +92,8 @@ class SCEntryPopupCoordinator {
|
|||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
var popupTapped = false;
|
||||||
|
final popupTapCompleter = Completer<void>();
|
||||||
_log('show key=${popup.key} popupId=${popup.popupId}');
|
_log('show key=${popup.key} popupId=${popup.popupId}');
|
||||||
await SCEntryPopupDialog.show(
|
await SCEntryPopupDialog.show(
|
||||||
context,
|
context,
|
||||||
@ -101,9 +103,22 @@ class SCEntryPopupCoordinator {
|
|||||||
unawaited(_markShown(popup));
|
unawaited(_markShown(popup));
|
||||||
},
|
},
|
||||||
onTap: () {
|
onTap: () {
|
||||||
unawaited(_handlePopupTap(context, popup, onSelectRootTab));
|
if (popupTapped) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
popupTapped = true;
|
||||||
|
unawaited(
|
||||||
|
_handlePopupTap(context, popup, onSelectRootTab).whenComplete(() {
|
||||||
|
if (!popupTapCompleter.isCompleted) {
|
||||||
|
popupTapCompleter.complete();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
if (popupTapped) {
|
||||||
|
await popupTapCompleter.future;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_log('skip reason=no-image-ready-candidates');
|
_log('skip reason=no-image-ready-candidates');
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:yumi/shared/tools/sc_network_image_utils.dart';
|
import 'package:yumi/shared/tools/sc_network_image_utils.dart';
|
||||||
import 'package:yumi/ui_kit/components/sc_rotating_dots_loading.dart';
|
import 'package:yumi/ui_kit/components/sc_rotating_dots_loading.dart';
|
||||||
@ -19,6 +20,9 @@ int? _resolveMemCacheDimension(double? logicalSize) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class CustomCachedImage extends StatelessWidget {
|
class CustomCachedImage extends StatelessWidget {
|
||||||
|
static final Set<String> _loggedLoadingUrls = <String>{};
|
||||||
|
static final Set<String> _loggedErrorUrls = <String>{};
|
||||||
|
|
||||||
final String imageUrl;
|
final String imageUrl;
|
||||||
final double? width;
|
final double? width;
|
||||||
final double? height;
|
final double? height;
|
||||||
@ -26,6 +30,7 @@ class CustomCachedImage extends StatelessWidget {
|
|||||||
final double borderRadius;
|
final double borderRadius;
|
||||||
final Widget? placeholder;
|
final Widget? placeholder;
|
||||||
final Widget? errorWidget;
|
final Widget? errorWidget;
|
||||||
|
final String? debugName;
|
||||||
|
|
||||||
const CustomCachedImage({
|
const CustomCachedImage({
|
||||||
super.key,
|
super.key,
|
||||||
@ -36,6 +41,7 @@ class CustomCachedImage extends StatelessWidget {
|
|||||||
this.borderRadius = 0,
|
this.borderRadius = 0,
|
||||||
this.placeholder,
|
this.placeholder,
|
||||||
this.errorWidget,
|
this.errorWidget,
|
||||||
|
this.debugName,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -51,13 +57,34 @@ class CustomCachedImage extends StatelessWidget {
|
|||||||
memCacheWidth: _resolveMemCacheDimension(width),
|
memCacheWidth: _resolveMemCacheDimension(width),
|
||||||
memCacheHeight: _resolveMemCacheDimension(height),
|
memCacheHeight: _resolveMemCacheDimension(height),
|
||||||
fit: fit,
|
fit: fit,
|
||||||
placeholder: (context, url) => placeholder ?? _defaultPlaceholder(),
|
placeholder: (context, url) {
|
||||||
errorWidget:
|
_debugLoading(normalizedUrl);
|
||||||
(context, url, error) => errorWidget ?? _defaultErrorWidget(),
|
return placeholder ?? _defaultPlaceholder();
|
||||||
|
},
|
||||||
|
errorWidget: (context, url, error) {
|
||||||
|
_debugError(normalizedUrl, error);
|
||||||
|
return errorWidget ?? _defaultErrorWidget();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _debugLoading(String url) {
|
||||||
|
if (!kDebugMode || !_loggedLoadingUrls.add(url)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final label = debugName == null ? '' : '[$debugName] ';
|
||||||
|
debugPrint('[NetworkImage][custom] ${label}loading url=$url');
|
||||||
|
}
|
||||||
|
|
||||||
|
void _debugError(String url, Object error) {
|
||||||
|
if (!kDebugMode || !_loggedErrorUrls.add('$url|$error')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final label = debugName == null ? '' : '[$debugName] ';
|
||||||
|
debugPrint('[NetworkImage][custom] ${label}failed url=$url error=$error');
|
||||||
|
}
|
||||||
|
|
||||||
Widget _defaultPlaceholder() {
|
Widget _defaultPlaceholder() {
|
||||||
return const SCRotatingDotsLoading();
|
return const SCRotatingDotsLoading();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1345,6 +1345,7 @@ class _RocketImage extends StatelessWidget {
|
|||||||
? CustomCachedImage(
|
? CustomCachedImage(
|
||||||
imageUrl: source,
|
imageUrl: source,
|
||||||
fit: fit,
|
fit: fit,
|
||||||
|
debugName: debugName,
|
||||||
errorWidget:
|
errorWidget:
|
||||||
allowStaticFallback ? null : const SizedBox.shrink(),
|
allowStaticFallback ? null : const SizedBox.shrink(),
|
||||||
)
|
)
|
||||||
@ -1355,6 +1356,7 @@ class _RocketImage extends StatelessWidget {
|
|||||||
return CustomCachedImage(
|
return CustomCachedImage(
|
||||||
imageUrl: source,
|
imageUrl: source,
|
||||||
fit: fit,
|
fit: fit,
|
||||||
|
debugName: debugName,
|
||||||
errorWidget: allowStaticFallback ? null : const SizedBox.shrink(),
|
errorWidget: allowStaticFallback ? null : const SizedBox.shrink(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,13 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:extended_image/extended_image.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:yumi/shared/tools/sc_network_image_utils.dart';
|
||||||
|
import 'package:yumi/ui_kit/components/sc_rotating_dots_loading.dart';
|
||||||
|
import 'package:yumi/ui_kit/widgets/svga/sc_network_svga_widget.dart';
|
||||||
|
|
||||||
|
const Duration _roomEmojiGifRestartInterval = Duration(milliseconds: 3500);
|
||||||
|
|
||||||
class RoomEmojiAssetImage extends StatefulWidget {
|
class RoomEmojiAssetImage extends StatefulWidget {
|
||||||
const RoomEmojiAssetImage({
|
const RoomEmojiAssetImage({
|
||||||
@ -20,12 +29,16 @@ class RoomEmojiAssetImage extends StatefulWidget {
|
|||||||
|
|
||||||
class _RoomEmojiAssetImageState extends State<RoomEmojiAssetImage>
|
class _RoomEmojiAssetImageState extends State<RoomEmojiAssetImage>
|
||||||
with AutomaticKeepAliveClientMixin {
|
with AutomaticKeepAliveClientMixin {
|
||||||
late AssetImage _imageProvider;
|
static final Set<String> _loggedNetworkLoading = <String>{};
|
||||||
|
static final Set<String> _loggedNetworkReady = <String>{};
|
||||||
|
static final Set<String> _loggedNetworkError = <String>{};
|
||||||
|
|
||||||
|
AssetImage? _imageProvider;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_imageProvider = AssetImage(widget.asset);
|
_syncImageProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -38,7 +51,7 @@ class _RoomEmojiAssetImageState extends State<RoomEmojiAssetImage>
|
|||||||
void didUpdateWidget(covariant RoomEmojiAssetImage oldWidget) {
|
void didUpdateWidget(covariant RoomEmojiAssetImage oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
if (oldWidget.asset != widget.asset) {
|
if (oldWidget.asset != widget.asset) {
|
||||||
_imageProvider = AssetImage(widget.asset);
|
_syncImageProvider();
|
||||||
_precache();
|
_precache();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -46,9 +59,44 @@ class _RoomEmojiAssetImageState extends State<RoomEmojiAssetImage>
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
super.build(context);
|
super.build(context);
|
||||||
|
final resource = widget.asset.trim();
|
||||||
|
if (resource.isEmpty) {
|
||||||
|
return SizedBox(width: widget.width, height: widget.height);
|
||||||
|
}
|
||||||
|
if (SCNetworkSvgaWidget.isSvga(resource)) {
|
||||||
|
return RepaintBoundary(
|
||||||
|
child: SCNetworkSvgaWidget(
|
||||||
|
resource: resource,
|
||||||
|
width: widget.width,
|
||||||
|
height: widget.height,
|
||||||
|
loop: true,
|
||||||
|
fit: widget.fit,
|
||||||
|
fallback: _buildNetworkFallback(resource, 'svga fallback'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (_isGif(resource)) {
|
||||||
|
return RepaintBoundary(
|
||||||
|
child: _RestartingGifImage(
|
||||||
|
resource: resource,
|
||||||
|
width: widget.width,
|
||||||
|
height: widget.height,
|
||||||
|
fit: widget.fit,
|
||||||
|
isLocalAsset: _isLocalAsset(resource),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!_isLocalAsset(resource)) {
|
||||||
|
return RepaintBoundary(child: _buildNetworkImage(resource));
|
||||||
|
}
|
||||||
|
|
||||||
|
final imageProvider = _imageProvider;
|
||||||
|
if (imageProvider == null) {
|
||||||
|
return SizedBox(width: widget.width, height: widget.height);
|
||||||
|
}
|
||||||
return RepaintBoundary(
|
return RepaintBoundary(
|
||||||
child: Image(
|
child: Image(
|
||||||
image: _imageProvider,
|
image: imageProvider,
|
||||||
width: widget.width,
|
width: widget.width,
|
||||||
height: widget.height,
|
height: widget.height,
|
||||||
fit: widget.fit,
|
fit: widget.fit,
|
||||||
@ -58,10 +106,298 @@ class _RoomEmojiAssetImageState extends State<RoomEmojiAssetImage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _syncImageProvider() {
|
||||||
|
final resource = widget.asset.trim();
|
||||||
|
_imageProvider = _isLocalAsset(resource) ? AssetImage(resource) : null;
|
||||||
|
}
|
||||||
|
|
||||||
void _precache() {
|
void _precache() {
|
||||||
precacheImage(_imageProvider, context);
|
final imageProvider = _imageProvider;
|
||||||
|
if (imageProvider == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
precacheImage(imageProvider, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isLocalAsset(String resource) {
|
||||||
|
return resource.startsWith("assets/") || resource.startsWith("sc_images/");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isGif(String resource) {
|
||||||
|
final lower = resource.trim().toLowerCase();
|
||||||
|
return lower.endsWith('.gif') || lower.contains('.gif?');
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildNetworkImage(String resource) {
|
||||||
|
final normalizedUrl = normalizeImageResourceUrl(resource);
|
||||||
|
return ExtendedImage.network(
|
||||||
|
normalizedUrl,
|
||||||
|
headers: buildNetworkImageHeaders(normalizedUrl),
|
||||||
|
cache: true,
|
||||||
|
clearMemoryCacheWhenDispose: false,
|
||||||
|
width: widget.width,
|
||||||
|
height: widget.height,
|
||||||
|
fit: widget.fit,
|
||||||
|
gaplessPlayback: true,
|
||||||
|
loadStateChanged: (state) {
|
||||||
|
switch (state.extendedImageLoadState) {
|
||||||
|
case LoadState.loading:
|
||||||
|
_logNetworkOnce(_loggedNetworkLoading, 'loading', normalizedUrl);
|
||||||
|
return SizedBox(
|
||||||
|
width: widget.width,
|
||||||
|
height: widget.height,
|
||||||
|
child: const Center(child: SCRotatingDotsLoading()),
|
||||||
|
);
|
||||||
|
case LoadState.completed:
|
||||||
|
final imageInfo = state.extendedImageInfo;
|
||||||
|
final image = imageInfo?.image;
|
||||||
|
final size =
|
||||||
|
image == null ? 'unknown' : '${image.width}x${image.height}';
|
||||||
|
_logNetworkOnce(
|
||||||
|
_loggedNetworkReady,
|
||||||
|
'ready size=$size',
|
||||||
|
normalizedUrl,
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
case LoadState.failed:
|
||||||
|
final error = state.lastException ?? 'unknown';
|
||||||
|
_logNetworkOnce(
|
||||||
|
_loggedNetworkError,
|
||||||
|
'failed error=$error',
|
||||||
|
normalizedUrl,
|
||||||
|
);
|
||||||
|
return _buildNetworkFallback(normalizedUrl, 'image failed');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildNetworkFallback(String resource, String reason) {
|
||||||
|
_logNetworkOnce(
|
||||||
|
_loggedNetworkError,
|
||||||
|
reason,
|
||||||
|
normalizeImageResourceUrl(resource),
|
||||||
|
);
|
||||||
|
return SizedBox(
|
||||||
|
width: widget.width,
|
||||||
|
height: widget.height,
|
||||||
|
child: const Center(child: SCRotatingDotsLoading()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _logNetworkOnce(Set<String> bucket, String message, String url) {
|
||||||
|
if (!kDebugMode || !bucket.add('$message|$url')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
debugPrint('[RoomEmoji][image] $message url=$url');
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get wantKeepAlive => true;
|
bool get wantKeepAlive => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _RestartingGifImage extends StatefulWidget {
|
||||||
|
const _RestartingGifImage({
|
||||||
|
required this.resource,
|
||||||
|
required this.width,
|
||||||
|
required this.height,
|
||||||
|
required this.fit,
|
||||||
|
required this.isLocalAsset,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String resource;
|
||||||
|
final double width;
|
||||||
|
final double height;
|
||||||
|
final BoxFit fit;
|
||||||
|
final bool isLocalAsset;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_RestartingGifImage> createState() => _RestartingGifImageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RestartingGifImageState extends State<_RestartingGifImage> {
|
||||||
|
static final Set<String> _loggedLoading = <String>{};
|
||||||
|
static final Set<String> _loggedReady = <String>{};
|
||||||
|
static final Set<String> _loggedError = <String>{};
|
||||||
|
|
||||||
|
Timer? _restartTimer;
|
||||||
|
int _restartToken = 0;
|
||||||
|
int _visibleToken = 0;
|
||||||
|
bool _hasCompletedOnce = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (widget.isLocalAsset) {
|
||||||
|
_startRestartTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant _RestartingGifImage oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (oldWidget.resource != widget.resource ||
|
||||||
|
oldWidget.isLocalAsset != widget.isLocalAsset) {
|
||||||
|
_restartToken = 0;
|
||||||
|
_visibleToken = 0;
|
||||||
|
_hasCompletedOnce = false;
|
||||||
|
_restartTimer?.cancel();
|
||||||
|
if (widget.isLocalAsset) {
|
||||||
|
_startRestartTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_restartTimer?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final resource = widget.resource.trim();
|
||||||
|
if (resource.isEmpty) {
|
||||||
|
return SizedBox(width: widget.width, height: widget.height);
|
||||||
|
}
|
||||||
|
if (widget.isLocalAsset) {
|
||||||
|
return Image.asset(
|
||||||
|
resource,
|
||||||
|
key: ValueKey('room_emoji_gif_asset_${resource}_$_restartToken'),
|
||||||
|
width: widget.width,
|
||||||
|
height: widget.height,
|
||||||
|
fit: widget.fit,
|
||||||
|
gaplessPlayback: true,
|
||||||
|
filterQuality: FilterQuality.low,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final normalizedUrl = normalizeImageResourceUrl(resource);
|
||||||
|
final foreground = _buildNetworkGifForeground(normalizedUrl);
|
||||||
|
if (!_hasCompletedOnce) {
|
||||||
|
return foreground;
|
||||||
|
}
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
_buildNetworkGifPlaceholder(normalizedUrl, _visibleToken),
|
||||||
|
foreground,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildNetworkGifPlaceholder(String normalizedUrl, int token) {
|
||||||
|
return ExtendedImage.network(
|
||||||
|
normalizedUrl,
|
||||||
|
key: ValueKey('room_emoji_gif_placeholder_${normalizedUrl}_$token'),
|
||||||
|
headers: buildNetworkImageHeaders(normalizedUrl),
|
||||||
|
cache: true,
|
||||||
|
cacheKey: '$normalizedUrl#room_emoji_gif_${token % 2}',
|
||||||
|
clearMemoryCacheWhenDispose: false,
|
||||||
|
width: widget.width,
|
||||||
|
height: widget.height,
|
||||||
|
fit: widget.fit,
|
||||||
|
gaplessPlayback: true,
|
||||||
|
loadStateChanged: (state) {
|
||||||
|
switch (state.extendedImageLoadState) {
|
||||||
|
case LoadState.completed:
|
||||||
|
return null;
|
||||||
|
case LoadState.loading:
|
||||||
|
case LoadState.failed:
|
||||||
|
return SizedBox(width: widget.width, height: widget.height);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildNetworkGifForeground(String normalizedUrl) {
|
||||||
|
return ExtendedImage.network(
|
||||||
|
normalizedUrl,
|
||||||
|
key: ValueKey(
|
||||||
|
'room_emoji_gif_foreground_${normalizedUrl}_$_restartToken',
|
||||||
|
),
|
||||||
|
headers: buildNetworkImageHeaders(normalizedUrl),
|
||||||
|
cache: true,
|
||||||
|
cacheKey: '$normalizedUrl#room_emoji_gif_${_restartToken % 2}',
|
||||||
|
clearMemoryCacheWhenDispose: true,
|
||||||
|
width: widget.width,
|
||||||
|
height: widget.height,
|
||||||
|
fit: widget.fit,
|
||||||
|
gaplessPlayback: true,
|
||||||
|
loadStateChanged: (state) {
|
||||||
|
switch (state.extendedImageLoadState) {
|
||||||
|
case LoadState.loading:
|
||||||
|
_debugGifOnce(_loggedLoading, 'loading', normalizedUrl);
|
||||||
|
if (_hasCompletedOnce) {
|
||||||
|
return SizedBox(width: widget.width, height: widget.height);
|
||||||
|
}
|
||||||
|
return SizedBox(
|
||||||
|
width: widget.width,
|
||||||
|
height: widget.height,
|
||||||
|
child: const Center(child: SCRotatingDotsLoading()),
|
||||||
|
);
|
||||||
|
case LoadState.completed:
|
||||||
|
final image = state.extendedImageInfo?.image;
|
||||||
|
final size =
|
||||||
|
image == null ? 'unknown' : '${image.width}x${image.height}';
|
||||||
|
_debugGifOnce(_loggedReady, 'ready size=$size', normalizedUrl);
|
||||||
|
if (!_hasCompletedOnce) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_hasCompletedOnce = true;
|
||||||
|
_visibleToken = _restartToken;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else if (_visibleToken != _restartToken) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_visibleToken = _restartToken;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_startRestartTimer();
|
||||||
|
return null;
|
||||||
|
case LoadState.failed:
|
||||||
|
final error = state.lastException ?? 'unknown';
|
||||||
|
_debugGifOnce(_loggedError, 'failed error=$error', normalizedUrl);
|
||||||
|
return SizedBox(
|
||||||
|
width: widget.width,
|
||||||
|
height: widget.height,
|
||||||
|
child: const Center(child: SCRotatingDotsLoading()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startRestartTimer() {
|
||||||
|
if (_restartTimer?.isActive == true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_restartTimer?.cancel();
|
||||||
|
_restartTimer = Timer.periodic(_roomEmojiGifRestartInterval, (_) {
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_restartToken += 1;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _debugGifOnce(
|
||||||
|
Set<String> bucket,
|
||||||
|
String message,
|
||||||
|
String resource,
|
||||||
|
) {
|
||||||
|
if (!kDebugMode || !bucket.add('$message|$resource')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
debugPrint('[RoomEmoji][gif] $message url=$resource');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import 'package:yumi/app/constants/sc_screen.dart';
|
|||||||
import 'package:yumi/app_localizations.dart';
|
import 'package:yumi/app_localizations.dart';
|
||||||
import 'package:yumi/services/audio/rtc_manager.dart';
|
import 'package:yumi/services/audio/rtc_manager.dart';
|
||||||
import 'package:yumi/services/audio/rtm_manager.dart';
|
import 'package:yumi/services/audio/rtm_manager.dart';
|
||||||
|
import 'package:yumi/services/general/sc_app_general_manager.dart';
|
||||||
|
import 'package:yumi/shared/business_logic/models/res/sc_room_emoji_res.dart';
|
||||||
import 'package:yumi/shared/business_logic/usecases/sc_case.dart';
|
import 'package:yumi/shared/business_logic/usecases/sc_case.dart';
|
||||||
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
||||||
import 'package:yumi/shared/tools/sc_keybord_util.dart';
|
import 'package:yumi/shared/tools/sc_keybord_util.dart';
|
||||||
@ -31,45 +33,10 @@ class RoomMsgInput extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _RoomMsgInputState extends State<RoomMsgInput> {
|
class _RoomMsgInputState extends State<RoomMsgInput> {
|
||||||
static const List<_RoomEmojiCategory> _roomEmojiCategories = [
|
|
||||||
_RoomEmojiCategory(
|
|
||||||
id: "fluent",
|
|
||||||
iconAsset: "sc_images/room/emoji/fluent_emoji_01.webp",
|
|
||||||
assets: [
|
|
||||||
"sc_images/room/emoji/fluent_emoji_01.webp",
|
|
||||||
"sc_images/room/emoji/fluent_emoji_02.webp",
|
|
||||||
"sc_images/room/emoji/fluent_emoji_03.webp",
|
|
||||||
"sc_images/room/emoji/fluent_emoji_04.webp",
|
|
||||||
"sc_images/room/emoji/fluent_emoji_05.webp",
|
|
||||||
"sc_images/room/emoji/fluent_emoji_06.webp",
|
|
||||||
"sc_images/room/emoji/fluent_emoji_07.webp",
|
|
||||||
"sc_images/room/emoji/fluent_emoji_08.webp",
|
|
||||||
"sc_images/room/emoji/fluent_emoji_09.webp",
|
|
||||||
"sc_images/room/emoji/fluent_emoji_10.webp",
|
|
||||||
],
|
|
||||||
),
|
|
||||||
_RoomEmojiCategory(
|
|
||||||
id: "monkey",
|
|
||||||
iconAsset: "sc_images/room/emoji/monkey_1.gif",
|
|
||||||
assets: [
|
|
||||||
"sc_images/room/emoji/monkey_1.gif",
|
|
||||||
"sc_images/room/emoji/monkey_2.gif",
|
|
||||||
"sc_images/room/emoji/monkey_3.gif",
|
|
||||||
"sc_images/room/emoji/monkey_4.gif",
|
|
||||||
"sc_images/room/emoji/monkey_5.gif",
|
|
||||||
"sc_images/room/emoji/monkey_6.gif",
|
|
||||||
"sc_images/room/emoji/monkey_7.gif",
|
|
||||||
"sc_images/room/emoji/monkey_8.gif",
|
|
||||||
"sc_images/room/emoji/monkey_9.gif",
|
|
||||||
"sc_images/room/emoji/monkey_10.gif",
|
|
||||||
],
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
bool showSend = false;
|
bool showSend = false;
|
||||||
bool showEmoji = false;
|
bool showEmoji = false;
|
||||||
bool _emojiAssetsPrecached = false;
|
|
||||||
int _selectedEmojiCategoryIndex = 0;
|
int _selectedEmojiCategoryIndex = 0;
|
||||||
|
String? _lastEmojiPanelLogSignature;
|
||||||
|
|
||||||
final FocusNode msgNode = FocusNode();
|
final FocusNode msgNode = FocusNode();
|
||||||
final TextEditingController controller = TextEditingController();
|
final TextEditingController controller = TextEditingController();
|
||||||
@ -98,7 +65,7 @@ class _RoomMsgInputState extends State<RoomMsgInput> {
|
|||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
if (showEmoji) {
|
if (showEmoji) {
|
||||||
_precacheRoomEmojis();
|
_loadRoomEmojisIfNeeded();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,7 +214,19 @@ class _RoomMsgInputState extends State<RoomMsgInput> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildEmojiPanel() {
|
Widget _buildEmojiPanel() {
|
||||||
final selectedCategory = _roomEmojiCategories[_selectedEmojiCategoryIndex];
|
final categories = _resolveRoomEmojiCategories(context);
|
||||||
|
_logEmojiPanelState(categories);
|
||||||
|
if (categories.isEmpty) {
|
||||||
|
return Container(
|
||||||
|
height: 220.w,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: _panelBackgroundColor,
|
||||||
|
borderRadius: BorderRadius.circular(height(5)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final selectedIndex = _clampedEmojiCategoryIndex(categories);
|
||||||
|
final selectedCategory = categories[selectedIndex];
|
||||||
return Container(
|
return Container(
|
||||||
height: 220.w,
|
height: 220.w,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@ -265,18 +244,18 @@ class _RoomMsgInputState extends State<RoomMsgInput> {
|
|||||||
crossAxisSpacing: 18.w,
|
crossAxisSpacing: 18.w,
|
||||||
mainAxisSpacing: 12.w,
|
mainAxisSpacing: 12.w,
|
||||||
),
|
),
|
||||||
itemCount: selectedCategory.assets.length,
|
itemCount: selectedCategory.items.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final emojiAsset = selectedCategory.assets[index];
|
final emojiItem = selectedCategory.items[index];
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_sendRoomEmoji(emojiAsset);
|
_sendRoomEmoji(emojiItem.sendResource);
|
||||||
},
|
},
|
||||||
child: Center(
|
child: Center(
|
||||||
child: RoomEmojiAssetImage(
|
child: RoomEmojiAssetImage(
|
||||||
key: ValueKey(emojiAsset),
|
key: ValueKey(emojiItem.displayResource),
|
||||||
asset: emojiAsset,
|
asset: emojiItem.displayResource,
|
||||||
width: 42.w,
|
width: 42.w,
|
||||||
height: 42.w,
|
height: 42.w,
|
||||||
),
|
),
|
||||||
@ -285,13 +264,16 @@ class _RoomMsgInputState extends State<RoomMsgInput> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_buildEmojiCategoryBar(),
|
_buildEmojiCategoryBar(categories, selectedIndex),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildEmojiCategoryBar() {
|
Widget _buildEmojiCategoryBar(
|
||||||
|
List<_RoomEmojiCategory> categories,
|
||||||
|
int selectedIndex,
|
||||||
|
) {
|
||||||
return Container(
|
return Container(
|
||||||
height: 50.w,
|
height: 50.w,
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 7.w),
|
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 7.w),
|
||||||
@ -303,11 +285,11 @@ class _RoomMsgInputState extends State<RoomMsgInput> {
|
|||||||
),
|
),
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
itemCount: _roomEmojiCategories.length,
|
itemCount: categories.length,
|
||||||
separatorBuilder: (_, __) => SizedBox(width: 10.w),
|
separatorBuilder: (_, __) => SizedBox(width: 10.w),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final category = _roomEmojiCategories[index];
|
final category = categories[index];
|
||||||
final selected = index == _selectedEmojiCategoryIndex;
|
final selected = index == selectedIndex;
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -340,7 +322,7 @@ class _RoomMsgInputState extends State<RoomMsgInput> {
|
|||||||
child: Center(
|
child: Center(
|
||||||
child: RoomEmojiAssetImage(
|
child: RoomEmojiAssetImage(
|
||||||
key: ValueKey("emoji_category_${category.id}"),
|
key: ValueKey("emoji_category_${category.id}"),
|
||||||
asset: category.iconAsset,
|
asset: category.iconResource,
|
||||||
width: 26.w,
|
width: 26.w,
|
||||||
height: 26.w,
|
height: 26.w,
|
||||||
),
|
),
|
||||||
@ -362,23 +344,59 @@ class _RoomMsgInputState extends State<RoomMsgInput> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SCKeybordUtil.hide(context);
|
SCKeybordUtil.hide(context);
|
||||||
_precacheRoomEmojis();
|
_roomEmojiInputDebugLog('toggle open: request remote emoji load');
|
||||||
|
_loadRoomEmojisIfNeeded(forceWhenEmpty: true);
|
||||||
setState(() {
|
setState(() {
|
||||||
showEmoji = true;
|
showEmoji = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _precacheRoomEmojis() {
|
void _loadRoomEmojisIfNeeded({bool forceWhenEmpty = false}) {
|
||||||
if (_emojiAssetsPrecached) {
|
final manager = Provider.of<SCAppGeneralManager>(context, listen: false);
|
||||||
|
_roomEmojiInputDebugLog(
|
||||||
|
'load if needed loaded=${manager.hasRoomEmojisLoaded} '
|
||||||
|
'loading=${manager.isFetchingRoomEmojis} '
|
||||||
|
'groups=${manager.roomEmojiGroups.length} '
|
||||||
|
'sourceAll=${manager.roomEmojisLoadedFromAll}',
|
||||||
|
);
|
||||||
|
final forceRefresh =
|
||||||
|
forceWhenEmpty &&
|
||||||
|
manager.hasRoomEmojisLoaded &&
|
||||||
|
manager.roomEmojiGroups.isEmpty;
|
||||||
|
manager.emojiAll(forceRefresh: forceRefresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<_RoomEmojiCategory> _resolveRoomEmojiCategories(BuildContext context) {
|
||||||
|
final generalManager = Provider.of<SCAppGeneralManager>(context);
|
||||||
|
return generalManager.roomEmojiGroups
|
||||||
|
.map(_RoomEmojiCategory.fromRemote)
|
||||||
|
.where((category) => category.items.isNotEmpty)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _logEmojiPanelState(List<_RoomEmojiCategory> categories) {
|
||||||
|
final manager = Provider.of<SCAppGeneralManager>(context, listen: false);
|
||||||
|
final signature =
|
||||||
|
'show=$showEmoji loaded=${manager.hasRoomEmojisLoaded} '
|
||||||
|
'loading=${manager.isFetchingRoomEmojis} '
|
||||||
|
'sourceAll=${manager.roomEmojisLoadedFromAll} '
|
||||||
|
'rawGroups=${manager.roomEmojiGroups.length} '
|
||||||
|
'renderGroups=${categories.length} '
|
||||||
|
'renderEmojiTotal=${_countRenderedEmojiItems(categories)}';
|
||||||
|
if (_lastEmojiPanelLogSignature == signature) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_emojiAssetsPrecached = true;
|
_lastEmojiPanelLogSignature = signature;
|
||||||
for (final category in _roomEmojiCategories) {
|
_roomEmojiInputDebugLog(
|
||||||
precacheImage(AssetImage(category.iconAsset), context);
|
'panel state $signature sample=${_renderedEmojiCategorySample(categories)}',
|
||||||
for (final asset in category.assets) {
|
);
|
||||||
precacheImage(AssetImage(asset), context);
|
}
|
||||||
}
|
|
||||||
|
int _clampedEmojiCategoryIndex(List<_RoomEmojiCategory> categories) {
|
||||||
|
if (_selectedEmojiCategoryIndex < categories.length) {
|
||||||
|
return _selectedEmojiCategoryIndex;
|
||||||
}
|
}
|
||||||
|
return categories.isEmpty ? 0 : categories.length - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _sendRoomEmoji(String emojiAsset) {
|
void _sendRoomEmoji(String emojiAsset) {
|
||||||
@ -435,13 +453,76 @@ class _RoomMsgInputState extends State<RoomMsgInput> {
|
|||||||
class _RoomEmojiCategory {
|
class _RoomEmojiCategory {
|
||||||
const _RoomEmojiCategory({
|
const _RoomEmojiCategory({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.iconAsset,
|
required this.iconResource,
|
||||||
required this.assets,
|
required this.items,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
factory _RoomEmojiCategory.fromRemote(SCRoomEmojiRes group) {
|
||||||
|
final items =
|
||||||
|
(group.emojis ?? const <Emojis>[])
|
||||||
|
.map(_RoomEmojiItem.fromRemote)
|
||||||
|
.where((item) => item.sendResource.isNotEmpty)
|
||||||
|
.toList();
|
||||||
|
final firstPreview =
|
||||||
|
items.isNotEmpty ? items.first.displayResource.trim() : "";
|
||||||
|
final cover = group.cover?.trim() ?? "";
|
||||||
|
final id =
|
||||||
|
(group.id ?? group.groupCode ?? group.groupName ?? firstPreview).trim();
|
||||||
|
return _RoomEmojiCategory(
|
||||||
|
id: id.isEmpty ? firstPreview : id,
|
||||||
|
iconResource: cover.isNotEmpty ? cover : firstPreview,
|
||||||
|
items: items,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final String id;
|
final String id;
|
||||||
final String iconAsset;
|
final String iconResource;
|
||||||
final List<String> assets;
|
final List<_RoomEmojiItem> items;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RoomEmojiItem {
|
||||||
|
const _RoomEmojiItem({
|
||||||
|
required this.displayResource,
|
||||||
|
required this.sendResource,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory _RoomEmojiItem.fromRemote(Emojis emoji) {
|
||||||
|
final display = emoji.previewUrl.trim();
|
||||||
|
final send = emoji.sendUrl.trim();
|
||||||
|
return _RoomEmojiItem(
|
||||||
|
displayResource: display.isNotEmpty ? display : send,
|
||||||
|
sendResource: send.isNotEmpty ? send : display,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String displayResource;
|
||||||
|
final String sendResource;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _roomEmojiInputDebugLog(String message) {
|
||||||
|
debugPrint('[RoomEmoji][input] $message');
|
||||||
|
}
|
||||||
|
|
||||||
|
int _countRenderedEmojiItems(List<_RoomEmojiCategory> categories) {
|
||||||
|
return categories.fold<int>(
|
||||||
|
0,
|
||||||
|
(total, category) => total + category.items.length,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _renderedEmojiCategorySample(List<_RoomEmojiCategory> categories) {
|
||||||
|
if (categories.isEmpty) {
|
||||||
|
return '[]';
|
||||||
|
}
|
||||||
|
return categories
|
||||||
|
.take(3)
|
||||||
|
.map((category) {
|
||||||
|
final first = category.items.isNotEmpty ? category.items.first : null;
|
||||||
|
return '{id=${category.id}, icon=${category.iconResource}, '
|
||||||
|
'count=${category.items.length}, firstDisplay=${first?.displayResource}, '
|
||||||
|
'firstSend=${first?.sendResource}}';
|
||||||
|
})
|
||||||
|
.join(', ');
|
||||||
}
|
}
|
||||||
|
|
||||||
class PopRoute extends PopupRoute {
|
class PopRoute extends PopupRoute {
|
||||||
|
|||||||
@ -1587,19 +1587,12 @@ class _MsgItemState extends State<MsgItem> {
|
|||||||
if (path.isEmpty) {
|
if (path.isEmpty) {
|
||||||
return SizedBox(width: width, height: height);
|
return SizedBox(width: width, height: height);
|
||||||
}
|
}
|
||||||
if (_isLocalAsset(path)) {
|
return RoomEmojiAssetImage(
|
||||||
return RoomEmojiAssetImage(
|
key: ValueKey(path),
|
||||||
key: ValueKey(path),
|
asset: path,
|
||||||
asset: path,
|
width: width,
|
||||||
width: width,
|
height: height,
|
||||||
height: height,
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
return netImage(url: path, width: width, height: height);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _isLocalAsset(String path) {
|
|
||||||
return path.startsWith("assets/") || path.startsWith("sc_images/");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDiceMsg(BuildContext context) {
|
Widget _buildDiceMsg(BuildContext context) {
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svga/flutter_svga.dart';
|
import 'package:flutter_svga/flutter_svga.dart';
|
||||||
import 'package:yumi/shared/tools/sc_gift_vap_svga_manager.dart';
|
import 'package:yumi/shared/tools/sc_gift_vap_svga_manager.dart';
|
||||||
@ -50,6 +51,10 @@ class SCNetworkSvgaWidget extends StatefulWidget {
|
|||||||
|
|
||||||
class _SCNetworkSvgaWidgetState extends State<SCNetworkSvgaWidget>
|
class _SCNetworkSvgaWidgetState extends State<SCNetworkSvgaWidget>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
|
static final Set<String> _loggedLoadingResources = <String>{};
|
||||||
|
static final Set<String> _loggedReadyResources = <String>{};
|
||||||
|
static final Set<String> _loggedErrorResources = <String>{};
|
||||||
|
|
||||||
late final SVGAAnimationController _controller;
|
late final SVGAAnimationController _controller;
|
||||||
String? _loadedResource;
|
String? _loadedResource;
|
||||||
bool _hasError = false;
|
bool _hasError = false;
|
||||||
@ -90,6 +95,7 @@ class _SCNetworkSvgaWidgetState extends State<SCNetworkSvgaWidget>
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
_debugSvgaOnce(_loggedLoadingResources, 'loading', resource);
|
||||||
final cache = SCGiftVapSvgaManager().videoItemCache;
|
final cache = SCGiftVapSvgaManager().videoItemCache;
|
||||||
var movieEntity = cache[resource];
|
var movieEntity = cache[resource];
|
||||||
if (movieEntity == null) {
|
if (movieEntity == null) {
|
||||||
@ -111,6 +117,7 @@ class _SCNetworkSvgaWidgetState extends State<SCNetworkSvgaWidget>
|
|||||||
_loadedResource = resource;
|
_loadedResource = resource;
|
||||||
_controller.videoItem = movieEntity;
|
_controller.videoItem = movieEntity;
|
||||||
});
|
});
|
||||||
|
_debugSvgaOnce(_loggedReadyResources, 'ready', resource);
|
||||||
_syncPlayback(restartIfActive: true);
|
_syncPlayback(restartIfActive: true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!mounted || widget.resource.trim() != resource) {
|
if (!mounted || widget.resource.trim() != resource) {
|
||||||
@ -121,9 +128,17 @@ class _SCNetworkSvgaWidgetState extends State<SCNetworkSvgaWidget>
|
|||||||
_loadedResource = null;
|
_loadedResource = null;
|
||||||
_controller.videoItem = null;
|
_controller.videoItem = null;
|
||||||
});
|
});
|
||||||
|
_debugSvgaOnce(_loggedErrorResources, 'failed error=$error', resource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _debugSvgaOnce(Set<String> bucket, String message, String resource) {
|
||||||
|
if (!kDebugMode || !bucket.add('$message|$resource')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
debugPrint('[NetworkSvga] $message resource=$resource');
|
||||||
|
}
|
||||||
|
|
||||||
void _syncPlayback({bool restartIfActive = false}) {
|
void _syncPlayback({bool restartIfActive = false}) {
|
||||||
if (!widget.active || _controller.videoItem == null) {
|
if (!widget.active || _controller.videoItem == null) {
|
||||||
_controller.stop();
|
_controller.stop();
|
||||||
|
|||||||
@ -176,7 +176,6 @@ flutter:
|
|||||||
- sc_images/room/cp_invite/
|
- sc_images/room/cp_invite/
|
||||||
- sc_images/room/background_examples/
|
- sc_images/room/background_examples/
|
||||||
- sc_images/room/cp_broadcast/
|
- sc_images/room/cp_broadcast/
|
||||||
- sc_images/room/emoji/
|
|
||||||
- sc_images/room/entrance/
|
- sc_images/room/entrance/
|
||||||
- sc_images/room/mic/
|
- sc_images/room/mic/
|
||||||
- sc_images/room/red_packet/
|
- sc_images/room/red_packet/
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 184 KiB |
|
Before Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 284 KiB |
|
Before Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 356 KiB |
|
Before Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 281 KiB |
|
Before Width: | Height: | Size: 197 KiB |
|
Before Width: | Height: | Size: 317 KiB |
|
Before Width: | Height: | Size: 175 KiB |
|
Before Width: | Height: | Size: 227 KiB |
|
Before Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 299 KiB |
|
Before Width: | Height: | Size: 174 KiB |
|
Before Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 671 KiB |
|
Before Width: | Height: | Size: 401 KiB |
|
Before Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 170 KiB |
|
Before Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 79 KiB |
@ -21,7 +21,7 @@ void main() {
|
|||||||
};
|
};
|
||||||
final msg = Msg(
|
final msg = Msg(
|
||||||
groupId: 'room-1',
|
groupId: 'room-1',
|
||||||
msg: 'sc_images/room/emoji/fluent_emoji_01.webp',
|
msg: 'https://cdn.example.com/emoji/fluent_emoji_01.webp',
|
||||||
type: SCRoomMsgType.emoticons,
|
type: SCRoomMsgType.emoticons,
|
||||||
number: 1,
|
number: 1,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -30,4 +30,29 @@ void main() {
|
|||||||
expect(rtcProvider.roomWheatMap[3]?.user?.id, 'user-1');
|
expect(rtcProvider.roomWheatMap[3]?.user?.id, 'user-1');
|
||||||
expect(rtcProvider.isOnMai(), isTrue);
|
expect(rtcProvider.isOnMai(), isTrue);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('self mic detection matches account when mic user id is missing', () {
|
||||||
|
SharedPreferences.setMockInitialValues({});
|
||||||
|
AccountStorage().setCurrentUser(
|
||||||
|
SocialChatLoginRes(
|
||||||
|
userProfile: SocialChatUserProfile(
|
||||||
|
id: 'user-1',
|
||||||
|
account: '8826517',
|
||||||
|
userNickname: 'Me',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final rtcProvider =
|
||||||
|
RealTimeCommunicationManager()
|
||||||
|
..roomWheatMap = {
|
||||||
|
2: MicRes(
|
||||||
|
micIndex: 2,
|
||||||
|
user: SocialChatUserProfile(account: '8826517'),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(rtcProvider.isOnMai(), isTrue);
|
||||||
|
expect(rtcProvider.isOnMaiInIndex(2), isTrue);
|
||||||
|
expect(rtcProvider.userOnMaiInIndex('user-1'), 2);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
34
test/sc_room_emoji_res_test.dart
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:yumi/shared/business_logic/models/res/sc_room_emoji_res.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('SCRoomEmojiRes', () {
|
||||||
|
test('parses backend emoji group fields defensively', () {
|
||||||
|
final group = SCRoomEmojiRes.fromJson({
|
||||||
|
'id': 1001,
|
||||||
|
'sysOrigin': 'LIKEI',
|
||||||
|
'groupCode': 'DEFAULT',
|
||||||
|
'groupName': 'Default',
|
||||||
|
'cover': 'https://cdn.example.com/emoji/default-cover.png',
|
||||||
|
'have': 'true',
|
||||||
|
'amount': '9.9',
|
||||||
|
'emojis': [
|
||||||
|
{
|
||||||
|
'sourceUrl': 'https://cdn.example.com/emoji/hello.svga',
|
||||||
|
'sourceType': '1',
|
||||||
|
'coverUrl': 'https://cdn.example.com/emoji/hello.png',
|
||||||
|
'type': 'NORMAL',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(group.id, '1001');
|
||||||
|
expect(group.have, isTrue);
|
||||||
|
expect(group.amount, 9.9);
|
||||||
|
expect(group.emojis, hasLength(1));
|
||||||
|
expect(group.emojis!.first.sourceType, 1);
|
||||||
|
expect(group.emojis!.first.previewUrl, contains('hello.png'));
|
||||||
|
expect(group.emojis!.first.sendUrl, contains('hello.svga'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
7
需求进度.md
@ -1048,3 +1048,10 @@
|
|||||||
- [ ] 16:41:11 开始格式化并静态检查首充类型识别与兜底图标改动。
|
- [ ] 16:41:11 开始格式化并静态检查首充类型识别与兜底图标改动。
|
||||||
|
|
||||||
- [x] 16:41:33 类型识别与兜底图标改动已通过 dart format 和 flutter analyze。
|
- [x] 16:41:33 类型识别与兜底图标改动已通过 dart format 和 flutter analyze。
|
||||||
|
|
||||||
|
- [ ] 2026-06-02 房间表情反馈:公屏 GIF 不循环,表情弹窗格子应展示动画预览而不是静态封面;开始定位面板资源字段和公屏渲染组件。
|
||||||
|
- [x] 2026-06-02 已定位:表情弹窗格子当前使用 `displayResource/coverUrl`,发送与公屏使用 `sendResource/sourceUrl`;若 cover 为 PNG 会导致面板静态,公屏 GIF 需要在首次加载完成后主动重建来重播有限循环资源。
|
||||||
|
- [x] 2026-06-02 按最新反馈收窄范围:表情面板继续使用 `displayResource` 不改;仅调整 GIF 媒体组件在首次加载完成后重建播放,修复公屏 GIF 停在最后一帧。
|
||||||
|
- [x] 2026-06-02 继续优化公屏 GIF 循环闪 loading:网络 GIF 重播时不再销毁整个 widget,加载下一轮期间复用上一帧画面,避免循环间隙出现转圈。
|
||||||
|
- [x] 2026-06-02 复核发现不销毁 key 会导致 GIF 解码器不重启并停在最后一帧;改为前景层用新 key 重播、背景层保留上一轮画面,兼顾循环播放和不显示 loading。
|
||||||
|
- [x] 2026-06-02 按接口文档恢复 `have` 权限判断:`/material/emoji/all` 返回的表情包仅 `have == true` 且非空时展示,`have=false/null` 计入 `filteredNoAccess` 日志。
|
||||||
|
|||||||