Compare commits
101 Commits
f2e9ac3c60
...
5501345a7e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5501345a7e | ||
|
|
7e10b77d10 | ||
|
|
cedfc912cf | ||
|
|
0c4acb28e5 | ||
|
|
15eb93c810 | ||
|
|
8b05bb367a | ||
|
|
10d5e821f7 | ||
|
|
fac8f9fbab | ||
|
|
27c74abf7e | ||
|
|
07624ac105 | ||
|
|
a73e2ff14b | ||
|
|
5242ef0870 | ||
|
|
13d0d1ef84 | ||
|
|
85925d3607 | ||
|
|
352ca150a0 | ||
|
|
5f6bd6f5f8 | ||
|
|
de4a06be77 | ||
|
|
06b21e0156 | ||
|
|
22d688f6f1 | ||
|
|
bb9b92230b | ||
|
|
ad0fd72180 | ||
|
|
2e1a3037b6 | ||
|
|
9a11487059 | ||
|
|
c3ad3be817 | ||
|
|
9aa22dcdd8 | ||
|
|
4d86236227 | ||
|
|
b4c45e6400 | ||
|
|
3be68c17f8 | ||
|
|
8096ca6acc | ||
|
|
b045b3b784 | ||
|
|
3b777540a7 | ||
|
|
f025c18211 | ||
|
|
6eb7cb91df | ||
|
|
f8e13f31ba | ||
|
|
8f95b72fd0 | ||
|
|
bc3cbabfd1 | ||
|
|
1e7261927e | ||
|
|
e842c41736 | ||
|
|
44e10028b9 | ||
|
|
27f4e75cb8 | ||
|
|
6da7e28fbc | ||
|
|
71993f438b | ||
|
|
cbb811a18a | ||
|
|
d8ef488a28 | ||
|
|
0e2f4cf4bf | ||
|
|
2999de23d1 | ||
|
|
79e78cd897 | ||
|
|
2476e6d94c | ||
|
|
0a3ace948a | ||
|
|
071ab9ffed | ||
|
|
f03b6c605f | ||
|
|
b5cb66997b | ||
|
|
a800a0f941 | ||
|
|
2684fd4673 | ||
|
|
9294f2a08f | ||
|
|
8e3798e85c | ||
|
|
27c89a9f9e | ||
|
|
5be1f05899 | ||
|
|
79ec634ba0 | ||
|
|
5a44ec0c6d | ||
|
|
c2b5ef9e16 | ||
|
|
d5653afa4f | ||
|
|
660f712ae6 | ||
|
|
d009f9cc01 | ||
|
|
9f012fdb27 | ||
|
|
d67b3181b0 | ||
|
|
024bcf4ffc | ||
|
|
8bf3d679a7 | ||
|
|
8be5c4c334 | ||
|
|
faaeeded32 | ||
|
|
409d2fc898 | ||
|
|
a44b7394fe | ||
|
|
0da4fe7922 | ||
|
|
4d0b21aa73 | ||
|
|
671f925232 | ||
|
|
56a562b770 | ||
|
|
5982be944b | ||
|
|
57ce270b4f | ||
|
|
1542f82dd2 | ||
|
|
dd4e2cf08c | ||
|
|
e196d55a27 | ||
|
|
d26cfa20c3 | ||
|
|
06d4b62190 | ||
|
|
e5fdb78fd6 | ||
|
|
ccb5579a85 | ||
|
|
95dcd7e625 | ||
|
|
84b68fbb4a | ||
|
|
4b637d4137 | ||
|
|
74e8162c1f | ||
|
|
be6b2f8f88 | ||
|
|
51083ef57f | ||
|
|
ea296e461b | ||
|
|
6e10d57808 | ||
|
|
54e40bd60b | ||
|
|
b595280805 | ||
|
|
539edffcfe | ||
|
|
a122935747 | ||
|
|
125b852590 | ||
|
|
77d1fd13b1 | ||
|
|
bd6d35ee1b | ||
|
|
3103d51d4c |
@ -88,6 +88,7 @@ android {
|
||||
dependencies {
|
||||
implementation("androidx.multidex:multidex:2.0.1")
|
||||
implementation("com.android.installreferrer:installreferrer:2.2")
|
||||
implementation("androidx.media3:media3-exoplayer:1.9.2")
|
||||
// implementation(platform("com.google.firebase:firebase-bom:34.0.0"))
|
||||
// implementation("com.google.firebase:firebase-auth")
|
||||
// implementation("com.google.firebase:firebase-core:16.0.8")
|
||||
|
||||
19
android/app/proguard-rules.pro
vendored
@ -27,12 +27,21 @@
|
||||
-keep class com.google.android.play.core.tasks.** { *; }
|
||||
-keep class io.flutter.embedding.engine.deferredcomponents.** { *; }
|
||||
|
||||
# 应用特定
|
||||
# 应用特定
|
||||
-keep class com.org.yumiparty.** { *; }
|
||||
|
||||
# Provider
|
||||
-keep class * extends ChangeNotifier { *; }
|
||||
-keep class * implements Listenable { *; }
|
||||
|
||||
# Tencent TRTC
|
||||
-keep class com.tencent.** { *; }
|
||||
-dontwarn com.tencent.**
|
||||
|
||||
# PAG / libpag. The native libpag runtime looks up Java entry points through JNI;
|
||||
# obfuscating these classes breaks PAG playback only in minified release builds.
|
||||
-keep class org.libpag.** { *; }
|
||||
-keep class com.example.flutter_pag_plugin.** { *; }
|
||||
|
||||
# Provider
|
||||
-keep class * extends ChangeNotifier { *; }
|
||||
-keep class * implements Listenable { *; }
|
||||
|
||||
# 其他保留
|
||||
-keepattributes *Annotation*
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
@ -57,7 +58,6 @@
|
||||
android:exported="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:launchMode="singleTask"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
@ -125,13 +125,13 @@
|
||||
android:exported="true"
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
||||
|
||||
<!-- Remove Agora local screen sharing declarations; we do not ship this capability. -->
|
||||
<!-- Remove Tencent TRTC screen sharing declarations; voice rooms only use audio. -->
|
||||
<activity
|
||||
android:name="io.agora.rtc2.extensions.MediaProjectionMgr$LocalScreenCaptureAssistantActivity"
|
||||
android:name="com.tencent.rtmp.video.TXScreenCapture$TXScreenCaptureAssistantActivity"
|
||||
tools:node="remove" />
|
||||
|
||||
<service
|
||||
android:name="io.agora.rtc2.extensions.MediaProjectionMgr$LocalScreenSharingService"
|
||||
android:name="com.tencent.rtmp.video.ScreenCaptureService"
|
||||
tools:node="remove" />
|
||||
|
||||
<service
|
||||
|
||||
@ -0,0 +1,341 @@
|
||||
package com.org.yumiparty
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.SurfaceTexture
|
||||
import android.opengl.GLES11Ext
|
||||
import android.opengl.GLES20
|
||||
import android.opengl.GLSurfaceView
|
||||
import android.opengl.Matrix
|
||||
import android.view.Surface
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.nio.FloatBuffer
|
||||
import javax.microedition.khronos.egl.EGLConfig
|
||||
import javax.microedition.khronos.opengles.GL10
|
||||
|
||||
class GiftMp4GlVideoView(context: Context) :
|
||||
GLSurfaceView(context),
|
||||
GLSurfaceView.Renderer,
|
||||
SurfaceTexture.OnFrameAvailableListener {
|
||||
var onSurfaceReady: ((Surface) -> Unit)? = null
|
||||
|
||||
private val texMatrix = FloatArray(16)
|
||||
private val vertexBuffer = floatBufferOf(VERTICES)
|
||||
private val positionScale = floatArrayOf(1f, 1f)
|
||||
private var program = 0
|
||||
private var oesTexture = 0
|
||||
private var surfaceTexture: SurfaceTexture? = null
|
||||
private var surface: Surface? = null
|
||||
private var positionHandle = 0
|
||||
private var texCoordHandle = 0
|
||||
private var texMatrixHandle = 0
|
||||
private var textureHandle = 0
|
||||
private var hasAlphaMaskHandle = 0
|
||||
private var useTextureMatrixHandle = 0
|
||||
private var flipYHandle = 0
|
||||
private var positionScaleHandle = 0
|
||||
private var colorRectHandle = 0
|
||||
private var alphaRectHandle = 0
|
||||
private var viewWidth = 1
|
||||
private var viewHeight = 1
|
||||
private var contentAspectRatio = 1f
|
||||
private var hasAlphaMask = true
|
||||
private var useTextureMatrix = true
|
||||
private var flipY = true
|
||||
private val colorRect = floatArrayOf(0.5f, 0f, 0.5f, 1f)
|
||||
private val alphaRect = floatArrayOf(0f, 0f, 0.5f, 1f)
|
||||
|
||||
init {
|
||||
setEGLContextClientVersion(2)
|
||||
setEGLConfigChooser(8, 8, 8, 8, 16, 0)
|
||||
holder.setFormat(PixelFormat.TRANSLUCENT)
|
||||
setZOrderMediaOverlay(true)
|
||||
preserveEGLContextOnPause = true
|
||||
setRenderer(this)
|
||||
renderMode = RENDERMODE_WHEN_DIRTY
|
||||
Matrix.setIdentityM(texMatrix, 0)
|
||||
}
|
||||
|
||||
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
|
||||
releaseSurfaceObjects()
|
||||
program = createProgram(VERTEX_SHADER, FRAGMENT_SHADER)
|
||||
positionHandle = GLES20.glGetAttribLocation(program, "aPosition")
|
||||
texCoordHandle = GLES20.glGetAttribLocation(program, "aTexCoord")
|
||||
texMatrixHandle = GLES20.glGetUniformLocation(program, "uTexMatrix")
|
||||
textureHandle = GLES20.glGetUniformLocation(program, "uTexture")
|
||||
hasAlphaMaskHandle = GLES20.glGetUniformLocation(program, "uHasAlphaMask")
|
||||
useTextureMatrixHandle = GLES20.glGetUniformLocation(program, "uUseTextureMatrix")
|
||||
flipYHandle = GLES20.glGetUniformLocation(program, "uFlipY")
|
||||
positionScaleHandle = GLES20.glGetUniformLocation(program, "uPositionScale")
|
||||
colorRectHandle = GLES20.glGetUniformLocation(program, "uColorRect")
|
||||
alphaRectHandle = GLES20.glGetUniformLocation(program, "uAlphaRect")
|
||||
oesTexture = createExternalTexture()
|
||||
surfaceTexture =
|
||||
SurfaceTexture(oesTexture).also {
|
||||
it.setOnFrameAvailableListener(this)
|
||||
}
|
||||
surface =
|
||||
Surface(surfaceTexture).also {
|
||||
post { onSurfaceReady?.invoke(it) }
|
||||
}
|
||||
GLES20.glClearColor(0f, 0f, 0f, 0f)
|
||||
GLES20.glEnable(GLES20.GL_BLEND)
|
||||
GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA)
|
||||
}
|
||||
|
||||
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
|
||||
viewWidth = maxOf(1, width)
|
||||
viewHeight = maxOf(1, height)
|
||||
updatePositionScale()
|
||||
GLES20.glViewport(0, 0, width, height)
|
||||
}
|
||||
|
||||
override fun onDrawFrame(gl: GL10?) {
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
||||
val texture = surfaceTexture ?: return
|
||||
|
||||
texture.updateTexImage()
|
||||
texture.getTransformMatrix(texMatrix)
|
||||
|
||||
GLES20.glUseProgram(program)
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
|
||||
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, oesTexture)
|
||||
GLES20.glUniform1i(textureHandle, 0)
|
||||
GLES20.glUniform1i(hasAlphaMaskHandle, if (hasAlphaMask) 1 else 0)
|
||||
GLES20.glUniform1i(useTextureMatrixHandle, if (useTextureMatrix) 1 else 0)
|
||||
GLES20.glUniform1i(flipYHandle, if (flipY) 1 else 0)
|
||||
GLES20.glUniform2fv(positionScaleHandle, 1, positionScale, 0)
|
||||
GLES20.glUniform4fv(colorRectHandle, 1, colorRect, 0)
|
||||
GLES20.glUniform4fv(alphaRectHandle, 1, alphaRect, 0)
|
||||
GLES20.glUniformMatrix4fv(texMatrixHandle, 1, false, texMatrix, 0)
|
||||
|
||||
vertexBuffer.position(0)
|
||||
GLES20.glEnableVertexAttribArray(positionHandle)
|
||||
GLES20.glVertexAttribPointer(
|
||||
positionHandle,
|
||||
3,
|
||||
GLES20.GL_FLOAT,
|
||||
false,
|
||||
STRIDE_BYTES,
|
||||
vertexBuffer,
|
||||
)
|
||||
|
||||
vertexBuffer.position(3)
|
||||
GLES20.glEnableVertexAttribArray(texCoordHandle)
|
||||
GLES20.glVertexAttribPointer(
|
||||
texCoordHandle,
|
||||
2,
|
||||
GLES20.GL_FLOAT,
|
||||
false,
|
||||
STRIDE_BYTES,
|
||||
vertexBuffer,
|
||||
)
|
||||
|
||||
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4)
|
||||
GLES20.glDisableVertexAttribArray(positionHandle)
|
||||
GLES20.glDisableVertexAttribArray(texCoordHandle)
|
||||
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0)
|
||||
}
|
||||
|
||||
override fun onFrameAvailable(texture: SurfaceTexture?) {
|
||||
requestRender()
|
||||
}
|
||||
|
||||
fun setSourceLayout(
|
||||
alphaEnabled: Boolean,
|
||||
textureMatrixEnabled: Boolean,
|
||||
flipYEnabled: Boolean,
|
||||
aspectRatio: Float,
|
||||
colorSourceRect: FloatArray,
|
||||
alphaSourceRect: FloatArray,
|
||||
) {
|
||||
queueEvent {
|
||||
hasAlphaMask = alphaEnabled
|
||||
useTextureMatrix = textureMatrixEnabled
|
||||
flipY = flipYEnabled
|
||||
contentAspectRatio = aspectRatio.coerceAtLeast(0.01f)
|
||||
colorSourceRect.copyInto(colorRect, endIndex = minOf(colorSourceRect.size, colorRect.size))
|
||||
alphaSourceRect.copyInto(alphaRect, endIndex = minOf(alphaSourceRect.size, alphaRect.size))
|
||||
updatePositionScale()
|
||||
requestRender()
|
||||
}
|
||||
}
|
||||
|
||||
fun release() {
|
||||
queueEvent {
|
||||
releaseSurfaceObjects()
|
||||
if (program != 0) {
|
||||
GLES20.glDeleteProgram(program)
|
||||
program = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePositionScale() {
|
||||
val viewAspect = viewWidth.toFloat() / viewHeight.toFloat()
|
||||
if (viewAspect > contentAspectRatio) {
|
||||
positionScale[0] = contentAspectRatio / viewAspect
|
||||
positionScale[1] = 1f
|
||||
} else {
|
||||
positionScale[0] = 1f
|
||||
positionScale[1] = viewAspect / contentAspectRatio
|
||||
}
|
||||
}
|
||||
|
||||
private fun releaseSurfaceObjects() {
|
||||
surface?.release()
|
||||
surface = null
|
||||
surfaceTexture?.release()
|
||||
surfaceTexture = null
|
||||
if (oesTexture != 0) {
|
||||
val textures = intArrayOf(oesTexture)
|
||||
GLES20.glDeleteTextures(1, textures, 0)
|
||||
oesTexture = 0
|
||||
}
|
||||
}
|
||||
|
||||
private fun createExternalTexture(): Int {
|
||||
val textures = IntArray(1)
|
||||
GLES20.glGenTextures(1, textures, 0)
|
||||
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textures[0])
|
||||
GLES20.glTexParameteri(
|
||||
GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
|
||||
GLES20.GL_TEXTURE_MIN_FILTER,
|
||||
GLES20.GL_LINEAR,
|
||||
)
|
||||
GLES20.glTexParameteri(
|
||||
GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
|
||||
GLES20.GL_TEXTURE_MAG_FILTER,
|
||||
GLES20.GL_LINEAR,
|
||||
)
|
||||
GLES20.glTexParameteri(
|
||||
GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
|
||||
GLES20.GL_TEXTURE_WRAP_S,
|
||||
GLES20.GL_CLAMP_TO_EDGE,
|
||||
)
|
||||
GLES20.glTexParameteri(
|
||||
GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
|
||||
GLES20.GL_TEXTURE_WRAP_T,
|
||||
GLES20.GL_CLAMP_TO_EDGE,
|
||||
)
|
||||
return textures[0]
|
||||
}
|
||||
|
||||
private fun createProgram(vertexSource: String, fragmentSource: String): Int {
|
||||
val vertexShader = compileShader(GLES20.GL_VERTEX_SHADER, vertexSource)
|
||||
val fragmentShader = compileShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource)
|
||||
val glProgram = GLES20.glCreateProgram()
|
||||
GLES20.glAttachShader(glProgram, vertexShader)
|
||||
GLES20.glAttachShader(glProgram, fragmentShader)
|
||||
GLES20.glLinkProgram(glProgram)
|
||||
val status = IntArray(1)
|
||||
GLES20.glGetProgramiv(glProgram, GLES20.GL_LINK_STATUS, status, 0)
|
||||
if (status[0] == 0) {
|
||||
val log = GLES20.glGetProgramInfoLog(glProgram)
|
||||
GLES20.glDeleteProgram(glProgram)
|
||||
throw IllegalStateException("OpenGL program link failed: $log")
|
||||
}
|
||||
GLES20.glDeleteShader(vertexShader)
|
||||
GLES20.glDeleteShader(fragmentShader)
|
||||
return glProgram
|
||||
}
|
||||
|
||||
private fun compileShader(type: Int, source: String): Int {
|
||||
val shader = GLES20.glCreateShader(type)
|
||||
GLES20.glShaderSource(shader, source)
|
||||
GLES20.glCompileShader(shader)
|
||||
val status = IntArray(1)
|
||||
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, status, 0)
|
||||
if (status[0] == 0) {
|
||||
val log = GLES20.glGetShaderInfoLog(shader)
|
||||
GLES20.glDeleteShader(shader)
|
||||
throw IllegalStateException("OpenGL shader compile failed: $log")
|
||||
}
|
||||
return shader
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val FLOAT_BYTES = 4
|
||||
private const val STRIDE_BYTES = 5 * FLOAT_BYTES
|
||||
|
||||
private val VERTICES =
|
||||
floatArrayOf(
|
||||
-1f, -1f, 0f, 0f, 1f,
|
||||
1f, -1f, 0f, 1f, 1f,
|
||||
-1f, 1f, 0f, 0f, 0f,
|
||||
1f, 1f, 0f, 1f, 0f,
|
||||
)
|
||||
|
||||
private val VERTEX_SHADER =
|
||||
"""
|
||||
attribute vec4 aPosition;
|
||||
attribute vec2 aTexCoord;
|
||||
uniform vec2 uPositionScale;
|
||||
varying vec2 vTexCoord;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(aPosition.x * uPositionScale.x, aPosition.y * uPositionScale.y, 0.0, 1.0);
|
||||
vTexCoord = aTexCoord;
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
private val FRAGMENT_SHADER =
|
||||
"""
|
||||
#extension GL_OES_EGL_image_external : require
|
||||
precision mediump float;
|
||||
|
||||
uniform samplerExternalOES uTexture;
|
||||
uniform mat4 uTexMatrix;
|
||||
uniform int uHasAlphaMask;
|
||||
uniform int uUseTextureMatrix;
|
||||
uniform int uFlipY;
|
||||
uniform vec4 uColorRect;
|
||||
uniform vec4 uAlphaRect;
|
||||
varying vec2 vTexCoord;
|
||||
|
||||
vec2 mapTexture(vec2 uv) {
|
||||
if (uUseTextureMatrix == 0) {
|
||||
return uv;
|
||||
}
|
||||
return (uTexMatrix * vec4(uv, 0.0, 1.0)).xy;
|
||||
}
|
||||
|
||||
void main() {
|
||||
float sourceY = uFlipY == 1 ? 1.0 - vTexCoord.y : vTexCoord.y;
|
||||
vec2 colorUv = vec2(
|
||||
uColorRect.x + vTexCoord.x * uColorRect.z,
|
||||
uColorRect.y + sourceY * uColorRect.w
|
||||
);
|
||||
vec2 mappedColor = mapTexture(colorUv);
|
||||
vec4 color = texture2D(uTexture, mappedColor);
|
||||
|
||||
if (uHasAlphaMask == 0) {
|
||||
float alpha = clamp(color.a, 0.0, 1.0);
|
||||
gl_FragColor = vec4(color.rgb * alpha, alpha);
|
||||
return;
|
||||
}
|
||||
|
||||
vec2 alphaUv = vec2(
|
||||
uAlphaRect.x + vTexCoord.x * uAlphaRect.z,
|
||||
uAlphaRect.y + sourceY * uAlphaRect.w
|
||||
);
|
||||
vec2 mappedAlpha = mapTexture(alphaUv);
|
||||
vec3 mask = texture2D(uTexture, mappedAlpha).rgb;
|
||||
float alpha = clamp(dot(mask, vec3(0.299, 0.587, 0.114)), 0.0, 1.0);
|
||||
gl_FragColor = vec4(color.rgb * alpha, alpha);
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
private fun floatBufferOf(values: FloatArray): FloatBuffer {
|
||||
return ByteBuffer
|
||||
.allocateDirect(values.size * FLOAT_BYTES)
|
||||
.order(ByteOrder.nativeOrder())
|
||||
.asFloatBuffer()
|
||||
.apply {
|
||||
put(values)
|
||||
position(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,867 @@
|
||||
package com.org.yumiparty
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.media.MediaMetadataRetriever
|
||||
import android.net.Uri
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import android.view.Surface
|
||||
import android.view.View
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.PlaybackException
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import io.flutter.FlutterInjector
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.plugin.common.MethodChannel.Result
|
||||
import io.flutter.plugin.platform.PlatformView
|
||||
import java.io.File
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
class GiftMp4VideoPlatformView(
|
||||
context: Context,
|
||||
private val viewId: Int,
|
||||
params: Map<*, *>,
|
||||
messenger: io.flutter.plugin.common.BinaryMessenger,
|
||||
) : PlatformView {
|
||||
private val mainHandler = Handler(Looper.getMainLooper())
|
||||
private val events = MethodChannel(messenger, CHANNEL_NAME)
|
||||
private val glView = GiftMp4GlVideoView(context)
|
||||
private val player = ExoPlayer.Builder(context).build()
|
||||
private val token = (params["token"] as? Number)?.toInt() ?: 0
|
||||
private var released = false
|
||||
private var prepared = false
|
||||
private var started = false
|
||||
private var playerSurface: Surface? = null
|
||||
|
||||
init {
|
||||
val path = params["path"] as? String ?: ""
|
||||
val sourceType = params["sourceType"] as? String ?: "file"
|
||||
val muted = params["muted"] as? Boolean ?: false
|
||||
val source = GiftMp4Source(context, path, sourceType)
|
||||
Log.d(
|
||||
"GiftMp4Video",
|
||||
"create viewId=$viewId token=$token sourceType=$sourceType path=$path",
|
||||
)
|
||||
val sourceLayout = resolveSourceLayout(source)
|
||||
|
||||
registry[viewId] = this
|
||||
glView.setSourceLayout(
|
||||
sourceLayout.hasAlphaMask,
|
||||
sourceLayout.useTextureMatrix,
|
||||
sourceLayout.flipY,
|
||||
sourceLayout.contentAspectRatio,
|
||||
sourceLayout.colorRect.toFloatArray(),
|
||||
sourceLayout.alphaRect.toFloatArray(),
|
||||
)
|
||||
player.volume = if (muted) 0f else 1f
|
||||
player.repeatMode = Player.REPEAT_MODE_OFF
|
||||
player.addListener(
|
||||
object : Player.Listener {
|
||||
override fun onPlaybackStateChanged(playbackState: Int) {
|
||||
when (playbackState) {
|
||||
Player.STATE_READY -> {
|
||||
if (!started) {
|
||||
started = true
|
||||
Log.d(
|
||||
"GiftMp4Video",
|
||||
"started viewId=$viewId token=$token",
|
||||
)
|
||||
postEvent("started")
|
||||
}
|
||||
}
|
||||
Player.STATE_ENDED -> {
|
||||
Log.d(
|
||||
"GiftMp4Video",
|
||||
"completed viewId=$viewId token=$token",
|
||||
)
|
||||
postEvent("completed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPlayerError(error: PlaybackException) {
|
||||
Log.e(
|
||||
"GiftMp4Video",
|
||||
"failed viewId=$viewId token=$token code=${error.errorCode}",
|
||||
error,
|
||||
)
|
||||
postEvent(
|
||||
"failed",
|
||||
mapOf(
|
||||
"code" to error.errorCode,
|
||||
"message" to error.message,
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
glView.onSurfaceReady = onSurfaceReady@{ surface ->
|
||||
if (released) {
|
||||
surface.release()
|
||||
return@onSurfaceReady
|
||||
}
|
||||
playerSurface?.release()
|
||||
playerSurface = surface
|
||||
player.setVideoSurface(surface)
|
||||
if (!prepared) {
|
||||
prepared = true
|
||||
player.setMediaItem(source.toMediaItem())
|
||||
player.prepare()
|
||||
player.seekTo(0)
|
||||
player.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getView(): View = glView
|
||||
|
||||
fun setMute(muted: Boolean) {
|
||||
player.volume = if (muted) 0f else 1f
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
player.stop()
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
released = true
|
||||
registry.remove(viewId)
|
||||
glView.onSurfaceReady = null
|
||||
player.clearVideoSurface()
|
||||
player.release()
|
||||
playerSurface?.release()
|
||||
playerSurface = null
|
||||
glView.release()
|
||||
}
|
||||
|
||||
private fun postEvent(
|
||||
method: String,
|
||||
extra: Map<String, Any?> = emptyMap(),
|
||||
) {
|
||||
mainHandler.post {
|
||||
events.invokeMethod(
|
||||
method,
|
||||
mapOf("viewId" to viewId, "token" to token) + extra,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class GiftMp4Source(
|
||||
private val context: Context,
|
||||
val path: String,
|
||||
sourceType: String,
|
||||
) {
|
||||
val type: SourceType =
|
||||
if (sourceType == "asset") SourceType.ASSET else SourceType.FILE
|
||||
val assetKey: String? =
|
||||
if (type == SourceType.ASSET) {
|
||||
FlutterInjector
|
||||
.instance()
|
||||
.flutterLoader()
|
||||
.getLookupKeyForAsset(path)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val cacheKey: String = "${type.name}:${assetKey ?: path}"
|
||||
|
||||
fun toMediaItem(): MediaItem {
|
||||
return if (type == SourceType.ASSET) {
|
||||
MediaItem.fromUri(Uri.parse("asset:///$assetKey"))
|
||||
} else {
|
||||
MediaItem.fromUri(Uri.fromFile(File(path)))
|
||||
}
|
||||
}
|
||||
|
||||
fun readBytes(): ByteArray {
|
||||
return if (type == SourceType.ASSET) {
|
||||
context.assets.open(assetKey ?: path).use { it.readBytes() }
|
||||
} else {
|
||||
File(path).readBytes()
|
||||
}
|
||||
}
|
||||
|
||||
fun newMetadataRetriever(): MediaMetadataRetriever {
|
||||
val retriever = MediaMetadataRetriever()
|
||||
if (type == SourceType.ASSET) {
|
||||
context.assets.openFd(assetKey ?: path).use { descriptor ->
|
||||
retriever.setDataSource(
|
||||
descriptor.fileDescriptor,
|
||||
descriptor.startOffset,
|
||||
descriptor.length,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
retriever.setDataSource(path)
|
||||
}
|
||||
return retriever
|
||||
}
|
||||
}
|
||||
|
||||
private enum class SourceType { ASSET, FILE }
|
||||
|
||||
private data class VideoSize(
|
||||
val width: Int,
|
||||
val height: Int,
|
||||
) {
|
||||
val aspectRatio: Float
|
||||
get() = if (height <= 0) 1f else width.toFloat() / height.toFloat()
|
||||
}
|
||||
|
||||
private data class SourceRect(
|
||||
val x: Float,
|
||||
val y: Float,
|
||||
val width: Float,
|
||||
val height: Float,
|
||||
) {
|
||||
fun toFloatArray(): FloatArray = floatArrayOf(x, y, width, height)
|
||||
}
|
||||
|
||||
private data class SourceLayout(
|
||||
val kind: String,
|
||||
val hasAlphaMask: Boolean,
|
||||
val useTextureMatrix: Boolean,
|
||||
val flipY: Boolean,
|
||||
val contentAspectRatio: Float,
|
||||
val colorRect: SourceRect,
|
||||
val alphaRect: SourceRect,
|
||||
)
|
||||
|
||||
private data class HalfStats(
|
||||
val saturation: Double,
|
||||
val grayError: Double,
|
||||
val brightness: Double,
|
||||
)
|
||||
|
||||
private enum class SplitOrientation { HORIZONTAL, VERTICAL }
|
||||
|
||||
private data class SplitAlphaCandidate(
|
||||
val orientation: SplitOrientation,
|
||||
val alphaFirst: Boolean,
|
||||
)
|
||||
|
||||
private data class SplitScore(
|
||||
var direction: Double = 0.0,
|
||||
var saturationGap: Double = 0.0,
|
||||
var maskSaturation: Double = 0.0,
|
||||
var maskGrayError: Double = 0.0,
|
||||
var frames: Int = 0,
|
||||
)
|
||||
|
||||
private data class PackedAlphaCandidate(
|
||||
val frameWidth: Int,
|
||||
val frameHeight: Int,
|
||||
val alphaStartX: Int,
|
||||
val alphaEndX: Int,
|
||||
)
|
||||
|
||||
companion object {
|
||||
const val VIEW_TYPE = "gift_mp4_video_view"
|
||||
const val CHANNEL_NAME = "com.org.yumiparty/gift_mp4_events"
|
||||
const val CONTROL_CHANNEL_NAME = "com.org.yumiparty/gift_mp4_control"
|
||||
private const val MIN_SPLIT_MATCH_FRAMES = 3
|
||||
private const val MIN_USEFUL_FRAME_BRIGHTNESS = 0.035
|
||||
private const val MIN_SPLIT_SATURATION_GAP = 0.08
|
||||
private const val MAX_SPLIT_MASK_SATURATION = 0.28
|
||||
private const val MAX_SPLIT_MASK_GRAY_ERROR = 0.06
|
||||
|
||||
private val registry = mutableMapOf<Int, GiftMp4VideoPlatformView>()
|
||||
private val layoutCache = mutableMapOf<String, SourceLayout>()
|
||||
|
||||
fun handleControlMethod(call: MethodCall, result: Result) {
|
||||
val viewId = call.argument<Int>("viewId")
|
||||
val view = viewId?.let { registry[it] }
|
||||
if (view == null) {
|
||||
result.success(null)
|
||||
return
|
||||
}
|
||||
when (call.method) {
|
||||
"setMute" -> {
|
||||
view.setMute(call.argument<Boolean>("muted") ?: false)
|
||||
result.success(null)
|
||||
}
|
||||
"stop" -> {
|
||||
view.stop()
|
||||
result.success(null)
|
||||
}
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveSourceLayout(source: GiftMp4Source): SourceLayout {
|
||||
layoutCache[source.cacheKey]?.let { return it }
|
||||
val startedAt = SystemClock.elapsedRealtime()
|
||||
val videoSize = probeVideoSize(source)
|
||||
val layout =
|
||||
parseVapcSourceLayout(source)
|
||||
// Top-right packed alpha can look like a gray half-frame,
|
||||
// so classify it before the looser split-alpha heuristic.
|
||||
?: detectPackedAlphaTopRightSourceLayout(source)
|
||||
?: detectSplitSourceLayout(source, videoSize)
|
||||
?: normalSourceLayout(videoSize)
|
||||
val elapsedMs = SystemClock.elapsedRealtime() - startedAt
|
||||
Log.d(
|
||||
"GiftMp4Video",
|
||||
"classified path=${source.path} mode=${layout.kind} elapsedMs=$elapsedMs",
|
||||
)
|
||||
layoutCache[source.cacheKey] = layout
|
||||
trimLayoutCache()
|
||||
return layout
|
||||
}
|
||||
|
||||
private fun trimLayoutCache() {
|
||||
while (layoutCache.size > 64) {
|
||||
layoutCache.remove(layoutCache.keys.first())
|
||||
}
|
||||
}
|
||||
|
||||
private fun probeVideoSize(source: GiftMp4Source): VideoSize {
|
||||
val retriever = source.newMetadataRetriever()
|
||||
return try {
|
||||
val width =
|
||||
retriever
|
||||
.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)
|
||||
?.toIntOrNull()
|
||||
?: 1
|
||||
val height =
|
||||
retriever
|
||||
.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)
|
||||
?.toIntOrNull()
|
||||
?: 1
|
||||
val rotation =
|
||||
retriever
|
||||
.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION)
|
||||
?.toIntOrNull()
|
||||
?: 0
|
||||
if (rotation == 90 || rotation == 270) {
|
||||
VideoSize(height, width)
|
||||
} else {
|
||||
VideoSize(width, height)
|
||||
}
|
||||
} finally {
|
||||
retriever.release()
|
||||
}
|
||||
}
|
||||
|
||||
private fun normalSourceLayout(videoSize: VideoSize): SourceLayout {
|
||||
val fullFrame = SourceRect(0f, 0f, 1f, 1f)
|
||||
return SourceLayout(
|
||||
kind = "normal",
|
||||
hasAlphaMask = false,
|
||||
useTextureMatrix = true,
|
||||
flipY = true,
|
||||
contentAspectRatio = videoSize.aspectRatio,
|
||||
colorRect = fullFrame,
|
||||
alphaRect = fullFrame,
|
||||
)
|
||||
}
|
||||
|
||||
private fun splitSourceLayout(
|
||||
candidate: SplitAlphaCandidate,
|
||||
videoSize: VideoSize,
|
||||
): SourceLayout {
|
||||
val left = SourceRect(0f, 0f, 0.5f, 1f)
|
||||
val right = SourceRect(0.5f, 0f, 0.5f, 1f)
|
||||
val top = SourceRect(0f, 0.5f, 1f, 0.5f)
|
||||
val bottom = SourceRect(0f, 0f, 1f, 0.5f)
|
||||
val isHorizontal = candidate.orientation == SplitOrientation.HORIZONTAL
|
||||
val first = if (isHorizontal) left else top
|
||||
val second = if (isHorizontal) right else bottom
|
||||
val outputAspectRatio =
|
||||
if (isHorizontal) {
|
||||
videoSize.width.toFloat() / 2f / videoSize.height.toFloat()
|
||||
} else {
|
||||
videoSize.width.toFloat() / (videoSize.height.toFloat() / 2f)
|
||||
}
|
||||
return SourceLayout(
|
||||
kind = if (isHorizontal) "splitAlphaHorizontal" else "splitAlphaVertical",
|
||||
hasAlphaMask = true,
|
||||
useTextureMatrix = true,
|
||||
flipY = true,
|
||||
contentAspectRatio = outputAspectRatio,
|
||||
colorRect = if (candidate.alphaFirst) second else first,
|
||||
alphaRect = if (candidate.alphaFirst) first else second,
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseVapcSourceLayout(source: GiftMp4Source): SourceLayout? {
|
||||
return try {
|
||||
val json = extractVapcJson(source.readBytes()) ?: return null
|
||||
val info = JSONObject(json).getJSONObject("info")
|
||||
val videoWidth = info.optDouble("videoW", 0.0).toFloat()
|
||||
val videoHeight = info.optDouble("videoH", 0.0).toFloat()
|
||||
val outputWidth = info.optDouble("w", 0.0).toFloat()
|
||||
val outputHeight = info.optDouble("h", 0.0).toFloat()
|
||||
if (videoWidth <= 0f || videoHeight <= 0f || outputWidth <= 0f || outputHeight <= 0f) {
|
||||
return null
|
||||
}
|
||||
|
||||
SourceLayout(
|
||||
kind = "vapc",
|
||||
hasAlphaMask = true,
|
||||
useTextureMatrix = false,
|
||||
flipY = false,
|
||||
contentAspectRatio = outputWidth / outputHeight,
|
||||
colorRect = parseNormalizedRect(info.getJSONArray("rgbFrame"), videoWidth, videoHeight),
|
||||
alphaRect = parseNormalizedRect(info.getJSONArray("aFrame"), videoWidth, videoHeight),
|
||||
)
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseNormalizedRect(
|
||||
values: JSONArray,
|
||||
videoWidth: Float,
|
||||
videoHeight: Float,
|
||||
): SourceRect {
|
||||
return SourceRect(
|
||||
x = (values.getDouble(0).toFloat() / videoWidth).coerceIn(0f, 1f),
|
||||
y = (values.getDouble(1).toFloat() / videoHeight).coerceIn(0f, 1f),
|
||||
width = (values.getDouble(2).toFloat() / videoWidth).coerceIn(0f, 1f),
|
||||
height = (values.getDouble(3).toFloat() / videoHeight).coerceIn(0f, 1f),
|
||||
)
|
||||
}
|
||||
|
||||
private fun extractVapcJson(bytes: ByteArray): String? {
|
||||
val marker = byteArrayOf('v'.code.toByte(), 'a'.code.toByte(), 'p'.code.toByte(), 'c'.code.toByte())
|
||||
var searchStart = 0
|
||||
while (searchStart < bytes.size) {
|
||||
val markerIndex = indexOf(bytes, marker, searchStart)
|
||||
if (markerIndex < 0) return null
|
||||
val scanEnd = minOf(bytes.size, markerIndex + 128)
|
||||
val jsonStart =
|
||||
(markerIndex + marker.size until scanEnd)
|
||||
.firstOrNull { bytes[it] == '{'.code.toByte() }
|
||||
if (jsonStart != null) {
|
||||
val jsonEnd = findJsonEnd(bytes, jsonStart)
|
||||
if (jsonEnd != null) {
|
||||
return String(bytes, jsonStart, jsonEnd - jsonStart + 1, Charsets.UTF_8)
|
||||
}
|
||||
}
|
||||
searchStart = markerIndex + marker.size
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun indexOf(
|
||||
bytes: ByteArray,
|
||||
marker: ByteArray,
|
||||
start: Int,
|
||||
): Int {
|
||||
var index = start
|
||||
while (index <= bytes.size - marker.size) {
|
||||
var matched = true
|
||||
for (offset in marker.indices) {
|
||||
if (bytes[index + offset] != marker[offset]) {
|
||||
matched = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if (matched) return index
|
||||
index++
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
private fun findJsonEnd(
|
||||
bytes: ByteArray,
|
||||
start: Int,
|
||||
): Int? {
|
||||
var depth = 0
|
||||
var inString = false
|
||||
var escaped = false
|
||||
var index = start
|
||||
while (index < bytes.size) {
|
||||
val char = bytes[index].toInt().toChar()
|
||||
if (inString) {
|
||||
when {
|
||||
escaped -> escaped = false
|
||||
char == '\\' -> escaped = true
|
||||
char == '"' -> inString = false
|
||||
}
|
||||
} else {
|
||||
when (char) {
|
||||
'"' -> inString = true
|
||||
'{' -> depth++
|
||||
'}' -> {
|
||||
depth--
|
||||
if (depth == 0) return index
|
||||
}
|
||||
}
|
||||
}
|
||||
index++
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun detectSplitSourceLayout(
|
||||
source: GiftMp4Source,
|
||||
videoSize: VideoSize,
|
||||
): SourceLayout? {
|
||||
val candidate = detectSplitAlpha(source) ?: return null
|
||||
return splitSourceLayout(candidate, videoSize)
|
||||
}
|
||||
|
||||
private fun detectSplitAlpha(source: GiftMp4Source): SplitAlphaCandidate? {
|
||||
val retriever = source.newMetadataRetriever()
|
||||
return try {
|
||||
val durationMs =
|
||||
retriever
|
||||
.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
|
||||
?.toLongOrNull()
|
||||
?: 0L
|
||||
val timesUs = splitSampleTimesUs(durationMs)
|
||||
|
||||
val horizontalScore = SplitScore()
|
||||
val verticalScore = SplitScore()
|
||||
for (timeUs in timesUs) {
|
||||
val frame =
|
||||
retriever.getFrameAtTime(
|
||||
timeUs,
|
||||
MediaMetadataRetriever.OPTION_CLOSEST,
|
||||
) ?: continue
|
||||
try {
|
||||
val horizontalStats = statsByHorizontalHalves(frame)
|
||||
val verticalStats = statsByVerticalHalves(frame)
|
||||
addSplitScore(horizontalScore, horizontalStats.first, horizontalStats.second)
|
||||
addSplitScore(verticalScore, verticalStats.first, verticalStats.second)
|
||||
} finally {
|
||||
frame.recycle()
|
||||
}
|
||||
}
|
||||
val horizontalCandidate =
|
||||
splitCandidateFromScore(SplitOrientation.HORIZONTAL, horizontalScore)
|
||||
val verticalCandidate =
|
||||
splitCandidateFromScore(SplitOrientation.VERTICAL, verticalScore)
|
||||
listOfNotNull(horizontalCandidate, verticalCandidate)
|
||||
.maxByOrNull { it.second }
|
||||
?.first
|
||||
} catch (_: RuntimeException) {
|
||||
null
|
||||
} finally {
|
||||
retriever.release()
|
||||
}
|
||||
}
|
||||
|
||||
private fun splitSampleTimesUs(durationMs: Long): List<Long> {
|
||||
val durationUs = durationMs * 1000L
|
||||
val desiredTimesUs =
|
||||
listOf(
|
||||
300_000L,
|
||||
600_000L,
|
||||
900_000L,
|
||||
1_200_000L,
|
||||
1_500_000L,
|
||||
1_800_000L,
|
||||
2_100_000L,
|
||||
2_400_000L,
|
||||
2_700_000L,
|
||||
3_000_000L,
|
||||
)
|
||||
return desiredTimesUs
|
||||
.filter { durationMs <= 0L || it <= durationUs }
|
||||
.ifEmpty {
|
||||
if (durationUs > 0L) {
|
||||
listOf(durationUs / 2L)
|
||||
} else {
|
||||
listOf(0L)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun detectPackedAlphaTopRightSourceLayout(
|
||||
source: GiftMp4Source,
|
||||
): SourceLayout? {
|
||||
val candidate = detectPackedAlphaTopRight(source) ?: return null
|
||||
val frameWidth = candidate.frameWidth.coerceAtLeast(1)
|
||||
val frameHeight = candidate.frameHeight.coerceAtLeast(1)
|
||||
val alphaStartX = candidate.alphaStartX.coerceIn(1, frameWidth - 1)
|
||||
val alphaEndX = candidate.alphaEndX.coerceIn(alphaStartX, frameWidth - 1)
|
||||
val colorWidth = alphaStartX
|
||||
val alphaWidth = (alphaEndX - alphaStartX + 1).coerceAtLeast(1)
|
||||
val alphaHeight =
|
||||
(frameHeight.toFloat() * alphaWidth.toFloat() / colorWidth.toFloat() + 0.5f)
|
||||
.toInt()
|
||||
.coerceIn(1, frameHeight)
|
||||
val alphaHeightRatio = alphaHeight.toFloat() / frameHeight.toFloat()
|
||||
|
||||
return SourceLayout(
|
||||
kind = "packedAlphaTopRight",
|
||||
hasAlphaMask = true,
|
||||
useTextureMatrix = true,
|
||||
flipY = true,
|
||||
contentAspectRatio = colorWidth.toFloat() / frameHeight.toFloat(),
|
||||
colorRect =
|
||||
SourceRect(
|
||||
x = 0f,
|
||||
y = 0f,
|
||||
width = colorWidth.toFloat() / frameWidth.toFloat(),
|
||||
height = 1f,
|
||||
),
|
||||
alphaRect =
|
||||
SourceRect(
|
||||
x = alphaStartX.toFloat() / frameWidth.toFloat(),
|
||||
y = 1f - alphaHeightRatio,
|
||||
width = alphaWidth.toFloat() / frameWidth.toFloat(),
|
||||
height = alphaHeightRatio,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun detectPackedAlphaTopRight(source: GiftMp4Source): PackedAlphaCandidate? {
|
||||
val retriever = source.newMetadataRetriever()
|
||||
return try {
|
||||
val durationMs =
|
||||
retriever
|
||||
.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
|
||||
?.toLongOrNull()
|
||||
?: 0L
|
||||
val timesUs =
|
||||
listOf(0L, 500_000L, 1_000_000L, 2_000_000L, 3_000_000L)
|
||||
.filter { durationMs == 0L || it <= durationMs * 1000L }
|
||||
.ifEmpty { listOf(0L) }
|
||||
|
||||
val candidates = mutableListOf<PackedAlphaCandidate>()
|
||||
for (timeUs in timesUs) {
|
||||
val frame =
|
||||
retriever.getFrameAtTime(
|
||||
timeUs,
|
||||
MediaMetadataRetriever.OPTION_CLOSEST_SYNC,
|
||||
) ?: continue
|
||||
detectPackedAlphaTopRightInFrame(frame)?.let { candidates.add(it) }
|
||||
frame.recycle()
|
||||
}
|
||||
if (candidates.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val frameWidth = medianInt(candidates.map { it.frameWidth })
|
||||
val frameHeight = medianInt(candidates.map { it.frameHeight })
|
||||
val alphaStartX = medianInt(candidates.map { it.alphaStartX })
|
||||
val alphaEndX = medianInt(candidates.map { it.alphaEndX })
|
||||
val alphaWidth = alphaEndX - alphaStartX + 1
|
||||
val alphaStartRatio = alphaStartX.toDouble() / frameWidth.toDouble()
|
||||
val alphaWidthRatio = alphaWidth.toDouble() / frameWidth.toDouble()
|
||||
val colorToAlphaRatio = alphaStartX.toDouble() / alphaWidth.toDouble()
|
||||
|
||||
if (alphaStartRatio !in 0.58..0.78 ||
|
||||
alphaWidthRatio !in 0.18..0.42 ||
|
||||
colorToAlphaRatio !in 1.45..2.65 ||
|
||||
alphaEndX < frameWidth * 0.82
|
||||
) {
|
||||
return null
|
||||
}
|
||||
PackedAlphaCandidate(
|
||||
frameWidth = frameWidth,
|
||||
frameHeight = frameHeight,
|
||||
alphaStartX = alphaStartX,
|
||||
alphaEndX = alphaEndX,
|
||||
)
|
||||
} catch (_: RuntimeException) {
|
||||
null
|
||||
} finally {
|
||||
retriever.release()
|
||||
}
|
||||
}
|
||||
|
||||
private fun detectPackedAlphaTopRightInFrame(bitmap: Bitmap): PackedAlphaCandidate? {
|
||||
val width = bitmap.width
|
||||
val height = bitmap.height
|
||||
if (width < 12 || height < 12) {
|
||||
return null
|
||||
}
|
||||
|
||||
val searchStart = (width * 0.54f).toInt().coerceIn(0, width - 1)
|
||||
val searchEnd = width
|
||||
val sampleHeight = (height * 0.58f).toInt().coerceIn(1, height)
|
||||
val stepY = maxOf(1, sampleHeight / 220)
|
||||
val rowSamples = ((sampleHeight + stepY - 1) / stepY).coerceAtLeast(1)
|
||||
val threshold = maxOf(8, (rowSamples * 0.32f).toInt())
|
||||
val grayColumns = BooleanArray(width)
|
||||
|
||||
var x = searchStart
|
||||
while (x < searchEnd) {
|
||||
var grayCount = 0
|
||||
var y = 0
|
||||
while (y < sampleHeight) {
|
||||
if (isLikelyAlphaMaskPixel(bitmap.getPixel(x, y))) {
|
||||
grayCount++
|
||||
}
|
||||
y += stepY
|
||||
}
|
||||
grayColumns[x] = grayCount >= threshold
|
||||
x++
|
||||
}
|
||||
|
||||
val minRangeWidth = maxOf(24, (width * 0.12f).toInt())
|
||||
var bestStart = -1
|
||||
var bestEnd = -1
|
||||
var index = searchStart
|
||||
while (index < searchEnd) {
|
||||
while (index < searchEnd && !grayColumns[index]) {
|
||||
index++
|
||||
}
|
||||
if (index >= searchEnd) {
|
||||
break
|
||||
}
|
||||
val start = index
|
||||
while (index < searchEnd && grayColumns[index]) {
|
||||
index++
|
||||
}
|
||||
val end = index - 1
|
||||
if (end - start + 1 >= minRangeWidth && end > bestEnd) {
|
||||
bestStart = start
|
||||
bestEnd = end
|
||||
}
|
||||
}
|
||||
|
||||
if (bestStart < 0 || bestEnd < 0) {
|
||||
return null
|
||||
}
|
||||
return PackedAlphaCandidate(
|
||||
frameWidth = width,
|
||||
frameHeight = height,
|
||||
alphaStartX = bestStart,
|
||||
alphaEndX = bestEnd,
|
||||
)
|
||||
}
|
||||
|
||||
private fun isLikelyAlphaMaskPixel(pixel: Int): Boolean {
|
||||
val red = Color.red(pixel)
|
||||
val green = Color.green(pixel)
|
||||
val blue = Color.blue(pixel)
|
||||
val maxChannel = maxOf(red, green, blue)
|
||||
if (maxChannel < 22) {
|
||||
return false
|
||||
}
|
||||
val minChannel = minOf(red, green, blue)
|
||||
val saturation =
|
||||
if (maxChannel == 0) 0.0 else (maxChannel - minChannel).toDouble() / maxChannel
|
||||
val grayError =
|
||||
(kotlin.math.abs(red - green) +
|
||||
kotlin.math.abs(green - blue) +
|
||||
kotlin.math.abs(blue - red)).toDouble() / (3.0 * 255.0)
|
||||
return saturation < 0.16 && grayError < 0.055
|
||||
}
|
||||
|
||||
private fun medianInt(values: List<Int>): Int {
|
||||
if (values.isEmpty()) {
|
||||
return 0
|
||||
}
|
||||
return values.sorted()[values.size / 2]
|
||||
}
|
||||
|
||||
private fun addSplitScore(
|
||||
score: SplitScore,
|
||||
first: HalfStats,
|
||||
second: HalfStats,
|
||||
) {
|
||||
if (isLowBrightnessFrame(first, second)) {
|
||||
return
|
||||
}
|
||||
val alphaStats = if (first.saturation < second.saturation) first else second
|
||||
val saturationGap = kotlin.math.abs(second.saturation - first.saturation)
|
||||
if (saturationGap < MIN_SPLIT_SATURATION_GAP ||
|
||||
alphaStats.saturation > MAX_SPLIT_MASK_SATURATION ||
|
||||
alphaStats.grayError > MAX_SPLIT_MASK_GRAY_ERROR
|
||||
) {
|
||||
return
|
||||
}
|
||||
score.direction += second.saturation - first.saturation
|
||||
score.saturationGap += saturationGap
|
||||
score.maskSaturation += alphaStats.saturation
|
||||
score.maskGrayError += alphaStats.grayError
|
||||
score.frames++
|
||||
}
|
||||
|
||||
private fun isLowBrightnessFrame(
|
||||
first: HalfStats,
|
||||
second: HalfStats,
|
||||
): Boolean = maxOf(first.brightness, second.brightness) < MIN_USEFUL_FRAME_BRIGHTNESS
|
||||
|
||||
private fun splitCandidateFromScore(
|
||||
orientation: SplitOrientation,
|
||||
score: SplitScore,
|
||||
): Pair<SplitAlphaCandidate, Double>? {
|
||||
if (score.frames < MIN_SPLIT_MATCH_FRAMES) return null
|
||||
val averageSaturationGap = score.saturationGap / score.frames
|
||||
val averageMaskSaturation = score.maskSaturation / score.frames
|
||||
val averageMaskGrayError = score.maskGrayError / score.frames
|
||||
if (averageSaturationGap < MIN_SPLIT_SATURATION_GAP ||
|
||||
averageMaskSaturation > MAX_SPLIT_MASK_SATURATION ||
|
||||
averageMaskGrayError > MAX_SPLIT_MASK_GRAY_ERROR
|
||||
) {
|
||||
return null
|
||||
}
|
||||
return SplitAlphaCandidate(
|
||||
orientation = orientation,
|
||||
alphaFirst = score.direction > 0.0,
|
||||
) to averageSaturationGap
|
||||
}
|
||||
|
||||
private fun statsByHorizontalHalves(bitmap: Bitmap): Pair<HalfStats, HalfStats> {
|
||||
val half = bitmap.width / 2
|
||||
return statsForRect(bitmap, 0, 0, half, bitmap.height) to
|
||||
statsForRect(bitmap, half, 0, bitmap.width, bitmap.height)
|
||||
}
|
||||
|
||||
private fun statsByVerticalHalves(bitmap: Bitmap): Pair<HalfStats, HalfStats> {
|
||||
val half = bitmap.height / 2
|
||||
return statsForRect(bitmap, 0, 0, bitmap.width, half) to
|
||||
statsForRect(bitmap, 0, half, bitmap.width, bitmap.height)
|
||||
}
|
||||
|
||||
private fun statsForRect(
|
||||
bitmap: Bitmap,
|
||||
startX: Int,
|
||||
startY: Int,
|
||||
endX: Int,
|
||||
endY: Int,
|
||||
): HalfStats {
|
||||
val stepX = maxOf(1, (endX - startX) / 80)
|
||||
val stepY = maxOf(1, (endY - startY) / 120)
|
||||
var saturationSum = 0.0
|
||||
var grayErrorSum = 0.0
|
||||
var brightnessSum = 0.0
|
||||
var count = 0
|
||||
var y = startY
|
||||
while (y < endY) {
|
||||
var x = startX
|
||||
while (x < endX) {
|
||||
val pixel = bitmap.getPixel(x, y)
|
||||
val red = Color.red(pixel) / 255.0
|
||||
val green = Color.green(pixel) / 255.0
|
||||
val blue = Color.blue(pixel) / 255.0
|
||||
val maxChannel = maxOf(red, green, blue)
|
||||
val minChannel = minOf(red, green, blue)
|
||||
saturationSum +=
|
||||
if (maxChannel == 0.0) 0.0 else (maxChannel - minChannel) / maxChannel
|
||||
grayErrorSum +=
|
||||
(kotlin.math.abs(red - green) +
|
||||
kotlin.math.abs(green - blue) +
|
||||
kotlin.math.abs(blue - red)) / 3.0
|
||||
brightnessSum += red * 0.299 + green * 0.587 + blue * 0.114
|
||||
count++
|
||||
x += stepX
|
||||
}
|
||||
y += stepY
|
||||
}
|
||||
if (count == 0) {
|
||||
return HalfStats(saturation = 0.0, grayError = 0.0, brightness = 0.0)
|
||||
}
|
||||
return HalfStats(
|
||||
saturation = saturationSum / count,
|
||||
grayError = grayErrorSum / count,
|
||||
brightness = brightnessSum / count,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.org.yumiparty
|
||||
|
||||
import android.content.Context
|
||||
import io.flutter.plugin.common.BinaryMessenger
|
||||
import io.flutter.plugin.common.StandardMessageCodec
|
||||
import io.flutter.plugin.platform.PlatformView
|
||||
import io.flutter.plugin.platform.PlatformViewFactory
|
||||
|
||||
class GiftMp4VideoViewFactory(
|
||||
private val messenger: BinaryMessenger,
|
||||
) : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
|
||||
override fun create(context: Context, viewId: Int, args: Any?): PlatformView {
|
||||
val params = args as? Map<*, *> ?: emptyMap<Any, Any>()
|
||||
return GiftMp4VideoPlatformView(context, viewId, params, messenger)
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
package com.org.yumiparty
|
||||
|
||||
import android.content.Intent
|
||||
import com.android.installreferrer.api.InstallReferrerClient
|
||||
import com.android.installreferrer.api.InstallReferrerStateListener
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
@ -57,6 +58,24 @@ class MainActivity : FlutterActivity() {
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
MethodChannel(
|
||||
flutterEngine.dartExecutor.binaryMessenger,
|
||||
GiftMp4VideoPlatformView.CONTROL_CHANNEL_NAME
|
||||
).setMethodCallHandler { call, result ->
|
||||
GiftMp4VideoPlatformView.handleControlMethod(call, result)
|
||||
}
|
||||
flutterEngine
|
||||
.platformViewsController
|
||||
.registry
|
||||
.registerViewFactory(
|
||||
GiftMp4VideoPlatformView.VIEW_TYPE,
|
||||
GiftMp4VideoViewFactory(flutterEngine.dartExecutor.binaryMessenger)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
setIntent(intent)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
||||
@ -54,7 +54,9 @@ class VoiceRoomForegroundService : Service() {
|
||||
packageManager.getLaunchIntentForPackage(packageName)
|
||||
?: Intent(this, MainActivity::class.java)
|
||||
).apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or
|
||||
Intent.FLAG_ACTIVITY_CLEAR_TOP or
|
||||
Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||
}
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
this,
|
||||
|
||||
BIN
assets/debug/manager-packed-alpha.mp4
Normal file
@ -241,10 +241,11 @@
|
||||
"useCoupontips": "هل أنت متأكد أنك تريد استخدام القسيمة؟",
|
||||
"searchUserId": "ابحث عن معرف المستخدم",
|
||||
"receiveSucc": "تم المطالبة بنجاح",
|
||||
"reject": "رفض",
|
||||
"sendUser": "إرسال المستخدم",
|
||||
"bio": "معلومات شخصية",
|
||||
"delete": "حذف",
|
||||
"reject": "رفض",
|
||||
"sendUser": "إرسال المستخدم",
|
||||
"bio": "معلومات شخصية",
|
||||
"introduction": "مقدمة",
|
||||
"delete": "حذف",
|
||||
"sendCoupontips": "هل أنت متأكد أنك تريد إرسال هذه القسيمة لهذا المستخدم؟",
|
||||
"recallThisMessage": "هل تريد استرجاع هذه الرسالة؟",
|
||||
"copy": "نسخ",
|
||||
@ -255,9 +256,24 @@
|
||||
"signedin": "تم تسجيل الدخول",
|
||||
"following": "يتابع",
|
||||
"inviteYouToBecomeBD": "ندعوك للانضمام كـ BD.",
|
||||
"confirmAcceptTheInvitation": "هل تريد تأكيد قبول الدعوة؟",
|
||||
"confirmDeclineTheInvitation": "تأكيد رفض الدعوة؟",
|
||||
"friends": "أصدقاء",
|
||||
"confirmAcceptTheInvitation": "هل تريد تأكيد قبول الدعوة؟",
|
||||
"confirmDeclineTheInvitation": "تأكيد رفض الدعوة؟",
|
||||
"cpInviteRejectedByOther": "رفض الطرف الآخر أن يصبح {1}.",
|
||||
"closeFriendInvitationTitle": "Invitation from a Close Friend",
|
||||
"closeFriendInvitationSubtitle": "Click to view the invitation",
|
||||
"closeFriendInvitationExpired": "The invitation to add a close friend has expired. This action cannot be performed.",
|
||||
"closeFriendRequestDeclined": "Friend request declined",
|
||||
"closeFriendsHaveBecome": "Have become close friends",
|
||||
"otherAcceptedCloseFriendInvite": "The other party accepted your close friend invitation.",
|
||||
"otherDeclinedCloseFriendInvite": "The other party declined your close friend invitation.",
|
||||
"youAcceptedCloseFriendInvite": "You accepted the other party's close friend invitation.",
|
||||
"youDeclinedCloseFriendInvite": "You declined the other party's close friend invitation.",
|
||||
"closeFriendInviteTimedOut": "Close friend invitation timed out.",
|
||||
"cpDismissConfirmMessage": "Removing this relationship will cost {1} coins. Confirm removal?",
|
||||
"cpRelationCp": "CP",
|
||||
"cpRelationBrother": "Brother",
|
||||
"cpRelationSister": "Sister",
|
||||
"friends": "أصدقاء",
|
||||
"language": "اللغة",
|
||||
"feedback": "ملاحظات",
|
||||
"coupon": "قسيمة",
|
||||
@ -280,14 +296,15 @@
|
||||
"bDLeaderInviteYouToBecomeBDLeader": "ندعوك لتصبح قائد تطوير الأعمال",
|
||||
"win2": "فوز {1}",
|
||||
"level": "مستوى",
|
||||
"wealthLevel": "مستوى الثروة",
|
||||
"userLevel": "مستوى المستخدم",
|
||||
"wealthLevel": "مستوى الثراء",
|
||||
"userLevel": "مستوى الجاذبية",
|
||||
"themeGoToUploadTips": "1. ستتم مراجعة التحميل خلال 24 ساعة بعد نجاحه.\n2. ستُعاد جميع العملات إذا فشلت المراجعة.",
|
||||
"pleaseUploadAccordingToExample": "يرجى الرفع وفقًا للمثال المعروض.",
|
||||
"goToUpload": "الانتقال للرفع",
|
||||
"rechargeAgency": "وكالة الشحن",
|
||||
"logout": "تسجيل الخروج",
|
||||
"adminCenter": "مركز الإدارة",
|
||||
"managerCenter": "مركز المدير",
|
||||
"explore": "استكشاف",
|
||||
"micManagement": "إدارة الميكروفون",
|
||||
"followList": "قائمة المتابعة",
|
||||
@ -552,8 +569,9 @@
|
||||
"days": "أيام",
|
||||
"permanent": "دائم",
|
||||
"unFollow": "إلغاء المتابعة",
|
||||
"searchInputHint": "أدخل رقم الحساب / الغرفة",
|
||||
"kickRoomTips": "لقد تم طردك من الغرفة",
|
||||
"searchInputHint": "أدخل رقم الحساب / الغرفة",
|
||||
"kickRoomTips": "لقد تم طردك من الغرفة",
|
||||
"roomKickedEntryBlockedTips": "لقد أخرجك أحد المشرفين من الغرفة، ولا يمكنك الدخول مؤقتًا.",
|
||||
"joinRoomTips": "انضم إلى الغرفة!",
|
||||
"roomSetting": "إعدادات الغرفة",
|
||||
"roomDetails": "تفاصيل الغرفة",
|
||||
@ -664,8 +682,10 @@
|
||||
"mountains": "مركبات",
|
||||
"buy": "شراء",
|
||||
"visitorList": "قائمة الزوار",
|
||||
"spendCoinsToGainExperiencePoints": "اصرف العملات لكسب نقاط الخبرة",
|
||||
"howToUpgrade": "كيف يمكن الترقية؟",
|
||||
"spendCoinsToGainExperiencePoints": "اصرف العملات لكسب نقاط الخبرة",
|
||||
"richLevelUpgradeAnswer": "دع ثروتك تغزو كل شيء",
|
||||
"charmLevelUpgradeAnswer": "اجعل الآخرين يخضعون لسحرك",
|
||||
"howToUpgrade": "كيف يمكن الترقية؟",
|
||||
"higherLevelFancierAvatarFrame": "مستوى أعلى، شارات/إطار صور أفخم.",
|
||||
"use": "استخدم",
|
||||
"expired": "منتهي",
|
||||
@ -719,5 +739,12 @@
|
||||
"enterRoomFailedRetry": "فشل دخول الغرفة. يرجى المحاولة مرة أخرى.",
|
||||
"voiceConnectionFailedRetry": "فشل الاتصال الصوتي. يرجى المحاولة مرة أخرى.",
|
||||
"doYouWantToKeepTheDraft": "هل تريد الاحتفاظ بالمسودة؟",
|
||||
"duration2": "المدة:{1}"
|
||||
}
|
||||
"duration2": "المدة:{1}",
|
||||
"roomRocketNoteTitle": "ملاحظة",
|
||||
"roomRocketNoteSectionTitle": "في كل مرة يتم فيها شراء مستوى VIP",
|
||||
"roomRocketNoteSectionBody": "1. في كل مرة يتم فيها شراء مستوى VIP، تكون صلاحيته 30 يومًا، ويمكن تجديده يدويًا بعد انتهاء الصلاحية.",
|
||||
"roomRocketRewardEmptyText": "لم تحصل على مكافآت. حظًا أوفر في المرة القادمة.",
|
||||
"roomRocketRecordEmptyText": "لا توجد سجلات مكافآت",
|
||||
"launchRocket": "إطلاق الصاروخ",
|
||||
"roomRocketFuelFullNotice": "اكتمل وقود الصاروخ! سيتم الإطلاق بعد {1} ثانية!"
|
||||
}
|
||||
|
||||
@ -247,6 +247,7 @@
|
||||
"delete": "মুছে ফেলুন",
|
||||
"copy": "কপি করুন",
|
||||
"bio": "জীবনী",
|
||||
"introduction": "পরিচিতি",
|
||||
"useCoupontips": "আপনি কি কুপন ব্যবহার করতে চান?",
|
||||
"searchUserId": "ব্যবহারকারী ID সার্চ করুন",
|
||||
"sendUser": "ব্যবহারকারীকে পাঠান",
|
||||
@ -267,7 +268,7 @@
|
||||
"aboutUs": "আমাদের সম্পর্কে",
|
||||
"theme": "থিম",
|
||||
"wealthLevel": "সম্পদ স্তর",
|
||||
"userLevel": "ব্যবহারকারী স্তর",
|
||||
"userLevel": "আকর্ষণ স্তর",
|
||||
"goToUpload": "আপলোড করতে যান",
|
||||
"logout": "লগআউট",
|
||||
"luck": "ভাগ্য",
|
||||
@ -324,6 +325,21 @@
|
||||
"adminInviteRechargeAgent": "আপনাকে রিচার্জ এজেন্ট হতে আমন্ত্রণ জানাচ্ছি।",
|
||||
"confirmAcceptTheInvitation": "আপনি কি আমন্ত্রণ গ্রহণ করতে নিশ্চিত করছেন?",
|
||||
"confirmDeclineTheInvitation": "আপনি কি আমন্ত্রণ প্রত্যাখ্যান করতে নিশ্চিত করছেন?",
|
||||
"cpInviteRejectedByOther": "অন্য পক্ষ {1} হতে অস্বীকার করেছে।",
|
||||
"closeFriendInvitationTitle": "Invitation from a Close Friend",
|
||||
"closeFriendInvitationSubtitle": "Click to view the invitation",
|
||||
"closeFriendInvitationExpired": "The invitation to add a close friend has expired. This action cannot be performed.",
|
||||
"closeFriendRequestDeclined": "Friend request declined",
|
||||
"closeFriendsHaveBecome": "Have become close friends",
|
||||
"otherAcceptedCloseFriendInvite": "The other party accepted your close friend invitation.",
|
||||
"otherDeclinedCloseFriendInvite": "The other party declined your close friend invitation.",
|
||||
"youAcceptedCloseFriendInvite": "You accepted the other party's close friend invitation.",
|
||||
"youDeclinedCloseFriendInvite": "You declined the other party's close friend invitation.",
|
||||
"closeFriendInviteTimedOut": "Close friend invitation timed out.",
|
||||
"cpDismissConfirmMessage": "Removing this relationship will cost {1} coins. Confirm removal?",
|
||||
"cpRelationCp": "CP",
|
||||
"cpRelationBrother": "Brother",
|
||||
"cpRelationSister": "Sister",
|
||||
"host": "হোস্ট",
|
||||
"following": "ফলো করা হচ্ছে",
|
||||
"agent": "এজেন্ট",
|
||||
@ -442,6 +458,7 @@
|
||||
"bdCenter": "BD সেন্টার",
|
||||
"rechargeAgency": "রিচার্জ এজেন্সি",
|
||||
"adminCenter": "অ্যাডমিন সেন্টার",
|
||||
"managerCenter": "ম্যানেজার সেন্টার",
|
||||
"goldList": "সোনা তালিকা",
|
||||
"followList": "ফলো তালিকা",
|
||||
"fansList": "ফ্যান তালিকা",
|
||||
@ -484,6 +501,7 @@
|
||||
"customized": "কাস্টমাইজড",
|
||||
"searchInputHint": "অ্যাকাউন্ট/রুম নম্বর লিখুন",
|
||||
"kickRoomTips": "আপনাকে রুম থেকে বের করা হয়েছে।",
|
||||
"roomKickedEntryBlockedTips": "একজন অ্যাডমিন আপনাকে রুম থেকে সরিয়ে দিয়েছেন, আপাতত আপনি প্রবেশ করতে পারবেন না।",
|
||||
"joinRoomTips": "রুমে যোগদান করেছেন !",
|
||||
"roomSetting": "রুম সেটিংস",
|
||||
"roomDetails": "রুম বিবরণ",
|
||||
@ -599,6 +617,8 @@
|
||||
"toConsume": "খরচ করার জন্য",
|
||||
"howToUpgrade": "কীভাবে উন্নত করব?",
|
||||
"spendCoinsToGainExperiencePoints": "অভিজ্ঞতা পয়েন্ট পাওয়ার জন্য কয়েন খরচ করুন",
|
||||
"richLevelUpgradeAnswer": "তোমার সম্পদ সবকিছু জয় করুক",
|
||||
"charmLevelUpgradeAnswer": "অন্যদের তোমার আকর্ষণের কাছে নত করো",
|
||||
"higherLevelFancierAvatarFrame": "উচ্চতর স্তর, আরও সুন্দর ব্যাজ/প্রোফাইল ফ্রেম",
|
||||
"all": "সব",
|
||||
"gift": "গিফট",
|
||||
@ -719,5 +739,12 @@
|
||||
"roomMusicListMode": "ক্রম অনুসারে চালান",
|
||||
"roomMusicShuffleMode": "শাফল চালান",
|
||||
"roomMusicSingleMode": "একক গান পুনরাবৃত্তি",
|
||||
"duration2": "সময়:{1}"
|
||||
"duration2": "সময়:{1}",
|
||||
"roomRocketNoteTitle": "নোট",
|
||||
"roomRocketNoteSectionTitle": "প্রতিবার VIP লেভেল কেনার সময়",
|
||||
"roomRocketNoteSectionBody": "1. প্রতিবার VIP লেভেল কেনার পর এটি ৩০ দিনের জন্য বৈধ থাকে এবং মেয়াদ শেষ হলে ম্যানুয়ালি নবায়ন করা যায়।",
|
||||
"roomRocketRewardEmptyText": "কোনো পুরস্কার পাওয়া যায়নি। পরের বার শুভকামনা।",
|
||||
"roomRocketRecordEmptyText": "কোনো পুরস্কারের রেকর্ড নেই",
|
||||
"launchRocket": "রকেট চালু করুন",
|
||||
"roomRocketFuelFullNotice": "রকেটের জ্বালানি পূর্ণ! {1} সেকেন্ড পরে উৎক্ষেপণ!"
|
||||
}
|
||||
|
||||
@ -217,6 +217,7 @@
|
||||
"delete": "Delete",
|
||||
"copy": "Copy",
|
||||
"bio": "Bio",
|
||||
"introduction": "Introduction",
|
||||
"useCoupontips": "Are you sure you want to use the coupon?",
|
||||
"searchUserId": "Search user's ID",
|
||||
"sendUser": "Send User",
|
||||
@ -236,8 +237,8 @@
|
||||
"about": "About",
|
||||
"aboutUs": "About Us",
|
||||
"theme": "Theme",
|
||||
"wealthLevel": "Wealth Level",
|
||||
"userLevel": "User Level",
|
||||
"wealthLevel": "Rich Level",
|
||||
"userLevel": "Charm Level",
|
||||
"goToUpload": "Go to upload",
|
||||
"logout": "Logout",
|
||||
"luck": "Luck",
|
||||
@ -294,6 +295,21 @@
|
||||
"adminInviteRechargeAgent": "Invite you to become a Recharge agent.",
|
||||
"confirmAcceptTheInvitation": "Confirm to accept the invitation?",
|
||||
"confirmDeclineTheInvitation": "Confirm declining the invitation?",
|
||||
"cpInviteRejectedByOther": "The other party declined to become {1}.",
|
||||
"closeFriendInvitationTitle": "Invitation from a Close Friend",
|
||||
"closeFriendInvitationSubtitle": "Click to view the invitation",
|
||||
"closeFriendInvitationExpired": "The invitation to add a close friend has expired. This action cannot be performed.",
|
||||
"closeFriendRequestDeclined": "Friend request declined",
|
||||
"closeFriendsHaveBecome": "Have become close friends",
|
||||
"otherAcceptedCloseFriendInvite": "The other party accepted your close friend invitation.",
|
||||
"otherDeclinedCloseFriendInvite": "The other party declined your close friend invitation.",
|
||||
"youAcceptedCloseFriendInvite": "You accepted the other party's close friend invitation.",
|
||||
"youDeclinedCloseFriendInvite": "You declined the other party's close friend invitation.",
|
||||
"closeFriendInviteTimedOut": "Close friend invitation timed out.",
|
||||
"cpDismissConfirmMessage": "Removing this relationship will cost {1} coins. Confirm removal?",
|
||||
"cpRelationCp": "CP",
|
||||
"cpRelationBrother": "Brother",
|
||||
"cpRelationSister": "Sister",
|
||||
"host": "Host",
|
||||
"following": "Following",
|
||||
"agent": "Agency",
|
||||
@ -412,6 +428,7 @@
|
||||
"bdCenter": "BD Center",
|
||||
"rechargeAgency": "Recharge Agency",
|
||||
"adminCenter": "Admin Center",
|
||||
"managerCenter": "Manager Center",
|
||||
"goldList": "Gold List",
|
||||
"followList": "Follow List",
|
||||
"fansList": "Fans List",
|
||||
@ -454,6 +471,7 @@
|
||||
"customized": "Customized",
|
||||
"searchInputHint": "Enter account/room number",
|
||||
"kickRoomTips": "You have been kicked out of the room.",
|
||||
"roomKickedEntryBlockedTips": "You have been removed from the room by an admin and cannot enter for now.",
|
||||
"joinRoomTips": "Joined the room!",
|
||||
"roomSetting": "Room Setting",
|
||||
"roomDetails": "Room Details",
|
||||
@ -557,7 +575,7 @@
|
||||
"openTheMic": "Open the mic",
|
||||
"muteTheMic": "Mute the mic",
|
||||
"unlockTheMic": "Unlock the mic",
|
||||
"leavelTheMic": "Leave the mic",
|
||||
"leavelTheMic": "Leave mic",
|
||||
"lockTheMic": "Lock the mic",
|
||||
"removeTheMic": "Remove the mic",
|
||||
"inviteToTheMicrophone": "Invite to the microphone",
|
||||
@ -568,6 +586,8 @@
|
||||
"toConsume": "To consume",
|
||||
"howToUpgrade": "How to upgrade?",
|
||||
"spendCoinsToGainExperiencePoints": "Spend Coins to gain experience points",
|
||||
"richLevelUpgradeAnswer": "Let your wealth conquer everything",
|
||||
"charmLevelUpgradeAnswer": "Make others submit to your charm",
|
||||
"higherLevelFancierAvatarFrame": "Higher level, fancier badges/avatar frame",
|
||||
"all": "All",
|
||||
"gift": "Gift",
|
||||
@ -719,5 +739,12 @@
|
||||
"roomMusicListMode": "Play in order",
|
||||
"roomMusicShuffleMode": "Shuffle play",
|
||||
"roomMusicSingleMode": "Single loop",
|
||||
"duration2": "Duration:{1}"
|
||||
"duration2": "Duration:{1}",
|
||||
"roomRocketNoteTitle": "Note",
|
||||
"roomRocketNoteSectionTitle": "Each time a VIP",
|
||||
"roomRocketNoteSectionBody": "1. Each time a VIP level is purchased, it is valid for 30 days, and can be manually renewed after expiration.",
|
||||
"roomRocketRewardEmptyText": "No reward",
|
||||
"roomRocketRecordEmptyText": "No reward records",
|
||||
"launchRocket": "Launch Rocket",
|
||||
"roomRocketFuelFullNotice": "Rocket fuel is full! Launching in {1}s!"
|
||||
}
|
||||
|
||||
750
assets/l10n/intl_fr.json
Normal file
@ -0,0 +1,750 @@
|
||||
{
|
||||
"signInWithGoogle": "Connectez-vous avec Google",
|
||||
"or": "Ou",
|
||||
"signInWithYourAccount": "Connectez-vous avec votre compte",
|
||||
"signInWithApple": "Connectez-vous avec Apple",
|
||||
"loginRepresentsAgreementTo": "La connexion représente l'accord de",
|
||||
"termsofService": "Conditions d'utilisation",
|
||||
"privaceyPolicy": "Politique de confidentialité",
|
||||
"tips": "Conseils",
|
||||
"searchNoDataTips": "Saisissez la salle ou l'ID utilisateur que vous souhaitez rechercher.",
|
||||
"games": "Jeux",
|
||||
"mine": "Le mien",
|
||||
"official": "Officiel",
|
||||
"party": "Fête",
|
||||
"other": "Autre",
|
||||
"yes": "Oui",
|
||||
"bag": "Sac",
|
||||
"sound2": "Son",
|
||||
"startYourBrandNewJourney": "Commencez votre tout nouveau voyage",
|
||||
"maliciousHarassment": "Harcèlement malveillant",
|
||||
"event": "Événement",
|
||||
"roomEdit": "Modifier la pièce",
|
||||
"cancelRoomPassword": "Êtes-vous sûr de vouloir supprimer le mot de passe de la salle ?",
|
||||
"roomMemberFee": "Frais de membre de la chambre",
|
||||
"roomTheme2": "Thème de la chambre",
|
||||
"background": "Contexte",
|
||||
"blockedList2": "Liste bloquée",
|
||||
"roomPassword": "Mot de passe de la salle",
|
||||
"numberOfMic": "Nombre de micro",
|
||||
"pleaseEnterContent": "Veuillez saisir le contenu",
|
||||
"profilePhoto": "Photo de profil",
|
||||
"aboutMe": "À propos de moi",
|
||||
"myRoom": "Ma chambre",
|
||||
"noHistoricalRecordsAvailable": "Aucun document historique disponible.",
|
||||
"inviteNewUsersToEarnCoins": "Invitez de nouveaux utilisateurs à gagner des pièces",
|
||||
"crateMyRoom": "CRÉEZ VOTRE PROPRE CHAMBRE.",
|
||||
"casualInteraction": "Interaction occasionnelle",
|
||||
"haveGamePlayingTips": "Vous avez un jeu en cours. Veuillez quitter le jeu en cours. Êtes-vous sûr de vouloir quitter ?",
|
||||
"historicalTour": "Visite historique",
|
||||
"gameCenter": "Centre de jeux",
|
||||
"returnToVoiceChat": "Revenir au chat vocal ?",
|
||||
"exitGameMode": "Quitter le mode jeu",
|
||||
"enterTheRoom": "Entrez dans la pièce",
|
||||
"invite": "Inviter",
|
||||
"recent": "Récent",
|
||||
"popularEvents": "Événements populaires",
|
||||
"inviteGoRoomTips": "Toujours là pour vous, beau temps, mauvais temps. Venez nous dire bonjour !",
|
||||
"confirmInviteThisUserToTheRoom": "Confirmer l'invitation de cet utilisateur (ID : {1}) dans le salon ?",
|
||||
"clearCacheSuccessfully": "Vider le cache avec succès",
|
||||
"sent": "Envoyé",
|
||||
"keep": "Garder",
|
||||
"open": "Ouvert",
|
||||
"deleteAccountTips2": "*Si vous changez d'avis, vous pouvez vous reconnecter à votre compte actuel dans les sept jours et nous restaurerons automatiquement votre compte. Si aucune restauration n'intervient dans les sept jours, le compte sera définitivement supprimé.",
|
||||
"deleteAccountTips": "Vous disposez de tous les droits d'administration sur ce compte. Si vous avez l'intention de supprimer le compte, veuillez être conscient des risques suivants associés à cette opération :\n1. Une fois le compte supprimé avec succès, vous ne pourrez plus vous connecter à votre compte actuel. La suppression du compte est une action permanente.\n2. Une fois le compte supprimé avec succès, vous ne pourrez plus récupérer aucune donnée du compte. Toutes les informations (y compris les chambres et les amis), la monnaie virtuelle, les cadeaux et les objets virtuels seront définitivement supprimés et ne pourront pas être restaurés.\n3. Délai de réflexion : si vous ne restaurez pas le compte, vous ne pourrez pas accéder à la page d'achat, à la page de retrait ou à toute autre page de l'application.\n4. Pendant la période de réflexion ou après la suppression du compte, la page de profil indiquera qu'il a été supprimé. Pour protéger votre compte contre toute recherche ou accès par d'autres personnes, vos informations personnelles seront supprimées des systèmes liés aux fonctions quotidiennes. Lorsque la suppression du compte implique la sécurité nationale, des procédures civiles ou pénales, ou la protection des droits et intérêts légitimes de tiers, l'équipe officielle se réserve le droit de rejeter la demande de suppression du compte de l'utilisateur.\nSi vous êtes certain de vouloir supprimer toutes les données personnelles de votre compte actuel, veuillez cliquer sur « Supprimer le compte ».",
|
||||
"accountDeletionNotice": "Avis de suppression de compte :",
|
||||
"thisUserHasBeenBlacklisted": "Cet utilisateur a été mis sur liste noire.",
|
||||
"trend": "Tendance",
|
||||
"like": "Comme",
|
||||
"more": "Plus",
|
||||
"discard": "Jeter",
|
||||
"catchFirstComment": "Attrapez le premier commentaire",
|
||||
"reply": "Répondre",
|
||||
"posting": "Publication",
|
||||
"multiple": "Plusieurs",
|
||||
"successfullyRemovedFromTheBlacklist": "Supprimé avec succès de la liste noire !",
|
||||
"successfullyAddedToTheBlacklist": "Ajout réussi à la liste noire !",
|
||||
"youAreCurrentlyCPRelationshipPleaseDissolve": "Vous êtes actuellement en relation CP.\nVeuillez d'abord le dissoudre.",
|
||||
"areYouSureToCancelBlacklist": "Etes-vous sûr d'annuler la liste noire ?",
|
||||
"areYouSureYouWantToBlockThisUser": "Êtes-vous sûr de vouloir bloquer cet utilisateur ?",
|
||||
"removeFromBlacklist": "Supprimer de la liste noire",
|
||||
"moveToBlacklist": "Passer à la liste noire",
|
||||
"userBlacklist": "Liste noire des utilisateurs",
|
||||
"specialEffectsManagement": "Gestion des effets spéciaux",
|
||||
"wishingYouHappinessEveryDay": "Je vous souhaite du bonheur chaque jour.",
|
||||
"newMessage": "Nouveau message",
|
||||
"createRoomSuccsess": "Salle créée avec succès !",
|
||||
"contactUs": "Contactez-nous",
|
||||
"systemAnnouncementTips1": "Se prémunir contre la fraude :",
|
||||
"systemAnnouncementTips": "Vérifiez les informations uniquement via les canaux officiels. Ne téléchargez jamais de logiciels tiers, ne partagez jamais de données personnelles et ne transférez jamais d’argent sur la base de demandes externes. Les identifiants officiels du personnel sont uniquement 10000, 10003 et 10086. Pour tout soupçon, arrêtez-vous et signalez-le via",
|
||||
"systemAnnouncement": "Annonce du système",
|
||||
"doNotClickUnfamiliarTips": "Ne cliquez pas sur des liens inconnus, car ils peuvent exposer vos informations personnelles. Ne partagez jamais vos informations d’identification ou de carte bancaire avec qui que ce soit.",
|
||||
"atTag": "@Étiquette",
|
||||
"sayHi2": "Dis bonjour",
|
||||
"roomBottomGreeting": "Salut...",
|
||||
"canSendMsgTips": "Les deux parties doivent se suivre avant de pouvoir envoyer des messages privés.",
|
||||
"msgSendRedEnvelopeTips": "*Des frais de service de 10 % seront facturés sur les enveloppes rouges et les destinataires ne recevront que 90 % de la valeur de l'enveloppe rouge. Le niveau de richesse de l'expéditeur doit être supérieur au niveau 10.",
|
||||
"reapply": "Réappliquer",
|
||||
"cancelRequest": "Annuler la demande",
|
||||
"pending": "En attente",
|
||||
"supporter": "Partisan",
|
||||
"coinsReceived": "Pièces reçues",
|
||||
"numberOfSign": "Numéro de signe : {1}",
|
||||
"hostWeeklyRank": "Classement hebdomadaire des hôtes",
|
||||
"supporterWeeklyRank": "Classement hebdomadaire des supporters",
|
||||
"memberList": "Liste des membres",
|
||||
"treasureChest": "Coffre au trésor",
|
||||
"applicationRecord": "Dossier de candidature",
|
||||
"appUpdateTip": "L'application a une nouvelle version ({1}). Souhaitez-vous le télécharger maintenant ?",
|
||||
"ownerIncomeCoins": "Revenu du propriétaire : pièces {1}",
|
||||
"game": "Jeu",
|
||||
"skip2": "Sauter",
|
||||
"skipCountdown": "Sauter {1}s",
|
||||
"coins4": "Pièces de monnaie",
|
||||
"weekStart": "Début de semaine",
|
||||
"forMoreRewardsPleaseCheckTheTaskCenter": "Pour plus de récompenses, veuillez consulter le centre de tâches",
|
||||
"kingQuuen": "Roi-Reine",
|
||||
"ramadan": "Ramadan",
|
||||
"updateNow": "Mettre à jour maintenant",
|
||||
"allGames": "Tous les jeux",
|
||||
"fishClass": "Classe de poisson",
|
||||
"greedyClass": "Classe gourmande",
|
||||
"raceSeries": "Série de courses",
|
||||
"slotsClass": "Classe de machines à sous",
|
||||
"others": "D'autres",
|
||||
"hotGames": "Jeux chauds",
|
||||
"chatBox": "Boîte de discussion",
|
||||
"termsOfServicePrivacyPolicyTips": "En continuant, vous acceptez les conditions d'utilisation et la politique de confidentialité",
|
||||
"and": "et",
|
||||
"pleaseSelectTheTypeContent": "Veuillez sélectionner le type de contenu offensant.",
|
||||
"illegalInformation": "Informations illégales",
|
||||
"inappropriateContent": "Contenu inapproprié",
|
||||
"personalAttack": "Attaque personnelle",
|
||||
"confirm": "Confirmer",
|
||||
"spam": "Pourriel",
|
||||
"countdownMinutes": "Minutes du compte à rebours :",
|
||||
"number2": "Numéro:",
|
||||
"fraud": "Fraude",
|
||||
"received": "Reçu",
|
||||
"currentProgress": "Progrès actuels",
|
||||
"currentStage": "Stade actuel : {1}",
|
||||
"roomReward2": "Récompense de salle : {1}",
|
||||
"roomReward": "Récompense de chambre",
|
||||
"expirationTime": "Délai d'expiration",
|
||||
"ownerSendTheRedEnvelope": "Le propriétaire a envoyé des pièces de récompense.",
|
||||
"rewardCoins": "Pièces de récompense : pièces {1}",
|
||||
"signInRewardReceived": "Connectez-vous avec succès. Récompense : {1}",
|
||||
"registerRewardReceived": "Récompense d'inscription reçue : {1}",
|
||||
"lastWeekProgress": "Les progrès de la semaine dernière",
|
||||
"redEnvelopeTips2": "*Si l'enveloppe rouge n'est pas réclamée dans le délai imparti, les pièces restantes seront restituées à l'utilisateur qui a envoyé l'enveloppe rouge.",
|
||||
"goToRecharge": "Allez recharger",
|
||||
"deleteAccount2": "Supprimer le compte ({1}s)",
|
||||
"areYouSureYouWantToDeleteYourAccount": "Êtes-vous sûr de vouloir supprimer votre compte ?",
|
||||
"insufhcientGoldsGoToRecharge": "Or insuffisant. Rechargez maintenant !",
|
||||
"coins2": "Pièces {1}",
|
||||
"remainingNumberTips": "Restant disponible : ({1}/{2})",
|
||||
"collectionTimeTips": "Heure de collecte : {1} ({2}/{3})",
|
||||
"sendARedEnvelope": "Envoyez une enveloppe rouge",
|
||||
"sendRedPackConfirmTips": "Êtes-vous sûr de vouloir envoyer le paquet rouge ?",
|
||||
"redEnvelopeSendingRecords": "Enveloppe rouge envoyant des enregistrements :",
|
||||
"redEnvelope": "Enveloppe rouge",
|
||||
"roomRedPacketGoldCoins": "Pièces d'or",
|
||||
"roomRedPacketQuantity": "Quantité",
|
||||
"roomRedPacketPickupTime": "Heure de ramassage",
|
||||
"roomRedPacketInMinutes": "En minutes {1}",
|
||||
"roomRedPacketClaimNow": "Réclamez maintenant",
|
||||
"roomRedPacketSendLuckyPack": "Envoyez un pack porte-bonheur",
|
||||
"roomRedPacketSending": "Envoi...",
|
||||
"roomRedPacketSendSuccess": "Pack porte-bonheur envoyé",
|
||||
"roomRedPacketComingSoon": "Bientôt disponible",
|
||||
"roomRedPacketRulesTitle": "Règles",
|
||||
"roomRedPacketRulesContent": "1. Vous pouvez dépenser des pièces pour envoyer un Lucky Pack. Si d'autres personnes ouvrent votre Lucky Pack, ils peuvent recevoir une quantité aléatoire de pièces.\n\n2. Deux heures après l'envoi d'un Lucky Pack, toutes les pièces non réclamées seront restituées dans votre portefeuille.\n\n3. Après avoir envoyé un Lucky Pack à l'échelle du serveur, une bannière Lucky Pack à l'échelle du serveur sera déclenchée, vous attirant davantage d'attention.\n\n4. Vous pouvez envoyer des Lucky Packs à la salle %1$s par jour.\n\n5. Les Lucky Packs envoyés par des utilisateurs en dehors de votre région ne peuvent pas être réclamés.",
|
||||
"roomRedPacketHistoryTitle": "Historique de livraison des packs chanceux",
|
||||
"roomRedPacketHistoryItemTitle": "Sac porte-bonheur (enveloppes rouges {1})",
|
||||
"roomRedPacketHistoryStatusSent": "Envoyé",
|
||||
"roomRedPacketHistoryStatusFinished": "Entièrement réclamé",
|
||||
"roomRedPacketHistoryStatusPending": "En attente",
|
||||
"roomRedPacketHistoryStatusReturned": "Retourné",
|
||||
"roomRedPacketHistoryRefund": "Remboursement : {1}",
|
||||
"roomRedPacketClaimRecordsTitle": "Dossiers de réclamation",
|
||||
"roomRedPacketChatMessage": "Je t'ai envoyé une enveloppe rouge, ouvre-la tout de suite !",
|
||||
"roomRedPacketReceivedMessage": "Réclamez des pièces {1} dans le pack chanceux {2}",
|
||||
"roomRedPacketOpenSubtitle": "Le Lucky Pack est arrivé",
|
||||
"roomRedPacketOpenYouGot": "Vous obtenez",
|
||||
"roomRedPacketClaimSuccessTips": "Les pièces ont été envoyées dans votre portefeuille.",
|
||||
"roomRedPacketOpenSoldOut": "Le Lucky Pack est déjà épuisé.",
|
||||
"roomRedPacketOpenExpired": "Le Lucky Pack a expiré.",
|
||||
"roomRedPacketRegionNotMatch": "Seuls les utilisateurs de la même région linguistique peuvent réclamer ce pack chanceux.",
|
||||
"roomRedPacketNotStarted": "Le pack porte-bonheur n’est pas encore prêt.",
|
||||
"roomRedPacketDailyLimitExceeded": "La limite d'envoi d'aujourd'hui a été atteinte.",
|
||||
"roomRedPacketInsufficientBalance": "Pièces d'or insuffisantes.",
|
||||
"roomRedPacketPresenceRequired": "Veuillez rester dans la chambre pour utiliser les packs porte-bonheur.",
|
||||
"roomRedPacketNetworkError": "Impossible de traiter le pack porte-bonheur pour le moment.",
|
||||
"roomRedPacketAlreadyClaimed": "Vous avez déjà réclamé ce pack porte-bonheur.",
|
||||
"roomRedPacketOpenWaiting": "Peut être ouvert en quelques minutes {1}.",
|
||||
"roomRedPacketOpenWaitingCountdown": "Disponible pour ramassage après {1}",
|
||||
"roomRedPacketOkay": "D'accord",
|
||||
"roomRedPacketFloatingTitle": "{1} a envoyé une enveloppe rouge",
|
||||
"roomRedPacketFloatingSubtitle": "J'ai envoyé une enveloppe rouge",
|
||||
"redEnvelopeRecTips2": "Les enveloppes rouges ont toutes été réclamées.",
|
||||
"redEnvelopeRecTips3": "Le délai de collecte des enveloppes rouges est expiré !",
|
||||
"openTheTreasureChest": "Ouvrez le coffre au trésor",
|
||||
"redEnvelopeRecTips1": "Les pièces gagnées ont été déposées dans votre portefeuille.",
|
||||
"redEnvelopeTips1": "Pièces :",
|
||||
"roomTools": "Outils de la salle :",
|
||||
"entertainment": "Divertissement :",
|
||||
"reportSucc": "Rapport réussi",
|
||||
"pornography": "Pornographie",
|
||||
"reportInputTips": "Veuillez décrire le problème de manière aussi détaillée que possible afin que nous puissions le comprendre et le résoudre.",
|
||||
"cancel": "Annuler",
|
||||
"join": "Rejoindre",
|
||||
"items": "Articles",
|
||||
"vistors": "Visiteurs",
|
||||
"fans": "Ventilateurs",
|
||||
"balanceNotEnough": "Solde de pièces d’or insuffisant. Voulez-vous aller recharger ?",
|
||||
"skip": "Sauter {1}",
|
||||
"letGoToWatch": "Allons regarder !",
|
||||
"launchedARocket": "a lancé une fusée",
|
||||
"sendUserId": "Envoyer l'ID utilisateur : {1}",
|
||||
"giveUpIdentity": "Abandonner son identité",
|
||||
"leaveRoomIdentityTips": "Êtes-vous sûr de vouloir abandonner l'identité de la salle ?",
|
||||
"joinMemberTips2": "Voulez-vous confirmer votre adhésion à la salle en tant que membre ?",
|
||||
"sureUnfollowThisRoom": "Vous êtes sûr de ne plus suivre cette salle ?",
|
||||
"welcomeMessage": "Bienvenue sur notre application, {name} !",
|
||||
"settings": "Paramètres",
|
||||
"account": "Compte",
|
||||
"common": "Commun",
|
||||
"delete": "Supprimer",
|
||||
"copy": "Copier",
|
||||
"bio": "Biographie",
|
||||
"introduction": "Présentation",
|
||||
"useCoupontips": "Êtes-vous sûr de vouloir utiliser le coupon ?",
|
||||
"searchUserId": "Rechercher l'ID de l'utilisateur",
|
||||
"sendUser": "Envoyer l'utilisateur",
|
||||
"hobby": "Passe-temps",
|
||||
"sendCoupontips": "Êtes-vous sûr de vouloir envoyer ce coupon à cet utilisateur ?",
|
||||
"youDontHaveAnyCouponsYet": "Vous n'avez pas encore de coupons.",
|
||||
"recall": "Rappel",
|
||||
"youHaventFollowed": "Vous n'avez suivi aucune pièce",
|
||||
"deleteFromMyDevice": "Supprimer de mon appareil",
|
||||
"deleteOnAllDevices": "Supprimer sur tous les appareils",
|
||||
"messageHasBeenRecalled": "Ce message a été rappelé",
|
||||
"recallThisMessage": "Vous vous souvenez de ce message ?",
|
||||
"language": "Langue",
|
||||
"feedback": "Commentaires",
|
||||
"signedin": "Connecté",
|
||||
"receiveSucc": "Revendiqué avec succès",
|
||||
"about": "À propos",
|
||||
"aboutUs": "À propos de nous",
|
||||
"theme": "Thème",
|
||||
"wealthLevel": "Niveau de richesse",
|
||||
"userLevel": "Niveau de charme",
|
||||
"goToUpload": "Aller au téléchargement",
|
||||
"logout": "Déconnexion",
|
||||
"luck": "Chance",
|
||||
"level": "Niveau",
|
||||
"themeGoToUploadTips": "1. L'examen sera terminé dans les 24 heures suivant la réussite du téléchargement.\n2. Toutes les pièces seront restituées si l'examen échoue.",
|
||||
"pleaseUploadAccordingToExample": "Veuillez télécharger selon l'exemple fourni.",
|
||||
"home": "Accueil",
|
||||
"explore": "Explorer",
|
||||
"me": "Moi",
|
||||
"socialPrivilege": "Privilège social",
|
||||
"information": "Informations",
|
||||
"myPhoto": "Ma photo",
|
||||
"areYouSureYouWantToSpend3": "*Si l'autre partie refuse l'invitation CP, vos pièces seront restituées dans votre portefeuille.",
|
||||
"areYouSureYouWantToSpend": "Êtes-vous sûr de vouloir dépenser",
|
||||
"areYouSureYouWantToSpend2": "envoyer une invitation CP à cet utilisateur ?",
|
||||
"underReview": "En cours de révision",
|
||||
"doYouWantToDeleteIt": "Voulez-vous le supprimer ?",
|
||||
"chooseFromAblum": "Choisissez parmi l'album",
|
||||
"spaceBackground": "Fond d'espace",
|
||||
"editProfile": "Modifier le profil",
|
||||
"sendTheCpRequest": "Envoyer la demande de CP",
|
||||
"addCp": "Ajouter un PC",
|
||||
"partWays": "Chemins partiels",
|
||||
"reconcile": "Réconcilier",
|
||||
"separated": "Séparé",
|
||||
"areYouSureYouWantToSpend5": "{1} vous a avoué ce sentiment ; si vous acceptez, vous deviendrez un couple.",
|
||||
"areYouSureYouWantToSpend6": "{1} souhaite se remettre avec vous. Si vous décidez de procéder à une réconciliation, toutes vos données précédentes seront restaurées.",
|
||||
"reconcileInvitationTips": "*Si l'autre partie refuse l'invitation CP, vos pièces seront restituées dans votre portefeuille.",
|
||||
"reconcileInvitation": "Réconcilier l'invitation",
|
||||
"areYouSureYouWantToSpend4": "envoyer une invitation de rapprochement à cet utilisateur ?",
|
||||
"partWaysTips": "*Si l'un des partenaires d'un couple choisit de se séparer, il y aura un délai de réflexion de 7 jours. Pendant ce temps, les deux parties peuvent choisir de se réconcilier et toutes les données seront restaurées lors du rapprochement. Si aucun rapprochement n'est choisi à l'issue du délai de 7 jours, les données du couple seront effacées.",
|
||||
"areYouSureYouWantToPartWaysWithYourCP": "Êtes-vous sûr de vouloir vous séparer de votre CP ?",
|
||||
"timeSpentTogether": "Temps passé ensemble : jours {1}",
|
||||
"firstDay": "Premier jour : {1}",
|
||||
"numberOfMyCPs": "Nombre de mes CP :({1}/{2})",
|
||||
"props": "Accessoires",
|
||||
"win": "Gagner",
|
||||
"dice": "Dés",
|
||||
"rps": "RPS",
|
||||
"operationFail": "L'opération a échoué.",
|
||||
"enterRoomFailedRetry": "Impossible d'entrer dans la pièce. Veuillez réessayer.",
|
||||
"voiceConnectionFailedRetry": "La connexion vocale a échoué. Veuillez réessayer.",
|
||||
"likedYourComment": "J'ai aimé votre commentaire.",
|
||||
"doYouWantToKeepTheDraft": "Voulez-vous conserver le brouillon ?",
|
||||
"operationsAreTooFrequent": "Les opérations sont trop fréquentes",
|
||||
"luckNumber": "Numéro de chance",
|
||||
"relationShip": "Relation",
|
||||
"couple": "Couple {1} :",
|
||||
"couple2": "couple",
|
||||
"reject": "Rejeter",
|
||||
"accept": "Accepter",
|
||||
"noMatchedCP": "Aucun CP correspondant",
|
||||
"inviteYouToBecomeBD": "Vous invite à devenir BD.",
|
||||
"adminInviteRechargeAgent": "Vous invite à devenir agent Recharge.",
|
||||
"confirmAcceptTheInvitation": "Confirmer pour accepter l'invitation ?",
|
||||
"confirmDeclineTheInvitation": "Confirmer le refus de l'invitation ?",
|
||||
"cpInviteRejectedByOther": "L'autre personne a refusé de devenir {1}.",
|
||||
"closeFriendInvitationTitle": "Invitation from a Close Friend",
|
||||
"closeFriendInvitationSubtitle": "Click to view the invitation",
|
||||
"closeFriendInvitationExpired": "The invitation to add a close friend has expired. This action cannot be performed.",
|
||||
"closeFriendRequestDeclined": "Friend request declined",
|
||||
"closeFriendsHaveBecome": "Have become close friends",
|
||||
"otherAcceptedCloseFriendInvite": "The other party accepted your close friend invitation.",
|
||||
"otherDeclinedCloseFriendInvite": "The other party declined your close friend invitation.",
|
||||
"youAcceptedCloseFriendInvite": "You accepted the other party's close friend invitation.",
|
||||
"youDeclinedCloseFriendInvite": "You declined the other party's close friend invitation.",
|
||||
"closeFriendInviteTimedOut": "Close friend invitation timed out.",
|
||||
"cpDismissConfirmMessage": "Removing this relationship will cost {1} coins. Confirm removal?",
|
||||
"cpRelationCp": "CP",
|
||||
"cpRelationBrother": "Brother",
|
||||
"cpRelationSister": "Sister",
|
||||
"host": "Hôte",
|
||||
"following": "Suite",
|
||||
"agent": "Agence",
|
||||
"approved": "Approuvé",
|
||||
"approvedWithinOneMinute": "Approuvé et affiché dans la minute suivant la soumission",
|
||||
"onlineUsers": "Utilisateurs en ligne ({1}/{2}) :",
|
||||
"applyToJoin": "Postuler pour rejoindre",
|
||||
"supporterList": "Liste des supporters",
|
||||
"hostList": "Liste des hôtes",
|
||||
"upToAdmins": "Jusqu'à Administrateurs {1}",
|
||||
"upToMembers": "Jusqu'à membres {1}",
|
||||
"levelPrivileges": "Privilèges de niveau",
|
||||
"ra": "RA",
|
||||
"roomAnnouncement": "Annonce de la salle",
|
||||
"help": "Aide",
|
||||
"rejected": "Rejeté",
|
||||
"boxContributeTips": "Des investissements ont déjà été réalisés aujourd'hui. S’il vous plaît, n’investissez plus.",
|
||||
"bd": "BD",
|
||||
"coupon": "Coupon",
|
||||
"search": "Rechercher",
|
||||
"get": "Obtenir",
|
||||
"inRocket": "Dans la fusée",
|
||||
"roomRocketHelpTips": "1. Envoyer des cadeaux dans la pièce augmente l’énergie de la fusée. *1 cadeau de pièce d'or = 1 point d'énergie de fusée ; Les cadeaux porte-bonheur augmentent l'énergie de la fusée de 4 % de la valeur de la pièce d'or du cadeau.\n2. Une fois l’énergie de la fusée complètement chargée, la pièce peut lancer la fusée. Les récompenses seront automatiquement distribuées après le lancement.\n3. Différents niveaux de fusée offrent différentes récompenses.\n4. Lorsque la fusée décolle, tous les utilisateurs présents dans la pièce peuvent réclamer la récompense de la fusée.\n5. L'énergie de la fusée est réinitialisée à minuit chaque jour.",
|
||||
"couponRecord": "Historique d'utilisation du coupon",
|
||||
"inRoom": "Dans la chambre",
|
||||
"searchCouponHint": "Rechercher un coupon",
|
||||
"giftCounter": "Compteur de cadeaux",
|
||||
"bDLeaderInviteYouToBecomeBDLeader": "Vous invite à devenir BDLeader",
|
||||
"wins": "gagne",
|
||||
"inviteYouToBecomeHost": "Vous invite à devenir hôte.",
|
||||
"friends": "Amis",
|
||||
"deleteConversationTips": "Êtes-vous sûr de vouloir supprimer l'historique des discussions avec cet utilisateur ?",
|
||||
"propMessagePrompt": "Invite de message d'accessoire",
|
||||
"inputUserId": "Entrez l'ID utilisateur",
|
||||
"fromLuckyGifts": "Des cadeaux chanceux/magiques",
|
||||
"receive": "Recevoir",
|
||||
"checkInSuccessful": "Enregistrement réussi",
|
||||
"sginTips": "Vous recevrez une récompense lors de votre première connexion chaque jour. Si vous interrompez votre séquence de connexion, la récompense sera recalculée à partir du premier jour de votre nouvelle connexion.",
|
||||
"popular": "Populaire",
|
||||
"recommend": "Recommander",
|
||||
"follow": "Suivre",
|
||||
"history": "Histoire",
|
||||
"hotRooms": "Chambres chaudes",
|
||||
"viewMore": "Voir plus",
|
||||
"noData": "Aucune donnée",
|
||||
"users": "Utilisateurs",
|
||||
"rooms": "Chambres",
|
||||
"coins": "pièces de monnaie",
|
||||
"unread": "Non lu",
|
||||
"read": "Lire",
|
||||
"image": "[image]",
|
||||
"video": "[Vidéo]",
|
||||
"sound": "[Son]",
|
||||
"gift2": "[Cadeau]",
|
||||
"clickHereToStartChatting": "Dis quelque chose.....",
|
||||
"receivedAMessage": "[Reçu un message]",
|
||||
"confirmSwitchMicModelTips": "Confirmez-vous le passage en mode assis ?",
|
||||
"number": "Numéro",
|
||||
"album": "Album",
|
||||
"camera": "Appareil photo",
|
||||
"system": "Système",
|
||||
"notifcation": "Notification",
|
||||
"inviteYouToBecomeAgent": "Vous invite à devenir une agence.",
|
||||
"myMusic": "Ma musique",
|
||||
"add": "Ajouter",
|
||||
"pullToLoadMore": "Tirez pour charger davantage",
|
||||
"loadingFailedClickToRetry": "Échec du chargement, cliquez pour réessayer",
|
||||
"releaseToLoadMore": "Relâchez pour charger plus",
|
||||
"haveMyLimits": "---J'ai mes limites.---",
|
||||
"music": "Musique",
|
||||
"free": "Gratuit",
|
||||
"charm": "Charme cadeau",
|
||||
"start": "Commencer",
|
||||
"stop": "Arrêter",
|
||||
"chats": "Discussions",
|
||||
"refuse": "Refuser",
|
||||
"agree": "D'accord",
|
||||
"thisFeatureIsCurrentlyUnavailable": "Cette fonctionnalité est actuellement indisponible.",
|
||||
"pleaseSelectTheRecipient": "Veuillez sélectionner le destinataire.",
|
||||
"searchMemberIdHint": "Veuillez saisir l'identifiant du membre",
|
||||
"unclaimedRedEnvelopes": "Les enveloppes rouges non réclamées sont remboursées en 24 heures.",
|
||||
"redEnvelopeNotYetClaimed": "Enveloppe rouge pas encore réclamée.",
|
||||
"redEnvelopeAmount": "Montant de l'enveloppe rouge : pièces {1}",
|
||||
"sentARedEnvelope": "envoyé une enveloppe rouge.",
|
||||
"theRedEnvelopeHasExpired": "L'enveloppe rouge est expirée.",
|
||||
"joinRequest": "Demande d'adhésion",
|
||||
"scrollToTheBottom": "Faites défiler vers le bas",
|
||||
"gameRules": "Règles du jeu :",
|
||||
"charmGameRulesTips": "Allumez le tableau de bord pour afficher les pièces cadeaux reçues de tous les utilisateurs sur le micro. 1 pièce = 1 score (Cadeau porte-bonheur : 1 pièce = 0,04 score).",
|
||||
"inputYourOldPassword": "Saisissez votre ancien mot de passe",
|
||||
"enterYourOldPassword": "Entrez votre ancien mot de passe",
|
||||
"setYourPassword": "Définissez votre mot de passe",
|
||||
"enterYourNewPassword": "Entrez votre nouveau mot de passe",
|
||||
"confirmYourPassword": "Confirmez votre mot de passe",
|
||||
"theTwoPasswordsDoNotMatch": "Les deux mots de passe ne correspondent pas.",
|
||||
"resetLoginPasswordtTips2": "Le mot de passe doit comporter entre 8 et 16 caractères et doit être une combinaison de lettres et de chiffres anglais majuscules et minuscules (pas seulement des chiffres).",
|
||||
"resetLoginPassword": "Réinitialiser le mot de passe de connexion",
|
||||
"resetLoginPasswordtTips1": "Connectez-vous avec votre identifiant utilisateur ou votre identifiant personnalisé. Il est plus sûr de se connecter avec un compte Yumi.",
|
||||
"localMusic": "Musique locale",
|
||||
"setLoginPassword": "Définir le mot de passe de connexion",
|
||||
"confirmSwitchMicThemeTips": "Confirmez-vous le changement de style de siège ?",
|
||||
"micTheme": "Thème du micro",
|
||||
"classicMic": "Micro classique {1}",
|
||||
"yesterday": "Hier {1}",
|
||||
"monday": "Lundi {1}",
|
||||
"tuesday": "Mardi {1}",
|
||||
"wednesday": "Mercredi {1}",
|
||||
"thursday": "Jeudi {1}",
|
||||
"friday": "Vendredi {1}",
|
||||
"saturday": "Samedi {1}",
|
||||
"sunday": "Dimanche {1}",
|
||||
"acceptedYour": "{1} a accepté votre",
|
||||
"youAccepted": "Vous avez accepté {1}",
|
||||
"openRedPackDialogTip": "L'enveloppe rouge",
|
||||
"micManagement": "Gestion des micros",
|
||||
"bdCenter": "Centre BD",
|
||||
"rechargeAgency": "Agence de recharge",
|
||||
"adminCenter": "Centre d'administration",
|
||||
"managerCenter": "Centre des gestionnaires",
|
||||
"goldList": "Liste d'or",
|
||||
"followList": "Suivre la liste",
|
||||
"fansList": "Liste des fans",
|
||||
"daily": "Quotidiennement",
|
||||
"female": "Femme",
|
||||
"male": "Mâle",
|
||||
"identity": "Identité",
|
||||
"adjust": "Ajuster",
|
||||
"warning": "Avertissement",
|
||||
"screenshotTips": "Capture d'écran (jusqu'à 3)",
|
||||
"roomNotice": "Avis de chambre",
|
||||
"roomTheme": "Thème de la chambre",
|
||||
"description": "Descriptif :",
|
||||
"inputDesHint": "Veuillez décrire le problème de manière aussi détaillée que possible afin que nous puissions le comprendre et le résoudre.",
|
||||
"roomProfilePicture": "Photo de profil de la chambre",
|
||||
"userProfilePicture": "Photo de profil utilisateur",
|
||||
"userName": "Nom d'utilisateur",
|
||||
"pleaseSelectTheTypeToProcess": "Veuillez sélectionner le type à traiter :",
|
||||
"roomEditing": "Modification de la salle",
|
||||
"setAccount": "Définir un compte",
|
||||
"userEditing": "Modification par l'utilisateur",
|
||||
"enterTheUserId": "Entrez l'ID utilisateur",
|
||||
"enterTheRoomId": "Entrez l'identifiant de la pièce",
|
||||
"deleteAccount": "Supprimer le compte",
|
||||
"becomeAgent": "Devenez agent",
|
||||
"enterNickname": "Entrez le pseudo",
|
||||
"enterInvitationCode": "entrez le code d'invitation",
|
||||
"selectYourCountry": "Sélectionnez votre pays",
|
||||
"inviteCode": "Code d'invitation",
|
||||
"magic": "Magie",
|
||||
"luckGiftSpecialEffects": "Effets d'animation de cadeaux chanceux",
|
||||
"theVideoSizeCannotExceed": "La taille de la vidéo ne peut pas dépasser 50 M",
|
||||
"weekly": "Hebdomadaire",
|
||||
"customizedGiftRulesContent": "Comment recevoir mon cadeau personnalisé :\n1. Déterminez si l'utilisateur est éligible pour recevoir un cadeau personnalisé en fonction de son niveau de richesse actuel.\n(1) Lorsque le niveau de richesse de l'utilisateur est ≥35 : l'utilisateur peut télécharger une fois une vidéo pour le cadeau personnalisé. Nous utiliserons la photo de profil actuelle de l'utilisateur comme image de cadeau et la vidéo fournie comme effet de cadeau sur « Personnalisé ». La production prendra un certain temps et nous vous informerons dès qu'elle sera disponible en magasin.\n(2) Lorsque le niveau de richesse de l'utilisateur est ≥45 : l'utilisateur peut télécharger une fois une vidéo pour le cadeau personnalisé. Nous utiliserons la photo de profil actuelle de l'utilisateur comme image de cadeau et la vidéo fournie comme effet de cadeau sur « Personnalisé ». La production prendra un certain temps et nous vous informerons dès qu'elle sera disponible en magasin.\n2.Vous pouvez participer à des activités spécifiques sur l'application et, après avoir rempli les critères, nous contacter via la section « Message » → « Contactez-nous ». Fournissez des captures d'écran de l'activité et de la vidéo que vous souhaitez utiliser pour le cadeau personnalisé. Nous utiliserons votre photo de profil actuelle comme image de cadeau et la vidéo fournie comme effet de cadeau, puis nous la listerons sous « Personnalisé ». La production prendra un certain temps et nous vous informerons dès qu'elle sera disponible en magasin.\nPériode de validité du cadeau personnalisé et comment la prolonger :\nLes cadeaux personnalisés exclusifs seront disponibles en rayon pour une durée de validité de 30 jours. Pour permettre aux utilisateurs influents et inspirants de continuer à profiter de cette nouvelle expérience et de mettre en valeur leur style personnel, les utilisateurs possédant des cadeaux personnalisés peuvent prolonger la durée de conservation de tous leurs cadeaux personnalisés de 30 jours en rechargeant 500 $ chaque mois.",
|
||||
"customizedGiftRules": "Règles de cadeaux personnalisées",
|
||||
"rulesUpload": "Règles et téléchargement",
|
||||
"monthly": "Mensuel",
|
||||
"message": "Message",
|
||||
"clearCache": "Vider le cache",
|
||||
"customized": "Personnalisé",
|
||||
"searchInputHint": "Entrez le numéro de compte/de chambre",
|
||||
"kickRoomTips": "Vous avez été expulsé de la pièce.",
|
||||
"roomKickedEntryBlockedTips": "Un administrateur vous a retiré de la salle, vous ne pouvez pas y entrer pour le moment.",
|
||||
"joinRoomTips": "J'ai rejoint la salle !",
|
||||
"roomSetting": "Cadre de la pièce",
|
||||
"roomDetails": "Détails de la chambre",
|
||||
"systemRoomTips": "Soyez poli et respectueux. Tout contenu pornographique ou vulgaire est strictement interdit sur yumi. Une fois découvert, le compte sera banni définitivement. Veuillez respecter consciemment les réglementations de la plateforme Yumi.",
|
||||
"copiedToClipboard": "Copié dans le presse-papiers",
|
||||
"recharge": "Recharger",
|
||||
"receivedFromALuckyGift": "Reçu d'un cadeau chanceux/magique.",
|
||||
"followedYou": "Je t'ai suivi",
|
||||
"agentCenter": "Centre d'agence",
|
||||
"areYouSureYouWantToClearLocalCache": "Êtes-vous sûr de vouloir vider le cache local ?",
|
||||
"clearMessage": "Effacer les messages à l'écran",
|
||||
"report": "Rapport",
|
||||
"coins3": "Pièces de monnaie",
|
||||
"giftSpecialEffects": "Effets spéciaux cadeaux",
|
||||
"basicFeatures": "Fonctionnalités de base",
|
||||
"task": "Tâche",
|
||||
"importantReminder": "Rappel important",
|
||||
"entryVehicleAnimation": "Animation du véhicule d'entrée",
|
||||
"floatingAnimationInGlobal": "Animation flottante en global",
|
||||
"broadcast": "Diffusion",
|
||||
"broadcastDisplay": "Affichage de diffusion",
|
||||
"broadcastAllPlaces": "Tous les lieux",
|
||||
"broadcastRoomOnly": "Dans la chambre",
|
||||
"broadcastOff": "Fermer la diffusion",
|
||||
"entryVehicleAnimation2": "Les utilisateurs disposant de privilèges VIP4 ou supérieurs peuvent utiliser cette fonction pour désactiver les animations des véhicules.",
|
||||
"dailyTasks": "Tâches quotidiennes",
|
||||
"exclusiveForNewcomers": "Exclusif pour les nouveaux arrivants",
|
||||
"resetsDailyAtMidnight": "Se réinitialise quotidiennement à minuit",
|
||||
"limitedToOneTime": "Limité à une seule fois",
|
||||
"useMicrophoneForOneMin": "Utilisez le microphone pendant 1 min",
|
||||
"taskRequirement": "Exigence",
|
||||
"taskProgress": "Progrès",
|
||||
"rewardClaimedSuccessfully": "Récompense réclamée avec succès",
|
||||
"rewardClaimFailed": "La demande de récompense a échoué",
|
||||
"enterRoomConfirmTips": "Êtes-vous sûr de vouloir entrer dans la pièce ?",
|
||||
"followSucc": "Suivi avec succès",
|
||||
"goldListort": "Liste d'or",
|
||||
"rechargeList": "Liste de recharge",
|
||||
"edit": "Modifier",
|
||||
"swipeLeftOnTheFloatingScreenAreaToQuicklyCloseIt": "*Conseil : faites glisser votre doigt vers la gauche sur la zone de l'écran flottant pour la fermer rapidement.",
|
||||
"enterThisVoiceChatRoom": "Entrer dans cette salle de chat vocal ?",
|
||||
"go": "Allez",
|
||||
"done": "Terminé",
|
||||
"improvementTasks": "Tâches d'amélioration",
|
||||
"save": "Enregistrer",
|
||||
"kickPrevention": "Prévention des coups de pied",
|
||||
"freeChatSpeak": "Chat et conversation gratuits",
|
||||
"nickName": "Surnom",
|
||||
"gender": "Sexe",
|
||||
"unFollow": "Ne plus suivre",
|
||||
"days": "Jours",
|
||||
"permanent": "Permanent",
|
||||
"country": "Pays",
|
||||
"birthday": "Anniversaire",
|
||||
"man": "Homme",
|
||||
"woman": "Femme",
|
||||
"apple": "Pomme",
|
||||
"google": "Google",
|
||||
"startVoiceParty": "Commencez une soirée vocale !",
|
||||
"enterRoomTips": "{1} Entrez dans la pièce",
|
||||
"roomName": "Nom de la pièce",
|
||||
"roomMember": "Membre de la salle",
|
||||
"pleaseSelectYourCountry": "Veuillez sélectionner votre pays.",
|
||||
"pleaseSelectYourGender": "Veuillez sélectionner votre sexe.",
|
||||
"pleaseEnterNickname": "Veuillez entrer un pseudo.",
|
||||
"countryRegion": "Pays et région",
|
||||
"dateOfBirth": "Date de naissance",
|
||||
"mute": "Muet",
|
||||
"exit": "Quitter",
|
||||
"mysteriousInvisibility": "Invisibilité mystérieuse",
|
||||
"antiBlock": "Anti-blocage",
|
||||
"privateChat": "Chat privé",
|
||||
"storeDiscount": "Remise en magasin {1} de réduction",
|
||||
"membershipFreeChatSpeak": "Chat et conversation sans abonnement",
|
||||
"priorityRoomSorting": "Tri prioritaire des chambres",
|
||||
"userColoredID": "ID coloré de l'utilisateur",
|
||||
"pleaseSelectaItem": "Veuillez sélectionner un article",
|
||||
"areYouRureRoRecharge": "Etes-vous sûr de vouloir recharger ?",
|
||||
"mInimize": "Garder",
|
||||
"everyone": "Tout le monde",
|
||||
"shop": "Boutique",
|
||||
"roomOwner": "Propriétaire de la chambre",
|
||||
"dailyTaskRewardBonus": "Bonus de récompense de tâche quotidienne ({1} XP)",
|
||||
"userLevelXPBoost": "Boost XP au niveau utilisateur ({1} XP)",
|
||||
"andAboveUsers": "Utilisateurs {1} et supérieurs",
|
||||
"privileges": "Privilèges {1}",
|
||||
"basicPermissions": "Autorisations de base",
|
||||
"hostCenter": "Centre d'accueil",
|
||||
"allOnMicrophone": "Tout au micro",
|
||||
"usersOnMicrophone": "Utilisateurs au micro",
|
||||
"allInTheRoom": "Tous dans la chambre",
|
||||
"send": "Envoyer",
|
||||
"crop": "Recadrer",
|
||||
"goToUpgrade": "Aller à la mise à niveau",
|
||||
"exclusiveEmojiWillBeReleasedAfterBecoming": "Des emoji exclusifs seront publiés après être devenus",
|
||||
"preventBeingBlocked": "Empêcher d'être bloqué",
|
||||
"enableRankIncognitoMode": "Activer le mode navigation privée de classement",
|
||||
"avoidBeingKicked": "Évitez d'être frappé",
|
||||
"finish": "Terminer",
|
||||
"takeTheMic": "Prends le micro",
|
||||
"openTheMic": "Ouvre le micro",
|
||||
"muteTheMic": "Couper le micro",
|
||||
"unlockTheMic": "Déverrouillez le micro",
|
||||
"leavelTheMic": "Laisse le micro",
|
||||
"lockTheMic": "Verrouillez le micro",
|
||||
"removeTheMic": "Retirez le micro",
|
||||
"inviteToTheMicrophone": "Inviter au micro",
|
||||
"openUserProfleCard": "Ouvrir la fiche de profil utilisateur",
|
||||
"obtain": "obtenir",
|
||||
"win2": "Gagnez {1}",
|
||||
"backTheRoom": "Arrière-salle",
|
||||
"toConsume": "Consommer",
|
||||
"howToUpgrade": "Comment mettre à niveau ?",
|
||||
"spendCoinsToGainExperiencePoints": "Dépensez des pièces pour gagner des points d'expérience",
|
||||
"richLevelUpgradeAnswer": "Que ta richesse conquière tout",
|
||||
"charmLevelUpgradeAnswer": "Fais s'incliner les autres devant ton charme",
|
||||
"higherLevelFancierAvatarFrame": "Cadre de badges/avatar de niveau supérieur et plus sophistiqué",
|
||||
"all": "Tout",
|
||||
"gift": "Cadeau",
|
||||
"chat": "Discuter",
|
||||
"owner": "Propriétaire",
|
||||
"store": "Magasin",
|
||||
"admin": "Administrateur",
|
||||
"member": "Membre",
|
||||
"guest": "Invité",
|
||||
"submit": "Soumettre",
|
||||
"claim": "Réclamation",
|
||||
"claimed": "Réclamé",
|
||||
"complete": "Terminé",
|
||||
"shareTo": "Partager avec",
|
||||
"copyLink": "Copier le lien",
|
||||
"faceBook": "Facebook",
|
||||
"whatsApp": "WhatsApp",
|
||||
"snapChat": "Snapchat",
|
||||
"taskNamePersonalGameConsume": "Dépenses de jeu",
|
||||
"taskNameRoomNewMember": "Nouveaux membres de la salle",
|
||||
"taskNameRoomOwnerSendRedPacket": "Le propriétaire de la salle envoie un paquet rouge",
|
||||
"taskNameRoomOwnerSendGiftUser": "Le propriétaire de la chambre envoie un cadeau",
|
||||
"taskNameRoomMicUser120Min": "Membres avec plus de 120 minutes au micro",
|
||||
"taskNameRoomMicUser60Min": "Membres avec plus de 60 minutes au micro",
|
||||
"taskNameRoomMicUser30Min": "Membres avec plus de 30 minutes au micro",
|
||||
"taskNamePersonalSendGift": "Envoyer un cadeau à l'utilisateur",
|
||||
"taskNameRoomOnlineUserCount": "Chambre membres en ligne",
|
||||
"taskNameRoomOwnerInviteMic": "Inviter un membre sur Mic",
|
||||
"taskNameRoomUserSendGiftGold": "Pièces offertes par les membres",
|
||||
"taskNameRoomOwnerSendGiftGold": "Pièces offertes par le propriétaire de la chambre",
|
||||
"taskNamePersonalMagicGiftGold": "Pièces gagnées en envoyant des cadeaux magiques",
|
||||
"taskNamePersonalLuckyGiftGold": "Pièces gagnées en envoyant des cadeaux chanceux",
|
||||
"taskNameRoomOwnerMicTime": "Le propriétaire de la salle passe au micro dans la pièce",
|
||||
"taskNamePersonalActiveInRoom": "Soyez actif dans les pièces des autres",
|
||||
"taskNamePersonalMicInRoom": "Allez au micro",
|
||||
"dailyCoinBonanzaRulesDetail": "1. Les tâches personnelles quotidiennes et les tâches quotidiennes du propriétaire de la salle peuvent être effectuées une fois par utilisateur et par jour. Les tâches sont réinitialisées à 00:00:00, heure saoudienne.\n2. Les tâches personnelles quotidiennes ne peuvent être accomplies que dans la chambre des autres ; Les tâches du propriétaire de la salle ne peuvent être effectuées que dans votre propre chambre.\n3. Les tâches d'offre de cadeaux ne comptent que les cadeaux envoyés dans les salles, pas les cadeaux envoyés via les flux.\n4. Si plusieurs comptes sont créés en utilisant le même appareil ou la même carte SIM, les récompenses pour toutes les tâches ne peuvent être réclamées qu'une seule fois.",
|
||||
"dailyCoinBonanzaRules": "Règles quotidiennes du bonus de pièces",
|
||||
"roomOwnerTasks": "Tâches du propriétaire de la salle",
|
||||
"personalTasks": "Tâches personnelles",
|
||||
"noPromptsToday": "Aucune tâche aujourd'hui.",
|
||||
"getPaidToRefer": "Soyez payé pour parrainer",
|
||||
"membershipFee": "Frais d'adhésion",
|
||||
"membershipFeeTips1": "Veuillez fixer les frais d'adhésion pour votre chambre. Les utilisateurs peuvent rejoindre votre salle en payant les frais.",
|
||||
"membershipFeeTips2": "Les ors requis pour que l'utilisateur devienne membre de la salle. Le propriétaire de la salle recevra 50% des ors.",
|
||||
"freePrice": "Frais : 0-10 000",
|
||||
"touristsSendText": "Un touriste envoie un SMS",
|
||||
"touristsTakeToTheMic": "Les touristes prennent le micro",
|
||||
"theMembershipFee": "La cotisation",
|
||||
"theModificationsMade": "Les modifications apportées cette fois ne seront pas enregistrées après la sortie",
|
||||
"viewFrame": "Cadre de vue",
|
||||
"enterRoomName": "Entrez le nom de la salle",
|
||||
"headdress": "Cadres",
|
||||
"mountains": "Véhicules",
|
||||
"purchaseIsSuccessful": "L'achat est réussi",
|
||||
"buy": "Acheter",
|
||||
"followed": "Suivi",
|
||||
"follow2": "Suivre : {1}",
|
||||
"fans2": "Ventilateurs : {1}",
|
||||
"vistors2": "Visiteurs :{1}",
|
||||
"personal2": "Personnel :",
|
||||
"conntinue": "Continuer",
|
||||
"confirmBuyTips": "Êtes-vous sûr de vouloir acheter ?",
|
||||
"purchase": "Acheter",
|
||||
"setRoomPassword": "Définir le mot de passe de la salle",
|
||||
"inputRoomPassword": "Saisir le mot de passe de la salle",
|
||||
"enter": "Entrez",
|
||||
"deleteCommentTips": "Êtes-vous sûr de vouloir supprimer ce commentaire ?",
|
||||
"deleteSuccessful": "Suppression réussie !",
|
||||
"itemsLeft": "Objets restants",
|
||||
"password": "Mot de passe",
|
||||
"replySucc": "Réponse réussie",
|
||||
"comment": "Commentaire",
|
||||
"showMore": "Afficher plus",
|
||||
"showLess": "Afficher moins",
|
||||
"enterPassword": "Entrez le mot de passe",
|
||||
"enterAccount": "Entrer un compte",
|
||||
"logIn": "Connectez-vous",
|
||||
"saySomething": "Dis quelque chose...",
|
||||
"sayHi": "Dis bonjour..",
|
||||
"pleaseChatFfriendly": "Veuillez discuter gentiment",
|
||||
"unLockTheRoom": "Déverrouillez la pièce",
|
||||
"operationSuccessful": "L'opération a réussi.",
|
||||
"adminByHomeowner": "est défini comme administrateur par le propriétaire.",
|
||||
"memberByHomeowner": "est défini comme membres par le propriétaire.",
|
||||
"touristByHomeowner": "est défini comme touriste par le propriétaire.",
|
||||
"becomeHost": "Postulez pour devenir hôte",
|
||||
"superFans": "Super fans :",
|
||||
"setUpAnIdentity": "Créer une identité",
|
||||
"kickedOutOfRoom": "Expulsé de la pièce",
|
||||
"knapsack": "Sac à dos",
|
||||
"bdLeader": "Responsable BD",
|
||||
"picture": "Image",
|
||||
"theImageSizeCannotExceed": "La taille de l'image ne peut pas dépasser 4 M",
|
||||
"activity": "Activité",
|
||||
"alreadyAnAdministrator": "Déjà administrateur",
|
||||
"alreadyAnMember": "Déjà membre",
|
||||
"alreadyAnTourist": "Déjà touriste",
|
||||
"touristsCannotSendMessages": "Les touristes ne peuvent pas envoyer de messages",
|
||||
"touristsAreNotAllowedToGoOnTheMic": "Les touristes ne sont pas autorisés à prendre le micro",
|
||||
"lockTheRoom": "Verrouiller la pièce",
|
||||
"special": "Spécial",
|
||||
"visitorList": "Liste des visiteurs",
|
||||
"successfulWear": "Un port réussi",
|
||||
"confirmUnUseTips": "Confirmez-vous pour le supprimer ?",
|
||||
"custom": "Personnalisé",
|
||||
"customBackground": "Arrière-plan personnalisé",
|
||||
"example": "Exemple",
|
||||
"endPreview": "Fin de l'aperçu",
|
||||
"selectAgain": "Sélectionnez à nouveau",
|
||||
"myItems": "Mes articles",
|
||||
"use": "Utiliser",
|
||||
"unUse": "Déséquiper",
|
||||
"renewal": "Renouvellement",
|
||||
"wallet": "Portefeuille",
|
||||
"profile": "Profil",
|
||||
"giftwall": "Mur de cadeaux",
|
||||
"announcement": "Annonce",
|
||||
"blockedList": "Liste bloquée",
|
||||
"country2": "Pays :",
|
||||
"sendTo": "Envoyer à",
|
||||
"credits": "Crédits : {1}",
|
||||
"successfullyUnloaded": "Déchargé avec succès",
|
||||
"expired": "Expiré",
|
||||
"day": "Jour",
|
||||
"inUse": "En cours d'utilisation",
|
||||
"staticOrGifImage": "Image statique / GIF",
|
||||
"confirmUseTips": "Voulez-vous confirmer son utilisation ?",
|
||||
"pleaseUploadUserAvatar": "Veuillez télécharger un avatar.",
|
||||
"joinMemberTips": "Si vous êtes un visiteur dans la salle, vous ne pouvez pas prendre le micro.",
|
||||
"giftGivingSuccessful": "Remise de cadeaux réussie.",
|
||||
"theAccountPasswordCannotBeEmpty": "Le compte ou le mot de passe ne peut pas être vide.",
|
||||
"invitesYouToTheMicrophone": "{1} vous invite au micro",
|
||||
"english": "Anglais",
|
||||
"chinese": "Chinois",
|
||||
"arabic": "arabe",
|
||||
"darkMode": "Mode sombre",
|
||||
"lightMode": "Mode lumière",
|
||||
"systemDefault": "Système par défaut",
|
||||
"pleaseGetOnTheMicFirst": "Veuillez d'abord prendre le micro.",
|
||||
"roomMusicAddMusic": "Ajouter de la musique",
|
||||
"roomMusicSelectAll": "Tout sélectionner",
|
||||
"roomMusicAddedCount": "Chansons {1} ajoutées",
|
||||
"roomMusicEmpty": "Pas de musique",
|
||||
"roomMusicScanFromPhone": "Numériser depuis le téléphone",
|
||||
"roomMusicTotalSongs": "Total : chansons {1}",
|
||||
"roomMusicNotFound": "Aucune musique trouvée",
|
||||
"roomMusicDeleteConfirm": "Supprimer cette musique ?",
|
||||
"roomMusicDeleted": "Musique supprimée",
|
||||
"roomMusicListMode": "Jouer dans l'ordre",
|
||||
"roomMusicShuffleMode": "Lecture aléatoire",
|
||||
"roomMusicSingleMode": "Boucle unique",
|
||||
"duration2": "Durée : {1}",
|
||||
"roomRocketNoteTitle": "Remarque",
|
||||
"roomRocketNoteSectionTitle": "Chaque fois un VIP",
|
||||
"roomRocketNoteSectionBody": "1. Chaque fois qu'un niveau VIP est acheté, il est valable 30 jours et peut être renouvelé manuellement après expiration.",
|
||||
"roomRocketRewardEmptyText": "Aucune récompense gagnée. Plus de chance la prochaine fois.",
|
||||
"roomRocketRecordEmptyText": "Aucun enregistrement de récompense",
|
||||
"launchRocket": "Lancement de fusée",
|
||||
"roomRocketFuelFullNotice": "Le carburant de la fusée est plein ! Lancement dans {1} s !"
|
||||
}
|
||||
750
assets/l10n/intl_it.json
Normal file
@ -0,0 +1,750 @@
|
||||
{
|
||||
"signInWithGoogle": "Accedi con Google",
|
||||
"or": "Oppure",
|
||||
"signInWithYourAccount": "Accedi con il tuo account",
|
||||
"signInWithApple": "Accedi con Apple",
|
||||
"loginRepresentsAgreementTo": "L'accesso rappresenta l'accordo",
|
||||
"termsofService": "Termini di servizio",
|
||||
"privaceyPolicy": "Informativa sulla privacy",
|
||||
"tips": "Suggerimenti",
|
||||
"searchNoDataTips": "Inserisci la stanza o l'ID utente che desideri cercare.",
|
||||
"games": "Giochi",
|
||||
"mine": "Il mio",
|
||||
"official": "Ufficiale",
|
||||
"party": "Festa",
|
||||
"other": "Altro",
|
||||
"yes": "Sì",
|
||||
"bag": "Borsa",
|
||||
"sound2": "Suono",
|
||||
"startYourBrandNewJourney": "Inizia il tuo nuovo viaggio",
|
||||
"maliciousHarassment": "Molestie dannose",
|
||||
"event": "Evento",
|
||||
"roomEdit": "Modifica della stanza",
|
||||
"cancelRoomPassword": "Sei sicuro di voler eliminare la password della stanza?",
|
||||
"roomMemberFee": "Quota per il membro della camera",
|
||||
"roomTheme2": "Tema della stanza",
|
||||
"background": "Sfondo",
|
||||
"blockedList2": "Elenco bloccati",
|
||||
"roomPassword": "Password della stanza",
|
||||
"numberOfMic": "Numero di mic",
|
||||
"pleaseEnterContent": "Inserisci il contenuto",
|
||||
"profilePhoto": "Foto del profilo",
|
||||
"aboutMe": "Su di me",
|
||||
"myRoom": "La mia stanza",
|
||||
"noHistoricalRecordsAvailable": "Nessun documento storico disponibile.",
|
||||
"inviteNewUsersToEarnCoins": "Invita nuovi utenti a guadagnare monete",
|
||||
"crateMyRoom": "CREA LA TUA STANZA.",
|
||||
"casualInteraction": "Interazione casuale",
|
||||
"haveGamePlayingTips": "Hai una partita in corso. Per favore, esci dal gioco corrente. Sei sicuro di voler uscire?",
|
||||
"historicalTour": "Giro storico",
|
||||
"gameCenter": "Centro giochi",
|
||||
"returnToVoiceChat": "Tornare alla chat vocale?",
|
||||
"exitGameMode": "Esci dalla modalità gioco",
|
||||
"enterTheRoom": "Entra nella stanza",
|
||||
"invite": "Invita",
|
||||
"recent": "Recente",
|
||||
"popularEvents": "Eventi popolari",
|
||||
"inviteGoRoomTips": "Sempre qui per te, con la pioggia o con il sole. Entra e saluta!",
|
||||
"confirmInviteThisUserToTheRoom": "Confermare l'invito di questo utente (ID:{1}) nella stanza virtuale?",
|
||||
"clearCacheSuccessfully": "Svuota la cache con successo",
|
||||
"sent": "Inviato",
|
||||
"keep": "Tieni",
|
||||
"open": "Aperto",
|
||||
"deleteAccountTips2": "*Se cambi idea, puoi accedere nuovamente al tuo account corrente entro sette giorni e ripristineremo automaticamente il tuo account. Se entro sette giorni non viene effettuato alcun ripristino, l'account verrà eliminato definitivamente.",
|
||||
"deleteAccountTips": "Disponi di diritti amministrativi completi per questo account. Se intendi eliminare l'account, tieni presente i seguenti rischi associati a questa operazione:\n1. Una volta eliminato con successo l'account, non sarai più in grado di accedere al tuo account corrente. La cancellazione dell'account è un'azione permanente.\n2. Una volta eliminato con successo l'account, non sarai in grado di recuperare alcun dato dell'account. Tutte le informazioni (incluse stanze e amici), la valuta virtuale, i regali e gli oggetti virtuali verranno eliminati in modo permanente e non potranno essere ripristinati.\n3. Periodo di riflessione: se non ripristini l'account, non potrai accedere alla pagina di acquisto, alla pagina di prelievo o a qualsiasi altra pagina dell'app.\n4. Durante il periodo di riflessione o dopo che l'account è stato eliminato, la pagina del profilo indicherà che è stato eliminato. Per proteggere il tuo account da ricerche o accessi da parte di altri, le tue informazioni personali verranno rimosse dai sistemi relativi alle funzioni quotidiane. Quando la cancellazione dell'account implica la sicurezza nazionale, procedimenti civili o penali o la protezione dei diritti e degli interessi legittimi di terzi, il team ufficiale si riserva il diritto di respingere la richiesta di cancellazione dell'account dell'utente.\nSe sei sicuro di voler eliminare tutti i dati personali dal tuo account corrente, fai clic su \"Elimina account\".",
|
||||
"accountDeletionNotice": "Avviso di cancellazione dell'account:",
|
||||
"thisUserHasBeenBlacklisted": "Questo utente è stato inserito nella lista nera.",
|
||||
"trend": "Tendenza",
|
||||
"like": "Mi piace",
|
||||
"more": "Di più",
|
||||
"discard": "Scartare",
|
||||
"catchFirstComment": "Cattura il primo commento",
|
||||
"reply": "Rispondi",
|
||||
"posting": "Pubblicazione",
|
||||
"multiple": "Molteplici",
|
||||
"successfullyRemovedFromTheBlacklist": "Rimosso con successo dalla lista nera!",
|
||||
"successfullyAddedToTheBlacklist": "Aggiunto con successo alla lista nera!",
|
||||
"youAreCurrentlyCPRelationshipPleaseDissolve": "Attualmente hai una relazione CP.\nPer favore, prima scioglietelo.",
|
||||
"areYouSureToCancelBlacklist": "Sei sicuro di voler cancellare la lista nera?",
|
||||
"areYouSureYouWantToBlockThisUser": "Sei sicuro di voler bloccare questo utente?",
|
||||
"removeFromBlacklist": "Rimuovi dalla lista nera",
|
||||
"moveToBlacklist": "Passa alla lista nera",
|
||||
"userBlacklist": "Lista nera degli utenti",
|
||||
"specialEffectsManagement": "Gestione degli effetti speciali",
|
||||
"wishingYouHappinessEveryDay": "Ti auguro felicità ogni giorno.",
|
||||
"newMessage": "Nuovo messaggio",
|
||||
"createRoomSuccsess": "Stanza creata con successo!",
|
||||
"contactUs": "Contattaci",
|
||||
"systemAnnouncementTips1": "Difendersi dalle frodi:",
|
||||
"systemAnnouncementTips": "Verificare le informazioni esclusivamente attraverso i canali ufficiali. Non scaricare mai software di terze parti, condividere dati personali o trasferire denaro in base a richieste esterne. Gli ID ufficiali dello staff sono solo 10000, 10003 e 10086. Per qualsiasi sospetto, fermati e segnala tramite",
|
||||
"systemAnnouncement": "Annuncio di sistema",
|
||||
"doNotClickUnfamiliarTips": "Non fare clic su collegamenti sconosciuti, poiché potrebbero esporre le tue informazioni personali. Non condividere mai con nessuno il tuo documento d'identità o i dettagli della tua carta bancaria.",
|
||||
"atTag": "@Tag",
|
||||
"sayHi2": "Salutami",
|
||||
"roomBottomGreeting": "Ciao...",
|
||||
"canSendMsgTips": "Entrambe le parti devono seguirsi a vicenda prima di poter inviare messaggi privati.",
|
||||
"msgSendRedEnvelopeTips": "* Sulle buste rosse verrà addebitata una commissione di servizio del 10% e i destinatari riceveranno solo il 90% del valore della busta rossa. Il livello di ricchezza del mittente deve essere superiore al livello 10.",
|
||||
"reapply": "Riapplicare",
|
||||
"cancelRequest": "Annulla richiesta",
|
||||
"pending": "In sospeso",
|
||||
"supporter": "Sostenitore",
|
||||
"coinsReceived": "Monete ricevute",
|
||||
"numberOfSign": "Numero di segno: {1}",
|
||||
"hostWeeklyRank": "Classifica settimanale dell'host",
|
||||
"supporterWeeklyRank": "Classifica settimanale dei sostenitori",
|
||||
"memberList": "Elenco dei membri",
|
||||
"treasureChest": "Scrigno del tesoro",
|
||||
"applicationRecord": "Registro della domanda",
|
||||
"appUpdateTip": "L'app ha una nuova versione ({1}). Vuoi scaricarlo adesso?",
|
||||
"ownerIncomeCoins": "Reddito del proprietario: monete {1}",
|
||||
"game": "Gioco",
|
||||
"skip2": "Salta",
|
||||
"skipCountdown": "Salta {1}s",
|
||||
"coins4": "Monete",
|
||||
"weekStart": "Inizio settimana",
|
||||
"forMoreRewardsPleaseCheckTheTaskCenter": "Per ulteriori premi, controlla il centro attività",
|
||||
"kingQuuen": "Re-Regina",
|
||||
"ramadan": "Ramadan",
|
||||
"updateNow": "Aggiorna ora",
|
||||
"allGames": "Tutti i giochi",
|
||||
"fishClass": "Classe di pesce",
|
||||
"greedyClass": "Classe golosa",
|
||||
"raceSeries": "Serie di gare",
|
||||
"slotsClass": "Classe di slot",
|
||||
"others": "Altri",
|
||||
"hotGames": "Giochi caldi",
|
||||
"chatBox": "Casella di chat",
|
||||
"termsOfServicePrivacyPolicyTips": "Continuando accetti i Termini di servizio e l'Informativa sulla privacy",
|
||||
"and": "e",
|
||||
"pleaseSelectTheTypeContent": "Seleziona il tipo di contenuto offensivo.",
|
||||
"illegalInformation": "Informazioni illegali",
|
||||
"inappropriateContent": "Contenuti inappropriati",
|
||||
"personalAttack": "Attacco personale",
|
||||
"confirm": "Conferma",
|
||||
"spam": "Spam",
|
||||
"countdownMinutes": "Minuti del conto alla rovescia:",
|
||||
"number2": "Numero:",
|
||||
"fraud": "Frode",
|
||||
"received": "Ricevuto",
|
||||
"currentProgress": "Progressi attuali",
|
||||
"currentStage": "Fase attuale:{1}",
|
||||
"roomReward2": "Premio camera:{1}",
|
||||
"roomReward": "Premio per la camera",
|
||||
"expirationTime": "Tempo di scadenza",
|
||||
"ownerSendTheRedEnvelope": "Il proprietario ha inviato monete di ricompensa.",
|
||||
"rewardCoins": "Monete premio: monete {1}",
|
||||
"signInRewardReceived": "Accesso effettuato con successo. Ricompensa: {1}",
|
||||
"registerRewardReceived": "Premio di registrazione ricevuto: {1}",
|
||||
"lastWeekProgress": "I progressi della settimana scorsa",
|
||||
"redEnvelopeTips2": "*Se la busta rossa non viene ritirata entro il tempo limite, le monete rimanenti verranno restituite all'utente che ha inviato la busta rossa.",
|
||||
"goToRecharge": "Vai a ricaricare",
|
||||
"deleteAccount2": "Elimina account ({1}s)",
|
||||
"areYouSureYouWantToDeleteYourAccount": "Sei sicuro di voler eliminare il tuo account?",
|
||||
"insufhcientGoldsGoToRecharge": "Oro insufficiente. Ricarica ora!",
|
||||
"coins2": "Monete {1}",
|
||||
"remainingNumberTips": "Rimanenza disponibile: ({1}/{2})",
|
||||
"collectionTimeTips": "Orario di ritiro:{1}({2}/{3})",
|
||||
"sendARedEnvelope": "Invia una busta rossa",
|
||||
"sendRedPackConfirmTips": "Sei sicuro di voler inviare il pacchetto rosso?",
|
||||
"redEnvelopeSendingRecords": "Record di invio busta rossa:",
|
||||
"redEnvelope": "Busta rossa",
|
||||
"roomRedPacketGoldCoins": "Monete d'oro",
|
||||
"roomRedPacketQuantity": "Quantità",
|
||||
"roomRedPacketPickupTime": "Orario di ritiro",
|
||||
"roomRedPacketInMinutes": "In {1} minuti",
|
||||
"roomRedPacketClaimNow": "Richiedi ora",
|
||||
"roomRedPacketSendLuckyPack": "Invia un pacchetto fortunato",
|
||||
"roomRedPacketSending": "Invio...",
|
||||
"roomRedPacketSendSuccess": "Pacchetto fortunato inviato",
|
||||
"roomRedPacketComingSoon": "Prossimamente",
|
||||
"roomRedPacketRulesTitle": "Regole",
|
||||
"roomRedPacketRulesContent": "1. Puoi spendere monete per inviare un pacchetto fortunato. Se altri aprono il tuo pacchetto fortunato, possono ricevere una quantità casuale di monete.\n\n2. Due ore dopo l'invio del Lucky Pack, tutte le monete non reclamate verranno restituite al tuo portafoglio.\n\n3. Dopo aver inviato un pacchetto fortunato a livello di server, verrà attivato un banner pacchetto fortunato a livello di server, attirandoti maggiore attenzione.\n\n4. Puoi inviare pacchetti fortunati della stanza %1$s al giorno.\n\n5. I pacchetti fortunati inviati da utenti al di fuori della tua regione non possono essere rivendicati.",
|
||||
"roomRedPacketHistoryTitle": "Cronologia delle consegne del pacchetto fortunato",
|
||||
"roomRedPacketHistoryItemTitle": "Borsa Portafortuna (buste rosse {1})",
|
||||
"roomRedPacketHistoryStatusSent": "Inviato",
|
||||
"roomRedPacketHistoryStatusFinished": "Completamente rivendicato",
|
||||
"roomRedPacketHistoryStatusPending": "In sospeso",
|
||||
"roomRedPacketHistoryStatusReturned": "Restituito",
|
||||
"roomRedPacketHistoryRefund": "Rimborso: {1}",
|
||||
"roomRedPacketClaimRecordsTitle": "Richiedi record",
|
||||
"roomRedPacketChatMessage": "Ti ho mandato una busta rossa, aprila subito!",
|
||||
"roomRedPacketReceivedMessage": "Richiedi le monete {1} dal pacchetto fortunato {2}",
|
||||
"roomRedPacketOpenSubtitle": "Il pacchetto fortunato è arrivato",
|
||||
"roomRedPacketOpenYouGot": "Ottieni",
|
||||
"roomRedPacketClaimSuccessTips": "Le monete sono state inviate al tuo portafoglio.",
|
||||
"roomRedPacketOpenSoldOut": "Il pacchetto fortunato è già esaurito.",
|
||||
"roomRedPacketOpenExpired": "Il pacchetto fortunato è scaduto.",
|
||||
"roomRedPacketRegionNotMatch": "Solo gli utenti nella stessa regione linguistica possono richiedere questo pacchetto fortunato.",
|
||||
"roomRedPacketNotStarted": "Il pacchetto fortunato non è ancora pronto.",
|
||||
"roomRedPacketDailyLimitExceeded": "Il limite di invio di oggi è stato raggiunto.",
|
||||
"roomRedPacketInsufficientBalance": "Monete d'oro insufficienti.",
|
||||
"roomRedPacketPresenceRequired": "Si prega di rimanere nella stanza per utilizzare i pacchetti fortunati.",
|
||||
"roomRedPacketNetworkError": "Impossibile elaborare il pacchetto fortunato in questo momento.",
|
||||
"roomRedPacketAlreadyClaimed": "Hai già richiesto questo pacchetto fortunato.",
|
||||
"roomRedPacketOpenWaiting": "Può essere aperto in {1} minuti.",
|
||||
"roomRedPacketOpenWaitingCountdown": "Disponibile per il ritiro dopo {1}",
|
||||
"roomRedPacketOkay": "Ok",
|
||||
"roomRedPacketFloatingTitle": "{1} ha inviato una busta rossa",
|
||||
"roomRedPacketFloatingSubtitle": "Ho inviato una busta rossa",
|
||||
"redEnvelopeRecTips2": "Le buste rosse sono state tutte reclamate.",
|
||||
"redEnvelopeRecTips3": "Il tempo per il ritiro della busta rossa è scaduto!",
|
||||
"openTheTreasureChest": "Apri lo scrigno del tesoro",
|
||||
"redEnvelopeRecTips1": "Le monete guadagnate sono state depositate nel tuo portafoglio.",
|
||||
"redEnvelopeTips1": "Monete:",
|
||||
"roomTools": "Strumenti della stanza:",
|
||||
"entertainment": "Intrattenimento:",
|
||||
"reportSucc": "Segnalazione riuscita",
|
||||
"pornography": "Pornografia",
|
||||
"reportInputTips": "Si prega di descrivere il problema nel modo più dettagliato possibile in modo che possiamo capirlo e risolverlo.",
|
||||
"cancel": "Annulla",
|
||||
"join": "Partecipa",
|
||||
"items": "Articoli",
|
||||
"vistors": "Visitatori",
|
||||
"fans": "Tifosi",
|
||||
"balanceNotEnough": "Saldo in monete d'oro insufficiente. Vuoi andare a ricaricare?",
|
||||
"skip": "Salta {1}",
|
||||
"letGoToWatch": "Andiamo a guardare!",
|
||||
"launchedARocket": "lanciato un razzo",
|
||||
"sendUserId": "Invia ID utente:{1}",
|
||||
"giveUpIdentity": "Rinunciare all'identità",
|
||||
"leaveRoomIdentityTips": "Sei sicuro di voler rinunciare all'identità della stanza?",
|
||||
"joinMemberTips2": "Vuoi confermare l'adesione alla stanza come membro?",
|
||||
"sureUnfollowThisRoom": "Sei sicuro di voler smettere di seguire questa stanza?",
|
||||
"welcomeMessage": "Benvenuti nella nostra applicazione, {name}!",
|
||||
"settings": "Impostazioni",
|
||||
"account": "Conto",
|
||||
"common": "Comune",
|
||||
"delete": "Elimina",
|
||||
"copy": "Copia",
|
||||
"bio": "Bio",
|
||||
"introduction": "Introduzione",
|
||||
"useCoupontips": "Sei sicuro di voler utilizzare il coupon?",
|
||||
"searchUserId": "Cerca l'ID dell'utente",
|
||||
"sendUser": "Invia utente",
|
||||
"hobby": "Hobby",
|
||||
"sendCoupontips": "Sei sicuro di voler inviare questo coupon a questo utente?",
|
||||
"youDontHaveAnyCouponsYet": "Non hai ancora alcun coupon.",
|
||||
"recall": "Ricorda",
|
||||
"youHaventFollowed": "Non hai seguito nessuna stanza",
|
||||
"deleteFromMyDevice": "Elimina dal mio dispositivo",
|
||||
"deleteOnAllDevices": "Elimina su tutti i dispositivi",
|
||||
"messageHasBeenRecalled": "Questo messaggio è stato richiamato",
|
||||
"recallThisMessage": "Ricordi questo messaggio?",
|
||||
"language": "Lingua",
|
||||
"feedback": "Feedback",
|
||||
"signedin": "Effettuato l'accesso",
|
||||
"receiveSucc": "Rivendicato con successo",
|
||||
"about": "Circa",
|
||||
"aboutUs": "Chi siamo",
|
||||
"theme": "Tema",
|
||||
"wealthLevel": "Livello di ricchezza",
|
||||
"userLevel": "Livello di fascino",
|
||||
"goToUpload": "Vai al caricamento",
|
||||
"logout": "Esci",
|
||||
"luck": "Fortuna",
|
||||
"level": "Livello",
|
||||
"themeGoToUploadTips": "1. La revisione verrà completata entro 24 ore dal completamento del caricamento.\n2. Tutte le monete verranno restituite se la revisione fallisce.",
|
||||
"pleaseUploadAccordingToExample": "Si prega di caricare secondo l'esempio fornito.",
|
||||
"home": "Casa",
|
||||
"explore": "Esplora",
|
||||
"me": "Io",
|
||||
"socialPrivilege": "Privilegio sociale",
|
||||
"information": "Informazioni",
|
||||
"myPhoto": "La mia foto",
|
||||
"areYouSureYouWantToSpend3": "*Se l'altra parte rifiuta l'invito CP, le tue monete verranno restituite al tuo portafoglio.",
|
||||
"areYouSureYouWantToSpend": "Sei sicuro di voler spendere?",
|
||||
"areYouSureYouWantToSpend2": "inviare un invito CP a questo utente?",
|
||||
"underReview": "In corso di revisione",
|
||||
"doYouWantToDeleteIt": "Vuoi eliminarlo?",
|
||||
"chooseFromAblum": "Scegli dall'album",
|
||||
"spaceBackground": "Sfondo dello spazio",
|
||||
"editProfile": "Modifica profilo",
|
||||
"sendTheCpRequest": "Invia la richiesta CP",
|
||||
"addCp": "Aggiungi CP",
|
||||
"partWays": "Modi di parte",
|
||||
"reconcile": "Riconciliarsi",
|
||||
"separated": "Separato",
|
||||
"areYouSureYouWantToSpend5": "{1} ti ha confessato la sensazione; se accetti diventerai una coppia.",
|
||||
"areYouSureYouWantToSpend6": "{1} vuole tornare insieme a te. Se decidi di effettuare la riconciliazione, tutti i tuoi dati precedenti verranno ripristinati.",
|
||||
"reconcileInvitationTips": "*Se l'altra parte rifiuta l'invito CP, le tue monete verranno restituite al tuo portafoglio.",
|
||||
"reconcileInvitation": "Invito alla riconciliazione",
|
||||
"areYouSureYouWantToSpend4": "inviare un invito alla riconciliazione a questo utente?",
|
||||
"partWaysTips": "*Se uno dei partner di una coppia sceglie di separarsi, ci sarà un periodo di riflessione di 7 giorni. Durante questo periodo, entrambe le parti possono scegliere di riconciliarsi e tutti i dati verranno ripristinati al momento della riconciliazione. Se non viene scelta alcuna riconciliazione entro la fine del periodo di 7 giorni, i dati della coppia verranno cancellati.",
|
||||
"areYouSureYouWantToPartWaysWithYourCP": "Sei sicuro di voler separarti dal tuo CP?",
|
||||
"timeSpentTogether": "Tempo trascorso insieme: {1} giorni",
|
||||
"firstDay": "Primo giorno:{1}",
|
||||
"numberOfMyCPs": "Numero dei miei CP:({1}/{2})",
|
||||
"props": "Oggetti di scena",
|
||||
"win": "Vinci",
|
||||
"dice": "Dadi",
|
||||
"rps": "RPS",
|
||||
"operationFail": "L'operazione è fallita.",
|
||||
"enterRoomFailedRetry": "Impossibile entrare nella stanza. Per favore riprova.",
|
||||
"voiceConnectionFailedRetry": "Connessione vocale non riuscita. Per favore riprova.",
|
||||
"likedYourComment": "Mi è piaciuto il tuo commento.",
|
||||
"doYouWantToKeepTheDraft": "Vuoi mantenere la bozza?",
|
||||
"operationsAreTooFrequent": "Le operazioni sono troppo frequenti",
|
||||
"luckNumber": "Numero di fortuna",
|
||||
"relationShip": "Relazione",
|
||||
"couple": "Coppia {1}:",
|
||||
"couple2": "Coppia",
|
||||
"reject": "Rifiutare",
|
||||
"accept": "Accetta",
|
||||
"noMatchedCP": "Nessun CP corrispondente",
|
||||
"inviteYouToBecomeBD": "Ti invito a diventare un BD.",
|
||||
"adminInviteRechargeAgent": "Invitati a diventare un agente Recharge.",
|
||||
"confirmAcceptTheInvitation": "Confermare per accettare l'invito?",
|
||||
"confirmDeclineTheInvitation": "Confermi di rifiutare l'invito?",
|
||||
"cpInviteRejectedByOther": "L'altra persona ha rifiutato di diventare {1}.",
|
||||
"closeFriendInvitationTitle": "Invitation from a Close Friend",
|
||||
"closeFriendInvitationSubtitle": "Click to view the invitation",
|
||||
"closeFriendInvitationExpired": "The invitation to add a close friend has expired. This action cannot be performed.",
|
||||
"closeFriendRequestDeclined": "Friend request declined",
|
||||
"closeFriendsHaveBecome": "Have become close friends",
|
||||
"otherAcceptedCloseFriendInvite": "The other party accepted your close friend invitation.",
|
||||
"otherDeclinedCloseFriendInvite": "The other party declined your close friend invitation.",
|
||||
"youAcceptedCloseFriendInvite": "You accepted the other party's close friend invitation.",
|
||||
"youDeclinedCloseFriendInvite": "You declined the other party's close friend invitation.",
|
||||
"closeFriendInviteTimedOut": "Close friend invitation timed out.",
|
||||
"cpDismissConfirmMessage": "Removing this relationship will cost {1} coins. Confirm removal?",
|
||||
"cpRelationCp": "CP",
|
||||
"cpRelationBrother": "Brother",
|
||||
"cpRelationSister": "Sister",
|
||||
"host": "Ospite",
|
||||
"following": "Segue",
|
||||
"agent": "Agenzia",
|
||||
"approved": "Approvato",
|
||||
"approvedWithinOneMinute": "Approvato e visualizzato entro 1 minuto dall'invio",
|
||||
"onlineUsers": "Utenti online({1}/{2}):",
|
||||
"applyToJoin": "Fai domanda per aderire",
|
||||
"supporterList": "Elenco dei sostenitori",
|
||||
"hostList": "Elenco degli host",
|
||||
"upToAdmins": "Fino agli amministratori {1}",
|
||||
"upToMembers": "Fino a membri {1}",
|
||||
"levelPrivileges": "Privilegi di livello",
|
||||
"ra": "RA",
|
||||
"roomAnnouncement": "Annuncio della stanza",
|
||||
"help": "Aiuto",
|
||||
"rejected": "Rifiutato",
|
||||
"boxContributeTips": "Gli investimenti sono già stati fatti oggi. Per favore, non investire di nuovo.",
|
||||
"bd": "B.D",
|
||||
"coupon": "Buono",
|
||||
"search": "Cerca",
|
||||
"get": "Ottieni",
|
||||
"inRocket": "Nel razzo",
|
||||
"roomRocketHelpTips": "1. L'invio di regali nella stanza aumenta l'energia del razzo. *1 regalo in moneta d'oro = 1 punto energia razzo; i regali fortunati aumentano l'energia del razzo del 4% del valore delle monete d'oro del regalo.\n2. Una volta che l'energia del razzo è completamente carica, la stanza può lanciare il razzo. I premi verranno distribuiti automaticamente dopo il lancio.\n3. Diversi livelli di razzi offrono ricompense diverse.\n4. Quando il razzo viene lanciato, tutti gli utenti nella stanza possono richiedere la ricompensa del razzo.\n5. L'energia del razzo viene ripristinata ogni giorno alle 00:00.",
|
||||
"couponRecord": "Registro dell'utilizzo del coupon",
|
||||
"inRoom": "Nella stanza",
|
||||
"searchCouponHint": "Cerca buono",
|
||||
"giftCounter": "Contatore di regali",
|
||||
"bDLeaderInviteYouToBecomeBDLeader": "Ti invito a diventare un BDLeader",
|
||||
"wins": "vince",
|
||||
"inviteYouToBecomeHost": "Invitarti a diventare un host.",
|
||||
"friends": "Amici",
|
||||
"deleteConversationTips": "Sei sicuro di voler eliminare la cronologia chat con questo utente?",
|
||||
"propMessagePrompt": "Prompt del messaggio di scena",
|
||||
"inputUserId": "Inserisci l'ID utente",
|
||||
"fromLuckyGifts": "Dai regali fortunati/magici",
|
||||
"receive": "Ricevi",
|
||||
"checkInSuccessful": "Check-in riuscito",
|
||||
"sginTips": "Riceverai un premio la prima volta che accedi ogni giorno. Se interrompi la serie di accessi, il premio verrà ricalcolato a partire dal primo giorno in cui effettuerai nuovamente l'accesso.",
|
||||
"popular": "Popolare",
|
||||
"recommend": "Consiglia",
|
||||
"follow": "Segui",
|
||||
"history": "Storia",
|
||||
"hotRooms": "Camere calde",
|
||||
"viewMore": "Visualizza altro",
|
||||
"noData": "Nessun dato",
|
||||
"users": "Utenti",
|
||||
"rooms": "Camere",
|
||||
"coins": "monete",
|
||||
"unread": "Non letto",
|
||||
"read": "Leggi",
|
||||
"image": "[Immagine]",
|
||||
"video": "[Video]",
|
||||
"sound": "[Suono]",
|
||||
"gift2": "[Regalo]",
|
||||
"clickHereToStartChatting": "Di' qualcosa.....",
|
||||
"receivedAMessage": "[Ricevuto un messaggio]",
|
||||
"confirmSwitchMicModelTips": "Confermi il passaggio in modalità seduta?",
|
||||
"number": "Numero",
|
||||
"album": "Album",
|
||||
"camera": "Fotocamera",
|
||||
"system": "Sistema",
|
||||
"notifcation": "Notifica",
|
||||
"inviteYouToBecomeAgent": "Invitati a diventare un'agenzia.",
|
||||
"myMusic": "La mia musica",
|
||||
"add": "Aggiungi",
|
||||
"pullToLoadMore": "Tirare per caricare altro",
|
||||
"loadingFailedClickToRetry": "Caricamento non riuscito, fare clic per riprovare",
|
||||
"releaseToLoadMore": "Rilascia per caricare altro",
|
||||
"haveMyLimits": "---Ho i miei limiti.---",
|
||||
"music": "Musica",
|
||||
"free": "Gratuito",
|
||||
"charm": "Fascino del regalo",
|
||||
"start": "Inizia",
|
||||
"stop": "Fermare",
|
||||
"chats": "Chat",
|
||||
"refuse": "Rifiutare",
|
||||
"agree": "D'accordo",
|
||||
"thisFeatureIsCurrentlyUnavailable": "Questa funzionalità non è attualmente disponibile.",
|
||||
"pleaseSelectTheRecipient": "Seleziona il destinatario.",
|
||||
"searchMemberIdHint": "Inserisci l'ID del membro",
|
||||
"unclaimedRedEnvelopes": "Le buste rosse non ritirate vengono rimborsate in 24 ore.",
|
||||
"redEnvelopeNotYetClaimed": "Busta rossa non ancora reclamata.",
|
||||
"redEnvelopeAmount": "Importo busta rossa: monete {1}",
|
||||
"sentARedEnvelope": "ha inviato una busta rossa.",
|
||||
"theRedEnvelopeHasExpired": "La busta rossa è scaduta.",
|
||||
"joinRequest": "Richiesta di adesione",
|
||||
"scrollToTheBottom": "Scorri fino in fondo",
|
||||
"gameRules": "Regole del gioco:",
|
||||
"charmGameRulesTips": "Accendi la dashboard per mostrare sul microfono le monete regalo ricevute da tutti gli utenti. 1 moneta = 1 punteggio (Regalo fortunato: 1 moneta = 0,04 punteggio).",
|
||||
"inputYourOldPassword": "Inserisci la tua vecchia password",
|
||||
"enterYourOldPassword": "Inserisci la tua vecchia password",
|
||||
"setYourPassword": "Imposta la tua password",
|
||||
"enterYourNewPassword": "Inserisci la tua nuova password",
|
||||
"confirmYourPassword": "Conferma la tua password",
|
||||
"theTwoPasswordsDoNotMatch": "Le due password non corrispondono.",
|
||||
"resetLoginPasswordtTips2": "La password deve contenere da 8 a 16 caratteri e deve essere una combinazione di lettere inglesi maiuscole e minuscole e numeri (non solo numeri).",
|
||||
"resetLoginPassword": "Reimposta password di accesso",
|
||||
"resetLoginPasswordtTips1": "Accedi con il tuo ID utente o Vanity ID. È più sicuro accedere con un account yumi.",
|
||||
"localMusic": "Musica locale",
|
||||
"setLoginPassword": "Imposta la password di accesso",
|
||||
"confirmSwitchMicThemeTips": "Confermi il cambio di stile del sedile?",
|
||||
"micTheme": "Tema del microfono",
|
||||
"classicMic": "Microfono classico {1}",
|
||||
"yesterday": "Ieri {1}",
|
||||
"monday": "Lunedì {1}",
|
||||
"tuesday": "Martedì {1}",
|
||||
"wednesday": "Mercoledì {1}",
|
||||
"thursday": "Giovedì {1}",
|
||||
"friday": "Venerdì {1}",
|
||||
"saturday": "Sabato {1}",
|
||||
"sunday": "Domenica {1}",
|
||||
"acceptedYour": "{1} ha accettato il tuo",
|
||||
"youAccepted": "Hai accettato {1}",
|
||||
"openRedPackDialogTip": "La busta rossa",
|
||||
"micManagement": "Gestione del microfono",
|
||||
"bdCenter": "Centro B.D",
|
||||
"rechargeAgency": "Agenzia di ricarica",
|
||||
"adminCenter": "Centro di amministrazione",
|
||||
"managerCenter": "Centro Dirigente",
|
||||
"goldList": "Lista d'oro",
|
||||
"followList": "Segui l'elenco",
|
||||
"fansList": "Elenco dei tifosi",
|
||||
"daily": "Ogni giorno",
|
||||
"female": "Femmina",
|
||||
"male": "Maschio",
|
||||
"identity": "Identità",
|
||||
"adjust": "Regolare",
|
||||
"warning": "Avvertimento",
|
||||
"screenshotTips": "Schermata (fino a 3)",
|
||||
"roomNotice": "Avviso di sala",
|
||||
"roomTheme": "Tema della stanza",
|
||||
"description": "Descrizione:",
|
||||
"inputDesHint": "Si prega di descrivere il problema nel modo più dettagliato possibile in modo che possiamo capirlo e risolverlo.",
|
||||
"roomProfilePicture": "Immagine del profilo della stanza",
|
||||
"userProfilePicture": "Immagine del profilo utente",
|
||||
"userName": "Nome utente",
|
||||
"pleaseSelectTheTypeToProcess": "Seleziona il tipo da elaborare:",
|
||||
"roomEditing": "Modifica della stanza",
|
||||
"setAccount": "Imposta conto",
|
||||
"userEditing": "Modifica utente",
|
||||
"enterTheUserId": "Inserisci l'ID utente",
|
||||
"enterTheRoomId": "Inserisci l'ID della stanza",
|
||||
"deleteAccount": "Elimina account",
|
||||
"becomeAgent": "Diventa un agente",
|
||||
"enterNickname": "Inserisci il soprannome",
|
||||
"enterInvitationCode": "inserisci il codice invito",
|
||||
"selectYourCountry": "Seleziona il tuo paese",
|
||||
"inviteCode": "Codice invito",
|
||||
"magic": "Magia",
|
||||
"luckGiftSpecialEffects": "Effetti di animazione regalo fortunato",
|
||||
"theVideoSizeCannotExceed": "La dimensione del video non può superare i 50 MB",
|
||||
"weekly": "Settimanale",
|
||||
"customizedGiftRulesContent": "Come ricevere il mio regalo personalizzato:\n1.Determinare se l'utente ha diritto a ricevere un regalo personalizzato in base al suo attuale livello di ricchezza.\n(1) Quando il livello di ricchezza dell'utente è ≥ 35: l'utente può caricare un video per il regalo personalizzato una volta. Utilizzeremo l'immagine del profilo attuale dell'utente come immagine regalo e il video fornito come effetto regalo su \"Personalizzato\". La produzione richiederà del tempo e ti avviseremo non appena sarà disponibile nel negozio.\n(2) Quando il livello di ricchezza dell'utente è ≥45: l'utente può caricare un video per il regalo personalizzato una volta. Utilizzeremo l'immagine del profilo attuale dell'utente come immagine regalo e il video fornito come effetto regalo su \"Personalizzato\". La produzione richiederà del tempo e ti avviseremo non appena sarà disponibile nel negozio.\n2.Puoi partecipare ad attività specifiche sull'app e, dopo aver soddisfatto i criteri, contattarci tramite la sezione \"Messaggio\" → \"Contattaci\". Fornisci gli screenshot dell'attività e il video che desideri utilizzare per il regalo personalizzato. Utilizzeremo la tua attuale immagine del profilo come immagine regalo e il video fornito come effetto regalo, quindi lo elencheremo in \"Personalizzato\". La produzione richiederà del tempo e ti avviseremo non appena sarà disponibile nel negozio.\nPeriodo di validità del regalo personalizzato e come estenderlo:\nGli esclusivi regali personalizzati saranno disponibili sullo scaffale per un periodo di validità di 30 giorni. Per consentire agli utenti influenti e stimolanti di continuare a godere di questa nuova esperienza e mostrare il proprio stile personale, gli utenti che possiedono regali personalizzati possono estendere la durata di conservazione di tutti i loro regali personalizzati di 30 giorni ricaricando $ 500 in un dato mese.",
|
||||
"customizedGiftRules": "Regole regalo personalizzate",
|
||||
"rulesUpload": "Regole e caricamento",
|
||||
"monthly": "Mensile",
|
||||
"message": "Messaggio",
|
||||
"clearCache": "Cancella cache",
|
||||
"customized": "Personalizzato",
|
||||
"searchInputHint": "Inserisci il numero di conto/stanza",
|
||||
"kickRoomTips": "Sei stato cacciato dalla stanza.",
|
||||
"roomKickedEntryBlockedTips": "Sei stato rimosso dalla stanza da un amministratore e al momento non puoi entrare.",
|
||||
"joinRoomTips": "Entra nella stanza!",
|
||||
"roomSetting": "Impostazione della stanza",
|
||||
"roomDetails": "Dettagli della camera",
|
||||
"systemRoomTips": "Sii educato e rispettoso. Qualsiasi contenuto pornografico o volgare è severamente proibito su Yumi. Una volta scoperto, l'account verrà bannato permanentemente. Si prega di rispettare consapevolmente i regolamenti della piattaforma yumi.",
|
||||
"copiedToClipboard": "Copiato negli appunti",
|
||||
"recharge": "Ricaricare",
|
||||
"receivedFromALuckyGift": "Ricevuto da un regalo fortunato/magico.",
|
||||
"followedYou": "Ti ho seguito",
|
||||
"agentCenter": "Centro Agenzia",
|
||||
"areYouSureYouWantToClearLocalCache": "Sei sicuro di voler svuotare la cache locale?",
|
||||
"clearMessage": "Cancella i messaggi sullo schermo",
|
||||
"report": "Rapporto",
|
||||
"coins3": "Monete",
|
||||
"giftSpecialEffects": "Regala effetti speciali",
|
||||
"basicFeatures": "Funzionalità di base",
|
||||
"task": "Compito",
|
||||
"importantReminder": "Promemoria importante",
|
||||
"entryVehicleAnimation": "Animazione del veicolo in entrata",
|
||||
"floatingAnimationInGlobal": "Animazione mobile in globale",
|
||||
"broadcast": "Trasmettere",
|
||||
"broadcastDisplay": "Visualizzazione della trasmissione",
|
||||
"broadcastAllPlaces": "Tutti i posti",
|
||||
"broadcastRoomOnly": "Nella stanza",
|
||||
"broadcastOff": "Chiudi trasmissione",
|
||||
"entryVehicleAnimation2": "Gli utenti con privilegi VIP4 o superiori possono utilizzare questa funzione per disabilitare le animazioni del veicolo.",
|
||||
"dailyTasks": "Compiti quotidiani",
|
||||
"exclusiveForNewcomers": "Esclusivo per i nuovi arrivati",
|
||||
"resetsDailyAtMidnight": "Si ripristina ogni giorno a mezzanotte",
|
||||
"limitedToOneTime": "Limitato a una volta",
|
||||
"useMicrophoneForOneMin": "Usa il microfono per 1 minuto",
|
||||
"taskRequirement": "Requisito",
|
||||
"taskProgress": "Progresso",
|
||||
"rewardClaimedSuccessfully": "Premio richiesto con successo",
|
||||
"rewardClaimFailed": "La richiesta del premio non è riuscita",
|
||||
"enterRoomConfirmTips": "Sei sicuro di voler entrare nella stanza?",
|
||||
"followSucc": "Seguito con successo",
|
||||
"goldListort": "Lista d'oro",
|
||||
"rechargeList": "Elenco ricarica",
|
||||
"edit": "Modifica",
|
||||
"swipeLeftOnTheFloatingScreenAreaToQuicklyCloseIt": "*Suggerimento: scorri verso sinistra nell'area dello schermo mobile per chiuderla rapidamente.",
|
||||
"enterThisVoiceChatRoom": "Entra in questa chat room vocale?",
|
||||
"go": "Vai",
|
||||
"done": "Fatto",
|
||||
"improvementTasks": "Compiti di miglioramento",
|
||||
"save": "Salva",
|
||||
"kickPrevention": "Prevenzione dei calci",
|
||||
"freeChatSpeak": "Chatta e parla gratuitamente",
|
||||
"nickName": "NickNome",
|
||||
"gender": "Genere",
|
||||
"unFollow": "Smetti di seguire",
|
||||
"days": "Giorni",
|
||||
"permanent": "Permanente",
|
||||
"country": "Paese",
|
||||
"birthday": "Compleanno",
|
||||
"man": "L'uomo",
|
||||
"woman": "Donna",
|
||||
"apple": "mela",
|
||||
"google": "Google",
|
||||
"startVoiceParty": "Inizia una festa vocale!",
|
||||
"enterRoomTips": "{1} Entra nella stanza",
|
||||
"roomName": "Nome della stanza",
|
||||
"roomMember": "Membro della stanza",
|
||||
"pleaseSelectYourCountry": "Seleziona il tuo paese.",
|
||||
"pleaseSelectYourGender": "Seleziona il tuo sesso.",
|
||||
"pleaseEnterNickname": "Inserisci un soprannome.",
|
||||
"countryRegion": "Paese e regione",
|
||||
"dateOfBirth": "Data di nascita",
|
||||
"mute": "Muto",
|
||||
"exit": "Esci",
|
||||
"mysteriousInvisibility": "Invisibilità misteriosa",
|
||||
"antiBlock": "Antiblocco",
|
||||
"privateChat": "Chat privata",
|
||||
"storeDiscount": "Sconto negozio {1} Off",
|
||||
"membershipFreeChatSpeak": "Chatta e parla senza abbonamento",
|
||||
"priorityRoomSorting": "Ordinamento delle stanze prioritarie",
|
||||
"userColoredID": "ID colorato dell'utente",
|
||||
"pleaseSelectaItem": "Seleziona un articolo",
|
||||
"areYouRureRoRecharge": "Sei sicuro di voler ricaricare?",
|
||||
"mInimize": "Tieni",
|
||||
"everyone": "Tutti",
|
||||
"shop": "Negozio",
|
||||
"roomOwner": "Proprietario della stanza",
|
||||
"dailyTaskRewardBonus": "Bonus ricompensa attività giornaliera ({1} XP)",
|
||||
"userLevelXPBoost": "Incremento XP livello utente ({1} XP)",
|
||||
"andAboveUsers": "{1} e utenti superiori",
|
||||
"privileges": "{1} Privilegi",
|
||||
"basicPermissions": "Autorizzazioni di base",
|
||||
"hostCenter": "Centro ospitante",
|
||||
"allOnMicrophone": "Tutto al microfono",
|
||||
"usersOnMicrophone": "Utenti al microfono",
|
||||
"allInTheRoom": "Tutto in camera",
|
||||
"send": "Invia",
|
||||
"crop": "Ritaglia",
|
||||
"goToUpgrade": "Vai all'aggiornamento",
|
||||
"exclusiveEmojiWillBeReleasedAfterBecoming": "Le emoji esclusive verranno rilasciate dopo essere diventate",
|
||||
"preventBeingBlocked": "Evita di essere bloccato",
|
||||
"enableRankIncognitoMode": "Abilita la modalità di navigazione in incognito del ranking",
|
||||
"avoidBeingKicked": "Evitare di essere presi a calci",
|
||||
"finish": "Finisci",
|
||||
"takeTheMic": "Prendi il microfono",
|
||||
"openTheMic": "Apri il microfono",
|
||||
"muteTheMic": "Disattiva il microfono",
|
||||
"unlockTheMic": "Sblocca il microfono",
|
||||
"leavelTheMic": "Lascia il microfono",
|
||||
"lockTheMic": "Blocca il microfono",
|
||||
"removeTheMic": "Rimuovi il microfono",
|
||||
"inviteToTheMicrophone": "Invito al microfono",
|
||||
"openUserProfleCard": "Apri la scheda del profilo utente",
|
||||
"obtain": "ottenere",
|
||||
"win2": "Vinci {1}",
|
||||
"backTheRoom": "Stanza sul retro",
|
||||
"toConsume": "Consumare",
|
||||
"howToUpgrade": "Come aggiornare?",
|
||||
"spendCoinsToGainExperiencePoints": "Spendi monete per guadagnare punti esperienza",
|
||||
"richLevelUpgradeAnswer": "Lascia che la tua ricchezza conquisti tutto",
|
||||
"charmLevelUpgradeAnswer": "Fa' che gli altri si arrendano al tuo fascino",
|
||||
"higherLevelFancierAvatarFrame": "Distintivi/cornice avatar di livello superiore e più elaborati",
|
||||
"all": "Tutto",
|
||||
"gift": "Regalo",
|
||||
"chat": "Chatta",
|
||||
"owner": "Proprietario",
|
||||
"store": "Negozio",
|
||||
"admin": "Ammin",
|
||||
"member": "Membro",
|
||||
"guest": "Ospite",
|
||||
"submit": "Invia",
|
||||
"claim": "Richiedi",
|
||||
"claimed": "Reclamato",
|
||||
"complete": "Completo",
|
||||
"shareTo": "Condividi su",
|
||||
"copyLink": "Copia collegamento",
|
||||
"faceBook": "Facebook",
|
||||
"whatsApp": "Whatsapp",
|
||||
"snapChat": "Snapchat",
|
||||
"taskNamePersonalGameConsume": "Spesa di gioco",
|
||||
"taskNameRoomNewMember": "Nuovi membri della stanza",
|
||||
"taskNameRoomOwnerSendRedPacket": "Il proprietario della stanza invia un pacchetto rosso",
|
||||
"taskNameRoomOwnerSendGiftUser": "Il proprietario della stanza invia un regalo",
|
||||
"taskNameRoomMicUser120Min": "Membri con più di 120 minuti al microfono",
|
||||
"taskNameRoomMicUser60Min": "Membri con più di 60 minuti al microfono",
|
||||
"taskNameRoomMicUser30Min": "Membri con più di 30 minuti al microfono",
|
||||
"taskNamePersonalSendGift": "Invia regalo all'utente",
|
||||
"taskNameRoomOnlineUserCount": "Membri online della stanza",
|
||||
"taskNameRoomOwnerInviteMic": "Invita membro al Mic",
|
||||
"taskNameRoomUserSendGiftGold": "Monete donate dai membri",
|
||||
"taskNameRoomOwnerSendGiftGold": "Monete regalate dal proprietario della stanza",
|
||||
"taskNamePersonalMagicGiftGold": "Monete vinte inviando regali magici",
|
||||
"taskNamePersonalLuckyGiftGold": "Monete vinte inviando regali fortunati",
|
||||
"taskNameRoomOwnerMicTime": "Il proprietario della stanza accende il microfono nella stanza",
|
||||
"taskNamePersonalActiveInRoom": "Sii attivo nelle stanze degli altri",
|
||||
"taskNamePersonalMicInRoom": "Vai al microfono",
|
||||
"dailyCoinBonanzaRulesDetail": "1. Le attività personali giornaliere e le attività giornaliere del proprietario della stanza possono essere completate una volta per utente al giorno. Le attività vengono ripristinate alle 00:00:00 ora saudita.\n2.Le attività personali quotidiane possono essere completate solo nelle stanze degli altri; Le attività del proprietario della stanza possono essere completate solo nella tua stanza.\n3. Le attività di donazione contano solo i regali inviati nelle stanze virtuali, non i regali inviati sui feed.\n4. Se vengono creati più account utilizzando lo stesso dispositivo o la stessa carta SIM in qualsiasi modo, i premi per tutte le attività possono essere richiesti una sola volta.",
|
||||
"dailyCoinBonanzaRules": "Regole della Bonanza giornaliera di monete",
|
||||
"roomOwnerTasks": "Compiti del proprietario della sala",
|
||||
"personalTasks": "Compiti personali",
|
||||
"noPromptsToday": "Nessun compito oggi.",
|
||||
"getPaidToRefer": "Fatti pagare per fare riferimento",
|
||||
"membershipFee": "Quota associativa",
|
||||
"membershipFeeTips1": "Imposta la quota associativa per la tua camera. Gli utenti possono unirsi alla tua stanza pagando la quota.",
|
||||
"membershipFeeTips2": "I gold necessari affinché l'utente diventi membro della stanza. Il proprietario della stanza riceverà il 50% dei gold.",
|
||||
"freePrice": "Tariffa: 0-10000",
|
||||
"touristsSendText": "Il turista invia un messaggio",
|
||||
"touristsTakeToTheMic": "I turisti prendono il microfono",
|
||||
"theMembershipFee": "La quota associativa",
|
||||
"theModificationsMade": "Le modifiche apportate questa volta non verranno salvate all'uscita",
|
||||
"viewFrame": "Visualizza cornice",
|
||||
"enterRoomName": "Inserisci il nome della stanza",
|
||||
"headdress": "Cornici",
|
||||
"mountains": "Veicoli",
|
||||
"purchaseIsSuccessful": "L'acquisto è andato a buon fine",
|
||||
"buy": "Acquista",
|
||||
"followed": "Seguito",
|
||||
"follow2": "Segui:{1}",
|
||||
"fans2": "Ventole:{1}",
|
||||
"vistors2": "Visitatori:{1}",
|
||||
"personal2": "Personale:",
|
||||
"conntinue": "Continua",
|
||||
"confirmBuyTips": "Sei sicuro di voler acquistare?",
|
||||
"purchase": "Acquistare",
|
||||
"setRoomPassword": "Imposta la password della stanza",
|
||||
"inputRoomPassword": "Inserisci la password della stanza",
|
||||
"enter": "Entra",
|
||||
"deleteCommentTips": "Sei sicuro di voler eliminare questo commento?",
|
||||
"deleteSuccessful": "Cancellazione riuscita!",
|
||||
"itemsLeft": "Oggetti rimasti",
|
||||
"password": "Parola d'ordine",
|
||||
"replySucc": "Risposta riuscita",
|
||||
"comment": "Commento",
|
||||
"showMore": "Mostra di più",
|
||||
"showLess": "Mostra meno",
|
||||
"enterPassword": "Inserisci la password",
|
||||
"enterAccount": "Inserisci Conto",
|
||||
"logIn": "Accedi",
|
||||
"saySomething": "Di' qualcosa...",
|
||||
"sayHi": "Di' ciao..",
|
||||
"pleaseChatFfriendly": "Per favore chatta gentilmente",
|
||||
"unLockTheRoom": "Sblocca la stanza",
|
||||
"operationSuccessful": "L'operazione ha avuto successo.",
|
||||
"adminByHomeowner": "è impostato come amministratore dal proprietario della casa.",
|
||||
"memberByHomeowner": "è impostato come membro dal proprietario della casa.",
|
||||
"touristByHomeowner": "è impostato come turista dal proprietario della casa.",
|
||||
"becomeHost": "Fai domanda per diventare un host",
|
||||
"superFans": "Super tifosi:",
|
||||
"setUpAnIdentity": "Configura un'identità",
|
||||
"kickedOutOfRoom": "Cacciato dalla stanza",
|
||||
"knapsack": "Zaino",
|
||||
"bdLeader": "Leader del BD",
|
||||
"picture": "Immagine",
|
||||
"theImageSizeCannotExceed": "La dimensione dell'immagine non può superare i 4M",
|
||||
"activity": "Attività",
|
||||
"alreadyAnAdministrator": "Già amministratore",
|
||||
"alreadyAnMember": "Già membro",
|
||||
"alreadyAnTourist": "Già turista",
|
||||
"touristsCannotSendMessages": "I turisti non possono inviare messaggi",
|
||||
"touristsAreNotAllowedToGoOnTheMic": "Ai turisti non è consentito avvicinarsi al microfono",
|
||||
"lockTheRoom": "Chiudi la stanza",
|
||||
"special": "Speciale",
|
||||
"visitorList": "Elenco dei visitatori",
|
||||
"successfulWear": "Indossare con successo",
|
||||
"confirmUnUseTips": "Confermi di rimuoverlo?",
|
||||
"custom": "Personalizzato",
|
||||
"customBackground": "Sfondo personalizzato",
|
||||
"example": "Esempio",
|
||||
"endPreview": "Fine anteprima",
|
||||
"selectAgain": "Seleziona di nuovo",
|
||||
"myItems": "I miei articoli",
|
||||
"use": "Utilizzare",
|
||||
"unUse": "Disequipaggiare",
|
||||
"renewal": "Rinnovo",
|
||||
"wallet": "Portafoglio",
|
||||
"profile": "Profilo",
|
||||
"giftwall": "Giftwall",
|
||||
"announcement": "Annuncio",
|
||||
"blockedList": "Elenco bloccato",
|
||||
"country2": "Paese:",
|
||||
"sendTo": "Invia a",
|
||||
"credits": "Crediti: {1}",
|
||||
"successfullyUnloaded": "Scaricato con successo",
|
||||
"expired": "Scaduto",
|
||||
"day": "Giorno",
|
||||
"inUse": "In uso",
|
||||
"staticOrGifImage": "Immagine statica/GIF",
|
||||
"confirmUseTips": "Vuoi confermarne l'utilizzo?",
|
||||
"pleaseUploadUserAvatar": "Per favore carica un avatar.",
|
||||
"joinMemberTips": "Se sei un visitatore nella stanza, non puoi prendere il microfono.",
|
||||
"giftGivingSuccessful": "Fare regali con successo.",
|
||||
"theAccountPasswordCannotBeEmpty": "L'account o la password non possono essere vuoti.",
|
||||
"invitesYouToTheMicrophone": "{1} ti invita al microfono",
|
||||
"english": "Inglese",
|
||||
"chinese": "Cinese",
|
||||
"arabic": "Arabo",
|
||||
"darkMode": "Modalità oscura",
|
||||
"lightMode": "Modalità luce",
|
||||
"systemDefault": "Predefinito del sistema",
|
||||
"pleaseGetOnTheMicFirst": "Per favore, prima prendi il microfono.",
|
||||
"roomMusicAddMusic": "Aggiungi musica",
|
||||
"roomMusicSelectAll": "Seleziona tutto",
|
||||
"roomMusicAddedCount": "Aggiunti brani {1}",
|
||||
"roomMusicEmpty": "Niente musica",
|
||||
"roomMusicScanFromPhone": "Scansione dal telefono",
|
||||
"roomMusicTotalSongs": "Totale: brani {1}",
|
||||
"roomMusicNotFound": "Nessuna musica trovata",
|
||||
"roomMusicDeleteConfirm": "Eliminare questa musica?",
|
||||
"roomMusicDeleted": "Musica cancellata",
|
||||
"roomMusicListMode": "Gioca in ordine",
|
||||
"roomMusicShuffleMode": "Riproduzione casuale",
|
||||
"roomMusicSingleMode": "Ciclo singolo",
|
||||
"duration2": "Durata:{1}",
|
||||
"roomRocketNoteTitle": "Nota",
|
||||
"roomRocketNoteSectionTitle": "Ogni volta un VIP",
|
||||
"roomRocketNoteSectionBody": "1. Ogni volta che viene acquistato un livello VIP, è valido per 30 giorni e può essere rinnovato manualmente dopo la scadenza.",
|
||||
"roomRocketRewardEmptyText": "Nessun premio guadagnato. Più fortuna la prossima volta.",
|
||||
"roomRocketRecordEmptyText": "Nessun record di ricompensa",
|
||||
"launchRocket": "Lancia il razzo",
|
||||
"roomRocketFuelFullNotice": "Carburante del razzo pieno! Lancio tra {1} s!"
|
||||
}
|
||||
750
assets/l10n/intl_pt.json
Normal file
@ -0,0 +1,750 @@
|
||||
{
|
||||
"signInWithGoogle": "Faça login com o Google",
|
||||
"or": "Ou",
|
||||
"signInWithYourAccount": "Faça login com sua conta",
|
||||
"signInWithApple": "Faça login com a Apple",
|
||||
"loginRepresentsAgreementTo": "Login representa acordo para",
|
||||
"termsofService": "Termos de serviço",
|
||||
"privaceyPolicy": "Política de Privacidade",
|
||||
"tips": "Dicas",
|
||||
"searchNoDataTips": "Insira a sala ou ID de usuário que você deseja pesquisar.",
|
||||
"games": "Jogos",
|
||||
"mine": "Meu",
|
||||
"official": "Oficial",
|
||||
"party": "Festa",
|
||||
"other": "Outro",
|
||||
"yes": "Sim",
|
||||
"bag": "Bolsa",
|
||||
"sound2": "Som",
|
||||
"startYourBrandNewJourney": "Comece sua nova jornada",
|
||||
"maliciousHarassment": "Assédio malicioso",
|
||||
"event": "Evento",
|
||||
"roomEdit": "Edição de sala",
|
||||
"cancelRoomPassword": "Tem certeza de que deseja excluir a senha da sala?",
|
||||
"roomMemberFee": "Taxa de membro do quarto",
|
||||
"roomTheme2": "Tema do quarto",
|
||||
"background": "Plano de fundo",
|
||||
"blockedList2": "Lista de Bloqueados",
|
||||
"roomPassword": "Senha da sala",
|
||||
"numberOfMic": "Número de microfone",
|
||||
"pleaseEnterContent": "Por favor insira o conteúdo",
|
||||
"profilePhoto": "Foto do perfil",
|
||||
"aboutMe": "Sobre mim",
|
||||
"myRoom": "Meu quarto",
|
||||
"noHistoricalRecordsAvailable": "Não há registros históricos disponíveis.",
|
||||
"inviteNewUsersToEarnCoins": "Convide novos usuários para ganhar moedas",
|
||||
"crateMyRoom": "CRIE SEU PRÓPRIO QUARTO.",
|
||||
"casualInteraction": "Interação casual",
|
||||
"haveGamePlayingTips": "Você tem um jogo em andamento. Por favor, saia do jogo atual. Tem certeza de que deseja sair?",
|
||||
"historicalTour": "Passeio Histórico",
|
||||
"gameCenter": "Centro de jogos",
|
||||
"returnToVoiceChat": "Voltar ao bate-papo por voz?",
|
||||
"exitGameMode": "Sair do modo de jogo",
|
||||
"enterTheRoom": "Entre na sala",
|
||||
"invite": "Convidar",
|
||||
"recent": "Recente",
|
||||
"popularEvents": "Eventos populares",
|
||||
"inviteGoRoomTips": "Sempre aqui para você, faça chuva ou faça sol. Entre e diga oi!",
|
||||
"confirmInviteThisUserToTheRoom": "Confirmar o convite deste usuário (ID:{1}) para a sala?",
|
||||
"clearCacheSuccessfully": "Limpe o cache com sucesso",
|
||||
"sent": "Enviado",
|
||||
"keep": "Manter",
|
||||
"open": "Abrir",
|
||||
"deleteAccountTips2": "*Se mudar de ideia, você poderá fazer login novamente em sua conta atual dentro de sete dias e nós restauraremos sua conta automaticamente. Se nenhuma restauração ocorrer dentro de sete dias, a conta será excluída permanentemente.",
|
||||
"deleteAccountTips": "Você tem direitos administrativos totais sobre esta conta. Caso pretenda eliminar a conta, esteja ciente dos seguintes riscos associados a esta operação:\n1. Depois que a conta for excluída com sucesso, você não poderá mais fazer login na sua conta atual. A exclusão da conta é uma ação permanente.\n2. Depois que a conta for excluída com sucesso, você não poderá recuperar nenhum dado da conta. Todas as informações (incluindo salas e amigos), moeda virtual, presentes e itens virtuais serão excluídos permanentemente e não poderão ser restaurados.\n3. Período de reflexão: Se você não restaurar a conta, não poderá acessar a página de compra, a página de retirada ou qualquer outra página do aplicativo.\n4. Durante o período de reflexão ou após a exclusão da conta, a página do perfil indicará que ela foi excluída. Para proteger a sua conta de ser pesquisada ou acessada por outras pessoas, suas informações pessoais serão removidas dos sistemas relacionados às funções diárias. Quando a exclusão de conta envolver segurança nacional, processos civis ou criminais, ou a proteção de direitos e interesses legítimos de terceiros, a equipe oficial reserva-se o direito de rejeitar a solicitação de exclusão de conta do usuário.\nSe você tiver certeza de que deseja excluir todos os dados pessoais da sua conta atual, clique em “Excluir conta”.",
|
||||
"accountDeletionNotice": "Aviso de exclusão de conta:",
|
||||
"thisUserHasBeenBlacklisted": "Este usuário foi colocado na lista negra.",
|
||||
"trend": "Tendência",
|
||||
"like": "Gosto",
|
||||
"more": "Mais",
|
||||
"discard": "Descartar",
|
||||
"catchFirstComment": "Pegue o primeiro comentário",
|
||||
"reply": "Responder",
|
||||
"posting": "Postagem",
|
||||
"multiple": "Múltiplo",
|
||||
"successfullyRemovedFromTheBlacklist": "Removido com sucesso da lista negra!",
|
||||
"successfullyAddedToTheBlacklist": "Adicionado com sucesso à lista negra!",
|
||||
"youAreCurrentlyCPRelationshipPleaseDissolve": "Você está atualmente em um relacionamento CP.\nPor favor, dissolva-o primeiro.",
|
||||
"areYouSureToCancelBlacklist": "Tem certeza de cancelar a lista negra?",
|
||||
"areYouSureYouWantToBlockThisUser": "Tem certeza de que deseja bloquear este usuário?",
|
||||
"removeFromBlacklist": "Remover da lista negra",
|
||||
"moveToBlacklist": "Mover para a lista negra",
|
||||
"userBlacklist": "Lista negra de usuários",
|
||||
"specialEffectsManagement": "Gerenciamento de efeitos especiais",
|
||||
"wishingYouHappinessEveryDay": "Desejando-lhe felicidade todos os dias.",
|
||||
"newMessage": "Nova mensagem",
|
||||
"createRoomSuccsess": "Sala criada com sucesso!",
|
||||
"contactUs": "Contate-nos",
|
||||
"systemAnnouncementTips1": "Proteja-se contra fraudes:",
|
||||
"systemAnnouncementTips": "Verifique as informações exclusivamente através dos canais oficiais. Nunca baixe software de terceiros, compartilhe dados pessoais ou transfira dinheiro com base em solicitações externas. Os IDs oficiais dos funcionários são apenas 10.000, 10.003 e 10.086. Para qualquer suspeita, pare e informe via",
|
||||
"systemAnnouncement": "Anúncio do sistema",
|
||||
"doNotClickUnfamiliarTips": "Não clique em links desconhecidos, pois eles podem expor suas informações pessoais. Nunca compartilhe seus dados de identidade ou cartão bancário com ninguém.",
|
||||
"atTag": "@Etiqueta",
|
||||
"sayHi2": "Diga oi",
|
||||
"roomBottomGreeting": "Olá...",
|
||||
"canSendMsgTips": "Ambas as partes precisam seguir uma à outra antes de enviar mensagens privadas.",
|
||||
"msgSendRedEnvelopeTips": "*Será cobrada uma taxa de serviço de 10% sobre os envelopes vermelhos e os destinatários receberão apenas 90% do valor do envelope vermelho. O nível de riqueza do remetente deve ser superior ao Nível 10.",
|
||||
"reapply": "Reaplicar",
|
||||
"cancelRequest": "Cancelar solicitação",
|
||||
"pending": "Pendente",
|
||||
"supporter": "Apoiador",
|
||||
"coinsReceived": "Moedas recebidas",
|
||||
"numberOfSign": "Número do sinal: {1}",
|
||||
"hostWeeklyRank": "Classificação semanal do anfitrião",
|
||||
"supporterWeeklyRank": "Classificação Semanal do Apoiador",
|
||||
"memberList": "Lista de membros",
|
||||
"treasureChest": "Baú do Tesouro",
|
||||
"applicationRecord": "Registro de aplicação",
|
||||
"appUpdateTip": "O aplicativo possui uma nova versão ({1}). Gostaria de baixá-lo agora?",
|
||||
"ownerIncomeCoins": "Renda do proprietário: moedas {1}",
|
||||
"game": "Jogo",
|
||||
"skip2": "Pular",
|
||||
"skipCountdown": "Pular {1}s",
|
||||
"coins4": "Moedas",
|
||||
"weekStart": "Início da semana",
|
||||
"forMoreRewardsPleaseCheckTheTaskCenter": "Para mais recompensas, verifique o centro de tarefas",
|
||||
"kingQuuen": "Rei-Rainha",
|
||||
"ramadan": "Ramadã",
|
||||
"updateNow": "Atualizar agora",
|
||||
"allGames": "Todos os jogos",
|
||||
"fishClass": "Classe de Peixe",
|
||||
"greedyClass": "Classe gananciosa",
|
||||
"raceSeries": "Série de corridas",
|
||||
"slotsClass": "Classe de caça-níqueis",
|
||||
"others": "Outros",
|
||||
"hotGames": "Jogos quentes",
|
||||
"chatBox": "Caixa de bate-papo",
|
||||
"termsOfServicePrivacyPolicyTips": "Ao continuar você concorda com os Termos de Serviço e Política de Privacidade",
|
||||
"and": "e",
|
||||
"pleaseSelectTheTypeContent": "Selecione o tipo de conteúdo ofensivo.",
|
||||
"illegalInformation": "Informação ilegal",
|
||||
"inappropriateContent": "Conteúdo impróprio",
|
||||
"personalAttack": "Ataque pessoal",
|
||||
"confirm": "Confirmar",
|
||||
"spam": "Spam",
|
||||
"countdownMinutes": "Minutos de contagem regressiva:",
|
||||
"number2": "Número:",
|
||||
"fraud": "Fraude",
|
||||
"received": "Recebido",
|
||||
"currentProgress": "Progresso atual",
|
||||
"currentStage": "Estágio atual: {1}",
|
||||
"roomReward2": "Recompensa do Quarto:{1}",
|
||||
"roomReward": "Recompensa de quarto",
|
||||
"expirationTime": "Prazo de validade",
|
||||
"ownerSendTheRedEnvelope": "O proprietário enviou moedas de recompensa.",
|
||||
"rewardCoins": "Moedas de recompensa: moedas {1}",
|
||||
"signInRewardReceived": "Login realizado com sucesso. Recompensa: {1}",
|
||||
"registerRewardReceived": "Recompensa de registro recebida: {1}",
|
||||
"lastWeekProgress": "Progresso da semana passada",
|
||||
"redEnvelopeTips2": "*Se o envelope vermelho não for reclamado dentro do prazo, as restantes moedas serão devolvidas ao utilizador que enviou o envelope vermelho.",
|
||||
"goToRecharge": "Vá para recarregar",
|
||||
"deleteAccount2": "Excluir conta ({1}s)",
|
||||
"areYouSureYouWantToDeleteYourAccount": "Tem certeza de que deseja excluir sua conta?",
|
||||
"insufhcientGoldsGoToRecharge": "Ouro insuficiente. Recarregue agora!",
|
||||
"coins2": "Moedas {1}",
|
||||
"remainingNumberTips": "Restante disponível: ({1}/{2})",
|
||||
"collectionTimeTips": "Tempo de coleta: {1} ({2}/{3})",
|
||||
"sendARedEnvelope": "Envie um envelope vermelho",
|
||||
"sendRedPackConfirmTips": "Tem certeza de que deseja enviar o pacote vermelho?",
|
||||
"redEnvelopeSendingRecords": "Registros de envio de envelope vermelho:",
|
||||
"redEnvelope": "Envelope Vermelho",
|
||||
"roomRedPacketGoldCoins": "Moedas de ouro",
|
||||
"roomRedPacketQuantity": "Quantidade",
|
||||
"roomRedPacketPickupTime": "Horário de coleta",
|
||||
"roomRedPacketInMinutes": "Em minutos {1}",
|
||||
"roomRedPacketClaimNow": "Reivindique agora",
|
||||
"roomRedPacketSendLuckyPack": "Envie um pacote da sorte",
|
||||
"roomRedPacketSending": "Enviando...",
|
||||
"roomRedPacketSendSuccess": "Pacote da sorte enviado",
|
||||
"roomRedPacketComingSoon": "Em breve",
|
||||
"roomRedPacketRulesTitle": "Regras",
|
||||
"roomRedPacketRulesContent": "1. Você pode gastar moedas para enviar um Lucky Pack. Se outras pessoas abrirem seu Lucky Pack, elas poderão receber uma quantidade aleatória de moedas.\n\n2. Duas horas após o envio do Lucky Pack, todas as moedas não reclamadas serão devolvidas à sua carteira.\n\n3. Depois de enviar um Lucky Pack para todo o servidor, um banner do Lucky Pack para todo o servidor será acionado, trazendo mais atenção para você.\n\n4. Você pode enviar Lucky Packs da sala %1$s por dia.\n\n5. Os Lucky Packs enviados por usuários fora da sua região não podem ser resgatados.",
|
||||
"roomRedPacketHistoryTitle": "Histórico de entrega do Lucky Pack",
|
||||
"roomRedPacketHistoryItemTitle": "Lucky Bag (envelopes vermelhos {1})",
|
||||
"roomRedPacketHistoryStatusSent": "Enviado",
|
||||
"roomRedPacketHistoryStatusFinished": "Totalmente reivindicado",
|
||||
"roomRedPacketHistoryStatusPending": "Pendente",
|
||||
"roomRedPacketHistoryStatusReturned": "Devolvido",
|
||||
"roomRedPacketHistoryRefund": "Reembolso: {1}",
|
||||
"roomRedPacketClaimRecordsTitle": "Registros de reivindicações",
|
||||
"roomRedPacketChatMessage": "Mandei para você um envelope vermelho, abra agora mesmo!",
|
||||
"roomRedPacketReceivedMessage": "Reivindique moedas {1} do Lucky Pack {2}",
|
||||
"roomRedPacketOpenSubtitle": "O Pacote da Sorte Chegou",
|
||||
"roomRedPacketOpenYouGot": "Você consegue",
|
||||
"roomRedPacketClaimSuccessTips": "As moedas foram enviadas para sua carteira.",
|
||||
"roomRedPacketOpenSoldOut": "O Lucky Pack já está esgotado.",
|
||||
"roomRedPacketOpenExpired": "O Pacote da Sorte expirou.",
|
||||
"roomRedPacketRegionNotMatch": "Somente usuários na mesma região linguística podem reivindicar este pacote da sorte.",
|
||||
"roomRedPacketNotStarted": "O pacote da sorte ainda não está pronto.",
|
||||
"roomRedPacketDailyLimitExceeded": "O limite de envio de hoje foi atingido.",
|
||||
"roomRedPacketInsufficientBalance": "Moedas de ouro insuficientes.",
|
||||
"roomRedPacketPresenceRequired": "Por favor, fique no quarto para usar os pacotes da sorte.",
|
||||
"roomRedPacketNetworkError": "Não é possível processar o pacote da sorte no momento.",
|
||||
"roomRedPacketAlreadyClaimed": "Você já reivindicou este pacote da sorte.",
|
||||
"roomRedPacketOpenWaiting": "Pode ser aberto em minutos {1}.",
|
||||
"roomRedPacketOpenWaitingCountdown": "Disponível para retirada após {1}",
|
||||
"roomRedPacketOkay": "Ok",
|
||||
"roomRedPacketFloatingTitle": "{1} enviou um envelope vermelho",
|
||||
"roomRedPacketFloatingSubtitle": "Enviou um envelope vermelho",
|
||||
"redEnvelopeRecTips2": "Os envelopes vermelhos foram todos reivindicados.",
|
||||
"redEnvelopeRecTips3": "O prazo de coleta do envelope vermelho expirou!",
|
||||
"openTheTreasureChest": "Abra o baú do tesouro",
|
||||
"redEnvelopeRecTips1": "As moedas ganhas foram depositadas em sua carteira.",
|
||||
"redEnvelopeTips1": "Moedas:",
|
||||
"roomTools": "Ferramentas da sala:",
|
||||
"entertainment": "Entretenimento:",
|
||||
"reportSucc": "Relatório bem-sucedido",
|
||||
"pornography": "Pornografia",
|
||||
"reportInputTips": "Descreva o problema com o máximo de detalhes possível para que possamos entendê-lo e resolvê-lo.",
|
||||
"cancel": "Cancelar",
|
||||
"join": "Junte-se",
|
||||
"items": "Itens",
|
||||
"vistors": "Visitantes",
|
||||
"fans": "Fãs",
|
||||
"balanceNotEnough": "Saldo insuficiente de moedas de ouro. Você quer ir para recarregar?",
|
||||
"skip": "Pular {1}",
|
||||
"letGoToWatch": "Vamos assistir!",
|
||||
"launchedARocket": "lançou um foguete",
|
||||
"sendUserId": "Enviar ID do usuário:{1}",
|
||||
"giveUpIdentity": "Desista da identidade",
|
||||
"leaveRoomIdentityTips": "Tem certeza de que deseja desistir da identidade do quarto?",
|
||||
"joinMemberTips2": "Deseja confirmar a entrada na sala como membro?",
|
||||
"sureUnfollowThisRoom": "Tem certeza de parar de seguir esta sala?",
|
||||
"welcomeMessage": "Bem-vindo ao nosso aplicativo, {name}!",
|
||||
"settings": "Configurações",
|
||||
"account": "Conta",
|
||||
"common": "Comum",
|
||||
"delete": "Excluir",
|
||||
"copy": "Copiar",
|
||||
"bio": "Biografia",
|
||||
"introduction": "Introdução",
|
||||
"useCoupontips": "Tem certeza que deseja usar o cupom?",
|
||||
"searchUserId": "Pesquisar ID do usuário",
|
||||
"sendUser": "Enviar usuário",
|
||||
"hobby": "Passatempo",
|
||||
"sendCoupontips": "Tem certeza de que deseja enviar este cupom para este usuário?",
|
||||
"youDontHaveAnyCouponsYet": "Você ainda não tem cupons.",
|
||||
"recall": "Lembre-se",
|
||||
"youHaventFollowed": "Você não seguiu nenhuma sala",
|
||||
"deleteFromMyDevice": "Excluir do meu dispositivo",
|
||||
"deleteOnAllDevices": "Excluir em todos os dispositivos",
|
||||
"messageHasBeenRecalled": "Esta mensagem foi recuperada",
|
||||
"recallThisMessage": "Lembra desta mensagem?",
|
||||
"language": "Idioma",
|
||||
"feedback": "Comentários",
|
||||
"signedin": "Conectado",
|
||||
"receiveSucc": "Reivindicado com sucesso",
|
||||
"about": "Sobre",
|
||||
"aboutUs": "Sobre nós",
|
||||
"theme": "Tema",
|
||||
"wealthLevel": "Nível de riqueza",
|
||||
"userLevel": "Nível de charme",
|
||||
"goToUpload": "Ir para carregar",
|
||||
"logout": "Sair",
|
||||
"luck": "Sorte",
|
||||
"level": "Nível",
|
||||
"themeGoToUploadTips": "1. A revisão será concluída dentro de 24 horas após o sucesso do upload.\n2. Todas as moedas serão devolvidas se a revisão falhar.",
|
||||
"pleaseUploadAccordingToExample": "Faça upload de acordo com o exemplo fornecido.",
|
||||
"home": "Página inicial",
|
||||
"explore": "Explorar",
|
||||
"me": "Eu",
|
||||
"socialPrivilege": "Privilégio social",
|
||||
"information": "Informação",
|
||||
"myPhoto": "Minha foto",
|
||||
"areYouSureYouWantToSpend3": "*Se a outra parte recusar o convite do CP, suas moedas serão devolvidas à sua carteira.",
|
||||
"areYouSureYouWantToSpend": "Tem certeza que deseja gastar",
|
||||
"areYouSureYouWantToSpend2": "enviar um convite CP para este usuário?",
|
||||
"underReview": "Em revisão",
|
||||
"doYouWantToDeleteIt": "Deseja excluí-lo?",
|
||||
"chooseFromAblum": "Escolha do álbum",
|
||||
"spaceBackground": "Fundo do espaço",
|
||||
"editProfile": "Editar perfil",
|
||||
"sendTheCpRequest": "Envie a solicitação de CP",
|
||||
"addCp": "Adicionar CP",
|
||||
"partWays": "Partes",
|
||||
"reconcile": "Reconciliar",
|
||||
"separated": "Separado",
|
||||
"areYouSureYouWantToSpend5": "{1} confessou o sentimento para você; se você aceitar, vocês se tornarão um casal.",
|
||||
"areYouSureYouWantToSpend6": "{1} quer voltar com você. Se você decidir reconciliar, todos os seus dados anteriores serão restaurados.",
|
||||
"reconcileInvitationTips": "*Se a outra parte recusar o convite do CP, suas moedas serão devolvidas à sua carteira.",
|
||||
"reconcileInvitation": "Convite de reconciliação",
|
||||
"areYouSureYouWantToSpend4": "enviar um convite de reconciliação para este usuário?",
|
||||
"partWaysTips": "*Se um dos parceiros de um casal decidir se separar, haverá um período de reflexão de 7 dias. Durante esse período, ambas as partes podem optar pela reconciliação e todos os dados serão restaurados após a reconciliação. Caso não seja escolhida nenhuma conciliação até o final do período de 7 dias, os dados do casal serão apagados.",
|
||||
"areYouSureYouWantToPartWaysWithYourCP": "Tem certeza de que deseja se separar do seu CP?",
|
||||
"timeSpentTogether": "Tempo que passamos juntos: dias {1}",
|
||||
"firstDay": "Primeiro dia: {1}",
|
||||
"numberOfMyCPs": "Número dos meus CPs:({1}/{2})",
|
||||
"props": "Adereços",
|
||||
"win": "Ganhar",
|
||||
"dice": "Dados",
|
||||
"rps": "RPS",
|
||||
"operationFail": "A operação falhou.",
|
||||
"enterRoomFailedRetry": "Falha ao entrar na sala. Por favor, tente novamente.",
|
||||
"voiceConnectionFailedRetry": "A conexão de voz falhou. Por favor, tente novamente.",
|
||||
"likedYourComment": "Gostei do seu comentário.",
|
||||
"doYouWantToKeepTheDraft": "Você quer manter o rascunho?",
|
||||
"operationsAreTooFrequent": "As operações são muito frequentes",
|
||||
"luckNumber": "Número da sorte",
|
||||
"relationShip": "Relacionamento",
|
||||
"couple": "Casal {1}:",
|
||||
"couple2": "Casal",
|
||||
"reject": "Rejeitar",
|
||||
"accept": "Aceitar",
|
||||
"noMatchedCP": "Nenhum CP correspondente",
|
||||
"inviteYouToBecomeBD": "Convido você a se tornar um BD.",
|
||||
"adminInviteRechargeAgent": "Convido você a se tornar um agente Recharge.",
|
||||
"confirmAcceptTheInvitation": "Confirmar para aceitar o convite?",
|
||||
"confirmDeclineTheInvitation": "Confirmar a recusa do convite?",
|
||||
"cpInviteRejectedByOther": "A outra pessoa recusou se tornar {1}.",
|
||||
"closeFriendInvitationTitle": "Invitation from a Close Friend",
|
||||
"closeFriendInvitationSubtitle": "Click to view the invitation",
|
||||
"closeFriendInvitationExpired": "The invitation to add a close friend has expired. This action cannot be performed.",
|
||||
"closeFriendRequestDeclined": "Friend request declined",
|
||||
"closeFriendsHaveBecome": "Have become close friends",
|
||||
"otherAcceptedCloseFriendInvite": "The other party accepted your close friend invitation.",
|
||||
"otherDeclinedCloseFriendInvite": "The other party declined your close friend invitation.",
|
||||
"youAcceptedCloseFriendInvite": "You accepted the other party's close friend invitation.",
|
||||
"youDeclinedCloseFriendInvite": "You declined the other party's close friend invitation.",
|
||||
"closeFriendInviteTimedOut": "Close friend invitation timed out.",
|
||||
"cpDismissConfirmMessage": "Removing this relationship will cost {1} coins. Confirm removal?",
|
||||
"cpRelationCp": "CP",
|
||||
"cpRelationBrother": "Brother",
|
||||
"cpRelationSister": "Sister",
|
||||
"host": "Anfitrião",
|
||||
"following": "Seguindo",
|
||||
"agent": "Agência",
|
||||
"approved": "Aprovado",
|
||||
"approvedWithinOneMinute": "Aprovado e exibido dentro de 1 minuto após o envio",
|
||||
"onlineUsers": "Usuários on-line ({1}/{2}):",
|
||||
"applyToJoin": "Inscreva-se para participar",
|
||||
"supporterList": "Lista de apoiadores",
|
||||
"hostList": "Lista de anfitriões",
|
||||
"upToAdmins": "Até administradores {1}",
|
||||
"upToMembers": "Até membros {1}",
|
||||
"levelPrivileges": "Privilégios de nível",
|
||||
"ra": "RA",
|
||||
"roomAnnouncement": "Anúncio da sala",
|
||||
"help": "Ajuda",
|
||||
"rejected": "Rejeitado",
|
||||
"boxContributeTips": "O investimento já foi feito hoje. Por favor, não invista novamente.",
|
||||
"bd": "BD",
|
||||
"coupon": "Cupom",
|
||||
"search": "Pesquisar",
|
||||
"get": "Obter",
|
||||
"inRocket": "No foguete",
|
||||
"roomRocketHelpTips": "1. Enviar presentes na sala aumenta a energia do foguete. *1 presente de moeda de ouro = 1 ponto de energia de foguete; presentes da sorte aumentam a energia do foguete em 4% do valor da moeda de ouro do presente.\n2. Assim que a energia do foguete estiver totalmente carregada, a sala poderá lançar o foguete. As recompensas serão distribuídas automaticamente após o lançamento.\n3. Diferentes níveis de foguete oferecem recompensas diferentes.\n4. Quando o foguete for lançado, todos os usuários na sala poderão reivindicar a recompensa do foguete.\n5. A energia do foguete é reiniciada às 00:00 todos os dias.",
|
||||
"couponRecord": "Registro de uso de cupom",
|
||||
"inRoom": "No quarto",
|
||||
"searchCouponHint": "Cupom de pesquisa",
|
||||
"giftCounter": "Contador de presentes",
|
||||
"bDLeaderInviteYouToBecomeBDLeader": "Convido você para se tornar um BDLader",
|
||||
"wins": "vence",
|
||||
"inviteYouToBecomeHost": "Convido você para se tornar um anfitrião.",
|
||||
"friends": "Amigos",
|
||||
"deleteConversationTips": "Tem certeza de que deseja excluir o histórico de bate-papo com este usuário?",
|
||||
"propMessagePrompt": "Prompt de mensagem prop",
|
||||
"inputUserId": "Insira o ID do usuário",
|
||||
"fromLuckyGifts": "De presentes de sorte/mágicos",
|
||||
"receive": "Receber",
|
||||
"checkInSuccessful": "Check-in bem-sucedido",
|
||||
"sginTips": "Você receberá uma recompensa na primeira vez que fizer login todos os dias. Se você interromper sua sequência de login, a recompensa será recalculada a partir do primeiro dia quando você fizer login novamente.",
|
||||
"popular": "Populares",
|
||||
"recommend": "Recomendar",
|
||||
"follow": "Siga",
|
||||
"history": "História",
|
||||
"hotRooms": "Salas Quentes",
|
||||
"viewMore": "Ver mais",
|
||||
"noData": "Sem dados",
|
||||
"users": "Usuários",
|
||||
"rooms": "Quartos",
|
||||
"coins": "moedas",
|
||||
"unread": "Não lido",
|
||||
"read": "Leia",
|
||||
"image": "[Imagem]",
|
||||
"video": "[Vídeo]",
|
||||
"sound": "[Som]",
|
||||
"gift2": "[Presente]",
|
||||
"clickHereToStartChatting": "Diga alguma coisa.....",
|
||||
"receivedAMessage": "[Recebi uma mensagem]",
|
||||
"confirmSwitchMicModelTips": "Você confirma a mudança no modo sentado?",
|
||||
"number": "Número",
|
||||
"album": "Álbum",
|
||||
"camera": "Câmera",
|
||||
"system": "Sistema",
|
||||
"notifcation": "Notificação",
|
||||
"inviteYouToBecomeAgent": "Convido você a se tornar uma agência.",
|
||||
"myMusic": "Minha música",
|
||||
"add": "Adicionar",
|
||||
"pullToLoadMore": "Puxe para carregar mais",
|
||||
"loadingFailedClickToRetry": "Falha ao carregar. Clique para tentar novamente",
|
||||
"releaseToLoadMore": "Solte para carregar mais",
|
||||
"haveMyLimits": "---Eu tenho meus limites.---",
|
||||
"music": "Música",
|
||||
"free": "Grátis",
|
||||
"charm": "Amuleto de presente",
|
||||
"start": "Começar",
|
||||
"stop": "Pare",
|
||||
"chats": "Bate-papos",
|
||||
"refuse": "Recusar",
|
||||
"agree": "Concordo",
|
||||
"thisFeatureIsCurrentlyUnavailable": "Este recurso não está disponível no momento.",
|
||||
"pleaseSelectTheRecipient": "Selecione o destinatário.",
|
||||
"searchMemberIdHint": "Por favor insira o ID do membro",
|
||||
"unclaimedRedEnvelopes": "Envelopes vermelhos não reclamados serão reembolsados em 24 horas.",
|
||||
"redEnvelopeNotYetClaimed": "Envelope vermelho ainda não reclamado.",
|
||||
"redEnvelopeAmount": "Valor do envelope vermelho: moedas {1}",
|
||||
"sentARedEnvelope": "enviou um envelope vermelho.",
|
||||
"theRedEnvelopeHasExpired": "O envelope vermelho expirou.",
|
||||
"joinRequest": "Solicitação de adesão",
|
||||
"scrollToTheBottom": "Role até o final",
|
||||
"gameRules": "Regras do jogo:",
|
||||
"charmGameRulesTips": "Ative o painel para mostrar as moedas de presente recebidas de todos os usuários no microfone. 1 moeda = 1 pontuação (Presente da sorte: 1 moeda = 0,04 pontuação).",
|
||||
"inputYourOldPassword": "Insira sua senha antiga",
|
||||
"enterYourOldPassword": "Digite sua senha antiga",
|
||||
"setYourPassword": "Defina sua senha",
|
||||
"enterYourNewPassword": "Digite sua nova senha",
|
||||
"confirmYourPassword": "Confirme sua senha",
|
||||
"theTwoPasswordsDoNotMatch": "As duas senhas não coincidem.",
|
||||
"resetLoginPasswordtTips2": "A senha deve ter de 8 a 16 caracteres e ser uma combinação de letras e números ingleses maiúsculos e minúsculos (não apenas números).",
|
||||
"resetLoginPassword": "Redefinir senha de login",
|
||||
"resetLoginPasswordtTips1": "Faça login com seu ID de usuário ou ID personalizado. É mais seguro fazer login com uma conta yumi.",
|
||||
"localMusic": "Música local",
|
||||
"setLoginPassword": "Definir senha de login",
|
||||
"confirmSwitchMicThemeTips": "Você confirma a mudança de estilo do assento?",
|
||||
"micTheme": "Tema do microfone",
|
||||
"classicMic": "Microfone {1} clássico",
|
||||
"yesterday": "Ontem {1}",
|
||||
"monday": "Segunda-feira {1}",
|
||||
"tuesday": "Terça-feira {1}",
|
||||
"wednesday": "Quarta-feira {1}",
|
||||
"thursday": "Quinta-feira {1}",
|
||||
"friday": "Sexta-feira {1}",
|
||||
"saturday": "Sábado {1}",
|
||||
"sunday": "Domingo {1}",
|
||||
"acceptedYour": "{1} aceitou seu",
|
||||
"youAccepted": "Você aceitou {1}",
|
||||
"openRedPackDialogTip": "O envelope vermelho",
|
||||
"micManagement": "Gerenciamento de microfone",
|
||||
"bdCenter": "Centro de BD",
|
||||
"rechargeAgency": "Agência de recarga",
|
||||
"adminCenter": "Centro de administração",
|
||||
"managerCenter": "Centro de gerenciamento",
|
||||
"goldList": "Lista de Ouro",
|
||||
"followList": "Seguir lista",
|
||||
"fansList": "Lista de fãs",
|
||||
"daily": "Diariamente",
|
||||
"female": "Feminino",
|
||||
"male": "Masculino",
|
||||
"identity": "Identidade",
|
||||
"adjust": "Ajustar",
|
||||
"warning": "Aviso",
|
||||
"screenshotTips": "Captura de tela (até 3)",
|
||||
"roomNotice": "Aviso de sala",
|
||||
"roomTheme": "Tema do quarto",
|
||||
"description": "Descrição:",
|
||||
"inputDesHint": "Descreva o problema com o máximo de detalhes possível para que possamos entendê-lo e resolvê-lo.",
|
||||
"roomProfilePicture": "Foto do perfil da sala",
|
||||
"userProfilePicture": "Foto do perfil do usuário",
|
||||
"userName": "Nome de usuário",
|
||||
"pleaseSelectTheTypeToProcess": "Selecione o tipo a ser processado:",
|
||||
"roomEditing": "Edição de sala",
|
||||
"setAccount": "Definir conta",
|
||||
"userEditing": "Edição do usuário",
|
||||
"enterTheUserId": "Insira o ID do usuário",
|
||||
"enterTheRoomId": "Insira o roomId",
|
||||
"deleteAccount": "Excluir conta",
|
||||
"becomeAgent": "Torne-se um agente",
|
||||
"enterNickname": "Digite o apelido",
|
||||
"enterInvitationCode": "insira o código do convite",
|
||||
"selectYourCountry": "Selecione seu país",
|
||||
"inviteCode": "Código de convite",
|
||||
"magic": "Magia",
|
||||
"luckGiftSpecialEffects": "Efeitos de animação de presente da sorte",
|
||||
"theVideoSizeCannotExceed": "O tamanho do vídeo não pode exceder 50M",
|
||||
"weekly": "Semanalmente",
|
||||
"customizedGiftRulesContent": "Como obter meu presente personalizado:\n1.Determine se o usuário está qualificado para receber um presente personalizado com base em seu nível de riqueza atual.\n(1) Quando o nível de riqueza do usuário for ≥35: O usuário pode enviar um vídeo para o presente personalizado uma vez. Usaremos a foto do perfil atual do usuário como imagem de presente e o vídeo fornecido como efeito de presente em \"Personalizado\". A produção levará algum tempo e avisaremos assim que estiver disponível na loja.\n(2) Quando o nível de riqueza do usuário for ≥45: O usuário pode enviar um vídeo para o presente personalizado uma vez. Usaremos a foto do perfil atual do usuário como imagem de presente e o vídeo fornecido como efeito de presente em \"Personalizado\". A produção levará algum tempo e avisaremos assim que estiver disponível na loja.\n2.Você pode participar de atividades específicas no app e, após atender aos critérios, entrar em contato conosco através da seção “Mensagem” → “Fale conosco”. Forneça capturas de tela da atividade e do vídeo que deseja usar para o presente personalizado. Usaremos sua foto de perfil atual como imagem de presente e o vídeo fornecido como efeito de presente e, em seguida, listaremos em \"Personalizado\". A produção levará algum tempo e avisaremos assim que estiver disponível na loja.\nPeríodo de validade do presente personalizado e como estendê-lo:\nOs brindes personalizados exclusivos ficarão disponíveis na gôndola pelo prazo de validade de 30 dias. Para permitir que usuários influentes e inspiradores continuem aproveitando essa nova experiência e mostrem seu estilo pessoal, os usuários que possuem presentes personalizados podem estender o prazo de validade de todos os seus presentes personalizados em 30 dias, recarregando US$ 500 em qualquer mês.",
|
||||
"customizedGiftRules": "Regras de presentes personalizados",
|
||||
"rulesUpload": "Regras e upload",
|
||||
"monthly": "Mensalmente",
|
||||
"message": "Mensagem",
|
||||
"clearCache": "Limpar Cache",
|
||||
"customized": "Personalizado",
|
||||
"searchInputHint": "Insira o número da conta/quarto",
|
||||
"kickRoomTips": "Você foi expulso da sala.",
|
||||
"roomKickedEntryBlockedTips": "Você foi removido da sala por um administrador e não pode entrar por enquanto.",
|
||||
"joinRoomTips": "Entrei na sala!",
|
||||
"roomSetting": "Configuração da sala",
|
||||
"roomDetails": "Detalhes do quarto",
|
||||
"systemRoomTips": "Seja educado e respeitoso. Qualquer conteúdo pornográfico ou vulgar é estritamente proibido no yumi. Uma vez descoberta, a conta será banida permanentemente. Por favor, cumpra conscientemente os regulamentos da plataforma yumi.",
|
||||
"copiedToClipboard": "Copiado para a área de transferência",
|
||||
"recharge": "Recarregar",
|
||||
"receivedFromALuckyGift": "Recebido de um presente de sorte/mágico.",
|
||||
"followedYou": "Segui você",
|
||||
"agentCenter": "Central de Agências",
|
||||
"areYouSureYouWantToClearLocalCache": "Tem certeza de que deseja limpar o cache local?",
|
||||
"clearMessage": "Limpar mensagens da tela",
|
||||
"report": "Relatório",
|
||||
"coins3": "Moedas",
|
||||
"giftSpecialEffects": "Efeitos especiais de presente",
|
||||
"basicFeatures": "Recursos básicos",
|
||||
"task": "Tarefa",
|
||||
"importantReminder": "Lembrete importante",
|
||||
"entryVehicleAnimation": "Animação de veículo de entrada",
|
||||
"floatingAnimationInGlobal": "Animação flutuante em global",
|
||||
"broadcast": "Transmitir",
|
||||
"broadcastDisplay": "Exibição de transmissão",
|
||||
"broadcastAllPlaces": "Todos os lugares",
|
||||
"broadcastRoomOnly": "No quarto",
|
||||
"broadcastOff": "Fechar transmissão",
|
||||
"entryVehicleAnimation2": "Usuários com privilégios VIP4 ou superiores podem usar esta função para desativar animações de veículos.",
|
||||
"dailyTasks": "Tarefas Diárias",
|
||||
"exclusiveForNewcomers": "Exclusivo para recém-chegados",
|
||||
"resetsDailyAtMidnight": "Reinicia diariamente à meia-noite",
|
||||
"limitedToOneTime": "Limitado a uma única vez",
|
||||
"useMicrophoneForOneMin": "Use o microfone por 1 minuto",
|
||||
"taskRequirement": "Requisito",
|
||||
"taskProgress": "Progresso",
|
||||
"rewardClaimedSuccessfully": "Recompensa reivindicada com sucesso",
|
||||
"rewardClaimFailed": "Falha na reivindicação do prêmio",
|
||||
"enterRoomConfirmTips": "Tem certeza de que deseja entrar na sala?",
|
||||
"followSucc": "Seguido com sucesso",
|
||||
"goldListort": "Lista de Ouro",
|
||||
"rechargeList": "Lista de recarga",
|
||||
"edit": "Editar",
|
||||
"swipeLeftOnTheFloatingScreenAreaToQuicklyCloseIt": "*Dica: deslize para a esquerda na área flutuante da tela para fechá-la rapidamente.",
|
||||
"enterThisVoiceChatRoom": "Entrar nesta sala de bate-papo por voz?",
|
||||
"go": "Vá",
|
||||
"done": "Concluído",
|
||||
"improvementTasks": "Tarefas de melhoria",
|
||||
"save": "Salvar",
|
||||
"kickPrevention": "Prevenção de chutes",
|
||||
"freeChatSpeak": "Bate-papo e conversa grátis",
|
||||
"nickName": "Apelido",
|
||||
"gender": "Gênero",
|
||||
"unFollow": "Deixar de seguir",
|
||||
"days": "Dias",
|
||||
"permanent": "Permanente",
|
||||
"country": "País",
|
||||
"birthday": "Aniversário",
|
||||
"man": "Homem",
|
||||
"woman": "Mulher",
|
||||
"apple": "maçã",
|
||||
"google": "Google",
|
||||
"startVoiceParty": "Comece uma festa de voz!",
|
||||
"enterRoomTips": "{1} Entre na sala",
|
||||
"roomName": "Nome do quarto",
|
||||
"roomMember": "Membro da Sala",
|
||||
"pleaseSelectYourCountry": "Por favor selecione seu país.",
|
||||
"pleaseSelectYourGender": "Por favor selecione seu gênero.",
|
||||
"pleaseEnterNickname": "Por favor insira um apelido.",
|
||||
"countryRegion": "País e região",
|
||||
"dateOfBirth": "Data de nascimento",
|
||||
"mute": "Mudo",
|
||||
"exit": "Sair",
|
||||
"mysteriousInvisibility": "Invisibilidade misteriosa",
|
||||
"antiBlock": "Anti-bloqueio",
|
||||
"privateChat": "Bate-papo privado",
|
||||
"storeDiscount": "Desconto da loja {1} desativado",
|
||||
"membershipFreeChatSpeak": "Bate-papo e conversa sem assinatura",
|
||||
"priorityRoomSorting": "Classificação prioritária de salas",
|
||||
"userColoredID": "ID colorido do usuário",
|
||||
"pleaseSelectaItem": "Selecione um item",
|
||||
"areYouRureRoRecharge": "Tem certeza de que deseja recarregar?",
|
||||
"mInimize": "Mantenha",
|
||||
"everyone": "Todos",
|
||||
"shop": "Loja",
|
||||
"roomOwner": "Proprietário do quarto",
|
||||
"dailyTaskRewardBonus": "Bônus de recompensa de tarefa diária ({1} XP)",
|
||||
"userLevelXPBoost": "Aumento de XP no nível do usuário ({1} XP)",
|
||||
"andAboveUsers": "{1} e usuários superiores",
|
||||
"privileges": "Privilégios {1}",
|
||||
"basicPermissions": "Permissões básicas",
|
||||
"hostCenter": "Centro de hospedagem",
|
||||
"allOnMicrophone": "Tudo no microfone",
|
||||
"usersOnMicrophone": "Usuários no microfone",
|
||||
"allInTheRoom": "Todos no quarto",
|
||||
"send": "Enviar",
|
||||
"crop": "Cortar",
|
||||
"goToUpgrade": "Ir para atualizar",
|
||||
"exclusiveEmojiWillBeReleasedAfterBecoming": "Emoji exclusivo será lançado após se tornar",
|
||||
"preventBeingBlocked": "Evite ser bloqueado",
|
||||
"enableRankIncognitoMode": "Ativar modo de classificação anônima",
|
||||
"avoidBeingKicked": "Evite ser chutado",
|
||||
"finish": "Concluir",
|
||||
"takeTheMic": "Pegue o microfone",
|
||||
"openTheMic": "Abra o microfone",
|
||||
"muteTheMic": "Silenciar o microfone",
|
||||
"unlockTheMic": "Desbloqueie o microfone",
|
||||
"leavelTheMic": "Deixe o microfone",
|
||||
"lockTheMic": "Bloqueie o microfone",
|
||||
"removeTheMic": "Remova o microfone",
|
||||
"inviteToTheMicrophone": "Convide para o microfone",
|
||||
"openUserProfleCard": "Abra o cartão de perfil do usuário",
|
||||
"obtain": "obter",
|
||||
"win2": "Ganhe {1}",
|
||||
"backTheRoom": "Sala dos fundos",
|
||||
"toConsume": "Para consumir",
|
||||
"howToUpgrade": "Como atualizar?",
|
||||
"spendCoinsToGainExperiencePoints": "Gaste moedas para ganhar pontos de experiência",
|
||||
"richLevelUpgradeAnswer": "Deixe sua riqueza conquistar tudo",
|
||||
"charmLevelUpgradeAnswer": "Faça os outros se renderem ao seu charme",
|
||||
"higherLevelFancierAvatarFrame": "Emblemas/moldura de avatar de nível superior e mais sofisticados",
|
||||
"all": "Todos",
|
||||
"gift": "Presente",
|
||||
"chat": "Bate-papo",
|
||||
"owner": "Proprietário",
|
||||
"store": "Loja",
|
||||
"admin": "Administrador",
|
||||
"member": "Membro",
|
||||
"guest": "Convidado",
|
||||
"submit": "Enviar",
|
||||
"claim": "Reivindicar",
|
||||
"claimed": "Reivindicado",
|
||||
"complete": "Completo",
|
||||
"shareTo": "Compartilhar para",
|
||||
"copyLink": "Copiar Link",
|
||||
"faceBook": "Facebook",
|
||||
"whatsApp": "Whatsapp",
|
||||
"snapChat": "Snapchat",
|
||||
"taskNamePersonalGameConsume": "Gastos com jogos",
|
||||
"taskNameRoomNewMember": "Novos membros da sala",
|
||||
"taskNameRoomOwnerSendRedPacket": "O proprietário do quarto envia um pacote vermelho",
|
||||
"taskNameRoomOwnerSendGiftUser": "Proprietário do quarto envia presente",
|
||||
"taskNameRoomMicUser120Min": "Membros com mais de 120 minutos no microfone",
|
||||
"taskNameRoomMicUser60Min": "Membros com mais de 60 minutos no microfone",
|
||||
"taskNameRoomMicUser30Min": "Membros com mais de 30 minutos no microfone",
|
||||
"taskNamePersonalSendGift": "Enviar presente ao usuário",
|
||||
"taskNameRoomOnlineUserCount": "Membros on-line da sala",
|
||||
"taskNameRoomOwnerInviteMic": "Convidar membro para o microfone",
|
||||
"taskNameRoomUserSendGiftGold": "Moedas doadas por membros",
|
||||
"taskNameRoomOwnerSendGiftGold": "Moedas doadas pelo proprietário do quarto",
|
||||
"taskNamePersonalMagicGiftGold": "Moedas ganhas com o envio de presentes mágicos",
|
||||
"taskNamePersonalLuckyGiftGold": "Moedas ganhas com o envio de presentes da sorte",
|
||||
"taskNameRoomOwnerMicTime": "O proprietário da sala liga o microfone na sala",
|
||||
"taskNamePersonalActiveInRoom": "Seja ativo nas salas dos outros",
|
||||
"taskNamePersonalMicInRoom": "Vá no microfone",
|
||||
"dailyCoinBonanzaRulesDetail": "1. As Tarefas Pessoais Diárias e as Tarefas Diárias do Proprietário da Sala podem ser concluídas uma vez por usuário por dia. As tarefas foram redefinidas às 00:00:00, horário da Arábia Saudita.\n2.As tarefas pessoais diárias só podem ser realizadas nos quartos de outras pessoas; as tarefas do proprietário da sala só podem ser concluídas em sua própria sala.\n3. As tarefas de dar presentes contam apenas os presentes enviados nas salas, não os presentes enviados nos feeds.\n4. Se várias contas forem criadas usando o mesmo dispositivo ou cartão SIM de alguma forma, as recompensas para todas as tarefas só poderão ser reivindicadas uma vez.",
|
||||
"dailyCoinBonanzaRules": "Regras diárias de bonança de moedas",
|
||||
"roomOwnerTasks": "Tarefas do proprietário da sala",
|
||||
"personalTasks": "Tarefas Pessoais",
|
||||
"noPromptsToday": "Nenhuma tarefa hoje.",
|
||||
"getPaidToRefer": "Seja pago para indicar",
|
||||
"membershipFee": "Taxa de adesão",
|
||||
"membershipFeeTips1": "Por favor, defina a taxa de adesão para o seu quarto. Os usuários podem ingressar na sua sala pagando a taxa.",
|
||||
"membershipFeeTips2": "Os ouros necessários para o usuário se tornar membro da sala. O proprietário da sala receberá 50% dos ouros.",
|
||||
"freePrice": "Taxa:0-10000",
|
||||
"touristsSendText": "Turista envia texto",
|
||||
"touristsTakeToTheMic": "Turistas pegam o microfone",
|
||||
"theMembershipFee": "A taxa de adesão",
|
||||
"theModificationsMade": "As modificações feitas desta vez não serão salvas após sair",
|
||||
"viewFrame": "Ver quadro",
|
||||
"enterRoomName": "Digite o nome da sala",
|
||||
"headdress": "Molduras",
|
||||
"mountains": "Veículos",
|
||||
"purchaseIsSuccessful": "A compra foi bem-sucedida",
|
||||
"buy": "Comprar",
|
||||
"followed": "Seguido",
|
||||
"follow2": "Siga: {1}",
|
||||
"fans2": "Fãs: {1}",
|
||||
"vistors2": "Visitantes:{1}",
|
||||
"personal2": "Pessoal:",
|
||||
"conntinue": "Continuar",
|
||||
"confirmBuyTips": "Tem certeza que deseja comprar?",
|
||||
"purchase": "Comprar",
|
||||
"setRoomPassword": "Definir senha da sala",
|
||||
"inputRoomPassword": "Insira a senha da sala",
|
||||
"enter": "Entrar",
|
||||
"deleteCommentTips": "Tem certeza de que deseja excluir este comentário?",
|
||||
"deleteSuccessful": "Exclusão bem-sucedida!",
|
||||
"itemsLeft": "Itens restantes",
|
||||
"password": "Senha",
|
||||
"replySucc": "Resposta bem-sucedida",
|
||||
"comment": "Comentário",
|
||||
"showMore": "Mostrar mais",
|
||||
"showLess": "Mostrar menos",
|
||||
"enterPassword": "Digite a senha",
|
||||
"enterAccount": "Insira a conta",
|
||||
"logIn": "Faça login",
|
||||
"saySomething": "Diga alguma coisa...",
|
||||
"sayHi": "Diga oi..",
|
||||
"pleaseChatFfriendly": "Por favor, converse gentilmente",
|
||||
"unLockTheRoom": "Desbloqueie a sala",
|
||||
"operationSuccessful": "A operação foi bem sucedida.",
|
||||
"adminByHomeowner": "é definido como administrador pelo proprietário.",
|
||||
"memberByHomeowner": "é definido como membro pelo proprietário.",
|
||||
"touristByHomeowner": "é definido como turista pelo proprietário.",
|
||||
"becomeHost": "Inscreva-se para se tornar um anfitrião",
|
||||
"superFans": "Superfãs:",
|
||||
"setUpAnIdentity": "Configure uma identidade",
|
||||
"kickedOutOfRoom": "Expulso da sala",
|
||||
"knapsack": "Mochila",
|
||||
"bdLeader": "Líder de BD",
|
||||
"picture": "Foto",
|
||||
"theImageSizeCannotExceed": "O tamanho da imagem não pode exceder 4M",
|
||||
"activity": "Atividade",
|
||||
"alreadyAnAdministrator": "Já sou administrador",
|
||||
"alreadyAnMember": "Já é membro",
|
||||
"alreadyAnTourist": "Já sou turista",
|
||||
"touristsCannotSendMessages": "Turistas não podem enviar mensagens",
|
||||
"touristsAreNotAllowedToGoOnTheMic": "Os turistas não estão autorizados a usar o microfone",
|
||||
"lockTheRoom": "Tranque a sala",
|
||||
"special": "Especial",
|
||||
"visitorList": "Lista de visitantes",
|
||||
"successfulWear": "Desgaste bem sucedido",
|
||||
"confirmUnUseTips": "Você confirma para removê-lo?",
|
||||
"custom": "Personalizado",
|
||||
"customBackground": "Plano de fundo personalizado",
|
||||
"example": "Exemplo",
|
||||
"endPreview": "Finalizar visualização",
|
||||
"selectAgain": "Selecione novamente",
|
||||
"myItems": "Meus itens",
|
||||
"use": "Usar",
|
||||
"unUse": "Desequipar",
|
||||
"renewal": "Renovação",
|
||||
"wallet": "Carteira",
|
||||
"profile": "Perfil",
|
||||
"giftwall": "Parede de presentes",
|
||||
"announcement": "Anúncio",
|
||||
"blockedList": "Lista bloqueada",
|
||||
"country2": "País:",
|
||||
"sendTo": "Enviar para",
|
||||
"credits": "Créditos: {1}",
|
||||
"successfullyUnloaded": "Descarregado com sucesso",
|
||||
"expired": "Expirado",
|
||||
"day": "Dia",
|
||||
"inUse": "Em uso",
|
||||
"staticOrGifImage": "Imagem estática/GIF",
|
||||
"confirmUseTips": "Quer confirmar o uso?",
|
||||
"pleaseUploadUserAvatar": "Por favor carregue um avatar.",
|
||||
"joinMemberTips": "Se você for um visitante na sala, não poderá levar o microfone.",
|
||||
"giftGivingSuccessful": "Doação de presentes com sucesso.",
|
||||
"theAccountPasswordCannotBeEmpty": "A conta ou senha não pode estar vazia.",
|
||||
"invitesYouToTheMicrophone": "{1} convida você para o microfone",
|
||||
"english": "Inglês",
|
||||
"chinese": "Chinês",
|
||||
"arabic": "Árabe",
|
||||
"darkMode": "Modo escuro",
|
||||
"lightMode": "Modo claro",
|
||||
"systemDefault": "Padrão do sistema",
|
||||
"pleaseGetOnTheMicFirst": "Por favor, pegue o microfone primeiro.",
|
||||
"roomMusicAddMusic": "Adicionar música",
|
||||
"roomMusicSelectAll": "Selecionar tudo",
|
||||
"roomMusicAddedCount": "Adicionadas músicas {1}",
|
||||
"roomMusicEmpty": "Sem música",
|
||||
"roomMusicScanFromPhone": "Digitalizar do telefone",
|
||||
"roomMusicTotalSongs": "Total: músicas {1}",
|
||||
"roomMusicNotFound": "Nenhuma música encontrada",
|
||||
"roomMusicDeleteConfirm": "Excluir esta música?",
|
||||
"roomMusicDeleted": "Música excluída",
|
||||
"roomMusicListMode": "Jogue em ordem",
|
||||
"roomMusicShuffleMode": "Reprodução aleatória",
|
||||
"roomMusicSingleMode": "Loop único",
|
||||
"duration2": "Duração: {1}",
|
||||
"roomRocketNoteTitle": "Nota",
|
||||
"roomRocketNoteSectionTitle": "Cada vez que um VIP",
|
||||
"roomRocketNoteSectionBody": "1. Cada vez que um nível VIP é adquirido, ele é válido por 30 dias, podendo ser renovado manualmente após expirar.",
|
||||
"roomRocketRewardEmptyText": "Nenhuma recompensa ganha. Melhor sorte da próxima vez.",
|
||||
"roomRocketRecordEmptyText": "Nenhum registro de recompensa",
|
||||
"launchRocket": "Lançar foguete",
|
||||
"roomRocketFuelFullNotice": "Combustível do foguete cheio! Lançamento em {1}s!"
|
||||
}
|
||||
@ -203,10 +203,11 @@
|
||||
"settings": "Ayarlar",
|
||||
"account": "Hesap",
|
||||
"common": "Genel",
|
||||
"delete": "Sil",
|
||||
"copy": "Kopyala",
|
||||
"bio": "Biyografi",
|
||||
"useCoupontips": "Kuponu kullanmak istediğinizden emin misiniz?",
|
||||
"delete": "Sil",
|
||||
"copy": "Kopyala",
|
||||
"bio": "Biyografi",
|
||||
"introduction": "Tanıtım",
|
||||
"useCoupontips": "Kuponu kullanmak istediğinizden emin misiniz?",
|
||||
"searchUserId": "Kullanıcı ID'sini Ara",
|
||||
"sendUser": "Kullanıcıya Gönder",
|
||||
"hobby": "Hobi",
|
||||
@ -225,8 +226,8 @@
|
||||
"about": "Hakkında",
|
||||
"aboutUs": "Hakkımızda",
|
||||
"theme": "Tema",
|
||||
"wealthLevel": "Servet Seviyesi",
|
||||
"userLevel": "Kullanıcı Seviyesi",
|
||||
"wealthLevel": "Zenginlik Seviyesi",
|
||||
"userLevel": "Çekicilik Seviyesi",
|
||||
"goToUpload": "Yüklemeye Git",
|
||||
"logout": "Çıkış Yap",
|
||||
"luck": "Şans",
|
||||
@ -291,9 +292,24 @@
|
||||
"noMatchedCP": "Eşleşen CP yok",
|
||||
"inviteYouToBecomeBD": "Sizi BD yapmaya davet ediyoruz.",
|
||||
"adminInviteRechargeAgent": "Sizi Yükleme temsilcisi yapmaya davet ediyoruz.",
|
||||
"confirmAcceptTheInvitation": "Daveti kabul etmek için onaylıyor musunuz?",
|
||||
"confirmDeclineTheInvitation": "Daveti reddetmek için onaylıyor musunuz?",
|
||||
"host": "Sunucu",
|
||||
"confirmAcceptTheInvitation": "Daveti kabul etmek için onaylıyor musunuz?",
|
||||
"confirmDeclineTheInvitation": "Daveti reddetmek için onaylıyor musunuz?",
|
||||
"cpInviteRejectedByOther": "Karşı taraf {1} olmayı reddetti.",
|
||||
"closeFriendInvitationTitle": "Invitation from a Close Friend",
|
||||
"closeFriendInvitationSubtitle": "Click to view the invitation",
|
||||
"closeFriendInvitationExpired": "The invitation to add a close friend has expired. This action cannot be performed.",
|
||||
"closeFriendRequestDeclined": "Friend request declined",
|
||||
"closeFriendsHaveBecome": "Have become close friends",
|
||||
"otherAcceptedCloseFriendInvite": "The other party accepted your close friend invitation.",
|
||||
"otherDeclinedCloseFriendInvite": "The other party declined your close friend invitation.",
|
||||
"youAcceptedCloseFriendInvite": "You accepted the other party's close friend invitation.",
|
||||
"youDeclinedCloseFriendInvite": "You declined the other party's close friend invitation.",
|
||||
"closeFriendInviteTimedOut": "Close friend invitation timed out.",
|
||||
"cpDismissConfirmMessage": "Removing this relationship will cost {1} coins. Confirm removal?",
|
||||
"cpRelationCp": "CP",
|
||||
"cpRelationBrother": "Brother",
|
||||
"cpRelationSister": "Sister",
|
||||
"host": "Sunucu",
|
||||
"following": "Takip Edilenler",
|
||||
"agent": "Temsilcilik",
|
||||
"approved": "Onaylandı",
|
||||
@ -411,6 +427,7 @@
|
||||
"bdCenter": "BD Merkezi",
|
||||
"rechargeAgency": "Yükleme Temsilciliği",
|
||||
"adminCenter": "Yönetici Merkezi",
|
||||
"managerCenter": "Manager Center",
|
||||
"goldList": "Altın Listesi",
|
||||
"followList": "Takip Listesi",
|
||||
"fansList": "Hayran Listesi",
|
||||
@ -451,9 +468,10 @@
|
||||
"message": "Mesaj",
|
||||
"clearCache": "Önbelleği Temizle",
|
||||
"customized": "Özel",
|
||||
"searchInputHint": "Hesap/oda numarasını girin",
|
||||
"kickRoomTips": "Odadan atıldınız.",
|
||||
"joinRoomTips": "odaya katıldı !",
|
||||
"searchInputHint": "Hesap/oda numarasını girin",
|
||||
"kickRoomTips": "Odadan atıldınız.",
|
||||
"roomKickedEntryBlockedTips": "Bir yönetici tarafından odadan çıkarıldınız, şu anda giremezsiniz.",
|
||||
"joinRoomTips": "odaya katıldı !",
|
||||
"roomSetting": "Oda Ayarları",
|
||||
"roomDetails": "Oda Detayları",
|
||||
"systemRoomTips": "Nezaketli ve saygılı olun. yumi'de pornografik veya uygunsuz içerik kesinlikle yasaktır. Tespit edilmesi halinde hesap kalıcı olarak engellenecektir. Lütfen yumi platform kurallarına bilinçli şekilde uyun.",
|
||||
@ -565,10 +583,12 @@
|
||||
"obtain": "Al",
|
||||
"win2": "{1} Kazan",
|
||||
"backTheRoom": "Odaya Dön",
|
||||
"toConsume": "Tüketmek İçin",
|
||||
"howToUpgrade": "Nasıl yükseltirim?",
|
||||
"spendCoinsToGainExperiencePoints": "Deneyim puanı kazanmak için jetton harcayın",
|
||||
"higherLevelFancierAvatarFrame": "Daha yüksek seviye, daha şık rozetler/profil çerçevesi",
|
||||
"toConsume": "Tüketmek İçin",
|
||||
"howToUpgrade": "Nasıl yükseltirim?",
|
||||
"spendCoinsToGainExperiencePoints": "Deneyim puanı kazanmak için jetton harcayın",
|
||||
"richLevelUpgradeAnswer": "Servetin her şeyi fethetsin",
|
||||
"charmLevelUpgradeAnswer": "Başkalarını cazibene boyun eğdir",
|
||||
"higherLevelFancierAvatarFrame": "Daha yüksek seviye, daha şık rozetler/profil çerçevesi",
|
||||
"all": "Tümü",
|
||||
"gift": "Hediye",
|
||||
"chat": "Sohbet",
|
||||
@ -719,5 +739,12 @@
|
||||
"roomMusicListMode": "Sırayla çal",
|
||||
"roomMusicShuffleMode": "Karışık çal",
|
||||
"roomMusicSingleMode": "Tek şarkı döngüsü",
|
||||
"duration2": "Süre:{1}"
|
||||
"duration2": "Süre:{1}",
|
||||
"roomRocketNoteTitle": "Not",
|
||||
"roomRocketNoteSectionTitle": "Her VIP seviyesi satın alındığında",
|
||||
"roomRocketNoteSectionBody": "1. Her VIP seviyesi satın alındığında 30 gün geçerlidir ve süresi dolduktan sonra manuel olarak yenilenebilir.",
|
||||
"roomRocketRewardEmptyText": "Ödül kazanmadın. Bir dahaki sefere bol şans.",
|
||||
"roomRocketRecordEmptyText": "Ödül kaydı yok",
|
||||
"launchRocket": "Roketi Fırlat",
|
||||
"roomRocketFuelFullNotice": "Roket yakıtı doldu! {1} saniye sonra fırlatılıyor!"
|
||||
}
|
||||
|
||||
22
ios/ExportOptions.plist
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>destination</key>
|
||||
<string>export</string>
|
||||
<key>distributionBundleIdentifier</key>
|
||||
<string>com.org.yumiparty</string>
|
||||
<key>manageAppVersionAndBuildNumber</key>
|
||||
<false/>
|
||||
<key>method</key>
|
||||
<string>app-store-connect</string>
|
||||
<key>signingStyle</key>
|
||||
<string>automatic</string>
|
||||
<key>stripSwiftSymbols</key>
|
||||
<true/>
|
||||
<key>teamID</key>
|
||||
<string>S9YG87C297</string>
|
||||
<key>uploadSymbols</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -1,50 +1,4 @@
|
||||
PODS:
|
||||
- agora_rtc_engine (6.5.3):
|
||||
- AgoraIrisRTC_iOS (= 4.5.2-build.1)
|
||||
- AgoraRtcEngine_iOS (= 4.5.2)
|
||||
- Flutter
|
||||
- AgoraInfra_iOS (1.2.13.1)
|
||||
- AgoraIrisRTC_iOS (4.5.2-build.1)
|
||||
- AgoraRtcEngine_iOS (4.5.2):
|
||||
- AgoraRtcEngine_iOS/AIAEC (= 4.5.2)
|
||||
- AgoraRtcEngine_iOS/AIAECLL (= 4.5.2)
|
||||
- AgoraRtcEngine_iOS/AINS (= 4.5.2)
|
||||
- AgoraRtcEngine_iOS/AINSLL (= 4.5.2)
|
||||
- AgoraRtcEngine_iOS/AudioBeauty (= 4.5.2)
|
||||
- AgoraRtcEngine_iOS/ClearVision (= 4.5.2)
|
||||
- AgoraRtcEngine_iOS/ContentInspect (= 4.5.2)
|
||||
- AgoraRtcEngine_iOS/FaceCapture (= 4.5.2)
|
||||
- AgoraRtcEngine_iOS/FaceDetection (= 4.5.2)
|
||||
- AgoraRtcEngine_iOS/LipSync (= 4.5.2)
|
||||
- AgoraRtcEngine_iOS/ReplayKit (= 4.5.2)
|
||||
- AgoraRtcEngine_iOS/RtcBasic (= 4.5.2)
|
||||
- AgoraRtcEngine_iOS/SpatialAudio (= 4.5.2)
|
||||
- AgoraRtcEngine_iOS/VideoAv1CodecDec (= 4.5.2)
|
||||
- AgoraRtcEngine_iOS/VideoAv1CodecEnc (= 4.5.2)
|
||||
- AgoraRtcEngine_iOS/VideoCodecDec (= 4.5.2)
|
||||
- AgoraRtcEngine_iOS/VideoCodecEnc (= 4.5.2)
|
||||
- AgoraRtcEngine_iOS/VirtualBackground (= 4.5.2)
|
||||
- AgoraRtcEngine_iOS/VQA (= 4.5.2)
|
||||
- AgoraRtcEngine_iOS/AIAEC (4.5.2)
|
||||
- AgoraRtcEngine_iOS/AIAECLL (4.5.2)
|
||||
- AgoraRtcEngine_iOS/AINS (4.5.2)
|
||||
- AgoraRtcEngine_iOS/AINSLL (4.5.2)
|
||||
- AgoraRtcEngine_iOS/AudioBeauty (4.5.2)
|
||||
- AgoraRtcEngine_iOS/ClearVision (4.5.2)
|
||||
- AgoraRtcEngine_iOS/ContentInspect (4.5.2)
|
||||
- AgoraRtcEngine_iOS/FaceCapture (4.5.2)
|
||||
- AgoraRtcEngine_iOS/FaceDetection (4.5.2)
|
||||
- AgoraRtcEngine_iOS/LipSync (4.5.2)
|
||||
- AgoraRtcEngine_iOS/ReplayKit (4.5.2)
|
||||
- AgoraRtcEngine_iOS/RtcBasic (4.5.2):
|
||||
- AgoraInfra_iOS (= 1.2.13.1)
|
||||
- AgoraRtcEngine_iOS/SpatialAudio (4.5.2)
|
||||
- AgoraRtcEngine_iOS/VideoAv1CodecDec (4.5.2)
|
||||
- AgoraRtcEngine_iOS/VideoAv1CodecEnc (4.5.2)
|
||||
- AgoraRtcEngine_iOS/VideoCodecDec (4.5.2)
|
||||
- AgoraRtcEngine_iOS/VideoCodecEnc (4.5.2)
|
||||
- AgoraRtcEngine_iOS/VirtualBackground (4.5.2)
|
||||
- AgoraRtcEngine_iOS/VQA (4.5.2)
|
||||
- app_links (6.4.1):
|
||||
- Flutter
|
||||
- AppAuth (1.7.6):
|
||||
@ -187,8 +141,7 @@ PODS:
|
||||
- in_app_purchase_storekit (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- iris_method_channel (0.0.1):
|
||||
- Flutter
|
||||
- libpag (4.5.52)
|
||||
- libwebp (1.5.0):
|
||||
- libwebp/demux (= 1.5.0)
|
||||
- libwebp/mux (= 1.5.0)
|
||||
@ -214,6 +167,9 @@ PODS:
|
||||
- SwiftyBeaver
|
||||
- package_info_plus (0.4.5):
|
||||
- Flutter
|
||||
- pag (0.0.1):
|
||||
- Flutter
|
||||
- libpag
|
||||
- permission_handler_apple (9.3.0):
|
||||
- Flutter
|
||||
- PromisesObjC (2.4.0)
|
||||
@ -241,8 +197,16 @@ PODS:
|
||||
- Flutter
|
||||
- HydraAsync
|
||||
- TXIMSDK_Plus_iOS_XCFramework (= 8.3.6498)
|
||||
- tencent_rtc_sdk (0.0.1):
|
||||
- Flutter
|
||||
- TXCustomBeautyProcesserPlugin (= 1.0.2)
|
||||
- TXLiteAVSDK_Professional (~> 13.2.20652)
|
||||
- TOCropViewController (2.6.1)
|
||||
- TXCustomBeautyProcesserPlugin (1.0.2)
|
||||
- TXIMSDK_Plus_iOS_XCFramework (8.3.6498)
|
||||
- TXLiteAVSDK_Professional (13.2.20652):
|
||||
- TXLiteAVSDK_Professional/Professional (= 13.2.20652)
|
||||
- TXLiteAVSDK_Professional/Professional (13.2.20652)
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
- video_player_avfoundation (0.0.1):
|
||||
@ -258,7 +222,6 @@ PODS:
|
||||
- FlutterMacOS
|
||||
|
||||
DEPENDENCIES:
|
||||
- agora_rtc_engine (from `.symlinks/plugins/agora_rtc_engine/ios`)
|
||||
- app_links (from `.symlinks/plugins/app_links/ios`)
|
||||
- audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/darwin`)
|
||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||
@ -272,15 +235,16 @@ DEPENDENCIES:
|
||||
- image_cropper (from `.symlinks/plugins/image_cropper/ios`)
|
||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||
- in_app_purchase_storekit (from `.symlinks/plugins/in_app_purchase_storekit/darwin`)
|
||||
- iris_method_channel (from `.symlinks/plugins/iris_method_channel/ios`)
|
||||
- on_audio_query_ios (from `.symlinks/plugins/on_audio_query_ios/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- pag (from `.symlinks/plugins/pag/ios`)
|
||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sign_in_with_apple (from `.symlinks/plugins/sign_in_with_apple/ios`)
|
||||
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||
- tancent_vap (from `.symlinks/plugins/tancent_vap/ios`)
|
||||
- tencent_cloud_chat_sdk (from `.symlinks/plugins/tencent_cloud_chat_sdk/ios`)
|
||||
- tencent_rtc_sdk (from `.symlinks/plugins/tencent_rtc_sdk/ios`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
|
||||
- video_thumbnail (from `.symlinks/plugins/video_thumbnail/ios`)
|
||||
@ -289,9 +253,6 @@ DEPENDENCIES:
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- AgoraInfra_iOS
|
||||
- AgoraIrisRTC_iOS
|
||||
- AgoraRtcEngine_iOS
|
||||
- AppAuth
|
||||
- AppCheckCore
|
||||
- Firebase
|
||||
@ -311,6 +272,7 @@ SPEC REPOS:
|
||||
- GTMAppAuth
|
||||
- GTMSessionFetcher
|
||||
- HydraAsync
|
||||
- libpag
|
||||
- libwebp
|
||||
- Mantle
|
||||
- nanopb
|
||||
@ -321,11 +283,11 @@ SPEC REPOS:
|
||||
- SDWebImageWebPCoder
|
||||
- SwiftyBeaver
|
||||
- TOCropViewController
|
||||
- TXCustomBeautyProcesserPlugin
|
||||
- TXIMSDK_Plus_iOS_XCFramework
|
||||
- TXLiteAVSDK_Professional
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
agora_rtc_engine:
|
||||
:path: ".symlinks/plugins/agora_rtc_engine/ios"
|
||||
app_links:
|
||||
:path: ".symlinks/plugins/app_links/ios"
|
||||
audioplayers_darwin:
|
||||
@ -352,12 +314,12 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||
in_app_purchase_storekit:
|
||||
:path: ".symlinks/plugins/in_app_purchase_storekit/darwin"
|
||||
iris_method_channel:
|
||||
:path: ".symlinks/plugins/iris_method_channel/ios"
|
||||
on_audio_query_ios:
|
||||
:path: ".symlinks/plugins/on_audio_query_ios/ios"
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
pag:
|
||||
:path: ".symlinks/plugins/pag/ios"
|
||||
permission_handler_apple:
|
||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||
shared_preferences_foundation:
|
||||
@ -370,6 +332,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/tancent_vap/ios"
|
||||
tencent_cloud_chat_sdk:
|
||||
:path: ".symlinks/plugins/tencent_cloud_chat_sdk/ios"
|
||||
tencent_rtc_sdk:
|
||||
:path: ".symlinks/plugins/tencent_rtc_sdk/ios"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
video_player_avfoundation:
|
||||
@ -382,10 +346,6 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/webview_flutter_wkwebview/darwin"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
agora_rtc_engine: 0c7d50312967c4dc31c3c45e50589ce48f57e08a
|
||||
AgoraInfra_iOS: 3691b2b277a1712a35ae96de25af319de0d73d08
|
||||
AgoraIrisRTC_iOS: eab58c126439adf5ec99632828a558ea216860da
|
||||
AgoraRtcEngine_iOS: 97e2398a2addda9057815a2a583a658e36796ff6
|
||||
app_links: 3dbc685f76b1693c66a6d9dd1e9ab6f73d97dc0a
|
||||
AppAuth: d4f13a8fe0baf391b2108511793e4b479691fb73
|
||||
AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f
|
||||
@ -418,12 +378,13 @@ SPEC CHECKSUMS:
|
||||
image_cropper: 655b3ba703c9e15e3111e79151624d6154288774
|
||||
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
|
||||
in_app_purchase_storekit: 22cca7d08eebca9babdf4d07d0baccb73325d3c8
|
||||
iris_method_channel: 7d661cf3259b3009ae423508470dbeb9374446ee
|
||||
libpag: 98742fad4b3ac2a2ee31e383d2483495fb9a5fb4
|
||||
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
|
||||
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
|
||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||
on_audio_query_ios: 28a780e2d0d85d92d500ba6e12c6c8167022b2fa
|
||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||
pag: f01aa9017ab0e04a83ba4a20d6070a50f0ac9da8
|
||||
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
|
||||
@ -436,10 +397,13 @@ SPEC CHECKSUMS:
|
||||
SwiftyBeaver: 84069991dd5dca07d7069100985badaca7f0ce82
|
||||
tancent_vap: fa8ad93814a9f950514a7074c662d2c937084c68
|
||||
tencent_cloud_chat_sdk: 55e5fffe20f6b7937a26a674ccccb639563a9790
|
||||
tencent_rtc_sdk: f77558b6b436a149d378557c2a837f73c09061bc
|
||||
TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863
|
||||
TXCustomBeautyProcesserPlugin: 099393b941cb40eda12b3a80bf6c0319957b1cfd
|
||||
TXIMSDK_Plus_iOS_XCFramework: 5d1933192fb3b7ef2fe933f1623de4a0486a7fe2
|
||||
TXLiteAVSDK_Professional: e792e6084a208ecef6196c5f28237ad8078b4d7c
|
||||
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
||||
video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a
|
||||
video_player_avfoundation: 3453f792138786248960ca029747fcd9f318ef52
|
||||
video_thumbnail: b637e0ad5f588ca9945f6e2c927f73a69a661140
|
||||
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
||||
webview_flutter_wkwebview: 8ebf4fded22593026f7dbff1fbff31ea98573c8d
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
B7A1D58E2EE51A0000000001 /* GiftMp4VideoPlatformView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7A1D58F2EE51A0000000002 /* GiftMp4VideoPlatformView.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@ -70,6 +71,7 @@
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
B7A1D58F2EE51A0000000002 /* GiftMp4VideoPlatformView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GiftMp4VideoPlatformView.swift; sourceTree = "<group>"; };
|
||||
D8CD6B2D6477A38E56A72C61 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
@ -170,6 +172,7 @@
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||
B7A1D58F2EE51A0000000002 /* GiftMp4VideoPlatformView.swift */,
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||
);
|
||||
path = Runner;
|
||||
@ -210,6 +213,7 @@
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
F1AFD9E9E8DBA4F1609CE22F /* [CP] Embed Pods Frameworks */,
|
||||
5DF67264ADC5268AC3021CED /* [CP] Copy Pods Resources */,
|
||||
B7A1D5902EE51A0000000003 /* Upload Crashlytics Symbols */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@ -352,6 +356,22 @@
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
B7A1D5902EE51A0000000003 /* Upload Crashlytics Symbols */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}",
|
||||
"${PROJECT_DIR}/Runner/GoogleService-Info.plist",
|
||||
);
|
||||
name = "Upload Crashlytics Symbols";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "if [ \"$CONFIGURATION\" = \"Release\" ] || [ \"$CONFIGURATION\" = \"Profile\" ]; then\n if [ -x \"${PODS_ROOT}/FirebaseCrashlytics/upload-symbols\" ]; then\n \"${PODS_ROOT}/FirebaseCrashlytics/upload-symbols\" -gsp \"${PROJECT_DIR}/Runner/GoogleService-Info.plist\" -p ios \"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}\"\n else\n echo \"warning: FirebaseCrashlytics upload-symbols not found at ${PODS_ROOT}/FirebaseCrashlytics/upload-symbols\"\n fi\nfi\n";
|
||||
};
|
||||
CFD1FCCAE92AFBF15E8B607E /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@ -407,6 +427,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||
B7A1D58E2EE51A0000000001 /* GiftMp4VideoPlatformView.swift in Sources */,
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -498,12 +519,13 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPat_icon_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 12;
|
||||
DEVELOPMENT_TEAM = S9X2AJ2US9;
|
||||
CURRENT_PROJECT_VERSION = 150;
|
||||
DEVELOPMENT_TEAM = S9YG87C297;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
@ -511,7 +533,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.3.1;
|
||||
MARKETING_VERSION = 1.5.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@ -688,13 +710,13 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPat_icon_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 12;
|
||||
DEVELOPMENT_TEAM = F33K8VUZ62;
|
||||
CURRENT_PROJECT_VERSION = 150;
|
||||
DEVELOPMENT_TEAM = S9YG87C297;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
@ -702,7 +724,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.3.1;
|
||||
MARKETING_VERSION = 1.5.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@ -717,13 +739,13 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPat_icon_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 12;
|
||||
DEVELOPMENT_TEAM = F33K8VUZ62;
|
||||
CURRENT_PROJECT_VERSION = 150;
|
||||
DEVELOPMENT_TEAM = S9YG87C297;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
@ -731,7 +753,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.3.1;
|
||||
MARKETING_VERSION = 1.5.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
||||
@ -9,11 +9,23 @@ import UIKit
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
registerGiftMp4VideoView()
|
||||
registerDurableAuthStorageChannel()
|
||||
registerMobileContextChannel()
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
|
||||
private func registerGiftMp4VideoView() {
|
||||
guard let registrar = self.registrar(forPlugin: "GiftMp4VideoViewPlugin") else {
|
||||
return
|
||||
}
|
||||
GiftMp4VideoPlatformView.installControlChannel(messenger: registrar.messenger())
|
||||
registrar.register(
|
||||
GiftMp4VideoViewFactory(messenger: registrar.messenger()),
|
||||
withId: GiftMp4VideoPlatformView.viewType
|
||||
)
|
||||
}
|
||||
|
||||
private func registerMobileContextChannel() {
|
||||
guard let controller = window?.rootViewController as? FlutterViewController else {
|
||||
return
|
||||
|
||||
23
ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "LaunchBackground.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "LaunchBackground@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "LaunchBackground@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
ios/Runner/Assets.xcassets/LaunchBackground.imageset/LaunchBackground.png
vendored
Normal file
|
After Width: | Height: | Size: 187 KiB |
BIN
ios/Runner/Assets.xcassets/LaunchBackground.imageset/LaunchBackground@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 811 KiB |
BIN
ios/Runner/Assets.xcassets/LaunchBackground.imageset/LaunchBackground@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 371 KiB |
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 187 KiB |
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 811 KiB |
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 371 KiB |
23
ios/Runner/Assets.xcassets/LaunchLogo.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "LaunchLogo.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "LaunchLogo@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "LaunchLogo@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
ios/Runner/Assets.xcassets/LaunchLogo.imageset/LaunchLogo.png
vendored
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
ios/Runner/Assets.xcassets/LaunchLogo.imageset/LaunchLogo@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
ios/Runner/Assets.xcassets/LaunchLogo.imageset/LaunchLogo@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 108 KiB |
@ -15,15 +15,25 @@
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||
</constraints>
|
||||
<subviews>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleAspectFill" image="LaunchBackground" translatesAutoresizingMaskIntoConstraints="NO" id="BGD-k0-Ey4">
|
||||
</imageView>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleAspectFit" image="LaunchLogo" translatesAutoresizingMaskIntoConstraints="NO" id="LOG-k0-Ey4">
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="107" id="LOG-width-107"/>
|
||||
<constraint firstAttribute="height" constant="97" id="LOG-height-97"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="0" green="0" blue="0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="BGD-k0-Ey4" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="BGD-top"/>
|
||||
<constraint firstItem="BGD-k0-Ey4" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="BGD-leading"/>
|
||||
<constraint firstItem="BGD-k0-Ey4" firstAttribute="trailing" secondItem="Ze5-6b-2t3" secondAttribute="trailing" id="BGD-trailing"/>
|
||||
<constraint firstItem="BGD-k0-Ey4" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="BGD-bottom"/>
|
||||
<constraint firstItem="LOG-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="LOG-centerX"/>
|
||||
<constraint firstItem="LOG-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="LOG-centerY"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
@ -32,6 +42,7 @@
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="LaunchImage" width="168" height="185"/>
|
||||
<image name="LaunchBackground" width="375" height="812"/>
|
||||
<image name="LaunchLogo" width="107" height="97"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
1182
ios/Runner/GiftMp4VideoPlatformView.swift
Normal file
@ -35,23 +35,39 @@
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>com.googleusercontent.apps.52796773508-dk441c9e7tdmqv47mbdt2vpbn2clrii8</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>com.googleusercontent.apps.52796773508-dk441c9e7tdmqv47mbdt2vpbn2clrii8</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>com.org.yumiparty</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>yumi</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>We need access to your camera so you can take a photo and make it a profile picture, or take and share a picture in real time in a voice chat room.</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>We need access to your microphone so that you can make live calls, record, and send voice messages with other users in the voice dating room.</string>
|
||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||
<string>We need your permission to save beautiful images you take or edit in the app to your phone album.</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>We need access to your microphone so that you can make live calls, record, and send voice messages with other users in the voice dating room.</string>
|
||||
<key>NSAppleMusicUsageDescription</key>
|
||||
<string>We need access to your media library so you can select local music to play in voice rooms.</string>
|
||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||
<string>We need your permission to save beautiful images you take or edit in the app to your phone album.</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>We need access to your photo album so you can choose photos from it to update your profile picture or share moments from your life in chat.</string>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
@ -77,9 +93,5 @@
|
||||
<array>
|
||||
<string>audio</string>
|
||||
</array>
|
||||
<key>com.apple.developer.applesignin</key>
|
||||
<array>
|
||||
<string>Default</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -2,9 +2,13 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.developer.applesignin</key>
|
||||
<array>
|
||||
<string>Default</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
<key>com.apple.developer.applesignin</key>
|
||||
<array>
|
||||
<string>Default</string>
|
||||
</array>
|
||||
<key>com.apple.developer.associated-domains</key>
|
||||
<array>
|
||||
<string>applinks:h5.yumi.com</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -2,9 +2,13 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.developer.applesignin</key>
|
||||
<array>
|
||||
<string>Default</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
<key>com.apple.developer.applesignin</key>
|
||||
<array>
|
||||
<string>Default</string>
|
||||
</array>
|
||||
<key>com.apple.developer.associated-domains</key>
|
||||
<array>
|
||||
<string>applinks:h5.yumi.com</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -46,6 +46,9 @@ abstract class AppConfig {
|
||||
/// 管理员URL
|
||||
String get adminUrl;
|
||||
|
||||
/// Yumi管理中心URL
|
||||
String get managerCenterUrl;
|
||||
|
||||
/// 代理中心URL
|
||||
String get agencyCenterUrl;
|
||||
|
||||
@ -70,8 +73,8 @@ abstract class AppConfig {
|
||||
/// 腾讯IM App ID
|
||||
String get tencentImAppid;
|
||||
|
||||
/// Agora RTC App ID
|
||||
String get agoraRtcAppid;
|
||||
/// TRTC SDK App ID
|
||||
int get trtcSdkAppId;
|
||||
|
||||
/// 游戏App ID
|
||||
num get gameAppid;
|
||||
|
||||
@ -16,8 +16,8 @@ class SCVariant1Config implements AppConfig {
|
||||
@override
|
||||
String get apiHost => const String.fromEnvironment(
|
||||
'API_HOST',
|
||||
defaultValue: 'https://jvapi.haiyihy.com/',
|
||||
); // 默认连线上环境,本地调试可通过 --dart-define=API_HOST 覆盖 本地“http://192.168.110.64:1100/”,线上“https://jvapi.haiyihy.com/”
|
||||
defaultValue: 'http://192.168.110.64:1100/',
|
||||
); // 默认连本地接口,本地调试可通过 --dart-define=API_HOST 覆盖,线上“https://jvapi.haiyihy.com/”
|
||||
|
||||
@override
|
||||
String get imgHost => 'https://img.atuchat.com/'; // 测试图片服务器,上架前需替换为正式域名
|
||||
@ -55,6 +55,10 @@ class SCVariant1Config implements AppConfig {
|
||||
@override
|
||||
String get adminUrl => 'https://h5.haiyihy.com/admin-center/index.html'; // 正式 H5 页面
|
||||
|
||||
@override
|
||||
String get managerCenterUrl =>
|
||||
'https://h5.haiyihy.com/hyapp/manager-center/index.html'; // 正式 H5 页面
|
||||
|
||||
@override
|
||||
String get agencyCenterUrl =>
|
||||
'https://h5.haiyihy.com/agency-center/index.html'; // 正式 H5 页面
|
||||
@ -85,7 +89,8 @@ class SCVariant1Config implements AppConfig {
|
||||
String get tencentImAppid => '20036101';
|
||||
|
||||
@override
|
||||
String get agoraRtcAppid => '4b5e5cea3b86476caf7f7a57d05b82d1';
|
||||
int get trtcSdkAppId =>
|
||||
const int.fromEnvironment('TRTC_SDK_APP_ID', defaultValue: 20036101);
|
||||
|
||||
@override
|
||||
num get gameAppid => 9999999999; // 需要注册新的游戏服务账户并获取独立App ID
|
||||
@ -136,9 +141,10 @@ class SCVariant1Config implements AppConfig {
|
||||
'imgHost': imgHost,
|
||||
'privacyAgreementUrl': privacyAgreementUrl,
|
||||
'userAgreementUrl': userAgreementUrl,
|
||||
'managerCenterUrl': managerCenterUrl,
|
||||
'primaryColor': primaryColor,
|
||||
'tencentImAppid': tencentImAppid,
|
||||
'agoraRtcAppid': agoraRtcAppid,
|
||||
'trtcSdkAppId': trtcSdkAppId,
|
||||
'gameAppid': gameAppid,
|
||||
'gameAppChannel': gameAppChannel,
|
||||
'bigBroadcastGroup': bigBroadcastGroup,
|
||||
@ -161,6 +167,9 @@ class SCVariant1Config implements AppConfig {
|
||||
if (apiHost.isEmpty || !apiHost.startsWith('http')) {
|
||||
errors.add('API主机地址无效或未设置');
|
||||
}
|
||||
if (trtcSdkAppId <= 0) {
|
||||
errors.add('TRTC SDK App ID 无效或未设置');
|
||||
}
|
||||
|
||||
// 如果有错误,根据模式处理
|
||||
if (errors.isNotEmpty) {
|
||||
|
||||
@ -20,6 +20,7 @@ class SCGlobalConfig {
|
||||
static String get bdLeaderUrl => AppConfig.current.bdLeaderUrl;
|
||||
static String get coinSellerUrl => AppConfig.current.coinSellerUrl;
|
||||
static String get adminUrl => AppConfig.current.adminUrl;
|
||||
static String get managerCenterUrl => AppConfig.current.managerCenterUrl;
|
||||
static String get agencyCenterUrl => AppConfig.current.agencyCenterUrl;
|
||||
static String get gamesKingUrl => AppConfig.current.gamesKingUrl;
|
||||
|
||||
@ -54,7 +55,7 @@ class SCGlobalConfig {
|
||||
static String originChild = "LIKEI";
|
||||
|
||||
static String get tencentImAppid => AppConfig.current.tencentImAppid;
|
||||
static String get agoraRtcAppid => AppConfig.current.agoraRtcAppid;
|
||||
static int get trtcSdkAppId => AppConfig.current.trtcSdkAppId;
|
||||
|
||||
static num get gameAppid => AppConfig.current.gameAppid;
|
||||
static String get gameAppChannel => AppConfig.current.gameAppChannel;
|
||||
@ -167,8 +168,7 @@ class SCGlobalConfig {
|
||||
}
|
||||
|
||||
static String clampFloatingBroadcastScope(String value) {
|
||||
final scope = normalizeFloatingBroadcastScope(value);
|
||||
return _isLowPerformanceDevice ? floatingBroadcastScopeOff : scope;
|
||||
return normalizeFloatingBroadcastScope(value);
|
||||
}
|
||||
|
||||
static String floatingBroadcastScopeStorageKey(String? account) {
|
||||
@ -187,8 +187,7 @@ class SCGlobalConfig {
|
||||
|
||||
static bool get isLowPerformanceDevice => _isLowPerformanceDevice;
|
||||
|
||||
static bool get allowsHighCostAnimations =>
|
||||
!_isLowPerformanceDevice && sdkInt > maxSdkNoAnim;
|
||||
static bool get allowsHighCostAnimations => true;
|
||||
|
||||
static int get recommendedImageCacheBytes =>
|
||||
_isLowPerformanceDevice ? 40 * 1024 * 1024 : 100 * 1024 * 1024;
|
||||
@ -210,19 +209,17 @@ class SCGlobalConfig {
|
||||
isLowRamDevice ||
|
||||
processorCount <= 6 ||
|
||||
matchesKnownLowPerformanceModel;
|
||||
resetVisualEffectSwitchesToRecommendedDefaults();
|
||||
}
|
||||
|
||||
static bool clampVisualEffectPreference(bool enabled) {
|
||||
return !_isLowPerformanceDevice && enabled;
|
||||
return enabled;
|
||||
}
|
||||
|
||||
static void resetVisualEffectSwitchesToRecommendedDefaults() {
|
||||
final enabled = !_isLowPerformanceDevice;
|
||||
isEntryVehicleAnimation = enabled;
|
||||
isGiftSpecialEffects = enabled;
|
||||
isFloatingAnimationInGlobal = enabled;
|
||||
isLuckGiftSpecialEffects = enabled;
|
||||
isEntryVehicleAnimation = true;
|
||||
isGiftSpecialEffects = true;
|
||||
isFloatingAnimationInGlobal = true;
|
||||
isLuckGiftSpecialEffects = true;
|
||||
}
|
||||
|
||||
/// 获取当前业务逻辑策略
|
||||
|
||||
@ -3,6 +3,7 @@ class SCRoomMsgType {
|
||||
static const String image = "IMAGE";
|
||||
static const String joinRoom = "JOIN_ROOM";
|
||||
static const String systemTips = "SYSTEM_TIPS";
|
||||
static const String cpSystemNotice = "CP_SYSTEM_NOTICE";
|
||||
|
||||
///表情包
|
||||
static const String emoticons = "EMOTICONS";
|
||||
@ -79,6 +80,25 @@ class SCRoomMsgType {
|
||||
///用户火箭中奖
|
||||
static const String rocketRewardUser = "ROCKET_REWARD_USER";
|
||||
|
||||
///语音房火箭用户中奖
|
||||
static const String voiceRoomRocketRewardUser =
|
||||
"VOICE_ROOM_ROCKET_REWARD_USER";
|
||||
|
||||
///火箭燃料满倒计时发射提示
|
||||
static const String rocketLaunchNotice = "ROCKET_LAUNCH_NOTICE";
|
||||
|
||||
///语音房火箭状态更新
|
||||
static const String voiceRoomRocketStatusUpdate =
|
||||
"VOICE_ROOM_ROCKET_STATUS_UPDATE";
|
||||
|
||||
///语音房火箭发射广播
|
||||
static const String voiceRoomRocketLaunchBroadcast =
|
||||
"VOICE_ROOM_ROCKET_LAUNCH_BROADCAST";
|
||||
|
||||
///语音房火箭中奖弹窗提醒
|
||||
static const String voiceRoomRocketRewardPopup =
|
||||
"VOICE_ROOM_ROCKET_REWARD_POPUP";
|
||||
|
||||
///管理变动
|
||||
static const String managerList = "MANAGER_LIST";
|
||||
|
||||
@ -114,4 +134,8 @@ class SCRoomMsgType {
|
||||
static const String gameBurstCrystalSprint = "GAME_BURST_CRYSTAL_SPRINT";
|
||||
static const String gameBurstCrystalBox = "GAME_BURST_CRYSTAL_BOX";
|
||||
static const String refreshOnlineUser = "REFRESH_ONLINE_USER";
|
||||
|
||||
static bool isRocketRewardUserType(String? type) {
|
||||
return type == rocketRewardUser || type == voiceRoomRocketRewardUser;
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,49 @@ class SCAppLocalizations {
|
||||
|
||||
SCAppLocalizations(this.locale);
|
||||
|
||||
static const List<String> supportedLanguageCodes = [
|
||||
'en',
|
||||
'ar',
|
||||
'tr',
|
||||
'bn',
|
||||
'it',
|
||||
'pt',
|
||||
'fr',
|
||||
];
|
||||
|
||||
static const List<Locale> supportedLocales = [
|
||||
Locale('en', ''),
|
||||
Locale('ar', ''),
|
||||
Locale('tr', ''),
|
||||
Locale('bn', ''),
|
||||
Locale('it', ''),
|
||||
Locale('pt', ''),
|
||||
Locale('fr', ''),
|
||||
];
|
||||
|
||||
static bool isSupportedLanguageCode(String languageCode) {
|
||||
return supportedLanguageCodes.contains(
|
||||
_normalizeLanguageCode(languageCode),
|
||||
);
|
||||
}
|
||||
|
||||
static String resolveSupportedLanguageCode(String languageCode) {
|
||||
final normalizedLanguageCode = _normalizeLanguageCode(languageCode);
|
||||
if (supportedLanguageCodes.contains(normalizedLanguageCode)) {
|
||||
return normalizedLanguageCode;
|
||||
}
|
||||
return 'en';
|
||||
}
|
||||
|
||||
static String _normalizeLanguageCode(String languageCode) {
|
||||
return languageCode
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replaceAll('_', '-')
|
||||
.split('-')
|
||||
.first;
|
||||
}
|
||||
|
||||
static SCAppLocalizations? of(BuildContext context) {
|
||||
return Localizations.of<SCAppLocalizations>(context, SCAppLocalizations);
|
||||
}
|
||||
@ -18,8 +61,9 @@ class SCAppLocalizations {
|
||||
Map<String, String>? _localizedStrings;
|
||||
|
||||
Future<bool> load() async {
|
||||
final languageCode = resolveSupportedLanguageCode(locale.languageCode);
|
||||
final jsonString = await rootBundle.loadString(
|
||||
'assets/l10n/intl_${locale.languageCode}.json',
|
||||
'assets/l10n/intl_$languageCode.json',
|
||||
);
|
||||
|
||||
final Map<String, dynamic> jsonMap = json.decode(jsonString);
|
||||
@ -799,6 +843,11 @@ class SCAppLocalizations {
|
||||
|
||||
String get launchedARocket => translate('launchedARocket');
|
||||
|
||||
String get launchRocket => translate('launchRocket');
|
||||
|
||||
String roomRocketFuelFullNotice(String seconds) =>
|
||||
translate('roomRocketFuelFullNotice').replaceAll('{1}', seconds);
|
||||
|
||||
String get useCoupontips => translate('useCoupontips');
|
||||
|
||||
String get joinMemberTips2 => translate('joinMemberTips2');
|
||||
@ -841,6 +890,8 @@ class SCAppLocalizations {
|
||||
|
||||
String get bio => translate('bio');
|
||||
|
||||
String get introduction => translate('introduction');
|
||||
|
||||
String get hobby => translate('hobby');
|
||||
|
||||
String get search => translate('search');
|
||||
@ -946,6 +997,47 @@ class SCAppLocalizations {
|
||||
String get confirmDeclineTheInvitation =>
|
||||
translate('confirmDeclineTheInvitation');
|
||||
|
||||
String cpInviteRejectedByOther(String relation) =>
|
||||
translate('cpInviteRejectedByOther').replaceAll('{1}', relation);
|
||||
|
||||
String get closeFriendInvitationTitle =>
|
||||
translate('closeFriendInvitationTitle');
|
||||
|
||||
String get closeFriendInvitationSubtitle =>
|
||||
translate('closeFriendInvitationSubtitle');
|
||||
|
||||
String get closeFriendInvitationExpired =>
|
||||
translate('closeFriendInvitationExpired');
|
||||
|
||||
String get closeFriendRequestDeclined =>
|
||||
translate('closeFriendRequestDeclined');
|
||||
|
||||
String get closeFriendsHaveBecome => translate('closeFriendsHaveBecome');
|
||||
|
||||
String get otherAcceptedCloseFriendInvite =>
|
||||
translate('otherAcceptedCloseFriendInvite');
|
||||
|
||||
String get otherDeclinedCloseFriendInvite =>
|
||||
translate('otherDeclinedCloseFriendInvite');
|
||||
|
||||
String get youAcceptedCloseFriendInvite =>
|
||||
translate('youAcceptedCloseFriendInvite');
|
||||
|
||||
String get youDeclinedCloseFriendInvite =>
|
||||
translate('youDeclinedCloseFriendInvite');
|
||||
|
||||
String get closeFriendInviteTimedOut =>
|
||||
translate('closeFriendInviteTimedOut');
|
||||
|
||||
String cpDismissConfirmMessage(String coins) =>
|
||||
translate('cpDismissConfirmMessage').replaceAll('{1}', coins);
|
||||
|
||||
String get cpRelationCp => translate('cpRelationCp');
|
||||
|
||||
String get cpRelationBrother => translate('cpRelationBrother');
|
||||
|
||||
String get cpRelationSister => translate('cpRelationSister');
|
||||
|
||||
String get propMessagePrompt => translate('propMessagePrompt');
|
||||
|
||||
String get chats => translate('chats');
|
||||
@ -996,6 +1088,10 @@ class SCAppLocalizations {
|
||||
String get spendCoinsToGainExperiencePoints =>
|
||||
translate('spendCoinsToGainExperiencePoints');
|
||||
|
||||
String get richLevelUpgradeAnswer => translate('richLevelUpgradeAnswer');
|
||||
|
||||
String get charmLevelUpgradeAnswer => translate('charmLevelUpgradeAnswer');
|
||||
|
||||
String get enterRoomName => translate('enterRoomName');
|
||||
|
||||
String get selectYourCountry => translate('selectYourCountry');
|
||||
@ -1025,6 +1121,8 @@ class SCAppLocalizations {
|
||||
|
||||
String get adminCenter => translate('adminCenter');
|
||||
|
||||
String get managerCenter => translate('managerCenter');
|
||||
|
||||
String get confirm => translate('confirm');
|
||||
|
||||
String get cancel => translate('cancel');
|
||||
@ -1497,6 +1595,9 @@ class SCAppLocalizations {
|
||||
|
||||
String get kickRoomTips => translate('kickRoomTips');
|
||||
|
||||
String get roomKickedEntryBlockedTips =>
|
||||
translate('roomKickedEntryBlockedTips');
|
||||
|
||||
String get joinRoomTips => translate('joinRoomTips');
|
||||
|
||||
String get youAreCurrentlyCPRelationshipPleaseDissolve =>
|
||||
@ -1788,7 +1889,7 @@ class _SCAppLocalizationsDelegate
|
||||
|
||||
@override
|
||||
bool isSupported(Locale locale) =>
|
||||
['en', 'zh', 'ar', 'bn', 'tr'].contains(locale.languageCode);
|
||||
SCAppLocalizations.isSupportedLanguageCode(locale.languageCode);
|
||||
|
||||
@override
|
||||
Future<SCAppLocalizations> load(Locale locale) async {
|
||||
|
||||
75
lib/debug/first_recharge_dialog_preview.dart
Normal file
@ -0,0 +1,75 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:yumi/ui_kit/widgets/first_recharge/first_recharge_dialog.dart';
|
||||
|
||||
void main() {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
|
||||
runApp(const FirstRechargeDialogPreviewApp());
|
||||
}
|
||||
|
||||
class FirstRechargeDialogPreviewApp extends StatelessWidget {
|
||||
const FirstRechargeDialogPreviewApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ScreenUtilInit(
|
||||
designSize: const Size(375, 812),
|
||||
minTextAdapt: true,
|
||||
splitScreenMode: true,
|
||||
builder:
|
||||
(_, child) => MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
builder: FlutterSmartDialog.init(),
|
||||
home: const _FirstRechargeDialogPreviewPage(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _FirstRechargeDialogPreviewPage extends StatefulWidget {
|
||||
const _FirstRechargeDialogPreviewPage();
|
||||
|
||||
@override
|
||||
State<_FirstRechargeDialogPreviewPage> createState() =>
|
||||
_FirstRechargeDialogPreviewPageState();
|
||||
}
|
||||
|
||||
class _FirstRechargeDialogPreviewPageState
|
||||
extends State<_FirstRechargeDialogPreviewPage> {
|
||||
bool _shown = false;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
if (_shown) {
|
||||
return;
|
||||
}
|
||||
_shown = true;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
FirstRechargeDialog.show(
|
||||
context,
|
||||
data: FirstRechargeDialogData.mock(),
|
||||
onBuyNow: (_) {},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
body: Image.asset(
|
||||
'sc_images/first_recharge/preview_background.png',
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
83
lib/debug/gift_mp4_demo_main.dart
Normal file
@ -0,0 +1,83 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/effect/gift_mp4_effect_view.dart';
|
||||
|
||||
const _demoVideoPath = 'assets/debug/manager-packed-alpha.mp4';
|
||||
const _demoBackgroundColor = Color(0xFF7C3AED);
|
||||
|
||||
void main() {
|
||||
runApp(const GiftMp4DemoApp());
|
||||
}
|
||||
|
||||
class GiftMp4DemoApp extends StatelessWidget {
|
||||
const GiftMp4DemoApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: ThemeData.dark(useMaterial3: true),
|
||||
home: const GiftMp4DemoPage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class GiftMp4DemoPage extends StatefulWidget {
|
||||
const GiftMp4DemoPage({super.key});
|
||||
|
||||
@override
|
||||
State<GiftMp4DemoPage> createState() => _GiftMp4DemoPageState();
|
||||
}
|
||||
|
||||
class _GiftMp4DemoPageState extends State<GiftMp4DemoPage> {
|
||||
late final SCGiftMp4Controller _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = SCGiftMp4Controller();
|
||||
_controller.setPlaybackListener(
|
||||
onComplete: () => unawaited(_replay()),
|
||||
onFailed: (error) => debugPrint('[GiftMp4Demo] failed: $error'),
|
||||
);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _replay());
|
||||
}
|
||||
|
||||
Future<void> _replay() async {
|
||||
await _controller.playAsset(_demoVideoPath);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: _demoBackgroundColor,
|
||||
body: Stack(
|
||||
children: [
|
||||
Positioned.fill(child: GiftMp4EffectView(controller: _controller)),
|
||||
Positioned(
|
||||
left: 16,
|
||||
right: 16,
|
||||
bottom: 32,
|
||||
child: Row(
|
||||
children: [
|
||||
FilledButton(onPressed: _replay, child: const Text('Replay')),
|
||||
const SizedBox(width: 12),
|
||||
FilledButton.tonal(
|
||||
onPressed: _controller.stop,
|
||||
child: const Text('Stop'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
173
lib/debug/gift_page_preview.dart
Normal file
@ -0,0 +1,173 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:yumi/app/config/app_config.dart';
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
import 'package:yumi/main.dart';
|
||||
import 'package:yumi/modules/gift/gift_page.dart';
|
||||
import 'package:yumi/services/audio/rtc_manager.dart';
|
||||
import 'package:yumi/services/audio/rtm_manager.dart';
|
||||
import 'package:yumi/services/auth/user_profile_manager.dart';
|
||||
import 'package:yumi/services/general/sc_app_general_manager.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/gift_res.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
AppConfig.initialize();
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await DataPersistence.initialize();
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
|
||||
runApp(const GiftPagePreviewApp());
|
||||
}
|
||||
|
||||
class GiftPagePreviewApp extends StatelessWidget {
|
||||
const GiftPagePreviewApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider<SCAppGeneralManager>(
|
||||
create: (_) => _PreviewAppGeneralManager(),
|
||||
),
|
||||
ChangeNotifierProvider<RtcProvider>(create: (_) => RtcProvider()),
|
||||
ChangeNotifierProvider<RtmProvider>(create: (_) => RtmProvider()),
|
||||
ChangeNotifierProvider<SocialChatUserProfileManager>(
|
||||
create: (_) => _PreviewUserProfileManager(),
|
||||
),
|
||||
],
|
||||
child: ScreenUtilInit(
|
||||
designSize: const Size(375, 812),
|
||||
minTextAdapt: true,
|
||||
splitScreenMode: true,
|
||||
builder:
|
||||
(_, child) => MaterialApp(
|
||||
navigatorKey: navigatorKey,
|
||||
debugShowCheckedModeBanner: false,
|
||||
locale: const Locale('en'),
|
||||
supportedLocales: SCAppLocalizations.supportedLocales,
|
||||
localizationsDelegates: const [
|
||||
SCAppLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
builder: FlutterSmartDialog.init(),
|
||||
home: const _GiftPagePreviewHome(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _GiftPagePreviewHome extends StatefulWidget {
|
||||
const _GiftPagePreviewHome();
|
||||
|
||||
@override
|
||||
State<_GiftPagePreviewHome> createState() => _GiftPagePreviewHomeState();
|
||||
}
|
||||
|
||||
class _GiftPagePreviewHomeState extends State<_GiftPagePreviewHome> {
|
||||
bool _shown = false;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
if (_shown) {
|
||||
return;
|
||||
}
|
||||
_shown = true;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
SmartDialog.show(
|
||||
tag: "showGiftControl",
|
||||
alignment: Alignment.bottomCenter,
|
||||
maskColor: Colors.transparent,
|
||||
animationType: SmartAnimationType.fade,
|
||||
clickMaskDismiss: true,
|
||||
builder: (_) => const GiftPage(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
body: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Image.asset(
|
||||
'sc_images/room/sc_icon_room_defaut_bg.png',
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder:
|
||||
(_, __, ___) => const ColoredBox(color: Color(0xFF062320)),
|
||||
),
|
||||
const Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: 88),
|
||||
child: Text(
|
||||
'Gift panel preview',
|
||||
style: TextStyle(color: Colors.white70, fontSize: 16),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PreviewAppGeneralManager extends SCAppGeneralManager {
|
||||
_PreviewAppGeneralManager() {
|
||||
final gifts = List<SocialChatGiftRes>.generate(
|
||||
8,
|
||||
(index) => SocialChatGiftRes(
|
||||
id: 'preview_$index',
|
||||
giftName: 'Gift ${index + 1}',
|
||||
giftCandy: (index + 1) * 10,
|
||||
giftTab: 'ALL',
|
||||
giftPhoto: '',
|
||||
),
|
||||
);
|
||||
giftResList = gifts;
|
||||
giftByTab = {'ALL': gifts};
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> fetchCountryList() async {}
|
||||
|
||||
@override
|
||||
Future<void> giftList({
|
||||
bool includeCustomized = true,
|
||||
bool forceRefresh = false,
|
||||
}) async {}
|
||||
|
||||
@override
|
||||
void giftActivityList() {}
|
||||
|
||||
@override
|
||||
Future<void> giftBackpack({bool forceRefresh = false}) async {}
|
||||
|
||||
@override
|
||||
Future<void> warmGiftPageCovers(
|
||||
String type, {
|
||||
required int pageIndex,
|
||||
int pageCount = 1,
|
||||
int pageSize = 8,
|
||||
}) async {}
|
||||
|
||||
@override
|
||||
bool isGiftTabLoading(String type) => false;
|
||||
}
|
||||
|
||||
class _PreviewUserProfileManager extends SocialChatUserProfileManager {
|
||||
@override
|
||||
void balance() {}
|
||||
}
|
||||
164
lib/debug/room_animation_switch_dialog_preview.dart
Normal file
@ -0,0 +1,164 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/room_animation_switch_dialog.dart';
|
||||
|
||||
void main() {
|
||||
SCGlobalConfig.isGiftSpecialEffects = true;
|
||||
SCGlobalConfig.isLuckGiftSpecialEffects = true;
|
||||
SCGlobalConfig.isEntryVehicleAnimation = false;
|
||||
runApp(const RoomAnimationSwitchDialogPreviewApp());
|
||||
}
|
||||
|
||||
class RoomAnimationSwitchDialogPreviewApp extends StatelessWidget {
|
||||
const RoomAnimationSwitchDialogPreviewApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ScreenUtilInit(
|
||||
designSize: const Size(375, 812),
|
||||
minTextAdapt: true,
|
||||
splitScreenMode: true,
|
||||
builder:
|
||||
(_, child) => MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
locale: const Locale('en'),
|
||||
supportedLocales: SCAppLocalizations.supportedLocales,
|
||||
localizationsDelegates: const [
|
||||
SCAppLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
builder: FlutterSmartDialog.init(),
|
||||
home: const _RoomAnimationSwitchDialogPreviewPage(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RoomAnimationSwitchDialogPreviewPage extends StatelessWidget {
|
||||
const _RoomAnimationSwitchDialogPreviewPage();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
body: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: const [
|
||||
_PreviewRoomBackground(),
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: RoomAnimationSwitchDialog(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PreviewRoomBackground extends StatelessWidget {
|
||||
const _PreviewRoomBackground();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DecoratedBox(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFF172F35), Color(0xFF071B17), Color(0xFF1B1308)],
|
||||
),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
left: 24.w,
|
||||
top: 70.w,
|
||||
child: _PreviewLine(width: 128.w, opacity: 0.55),
|
||||
),
|
||||
for (var index = 0; index < 10; index++)
|
||||
Positioned(
|
||||
left: (38 + (index % 5) * 66).w,
|
||||
top: (145 + (index ~/ 5) * 80).w,
|
||||
child: const _PreviewSeat(),
|
||||
),
|
||||
Positioned(
|
||||
left: 26.w,
|
||||
right: 26.w,
|
||||
top: 355.w,
|
||||
child: _PreviewPanel(height: 150.w),
|
||||
),
|
||||
Positioned(
|
||||
left: 24.w,
|
||||
right: 24.w,
|
||||
bottom: 36.w,
|
||||
child: _PreviewPanel(height: 52.w),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PreviewSeat extends StatelessWidget {
|
||||
const _PreviewSeat();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: const Color(0xFFFFCE7C).withValues(alpha: 0.36),
|
||||
width: 2.w,
|
||||
),
|
||||
color: Colors.black.withValues(alpha: 0.18),
|
||||
),
|
||||
child: SizedBox(width: 44.w, height: 44.w),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PreviewLine extends StatelessWidget {
|
||||
const _PreviewLine({required this.width, required this.opacity});
|
||||
|
||||
final double width;
|
||||
final double opacity;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: width,
|
||||
height: 14.w,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: opacity),
|
||||
borderRadius: BorderRadius.circular(7.w),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PreviewPanel extends StatelessWidget {
|
||||
const _PreviewPanel({required this.height});
|
||||
|
||||
final double height;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: height,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.24),
|
||||
borderRadius: BorderRadius.circular(16.w),
|
||||
border: Border.all(color: Colors.white.withValues(alpha: 0.08)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
72
lib/debug/room_cp_progress_dialog_preview.dart
Normal file
@ -0,0 +1,72 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/cp/room_cp_progress_dialog.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const RoomCpProgressDialogPreviewApp());
|
||||
}
|
||||
|
||||
class RoomCpProgressDialogPreviewApp extends StatelessWidget {
|
||||
const RoomCpProgressDialogPreviewApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ScreenUtilInit(
|
||||
designSize: const Size(375, 812),
|
||||
minTextAdapt: true,
|
||||
splitScreenMode: true,
|
||||
builder:
|
||||
(_, child) => MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
builder: FlutterSmartDialog.init(),
|
||||
home: const _RoomCpProgressDialogPreviewPage(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RoomCpProgressDialogPreviewPage extends StatelessWidget {
|
||||
const _RoomCpProgressDialogPreviewPage();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xff1B1224),
|
||||
body: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
const DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xff281B35), Color(0xff120B19)],
|
||||
),
|
||||
),
|
||||
),
|
||||
const RoomCpProgressDialog(
|
||||
daysValue: '999',
|
||||
daysLabel: 'Days',
|
||||
levelText: 'Lv.0',
|
||||
currentValue: 1,
|
||||
targetValue: 1,
|
||||
expAwayText: '1,000',
|
||||
nextLevelText: 'Lv. 2',
|
||||
cancelText: 'Cancel',
|
||||
leftUser: RoomCpProgressDialogUser(
|
||||
nickname: 'NameName',
|
||||
userId: '123456',
|
||||
levelText: 'Lv.1',
|
||||
),
|
||||
rightUser: RoomCpProgressDialogUser(
|
||||
nickname: 'NameName',
|
||||
userId: '123456',
|
||||
levelText: 'Lv.1',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
113
lib/debug/room_cp_relation_formed_effect_preview.dart
Normal file
@ -0,0 +1,113 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:yumi/app/constants/sc_screen.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/cp/room_cp_relation_formed_effect.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const RoomCpRelationFormedEffectPreviewApp());
|
||||
}
|
||||
|
||||
class RoomCpRelationFormedEffectPreviewApp extends StatelessWidget {
|
||||
const RoomCpRelationFormedEffectPreviewApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ScreenUtilInit(
|
||||
designSize: Size(SCScreen.designWidth, SCScreen.designHeight),
|
||||
splitScreenMode: false,
|
||||
minTextAdapt: true,
|
||||
builder: (context, child) {
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
builder: FlutterSmartDialog.init(),
|
||||
home: const RoomCpRelationFormedEffectPreviewPage(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RoomCpRelationFormedEffectPreviewPage extends StatefulWidget {
|
||||
const RoomCpRelationFormedEffectPreviewPage({super.key});
|
||||
|
||||
@override
|
||||
State<RoomCpRelationFormedEffectPreviewPage> createState() =>
|
||||
_RoomCpRelationFormedEffectPreviewPageState();
|
||||
}
|
||||
|
||||
class _RoomCpRelationFormedEffectPreviewPageState
|
||||
extends State<RoomCpRelationFormedEffectPreviewPage> {
|
||||
Timer? _loopTimer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _showEffect());
|
||||
_loopTimer = Timer.periodic(
|
||||
const Duration(seconds: 7),
|
||||
(_) => _showEffect(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_loopTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _showEffect() {
|
||||
RoomCpRelationFormedEffect.show(
|
||||
leftName: "Avery",
|
||||
rightName: "Jordan",
|
||||
leftAvatar: "https://i.pravatar.cc/180?img=32",
|
||||
rightAvatar: "https://i.pravatar.cc/180?img=47",
|
||||
relationType: "CP",
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFF170D24),
|
||||
body: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: DecoratedBox(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFF2B163A), Color(0xFF07040D)],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 16,
|
||||
right: 16,
|
||||
bottom: 34,
|
||||
child: Row(
|
||||
children: [
|
||||
FilledButton(
|
||||
onPressed: _showEffect,
|
||||
child: const Text("Replay"),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
FilledButton.tonal(
|
||||
onPressed:
|
||||
() => SmartDialog.dismiss(
|
||||
tag: RoomCpRelationFormedEffect.dialogTag,
|
||||
),
|
||||
child: const Text("Dismiss"),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
103
lib/debug/room_recharge_bottom_sheet_preview.dart
Normal file
@ -0,0 +1,103 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
import 'package:yumi/main.dart';
|
||||
import 'package:yumi/services/auth/user_profile_manager.dart';
|
||||
import 'package:yumi/services/payment/google_payment_manager.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/room_recharge_bottom_sheet.dart';
|
||||
|
||||
void main() {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
|
||||
runApp(const RoomRechargeBottomSheetPreviewApp());
|
||||
}
|
||||
|
||||
class RoomRechargeBottomSheetPreviewApp extends StatelessWidget {
|
||||
const RoomRechargeBottomSheetPreviewApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider<SocialChatUserProfileManager>(
|
||||
create: (_) => _PreviewUserProfileManager(),
|
||||
),
|
||||
ChangeNotifierProvider<AndroidPaymentProcessor>(
|
||||
create: (_) => AndroidPaymentProcessor(),
|
||||
),
|
||||
],
|
||||
child: ScreenUtilInit(
|
||||
designSize: const Size(375, 812),
|
||||
minTextAdapt: true,
|
||||
splitScreenMode: true,
|
||||
builder:
|
||||
(_, child) => MaterialApp(
|
||||
navigatorKey: navigatorKey,
|
||||
debugShowCheckedModeBanner: false,
|
||||
locale: const Locale('en'),
|
||||
supportedLocales: SCAppLocalizations.supportedLocales,
|
||||
localizationsDelegates: const [
|
||||
SCAppLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
builder: FlutterSmartDialog.init(),
|
||||
home: const _RoomRechargeBottomSheetPreviewPage(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RoomRechargeBottomSheetPreviewPage extends StatefulWidget {
|
||||
const _RoomRechargeBottomSheetPreviewPage();
|
||||
|
||||
@override
|
||||
State<_RoomRechargeBottomSheetPreviewPage> createState() =>
|
||||
_RoomRechargeBottomSheetPreviewPageState();
|
||||
}
|
||||
|
||||
class _RoomRechargeBottomSheetPreviewPageState
|
||||
extends State<_RoomRechargeBottomSheetPreviewPage> {
|
||||
bool _shown = false;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
if (_shown) {
|
||||
return;
|
||||
}
|
||||
_shown = true;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
RoomRechargeBottomSheet.show(context);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
body: Image.asset(
|
||||
'sc_images/room/sc_icon_room_defaut_bg.png',
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PreviewUserProfileManager extends SocialChatUserProfileManager {
|
||||
@override
|
||||
void balance() {
|
||||
updateBalance(0);
|
||||
}
|
||||
}
|
||||
160
lib/debug/room_rocket_dialog_preview.dart
Normal file
@ -0,0 +1,160 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_note_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_record_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_reward_dialog.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const RoomRocketDialogPreviewApp());
|
||||
}
|
||||
|
||||
class RoomRocketDialogPreviewApp extends StatelessWidget {
|
||||
const RoomRocketDialogPreviewApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ScreenUtilInit(
|
||||
designSize: const Size(375, 812),
|
||||
minTextAdapt: true,
|
||||
splitScreenMode: true,
|
||||
builder:
|
||||
(_, child) => MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
builder: FlutterSmartDialog.init(),
|
||||
home: const _RoomRocketDialogPreviewPage(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RoomRocketDialogPreviewPage extends StatefulWidget {
|
||||
const _RoomRocketDialogPreviewPage();
|
||||
|
||||
@override
|
||||
State<_RoomRocketDialogPreviewPage> createState() =>
|
||||
_RoomRocketDialogPreviewPageState();
|
||||
}
|
||||
|
||||
class _RoomRocketDialogPreviewPageState
|
||||
extends State<_RoomRocketDialogPreviewPage> {
|
||||
RoomRocketRewardItem? _selectedReward;
|
||||
_PreviewDialog _dialog = _PreviewDialog.rocket;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
body: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
const _RoomBackground(),
|
||||
if (_dialog == _PreviewDialog.rocket)
|
||||
RoomRocketDialog(
|
||||
onRewardSelected:
|
||||
(reward) => setState(() {
|
||||
_selectedReward = reward;
|
||||
_dialog = _PreviewDialog.reward;
|
||||
}),
|
||||
onOpenRecord:
|
||||
() => setState(() => _dialog = _PreviewDialog.record),
|
||||
onOpenNote: () => setState(() => _dialog = _PreviewDialog.note),
|
||||
)
|
||||
else if (_dialog == _PreviewDialog.reward)
|
||||
RoomRocketRewardDialog(
|
||||
selectedReward: _selectedReward,
|
||||
onClose: _backToRocket,
|
||||
)
|
||||
else if (_dialog == _PreviewDialog.record)
|
||||
RoomRocketRecordDialog(onClose: _backToRocket)
|
||||
else
|
||||
RoomRocketNoteDialog(onClose: _backToRocket),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _backToRocket() {
|
||||
setState(() {
|
||||
_selectedReward = null;
|
||||
_dialog = _PreviewDialog.rocket;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
enum _PreviewDialog { rocket, reward, record, note }
|
||||
|
||||
class _RoomBackground extends StatelessWidget {
|
||||
const _RoomBackground();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DecoratedBox(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFF2A120E), Color(0xFF071917), Color(0xFF22140B)],
|
||||
),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
left: 20.w,
|
||||
top: 48.w,
|
||||
child: _PreviewLine(width: 104.w, opacity: 0.42),
|
||||
),
|
||||
for (var index = 0; index < 8; index++)
|
||||
Positioned(
|
||||
left: (40 + (index % 4) * 72).w,
|
||||
top: (146 + (index ~/ 4) * 84).w,
|
||||
child: const _PreviewSeat(),
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 118.w,
|
||||
child: _PreviewLine(width: 330.w, opacity: 0.28),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PreviewSeat extends StatelessWidget {
|
||||
const _PreviewSeat();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: const Color(0xFFFFCE7C).withValues(alpha: 0.32),
|
||||
width: 2.w,
|
||||
),
|
||||
),
|
||||
child: SizedBox(width: 44.w, height: 44.w),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PreviewLine extends StatelessWidget {
|
||||
const _PreviewLine({required this.width, required this.opacity});
|
||||
|
||||
final double width;
|
||||
final double opacity;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Container(
|
||||
width: width,
|
||||
height: 1.w,
|
||||
color: Colors.white.withValues(alpha: opacity),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
71
lib/debug/room_rocket_empty_dialog_preview.dart
Normal file
@ -0,0 +1,71 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/sc_room_rocket_status_res.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_dialog.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const RoomRocketEmptyDialogPreviewApp());
|
||||
}
|
||||
|
||||
class RoomRocketEmptyDialogPreviewApp extends StatelessWidget {
|
||||
const RoomRocketEmptyDialogPreviewApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ScreenUtilInit(
|
||||
designSize: const Size(375, 812),
|
||||
minTextAdapt: true,
|
||||
splitScreenMode: true,
|
||||
builder:
|
||||
(_, child) => const MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: _RoomRocketEmptyDialogPreviewPage(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RoomRocketEmptyDialogPreviewPage extends StatelessWidget {
|
||||
const _RoomRocketEmptyDialogPreviewPage();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
body: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
const _RoomBackground(),
|
||||
RoomRocketDialog(
|
||||
status: SCRoomRocketStatusRes(
|
||||
level: 1,
|
||||
energyPercent: 0,
|
||||
totalContributors: 0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RoomBackground extends StatelessWidget {
|
||||
const _RoomBackground();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DecoratedBox(
|
||||
decoration: const BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage(
|
||||
'sc_images/room/background_examples/bg_example_1.png',
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(color: Colors.black.withValues(alpha: 0.24)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
66
lib/debug/room_rocket_floating_preview.dart
Normal file
@ -0,0 +1,66 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:yumi/shared/data_sources/models/message/sc_floating_message.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/floating/floating_room_rocket_screen_widget.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const RoomRocketFloatingPreviewApp());
|
||||
}
|
||||
|
||||
class RoomRocketFloatingPreviewApp extends StatelessWidget {
|
||||
const RoomRocketFloatingPreviewApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ScreenUtilInit(
|
||||
designSize: const Size(375, 812),
|
||||
minTextAdapt: true,
|
||||
builder: (_, child) {
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: const _RoomRocketFloatingPreviewPage(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RoomRocketFloatingPreviewPage extends StatelessWidget {
|
||||
const _RoomRocketFloatingPreviewPage();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
body: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: DecoratedBox(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFF20100B), Color(0xFF5A2A13)],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Transform.translate(
|
||||
offset: Offset(0, 70.w),
|
||||
child: FloatingRoomRocketScreenWidget(
|
||||
message: SCFloatingMessage(
|
||||
type: 3,
|
||||
roomId: '123456',
|
||||
userName: 'Namenamna...',
|
||||
userAvatarUrl:
|
||||
'https://images.unsplash.com/photo-1494790108377-be9c29b29330',
|
||||
durationSeconds: 20,
|
||||
),
|
||||
onAnimationCompleted: () {},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
65
lib/debug/room_rocket_note_dialog_preview.dart
Normal file
@ -0,0 +1,65 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_note_dialog.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const RoomRocketNoteDialogPreviewApp());
|
||||
}
|
||||
|
||||
class RoomRocketNoteDialogPreviewApp extends StatelessWidget {
|
||||
const RoomRocketNoteDialogPreviewApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
locale: Locale('en'),
|
||||
supportedLocales: [
|
||||
Locale('en'),
|
||||
Locale('ar'),
|
||||
Locale('bn'),
|
||||
Locale('tr'),
|
||||
],
|
||||
localizationsDelegates: [
|
||||
SCAppLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
],
|
||||
home: _RoomRocketNoteDialogPreviewPage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RoomRocketNoteDialogPreviewPage extends StatelessWidget {
|
||||
const _RoomRocketNoteDialogPreviewPage();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
body: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [_RoomBackground(), RoomRocketNoteDialog()],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RoomBackground extends StatelessWidget {
|
||||
const _RoomBackground();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFF2A120E), Color(0xFF071917), Color(0xFF22140B)],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
18
lib/debug/room_rocket_rank_preview.dart
Normal file
@ -0,0 +1,18 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:yumi/modules/room/rocket/room_rocket_rank_page.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const RoomRocketRankPreviewApp());
|
||||
}
|
||||
|
||||
class RoomRocketRankPreviewApp extends StatelessWidget {
|
||||
const RoomRocketRankPreviewApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: RoomRocketRankPage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
50
lib/debug/room_rocket_record_dialog_preview.dart
Normal file
@ -0,0 +1,50 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_record_dialog.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const RoomRocketRecordDialogPreviewApp());
|
||||
}
|
||||
|
||||
class RoomRocketRecordDialogPreviewApp extends StatelessWidget {
|
||||
const RoomRocketRecordDialogPreviewApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: _RoomRocketRecordDialogPreviewPage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RoomRocketRecordDialogPreviewPage extends StatelessWidget {
|
||||
const _RoomRocketRecordDialogPreviewPage();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
body: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [_RoomBackground(), RoomRocketRecordDialog()],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RoomBackground extends StatelessWidget {
|
||||
const _RoomBackground();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFF2A120E), Color(0xFF071917), Color(0xFF22140B)],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
59
lib/debug/room_rocket_reward_dialog_preview.dart
Normal file
@ -0,0 +1,59 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_reward_dialog.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const RoomRocketRewardDialogPreviewApp());
|
||||
}
|
||||
|
||||
class RoomRocketRewardDialogPreviewApp extends StatelessWidget {
|
||||
const RoomRocketRewardDialogPreviewApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: _RoomRocketRewardDialogPreviewPage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RoomRocketRewardDialogPreviewPage extends StatelessWidget {
|
||||
const _RoomRocketRewardDialogPreviewPage();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
body: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
_RoomBackground(),
|
||||
RoomRocketRewardDialog(
|
||||
selectedReward: RoomRocketRewardItem(
|
||||
asset: 'sc_images/room/rocket/reward_thumb.png',
|
||||
amount: '10000',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RoomBackground extends StatelessWidget {
|
||||
const _RoomBackground();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFF2A120E), Color(0xFF071917), Color(0xFF22140B)],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -44,9 +44,6 @@ import 'services/shop/shop_manager.dart';
|
||||
import 'services/theme/theme_manager.dart';
|
||||
import 'services/auth/user_profile_manager.dart';
|
||||
import 'ui_kit/theme/socialchat_theme.dart';
|
||||
import 'ui_kit/widgets/room/anim/room_gift_seat_flight_overlay.dart';
|
||||
import 'ui_kit/widgets/room/effect/vapp_svga_layer_widget.dart';
|
||||
import 'ui_kit/widgets/svga/sc_svga_asset_widget.dart';
|
||||
|
||||
bool _isCrashlyticsReady = false;
|
||||
|
||||
@ -109,7 +106,6 @@ void _configureImageCache() {
|
||||
|
||||
void _releaseVisualMemoryCaches({bool clearLiveImages = false}) {
|
||||
SCGiftVapSvgaManager().clearMemoryCache();
|
||||
SCSvgaAssetWidget.clearMemoryCache();
|
||||
final imageCache = PaintingBinding.instance.imageCache;
|
||||
imageCache.clear();
|
||||
if (clearLiveImages) {
|
||||
@ -280,6 +276,8 @@ class RootAppWithProviders extends StatelessWidget {
|
||||
|
||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
|
||||
final RouteObserver<ModalRoute<dynamic>> modalRouteObserver =
|
||||
RouteObserver<ModalRoute<dynamic>>();
|
||||
|
||||
class YumiApplication extends StatefulWidget {
|
||||
const YumiApplication({super.key});
|
||||
@ -419,13 +417,7 @@ class _YumiApplicationState extends State<YumiApplication>
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [
|
||||
Locale('en', ''),
|
||||
Locale('zh', ''),
|
||||
Locale('ar', ''),
|
||||
Locale('tr', ''),
|
||||
Locale('bn', ''),
|
||||
],
|
||||
supportedLocales: SCAppLocalizations.supportedLocales,
|
||||
navigatorKey: navigatorKey,
|
||||
debugShowCheckedModeBanner: false,
|
||||
onGenerateRoute: SCLkApplication.router.generator,
|
||||
@ -435,46 +427,11 @@ class _YumiApplicationState extends State<YumiApplication>
|
||||
builder: (context, child) {
|
||||
return Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
child ?? const SizedBox.shrink(),
|
||||
if (SCGlobalConfig.allowsHighCostAnimations)
|
||||
Consumer<RtcProvider>(
|
||||
builder: (context, rtcProvider, _) {
|
||||
if (!rtcProvider
|
||||
.shouldShowRoomVisualEffects) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return const Positioned.fill(
|
||||
child: VapPlusSvgaPlayer(
|
||||
tag: "room_gift",
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Consumer<RtcProvider>(
|
||||
builder: (context, rtcProvider, _) {
|
||||
if (!rtcProvider
|
||||
.shouldShowRoomVisualEffects) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Positioned.fill(
|
||||
child: RoomGiftSeatFlightOverlay(
|
||||
controller:
|
||||
RoomGiftSeatFlightController(),
|
||||
resolveTargetKey:
|
||||
(userId) => Provider.of<RtcProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).getSeatGlobalKeyByIndex(userId),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
children: [child ?? const SizedBox.shrink()],
|
||||
);
|
||||
},
|
||||
),
|
||||
navigatorObservers: [routeObserver],
|
||||
navigatorObservers: [routeObserver, modalRouteObserver],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@ -16,7 +16,6 @@ import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||
import 'package:yumi/shared/tools/sc_loading_manager.dart';
|
||||
import 'package:yumi/shared/tools/sc_version_utils.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:yumi/services/audio/rtc_manager.dart';
|
||||
import 'package:yumi/services/audio/rtm_manager.dart';
|
||||
@ -376,14 +375,16 @@ class SCLoginWithAccountPageState extends State<SCLoginWithAccountPage>
|
||||
}
|
||||
SCLoadingManager.show(context: context);
|
||||
try {
|
||||
final results = await Future.wait([
|
||||
SCAccountRepository().loginForAccount(account, pass),
|
||||
SCVersionUtils.checkReview(),
|
||||
]);
|
||||
var user = (results[0] as SocialChatLoginRes);
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
var user = await SCAccountRepository().loginForAccount(account, pass);
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
AccountStorage().setCurrentUser(user);
|
||||
final canContinue = await SCVersionUtils.checkReview(context: context);
|
||||
if (!mounted || !canContinue) {
|
||||
SCLoadingManager.hide();
|
||||
return;
|
||||
}
|
||||
await Provider.of<RtcProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
@ -393,7 +394,6 @@ class SCLoginWithAccountPageState extends State<SCLoginWithAccountPage>
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
AccountStorage().setCurrentUser(user);
|
||||
SCLoadingManager.hide();
|
||||
try {
|
||||
await Future.wait([
|
||||
|
||||
@ -4,37 +4,38 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_debounce_widget.dart';
|
||||
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
||||
import 'package:yumi/services/audio/rtm_manager.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart';
|
||||
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||
import 'package:yumi/services/audio/rtm_manager.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart';
|
||||
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||
import 'package:yumi/app/config/business_logic_strategy.dart';
|
||||
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
||||
import 'package:yumi/ui_kit/theme/socialchat_theme.dart';
|
||||
import 'package:yumi/ui_kit/widgets/msg/message_conversation_list_page.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
||||
import 'package:yumi/ui_kit/theme/socialchat_theme.dart';
|
||||
import 'package:yumi/ui_kit/widgets/msg/message_conversation_list_page.dart';
|
||||
|
||||
import '../../index/main_route.dart';
|
||||
import '../chat_route.dart';
|
||||
|
||||
///消息
|
||||
class SCMessagePage extends StatefulWidget {
|
||||
bool isFromRoom = false;
|
||||
final bool isFromRoom;
|
||||
|
||||
SCMessagePage({this.isFromRoom = false});
|
||||
const SCMessagePage({super.key, this.isFromRoom = false});
|
||||
|
||||
@override
|
||||
_MessagePageState createState() => _MessagePageState(isFromRoom);
|
||||
State<SCMessagePage> createState() => _MessagePageState();
|
||||
}
|
||||
|
||||
class _MessagePageState extends State<SCMessagePage> {
|
||||
BusinessLogicStrategy get _strategy => SCGlobalConfig.businessLogicStrategy;
|
||||
bool isFromRoom = false;
|
||||
bool showSystemAnnouncementTips = true;
|
||||
|
||||
_MessagePageState(this.isFromRoom);
|
||||
final TextEditingController _messageSearchController =
|
||||
TextEditingController();
|
||||
final FocusNode _messageSearchFocusNode = FocusNode();
|
||||
bool _isSearchingMessages = false;
|
||||
String _messageSearchText = "";
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -84,6 +85,8 @@ class _MessagePageState extends State<SCMessagePage> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_messageSearchController.dispose();
|
||||
_messageSearchFocusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@ -105,46 +108,59 @@ class _MessagePageState extends State<SCMessagePage> {
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
leading: null,
|
||||
title: TabBar(
|
||||
tabs:
|
||||
[SCAppLocalizations.of(context)!.message]
|
||||
.map(
|
||||
(e) => Padding(
|
||||
padding: EdgeInsets.only(bottom: 3.w, top: 4.w),
|
||||
child: Text(e),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
dividerColor: Colors.transparent,
|
||||
labelStyle: TextStyle(
|
||||
fontSize: 16.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
unselectedLabelStyle: TextStyle(
|
||||
fontSize: 15.sp,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
indicator: BoxDecoration(),
|
||||
//TabBarIndicator(),
|
||||
indicatorSize: TabBarIndicatorSize.label,
|
||||
isScrollable: true,
|
||||
tabAlignment: TabAlignment.start,
|
||||
labelColor: SocialChatTheme.primaryLight,
|
||||
),
|
||||
title:
|
||||
_isSearchingMessages
|
||||
? _messageSearchField()
|
||||
: TabBar(
|
||||
tabs:
|
||||
[SCAppLocalizations.of(context)!.message]
|
||||
.map(
|
||||
(e) => Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: 3.w,
|
||||
top: 4.w,
|
||||
),
|
||||
child: Text(e),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
dividerColor: Colors.transparent,
|
||||
labelStyle: TextStyle(
|
||||
fontSize: 16.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
unselectedLabelStyle: TextStyle(
|
||||
fontSize: 15.sp,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
indicator: BoxDecoration(),
|
||||
//TabBarIndicator(),
|
||||
indicatorSize: TabBarIndicatorSize.label,
|
||||
isScrollable: true,
|
||||
tabAlignment: TabAlignment.start,
|
||||
labelColor: SocialChatTheme.primaryLight,
|
||||
),
|
||||
actions: [
|
||||
SCDebounceWidget(
|
||||
child: Container(
|
||||
padding: EdgeInsetsDirectional.only(end: 10.w),
|
||||
child: Image.asset(
|
||||
"sc_images/index/sc_icon_serach.png",
|
||||
width: 20.w,
|
||||
height: 20.w,
|
||||
),
|
||||
padding: EdgeInsetsDirectional.only(start: 10.w, end: 10.w),
|
||||
child:
|
||||
_isSearchingMessages
|
||||
? Icon(Icons.close, size: 22.w, color: Colors.white)
|
||||
: Image.asset(
|
||||
"sc_images/index/sc_icon_serach.png",
|
||||
width: 20.w,
|
||||
height: 20.w,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
SCNavigatorUtils.push(context, SCMainRoute.mainSearch);
|
||||
if (_isSearchingMessages) {
|
||||
_stopMessageSearch();
|
||||
} else {
|
||||
_startMessageSearch();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
@ -165,7 +181,7 @@ class _MessagePageState extends State<SCMessagePage> {
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
_headMenu(provider),
|
||||
if (!_isSearchingMessages) _headMenu(provider),
|
||||
Expanded(
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(
|
||||
@ -173,7 +189,10 @@ class _MessagePageState extends State<SCMessagePage> {
|
||||
left: 8.w,
|
||||
right: 8.w,
|
||||
),
|
||||
child: MessageConversationListPage(false),
|
||||
child: MessageConversationListPage(
|
||||
false,
|
||||
searchQuery: _messageSearchText,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -189,9 +208,76 @@ class _MessagePageState extends State<SCMessagePage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _messageSearchField() {
|
||||
return Container(
|
||||
height: 36.w,
|
||||
alignment: AlignmentDirectional.center,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xff18F2B1).withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(20.w),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(width: 10.w),
|
||||
Image.asset(
|
||||
"sc_images/index/sc_icon_serach2.png",
|
||||
width: 18.w,
|
||||
height: 18.w,
|
||||
color: const Color(0xff18F2B1),
|
||||
),
|
||||
SizedBox(width: 6.w),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _messageSearchController,
|
||||
focusNode: _messageSearchFocusNode,
|
||||
autofocus: true,
|
||||
cursorColor: SocialChatTheme.primaryLight,
|
||||
style: TextStyle(color: Colors.white, fontSize: 14.sp),
|
||||
decoration: InputDecoration(
|
||||
hintText:
|
||||
"${SCAppLocalizations.of(context)!.search} ID/${SCAppLocalizations.of(context)!.userName}",
|
||||
hintStyle: TextStyle(color: Colors.white54, fontSize: 13.sp),
|
||||
border: InputBorder.none,
|
||||
isDense: true,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_messageSearchText = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _startMessageSearch() {
|
||||
setState(() {
|
||||
_isSearchingMessages = true;
|
||||
});
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
_messageSearchFocusNode.requestFocus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _stopMessageSearch() {
|
||||
_messageSearchController.clear();
|
||||
_messageSearchFocusNode.unfocus();
|
||||
setState(() {
|
||||
_isSearchingMessages = false;
|
||||
_messageSearchText = "";
|
||||
});
|
||||
}
|
||||
|
||||
Widget _headMenu(RtmProvider provider) {
|
||||
return Container(
|
||||
margin: EdgeInsetsDirectional.symmetric(horizontal: 25.w,).copyWith(top: 10.w),
|
||||
margin: EdgeInsetsDirectional.symmetric(
|
||||
horizontal: 25.w,
|
||||
).copyWith(top: 10.w),
|
||||
child: Column(
|
||||
spacing: 15.w,
|
||||
children: [
|
||||
@ -323,12 +409,12 @@ class _MessagePageState extends State<SCMessagePage> {
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () async {
|
||||
var conversation = provider.getPreferredSystemConversation();
|
||||
provider.updateSystemCount(0);
|
||||
var bool = await provider.startConversation(conversation);
|
||||
if (!bool) return;
|
||||
),
|
||||
onTap: () async {
|
||||
var conversation = provider.getPreferredSystemConversation();
|
||||
provider.updateSystemCount(0);
|
||||
var bool = await provider.startConversation(conversation);
|
||||
if (!bool) return;
|
||||
var json = jsonEncode(conversation.toJson());
|
||||
SCNavigatorUtils.push(
|
||||
context,
|
||||
@ -366,7 +452,7 @@ class _MessagePageState extends State<SCMessagePage> {
|
||||
textColor: Colors.white54,
|
||||
),
|
||||
],
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
928
lib/modules/gift/cp_rights/cp_rights_guide_page.dart
Normal file
@ -0,0 +1,928 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||
import 'package:yumi/services/audio/rtc_manager.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/sc_cp_rights_config_res.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/local/floating_screen_manager.dart';
|
||||
import 'package:yumi/shared/tools/sc_gift_vap_svga_manager.dart';
|
||||
import 'package:yumi/shared/tools/sc_room_effect_scheduler.dart';
|
||||
|
||||
enum _CpGuideTab { note, rights }
|
||||
|
||||
enum _CpRightsCategory { cp, brother, sisters }
|
||||
|
||||
class CpRightsGuidePage extends StatefulWidget {
|
||||
const CpRightsGuidePage({super.key, this.cpUserId});
|
||||
|
||||
final String? cpUserId;
|
||||
|
||||
@override
|
||||
State<CpRightsGuidePage> createState() => _CpRightsGuidePageState();
|
||||
}
|
||||
|
||||
class _CpRightsGuidePageState extends State<CpRightsGuidePage> {
|
||||
static const String _assetBase = "sc_images/room/cp_rights";
|
||||
static const Color _textColor = Color(0xff543356);
|
||||
static const Color _accentColor = Color(0xffff2a70);
|
||||
static const String _effectSuppressReason = "cp_rights_guide_page";
|
||||
static const String _roomRocketPagLaunchDialogTag =
|
||||
"showRoomRocketPagLaunchDialog";
|
||||
|
||||
_CpGuideTab _tab = _CpGuideTab.note;
|
||||
_CpRightsCategory _rightsCategory = _CpRightsCategory.cp;
|
||||
RtcProvider? _suppressedRtcProvider;
|
||||
bool _roomEffectsSuppressed = false;
|
||||
bool _restoreRoomVisualEffects = false;
|
||||
final Map<_CpRightsCategory, List<_CpRightsLevel>> _rightsRowsByCategory = {};
|
||||
final Set<_CpRightsCategory> _loadingRightsCategories = {};
|
||||
final Set<_CpRightsCategory> _loadedRightsCategories = {};
|
||||
final Set<_CpRightsCategory> _failedRightsCategories = {};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_suppressRoomEffects();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_releaseRoomEffects();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xffffe7eb),
|
||||
body: Stack(
|
||||
children: [
|
||||
Positioned.fill(child: _buildBackground()),
|
||||
SafeArea(
|
||||
top: true,
|
||||
bottom: false,
|
||||
child: Column(
|
||||
children: [
|
||||
_buildNavigationBar(),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
padding: EdgeInsets.only(bottom: 28.w),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(height: 15.w),
|
||||
_buildTopTabs(),
|
||||
SizedBox(height: 14.w),
|
||||
if (_tab == _CpGuideTab.note)
|
||||
_buildNoteContent()
|
||||
else
|
||||
_buildRightsContent(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBackground() {
|
||||
return DecoratedBox(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xffffd8e6), Color(0xffffe7eb)],
|
||||
),
|
||||
),
|
||||
child: Opacity(
|
||||
opacity: 0.6,
|
||||
child: Image.asset(
|
||||
"$_assetBase/sc_cp_rights_bg.png",
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNavigationBar() {
|
||||
return SizedBox(
|
||||
height: 44.w,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
PositionedDirectional(
|
||||
start: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => Navigator.of(context).maybePop(),
|
||||
child: SizedBox(
|
||||
width: 44.w,
|
||||
child: Center(
|
||||
child: Transform.flip(
|
||||
flipX: SCGlobalConfig.lang == "ar",
|
||||
child: Icon(
|
||||
Icons.arrow_back_ios_new,
|
||||
color: Colors.black,
|
||||
size: 18.w,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 54.w),
|
||||
child: Text(
|
||||
"Gifts For Close Friends",
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.center,
|
||||
textScaler: TextScaler.noScaling,
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 18.sp,
|
||||
height: 1,
|
||||
fontWeight: FontWeight.w500,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTopTabs() {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
_buildMainTab(label: "Note", tab: _CpGuideTab.note),
|
||||
SizedBox(width: 44.w),
|
||||
_buildMainTab(label: "Rights", tab: _CpGuideTab.rights),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMainTab({required String label, required _CpGuideTab tab}) {
|
||||
final selected = _tab == tab;
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
if (_tab == tab) {
|
||||
return;
|
||||
}
|
||||
setState(() => _tab = tab);
|
||||
if (tab == _CpGuideTab.rights) {
|
||||
_ensureRightsConfigLoaded(_rightsCategory);
|
||||
}
|
||||
},
|
||||
child: SizedBox(
|
||||
width: 116.w,
|
||||
height: 36.w,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Image.asset(
|
||||
selected
|
||||
? "$_assetBase/sc_cp_rights_tab_selected.png"
|
||||
: "$_assetBase/sc_cp_rights_tab_unselected.png",
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
label,
|
||||
textScaler: TextScaler.noScaling,
|
||||
style: TextStyle(
|
||||
color: selected ? Colors.white : _textColor,
|
||||
fontSize: 14.sp,
|
||||
height: 1,
|
||||
fontWeight: FontWeight.w400,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNoteContent() {
|
||||
return Column(
|
||||
children: [
|
||||
_CpOrnatePanel(
|
||||
assetBase: _assetBase,
|
||||
title: "What is a close friendship?",
|
||||
height: 223.w,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(20.w, 67.w, 20.w, 18.w),
|
||||
child: Text(
|
||||
"The \"Best Friends\" feature is a brand-new interactive way to celebrate close relationships between users. By sending gifts to someone and requesting to become best friends, you can form a best friend connection once they accept.\n"
|
||||
"'CP' connections can only be formed between a man and a woman, while \"brother\" and \"sister\" connections have no gender restrictions.",
|
||||
textAlign: TextAlign.left,
|
||||
textScaler: TextScaler.noScaling,
|
||||
style: _bodyStyle(),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 12.w),
|
||||
_CpOrnatePanel(
|
||||
assetBase: _assetBase,
|
||||
title: "How to Form Relationships?",
|
||||
height: 940.w,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(20.w, 67.w, 20.w, 16.w),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_bodyText(
|
||||
"Request for a Gift Panel in a Group Chat Room",
|
||||
color: _accentColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
SizedBox(height: 5.w),
|
||||
_bodyText(
|
||||
"1. In the room, tap [Gift Panel] > select [Best Friends];",
|
||||
),
|
||||
SizedBox(height: 8.w),
|
||||
_noteImage("sc_cp_rights_note_gift_panel.png", 122),
|
||||
SizedBox(height: 13.w),
|
||||
_bodyText(
|
||||
"2. Choose a gift tailored to a specific type of close friend; once you send it, a friend request will be automatically sent;",
|
||||
),
|
||||
SizedBox(height: 10.w),
|
||||
_noteImage("sc_cp_rights_note_relation_types.png", 42),
|
||||
SizedBox(height: 13.w),
|
||||
_bodyText(
|
||||
"3. The other party must accept the match within 24 hours;",
|
||||
),
|
||||
SizedBox(height: 8.w),
|
||||
_noteImage("sc_cp_rights_note_invitation.png", 408),
|
||||
SizedBox(height: 9.w),
|
||||
_bodyText(
|
||||
"*If the other party does not respond within 24 hours of the request being sent, the connection will be automatically declined.",
|
||||
color: _accentColor,
|
||||
fontSize: 10,
|
||||
lineHeight: 1.4,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 12.w),
|
||||
_CpOrnatePanel(
|
||||
assetBase: _assetBase,
|
||||
title: "Intimacy Level",
|
||||
height: 174.w,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(20.w, 67.w, 20.w, 16.w),
|
||||
child: _bodyText(
|
||||
"Intimacy Points are a numerical representation of the level of closeness between two players. 1 Gold Coin equals 1 Intimacy Point, and you can increase your Intimacy Points by sending gifts to your best friend every day.",
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 12.w),
|
||||
_CpOrnatePanel(
|
||||
assetBase: _assetBase,
|
||||
title: "FAQ",
|
||||
height: 486.w,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(20.w, 67.w, 20.w, 18.w),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: const [
|
||||
_FaqItem(
|
||||
question:
|
||||
"Q1: Does giving someone a regular gift increase intimacy?",
|
||||
answer: "No increase.",
|
||||
),
|
||||
_FaqItem(
|
||||
question:
|
||||
"Q2: Is there a limit to the number of relationships?",
|
||||
answer:
|
||||
"You can only have one CP, and a maximum of three brothers or sisters. More slots will be added in the future.",
|
||||
),
|
||||
_FaqItem(
|
||||
question: "Q3: Who can I invite to be a CP?",
|
||||
answer:
|
||||
"As long as neither party has a CP, you can send a CP gift to extend a relationship invitation.",
|
||||
),
|
||||
_FaqItem(
|
||||
question:
|
||||
"Q4: If my application is denied, will the Best Friend Gift be refunded?",
|
||||
answer:
|
||||
"Experience points are awarded the moment the gift is sent. If the request expires or is rejected, the gift will not be returned.",
|
||||
),
|
||||
_FaqItem(
|
||||
question:
|
||||
"Q5: Can the termination of the relationship be reversed?",
|
||||
answer:
|
||||
"Once the relationship is dissolved, all rights and interests will be reclaimed by the platform. The action cannot be undone; the relationship can only be reestablished.",
|
||||
isLast: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _noteImage(String name, double height) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8.w),
|
||||
child: Image.asset(
|
||||
"$_assetBase/$name",
|
||||
width: 310.w,
|
||||
height: height.w,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TextStyle _bodyStyle({
|
||||
Color color = _textColor,
|
||||
FontWeight fontWeight = FontWeight.w400,
|
||||
double fontSize = 12,
|
||||
double lineHeight = 1.333,
|
||||
}) {
|
||||
return TextStyle(
|
||||
color: color,
|
||||
fontSize: fontSize.sp,
|
||||
height: lineHeight,
|
||||
fontWeight: fontWeight,
|
||||
decoration: TextDecoration.none,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _bodyText(
|
||||
String value, {
|
||||
Color color = _textColor,
|
||||
FontWeight fontWeight = FontWeight.w400,
|
||||
double fontSize = 12,
|
||||
double lineHeight = 1.333,
|
||||
}) {
|
||||
return Text(
|
||||
value,
|
||||
textAlign: TextAlign.left,
|
||||
textScaler: TextScaler.noScaling,
|
||||
style: _bodyStyle(
|
||||
color: color,
|
||||
fontWeight: fontWeight,
|
||||
fontSize: fontSize,
|
||||
lineHeight: lineHeight,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRightsContent() {
|
||||
final rows = _currentRightsRowsFor(_rightsCategory);
|
||||
final isInitialLoading =
|
||||
_loadingRightsCategories.contains(_rightsCategory) &&
|
||||
!_rightsRowsByCategory.containsKey(_rightsCategory);
|
||||
final hasLoadError =
|
||||
_failedRightsCategories.contains(_rightsCategory) &&
|
||||
!_rightsRowsByCategory.containsKey(_rightsCategory);
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 52.w),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
_buildRightsCategoryTab("CP", _CpRightsCategory.cp),
|
||||
_buildRightsCategoryTab("Brother", _CpRightsCategory.brother),
|
||||
_buildRightsCategoryTab("Sisters", _CpRightsCategory.sisters),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 13.w),
|
||||
if (hasLoadError)
|
||||
_buildRightsLoadError(_rightsCategory)
|
||||
else
|
||||
Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
_buildRightsTable(rows),
|
||||
if (isInitialLoading)
|
||||
SizedBox(
|
||||
width: 22.w,
|
||||
height: 22.w,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.w,
|
||||
color: _accentColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRightsCategoryTab(String label, _CpRightsCategory category) {
|
||||
final selected = _rightsCategory == category;
|
||||
final textStyle = TextStyle(
|
||||
color: selected ? _accentColor : _textColor.withValues(alpha: 0.5),
|
||||
fontSize: 16.sp,
|
||||
height: 1,
|
||||
fontWeight: FontWeight.w400,
|
||||
decoration: TextDecoration.none,
|
||||
);
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
if (_rightsCategory == category) {
|
||||
return;
|
||||
}
|
||||
setState(() => _rightsCategory = category);
|
||||
_ensureRightsConfigLoaded(category);
|
||||
},
|
||||
child: SizedBox(
|
||||
height: 26.w,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(label, textScaler: TextScaler.noScaling, style: textStyle),
|
||||
SizedBox(height: 6.w),
|
||||
if (selected)
|
||||
Container(
|
||||
width: _rightsIndicatorWidth(label, textStyle),
|
||||
height: 3.w,
|
||||
decoration: BoxDecoration(
|
||||
color: _accentColor,
|
||||
borderRadius: BorderRadius.circular(31.5.w),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRightsLoadError(_CpRightsCategory category) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => _ensureRightsConfigLoaded(category, force: true),
|
||||
child: Container(
|
||||
width: 351.w,
|
||||
height: 156.w,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.3),
|
||||
borderRadius: BorderRadius.circular(12.w),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
padding: EdgeInsets.symmetric(horizontal: 24.w),
|
||||
child: Text(
|
||||
"Failed to load rights. Tap to retry.",
|
||||
textAlign: TextAlign.center,
|
||||
textScaler: TextScaler.noScaling,
|
||||
style: _bodyStyle(color: _accentColor, lineHeight: 1.3),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<_CpRightsLevel> _rightsRowsFor(_CpRightsCategory category) {
|
||||
switch (category) {
|
||||
case _CpRightsCategory.cp:
|
||||
return const [
|
||||
_CpRightsLevel(level: "Lv.1", intimacy: "--"),
|
||||
_CpRightsLevel(level: "Lv.2", intimacy: "--"),
|
||||
_CpRightsLevel(level: "Lv.3", intimacy: "--"),
|
||||
_CpRightsLevel(level: "Lv.4", intimacy: "--"),
|
||||
_CpRightsLevel(level: "Lv.5", intimacy: "--"),
|
||||
];
|
||||
case _CpRightsCategory.brother:
|
||||
return const [
|
||||
_CpRightsLevel(level: "Lv.1", intimacy: "--"),
|
||||
_CpRightsLevel(level: "Lv.2", intimacy: "--"),
|
||||
_CpRightsLevel(level: "Lv.3", intimacy: "--"),
|
||||
_CpRightsLevel(level: "Lv.4", intimacy: "--"),
|
||||
_CpRightsLevel(level: "Lv.5", intimacy: "--"),
|
||||
];
|
||||
case _CpRightsCategory.sisters:
|
||||
return const [
|
||||
_CpRightsLevel(level: "Lv.1", intimacy: "--"),
|
||||
_CpRightsLevel(level: "Lv.2", intimacy: "--"),
|
||||
_CpRightsLevel(level: "Lv.3", intimacy: "--"),
|
||||
_CpRightsLevel(level: "Lv.4", intimacy: "--"),
|
||||
_CpRightsLevel(level: "Lv.5", intimacy: "--"),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
List<_CpRightsLevel> _currentRightsRowsFor(_CpRightsCategory category) {
|
||||
return _rightsRowsByCategory[category] ?? _rightsRowsFor(category);
|
||||
}
|
||||
|
||||
Future<void> _ensureRightsConfigLoaded(
|
||||
_CpRightsCategory category, {
|
||||
bool force = false,
|
||||
}) async {
|
||||
if (!force &&
|
||||
(_loadedRightsCategories.contains(category) ||
|
||||
_loadingRightsCategories.contains(category))) {
|
||||
return;
|
||||
}
|
||||
final relationType = _relationTypeForRightsCategory(category);
|
||||
setState(() {
|
||||
_loadingRightsCategories.add(category);
|
||||
_failedRightsCategories.remove(category);
|
||||
});
|
||||
_rightsDebugLog(
|
||||
'load start relationType=$relationType cpUserId=${widget.cpUserId}',
|
||||
);
|
||||
try {
|
||||
final config = await SCAccountRepository().cpRightsConfig(
|
||||
relationType: relationType,
|
||||
cpUserId: widget.cpUserId,
|
||||
);
|
||||
final rows = _rightsRowsFromConfig(config, category);
|
||||
if (rows.isEmpty) {
|
||||
throw StateError('empty CP rights levels');
|
||||
}
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_rightsRowsByCategory[category] = rows;
|
||||
_loadedRightsCategories.add(category);
|
||||
_failedRightsCategories.remove(category);
|
||||
_loadingRightsCategories.remove(category);
|
||||
});
|
||||
_rightsDebugLog(
|
||||
'load success relationType=$relationType levels=${rows.length}',
|
||||
);
|
||||
} catch (error) {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_loadingRightsCategories.remove(category);
|
||||
_failedRightsCategories.add(category);
|
||||
});
|
||||
_rightsDebugLog('load failed relationType=$relationType error=$error');
|
||||
}
|
||||
}
|
||||
|
||||
List<_CpRightsLevel> _rightsRowsFromConfig(
|
||||
SCCpRightsConfigRes config,
|
||||
_CpRightsCategory category,
|
||||
) {
|
||||
final rows = <_CpRightsLevel>[];
|
||||
for (final level in config.levels) {
|
||||
final levelNumber = level.level;
|
||||
final levelName = level.levelName?.trim();
|
||||
final requiredValue = level.requiredIntimacyValue ?? 0;
|
||||
rows.add(
|
||||
_CpRightsLevel(
|
||||
level:
|
||||
levelName?.isNotEmpty == true
|
||||
? levelName!
|
||||
: (levelNumber != null
|
||||
? "Lv.$levelNumber"
|
||||
: "Lv.${rows.length + 1}"),
|
||||
intimacy: _formatRightsIntimacyValue(requiredValue),
|
||||
),
|
||||
);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
String _relationTypeForRightsCategory(_CpRightsCategory category) {
|
||||
switch (category) {
|
||||
case _CpRightsCategory.cp:
|
||||
return "CP";
|
||||
case _CpRightsCategory.brother:
|
||||
return "BROTHER";
|
||||
case _CpRightsCategory.sisters:
|
||||
return "SISTERS";
|
||||
}
|
||||
}
|
||||
|
||||
String _formatRightsIntimacyValue(int value) {
|
||||
final text = value.abs().toString();
|
||||
final buffer = StringBuffer();
|
||||
for (var index = 0; index < text.length; index++) {
|
||||
if (index > 0 && (text.length - index) % 3 == 0) {
|
||||
buffer.write(",");
|
||||
}
|
||||
buffer.write(text[index]);
|
||||
}
|
||||
return value < 0 ? "-$buffer" : buffer.toString();
|
||||
}
|
||||
|
||||
void _rightsDebugLog(String message) {
|
||||
if (kDebugMode) {
|
||||
debugPrint("[CP][Rights] $message");
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildRightsTable(List<_CpRightsLevel> rows) {
|
||||
const tableWidth = 351.0;
|
||||
const columnWidth = tableWidth / 2;
|
||||
const headerHeight = 39.0;
|
||||
const rowHeight = 77.0;
|
||||
final tableHeight = headerHeight + rows.length * rowHeight;
|
||||
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12.w),
|
||||
child: Container(
|
||||
width: tableWidth.w,
|
||||
height: tableHeight.w,
|
||||
color: Colors.white.withValues(alpha: 0.3),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: columnWidth.w,
|
||||
width: columnWidth.w,
|
||||
child: ColoredBox(color: Colors.white.withValues(alpha: 0.24)),
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: headerHeight.w,
|
||||
child: _tableLine(width: tableWidth),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: headerHeight.w,
|
||||
child: Row(
|
||||
children: [
|
||||
_tableHeader("Level", columnWidth),
|
||||
_tableHeader("Intimacy Level", columnWidth),
|
||||
],
|
||||
),
|
||||
),
|
||||
for (final row in rows) _buildRightsLevelRow(row),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _tableHeader(String value, double width) {
|
||||
return SizedBox(
|
||||
width: width.w,
|
||||
child: Center(
|
||||
child: Text(
|
||||
value,
|
||||
textScaler: TextScaler.noScaling,
|
||||
style: _bodyStyle(fontSize: 12, lineHeight: 1.2),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRightsLevelRow(_CpRightsLevel row) {
|
||||
const tableWidth = 351.0;
|
||||
const columnWidth = tableWidth / 2;
|
||||
const rowHeight = 77.0;
|
||||
return SizedBox(
|
||||
height: rowHeight.w,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Row(
|
||||
children: [
|
||||
_spanCell(row.level, width: columnWidth, height: rowHeight),
|
||||
_spanCell(row.intimacy, width: columnWidth, height: rowHeight),
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: _tableLine(width: tableWidth),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _spanCell(
|
||||
String value, {
|
||||
required double width,
|
||||
required double height,
|
||||
}) {
|
||||
return SizedBox(
|
||||
width: width.w,
|
||||
height: height.w,
|
||||
child: Center(
|
||||
child: Text(
|
||||
value,
|
||||
textScaler: TextScaler.noScaling,
|
||||
style: _bodyStyle(fontSize: 12, lineHeight: 1.2),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _tableLine({required double width}) {
|
||||
return Container(width: width.w, height: 1.w, color: Colors.white);
|
||||
}
|
||||
|
||||
double _rightsIndicatorWidth(String label, TextStyle style) {
|
||||
final painter = TextPainter(
|
||||
text: TextSpan(text: label, style: style),
|
||||
textDirection: Directionality.of(context),
|
||||
textScaler: TextScaler.noScaling,
|
||||
maxLines: 1,
|
||||
)..layout();
|
||||
return painter.width.clamp(16.w, 72.w);
|
||||
}
|
||||
|
||||
void _suppressRoomEffects() {
|
||||
if (_roomEffectsSuppressed) {
|
||||
return;
|
||||
}
|
||||
_roomEffectsSuppressed = true;
|
||||
try {
|
||||
final rtcProvider = context.read<RtcProvider>();
|
||||
_suppressedRtcProvider = rtcProvider;
|
||||
_restoreRoomVisualEffects = rtcProvider.roomVisualEffectsEnabled;
|
||||
rtcProvider.setRoomVisualEffectsEnabled(false);
|
||||
} catch (_) {}
|
||||
OverlayManager().beginSuppressFloatingScreens(
|
||||
reason: _effectSuppressReason,
|
||||
);
|
||||
SCRoomEffectScheduler().clearDeferredTasks(reason: _effectSuppressReason);
|
||||
SmartDialog.dismiss(tag: _roomRocketPagLaunchDialogTag);
|
||||
SCGiftVapSvgaManager().stopPlayback();
|
||||
SCGiftVapSvgaManager().pauseAnim();
|
||||
}
|
||||
|
||||
void _releaseRoomEffects() {
|
||||
if (!_roomEffectsSuppressed) {
|
||||
return;
|
||||
}
|
||||
_roomEffectsSuppressed = false;
|
||||
SCGiftVapSvgaManager().stopPlayback();
|
||||
OverlayManager().endSuppressFloatingScreens(reason: _effectSuppressReason);
|
||||
final rtcProvider = _suppressedRtcProvider;
|
||||
if (rtcProvider != null && _restoreRoomVisualEffects) {
|
||||
rtcProvider.setRoomVisualEffectsEnabled(true);
|
||||
}
|
||||
SCGiftVapSvgaManager().resumeAnim();
|
||||
_suppressedRtcProvider = null;
|
||||
}
|
||||
}
|
||||
|
||||
class _CpOrnatePanel extends StatelessWidget {
|
||||
const _CpOrnatePanel({
|
||||
required this.assetBase,
|
||||
required this.title,
|
||||
required this.height,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
static const Color _textColor = Color(0xff543356);
|
||||
|
||||
final String assetBase;
|
||||
final String title;
|
||||
final double height;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final middleHeight = (height - 65.w - 73.w).clamp(1.0, double.infinity);
|
||||
return SizedBox(
|
||||
width: 350.w,
|
||||
height: height,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Column(
|
||||
children: [
|
||||
Image.asset(
|
||||
"$assetBase/sc_cp_rights_panel_top.png",
|
||||
width: 350.w,
|
||||
height: 65.w,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
Image.asset(
|
||||
"$assetBase/sc_cp_rights_panel_mid.png",
|
||||
width: 350.w,
|
||||
height: middleHeight,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
Image.asset(
|
||||
"$assetBase/sc_cp_rights_panel_bottom.png",
|
||||
width: 350.w,
|
||||
height: 73.w,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 24.w,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Text(
|
||||
title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.center,
|
||||
textScaler: TextScaler.noScaling,
|
||||
style: TextStyle(
|
||||
color: _textColor,
|
||||
fontSize: 16.sp,
|
||||
height: 1,
|
||||
fontWeight: FontWeight.w400,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 30.w,
|
||||
right: 30.w,
|
||||
top: 44.w,
|
||||
child: Image.asset(
|
||||
"$assetBase/sc_cp_rights_panel_divider.png",
|
||||
height: 20.w,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
Positioned.fill(child: child),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _FaqItem extends StatelessWidget {
|
||||
const _FaqItem({
|
||||
required this.question,
|
||||
required this.answer,
|
||||
this.isLast = false,
|
||||
});
|
||||
|
||||
static const Color _textColor = Color(0xff543356);
|
||||
static const Color _accentColor = Color(0xffff2a70);
|
||||
|
||||
final String question;
|
||||
final String answer;
|
||||
final bool isLast;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: isLast ? 0 : 8.w),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
question,
|
||||
textAlign: TextAlign.left,
|
||||
textScaler: TextScaler.noScaling,
|
||||
style: TextStyle(
|
||||
color: _accentColor,
|
||||
fontSize: 12.sp,
|
||||
height: 1.333,
|
||||
fontWeight: FontWeight.w500,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 2.w),
|
||||
Text(
|
||||
answer,
|
||||
textAlign: TextAlign.left,
|
||||
textScaler: TextScaler.noScaling,
|
||||
style: TextStyle(
|
||||
color: _textColor,
|
||||
fontSize: 12.sp,
|
||||
height: 1.333,
|
||||
fontWeight: FontWeight.w400,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CpRightsLevel {
|
||||
const _CpRightsLevel({required this.level, required this.intimacy});
|
||||
|
||||
final String level;
|
||||
final String intimacy;
|
||||
}
|
||||
@ -13,11 +13,35 @@ import 'package:provider/provider.dart';
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
import 'package:yumi/app/config/business_logic_strategy.dart';
|
||||
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||
import 'package:yumi/shared/tools/sc_cp_gift_relation_utils.dart';
|
||||
|
||||
enum _GiftGridPageStatus { idle, loading, ready }
|
||||
|
||||
const Duration _kGiftPageSkeletonMinDuration = Duration(milliseconds: 420);
|
||||
const Duration _kGiftPageSkeletonMaxDuration = Duration(milliseconds: 900);
|
||||
const int _kGiftGridColumnCount = 4;
|
||||
const int _kGiftGridRowCount = 2;
|
||||
const double _kGiftGridCardHeight = 76;
|
||||
const double _kGiftGridVerticalPadding = 5;
|
||||
const double _kGiftGridMainAxisSpacing = 2;
|
||||
const double _kGiftGridCrossAxisSpacing = 8;
|
||||
const double _kGiftCardVerticalPadding = 5;
|
||||
const double _kGiftIconPriceSpacing = 1;
|
||||
const double _kGiftPriceRowHeight = 14;
|
||||
const double _kGiftGridTopGap = 5;
|
||||
const double _kGiftIndicatorTopGap = 5;
|
||||
const double _kGiftPageIndicatorHeight = 6.5;
|
||||
const double _kGiftGridBottomPadding = 6;
|
||||
const double _kGiftGridHeight =
|
||||
_kGiftGridCardHeight * _kGiftGridRowCount +
|
||||
_kGiftGridMainAxisSpacing * (_kGiftGridRowCount - 1) +
|
||||
_kGiftGridVerticalPadding * 2;
|
||||
const double kGiftTabPageHeight =
|
||||
_kGiftGridTopGap +
|
||||
_kGiftGridHeight +
|
||||
_kGiftIndicatorTopGap +
|
||||
_kGiftPageIndicatorHeight +
|
||||
_kGiftGridBottomPadding;
|
||||
|
||||
class GiftTabPage extends StatefulWidget {
|
||||
final String type;
|
||||
@ -64,7 +88,7 @@ class _GiftTabPageState extends State<GiftTabPage>
|
||||
_schedulePageWarmup(ref, gifts, _index);
|
||||
}
|
||||
return gifts.isEmpty
|
||||
? (ref.isGiftListLoading
|
||||
? (ref.isGiftTabLoading(widget.type)
|
||||
? _buildGiftPageSkeleton()
|
||||
: mainEmpty(
|
||||
textColor: Colors.white54,
|
||||
@ -72,8 +96,9 @@ class _GiftTabPageState extends State<GiftTabPage>
|
||||
))
|
||||
: Column(
|
||||
children: [
|
||||
SizedBox(height: 23.w),
|
||||
Expanded(
|
||||
SizedBox(height: _kGiftGridTopGap.w),
|
||||
SizedBox(
|
||||
height: _kGiftGridHeight.w,
|
||||
child: PageView.builder(
|
||||
controller: _giftPageController,
|
||||
onPageChanged: (i) {
|
||||
@ -95,16 +120,7 @@ class _GiftTabPageState extends State<GiftTabPage>
|
||||
_GiftGridPageStatus.ready) {
|
||||
return _buildGiftPageSkeleton();
|
||||
}
|
||||
return GridView.builder(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 4,
|
||||
childAspectRatio: 0.75,
|
||||
mainAxisSpacing: 8.w,
|
||||
crossAxisSpacing: 8.w,
|
||||
),
|
||||
return _buildGiftGrid(
|
||||
itemCount: size,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return _bagItem(gifts[(i * 8) + index], ref);
|
||||
@ -114,8 +130,9 @@ class _GiftTabPageState extends State<GiftTabPage>
|
||||
itemCount: (gifts.length / 8).ceil(),
|
||||
),
|
||||
),
|
||||
SizedBox(height: _kGiftIndicatorTopGap.w),
|
||||
_indicator(ref),
|
||||
SizedBox(height: 10.w),
|
||||
SizedBox(height: _kGiftGridBottomPadding.w),
|
||||
],
|
||||
);
|
||||
},
|
||||
@ -206,18 +223,11 @@ class _GiftTabPageState extends State<GiftTabPage>
|
||||
),
|
||||
),
|
||||
),
|
||||
GridView.builder(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 4,
|
||||
childAspectRatio: 0.75,
|
||||
mainAxisSpacing: 8.w,
|
||||
crossAxisSpacing: 8.w,
|
||||
Positioned.fill(
|
||||
child: _buildGiftGrid(
|
||||
itemCount: 8,
|
||||
itemBuilder: (context, index) => _buildGiftSkeletonCard(),
|
||||
),
|
||||
itemCount: 8,
|
||||
itemBuilder: (context, index) => _buildGiftSkeletonCard(),
|
||||
),
|
||||
],
|
||||
);
|
||||
@ -226,42 +236,71 @@ class _GiftTabPageState extends State<GiftTabPage>
|
||||
Widget _buildGiftSkeletonCard() {
|
||||
final baseColor = Colors.white.withValues(alpha: 0.12);
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12.w),
|
||||
color: Colors.white.withValues(alpha: 0.08),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(12.w)),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: _kGiftCardVerticalPadding.w),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 48.w,
|
||||
height: 48.w,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12.w),
|
||||
color: baseColor,
|
||||
),
|
||||
child: Center(child: SCRotatingDotsLoading(size: 24.w)),
|
||||
),
|
||||
SizedBox(height: _kGiftIconPriceSpacing.w),
|
||||
Container(
|
||||
width: 52.w,
|
||||
height: 8.w,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(999.w),
|
||||
color: baseColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 48.w,
|
||||
height: 48.w,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12.w),
|
||||
color: baseColor,
|
||||
),
|
||||
child: Center(child: SCRotatingDotsLoading(size: 24.w)),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGiftGrid({
|
||||
required int itemCount,
|
||||
required IndexedWidgetBuilder itemBuilder,
|
||||
}) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final horizontalPadding = 12.w;
|
||||
final verticalPadding = _kGiftGridVerticalPadding.w;
|
||||
final mainAxisSpacing = _kGiftGridMainAxisSpacing.w;
|
||||
final crossAxisSpacing = _kGiftGridCrossAxisSpacing.w;
|
||||
final itemHeight = _kGiftGridCardHeight.w;
|
||||
final gridWidth =
|
||||
constraints.maxWidth -
|
||||
horizontalPadding * 2 -
|
||||
crossAxisSpacing * (_kGiftGridColumnCount - 1);
|
||||
final itemWidth = (gridWidth / _kGiftGridColumnCount).clamp(
|
||||
1.0,
|
||||
double.infinity,
|
||||
);
|
||||
return GridView.builder(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: horizontalPadding,
|
||||
vertical: verticalPadding,
|
||||
),
|
||||
SizedBox(height: 7.w),
|
||||
Container(
|
||||
width: 52.w,
|
||||
height: 8.w,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(999.w),
|
||||
color: baseColor,
|
||||
),
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: _kGiftGridColumnCount,
|
||||
childAspectRatio: itemWidth / itemHeight,
|
||||
mainAxisSpacing: mainAxisSpacing,
|
||||
crossAxisSpacing: crossAxisSpacing,
|
||||
),
|
||||
SizedBox(height: 6.w),
|
||||
Container(
|
||||
width: 40.w,
|
||||
height: 8.w,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(999.w),
|
||||
color: baseColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
itemCount: itemCount,
|
||||
itemBuilder: itemBuilder,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -297,40 +336,45 @@ class _GiftTabPageState extends State<GiftTabPage>
|
||||
Widget _bagItem(SocialChatGiftRes gift, SCAppGeneralManager ref) {
|
||||
return gift.id == "-1000"
|
||||
? GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12.w),
|
||||
color: Colors.white10,
|
||||
border: Border.all(color: Colors.transparent, width: 1.w),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Image.asset(
|
||||
_strategy.getGiftPageCustomizedRuleIcon(),
|
||||
fit: BoxFit.cover,
|
||||
width: 48.w,
|
||||
height: 48.w,
|
||||
),
|
||||
SizedBox(height: 5.w),
|
||||
Container(
|
||||
height: 23.w,
|
||||
margin: EdgeInsets.symmetric(horizontal: 3.w),
|
||||
alignment: Alignment.center,
|
||||
child: text(
|
||||
SCAppLocalizations.of(context)!.rulesUpload,
|
||||
maxLines: 2,
|
||||
fontSize: 10.sp,
|
||||
letterSpacing: 0.1,
|
||||
lineHeight: 1,
|
||||
textAlign: TextAlign.center,
|
||||
fontWeight: FontWeight.w600,
|
||||
textColor: Colors.white,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: _kGiftCardVerticalPadding.w,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Image.asset(
|
||||
_strategy.getGiftPageCustomizedRuleIcon(),
|
||||
fit: BoxFit.cover,
|
||||
width: 48.w,
|
||||
height: 48.w,
|
||||
),
|
||||
),
|
||||
],
|
||||
SizedBox(height: _kGiftIconPriceSpacing.w),
|
||||
Container(
|
||||
height: 12.w,
|
||||
margin: EdgeInsets.symmetric(horizontal: 3.w),
|
||||
alignment: Alignment.center,
|
||||
child: text(
|
||||
SCAppLocalizations.of(context)!.rulesUpload,
|
||||
maxLines: 1,
|
||||
fontSize: 10,
|
||||
letterSpacing: 0.1,
|
||||
lineHeight: 1,
|
||||
textAlign: TextAlign.center,
|
||||
fontWeight: FontWeight.w600,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -403,63 +447,85 @@ class _GiftTabPageState extends State<GiftTabPage>
|
||||
},
|
||||
)
|
||||
: GestureDetector(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12.w),
|
||||
color: Colors.white10,
|
||||
border: Border.all(
|
||||
color:
|
||||
checkedIndex == ref.giftByTab[widget.type]!.indexOf(gift)
|
||||
? SocialChatTheme.primaryLight
|
||||
: Colors.transparent,
|
||||
width: 1.w,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
netImage(
|
||||
url: gift.giftPhoto ?? "",
|
||||
fit: BoxFit.cover,
|
||||
width: 48.w,
|
||||
height: 48.w,
|
||||
loadingWidget: _buildGiftCoverLoading(),
|
||||
errorWidget: _buildGiftCoverNoData(),
|
||||
),
|
||||
SizedBox(height: 5.w),
|
||||
Container(
|
||||
height: 23.w,
|
||||
margin: EdgeInsets.symmetric(horizontal: 3.w),
|
||||
alignment: Alignment.center,
|
||||
child: text(
|
||||
gift.giftName ?? "",
|
||||
maxLines: 2,
|
||||
fontSize: 10.sp,
|
||||
letterSpacing: 0.1,
|
||||
lineHeight: 1,
|
||||
textAlign: TextAlign.center,
|
||||
fontWeight: FontWeight.w600,
|
||||
textColor: widget.isDark ? Colors.white : Colors.black,
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12.w),
|
||||
border: Border.all(
|
||||
color:
|
||||
checkedIndex ==
|
||||
ref.giftByTab[widget.type]!.indexOf(gift)
|
||||
? SocialChatTheme.primaryLight
|
||||
: Colors.transparent,
|
||||
width: 1.w,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Image.asset(
|
||||
_strategy.getGiftPageGoldCoinIcon(),
|
||||
width: 14.w,
|
||||
height: 14.w,
|
||||
),
|
||||
SizedBox(width: 3.w),
|
||||
text(
|
||||
"${gift.giftCandy}",
|
||||
fontSize: 10.sp,
|
||||
textColor: widget.isDark ? Colors.white : Colors.black,
|
||||
),
|
||||
],
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: _kGiftCardVerticalPadding.w,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
netImage(
|
||||
url: gift.giftPhoto ?? "",
|
||||
fit: BoxFit.cover,
|
||||
width: 48.w,
|
||||
height: 48.w,
|
||||
loadingWidget: _buildGiftCoverLoading(),
|
||||
errorWidget: _buildGiftCoverNoData(),
|
||||
),
|
||||
SizedBox(height: _kGiftIconPriceSpacing.w),
|
||||
SizedBox(
|
||||
height: _kGiftPriceRowHeight.w,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children:
|
||||
_isBackpackTab
|
||||
? [
|
||||
text(
|
||||
"x${_formatBackpackQuantity(gift)}",
|
||||
fontSize: 10,
|
||||
lineHeight: 1,
|
||||
fontWeight: FontWeight.w600,
|
||||
textColor:
|
||||
widget.isDark
|
||||
? SocialChatTheme.primaryLight
|
||||
: SocialChatTheme.primaryColor,
|
||||
),
|
||||
]
|
||||
: [
|
||||
Image.asset(
|
||||
_strategy.getGiftPageGoldCoinIcon(),
|
||||
width: 14.w,
|
||||
height: 14.w,
|
||||
),
|
||||
SizedBox(width: 3.w),
|
||||
text(
|
||||
"${gift.giftCandy}",
|
||||
fontSize: 10,
|
||||
lineHeight: 1,
|
||||
textColor:
|
||||
widget.isDark
|
||||
? Colors.white
|
||||
: Colors.black,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (_shouldShowCpRelationBadge(gift))
|
||||
PositionedDirectional(
|
||||
top: 0,
|
||||
end: 0,
|
||||
child: _buildCpRelationBadge(gift),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
@ -470,6 +536,81 @@ class _GiftTabPageState extends State<GiftTabPage>
|
||||
);
|
||||
}
|
||||
|
||||
bool _shouldShowCpRelationBadge(SocialChatGiftRes gift) =>
|
||||
scIsCpGiftTab(widget.type) || scIsCpGift(gift);
|
||||
|
||||
Widget _buildCpRelationBadge(SocialChatGiftRes gift) {
|
||||
final spec = _cpRelationBadgeSpec(scCpRelationTypeForGift(gift));
|
||||
return Container(
|
||||
width: spec.width.w,
|
||||
height: 14.w,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: spec.backgroundColor,
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(7.w),
|
||||
bottomLeft: Radius.circular(7.w),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
spec.label,
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.visible,
|
||||
style: TextStyle(
|
||||
color: spec.color,
|
||||
fontFamily: spec.fontFamily,
|
||||
fontSize: 10.sp,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 1,
|
||||
letterSpacing: 0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_CpRelationBadgeSpec _cpRelationBadgeSpec(String relationType) {
|
||||
switch (relationType) {
|
||||
case "BROTHER":
|
||||
return const _CpRelationBadgeSpec(
|
||||
label: "brother",
|
||||
width: 44,
|
||||
color: Color(0xFF024591),
|
||||
backgroundColor: Color(0xFFCEE9FF),
|
||||
fontFamily: "Source Han Sans SC",
|
||||
);
|
||||
case "SISTERS":
|
||||
return const _CpRelationBadgeSpec(
|
||||
label: "sister",
|
||||
width: 39,
|
||||
color: Color(0xFF880291),
|
||||
backgroundColor: Color(0xFFEECEFF),
|
||||
fontFamily: "Source Han Sans SC",
|
||||
);
|
||||
default:
|
||||
return const _CpRelationBadgeSpec(
|
||||
label: "cp",
|
||||
width: 26,
|
||||
color: Color(0xFFFF59B7),
|
||||
backgroundColor: Color(0xFFFFCEEA),
|
||||
fontFamily: "Inter",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bool get _isBackpackTab => widget.type == SCAppGeneralManager.backpackGiftTab;
|
||||
|
||||
String _formatBackpackQuantity(SocialChatGiftRes gift) {
|
||||
final quantity = gift.backpackQuantity;
|
||||
if (quantity != null) {
|
||||
if (quantity % 1 == 0) {
|
||||
return quantity.toInt().toString();
|
||||
}
|
||||
return quantity.toString();
|
||||
}
|
||||
return gift.quantity ?? '0';
|
||||
}
|
||||
|
||||
_indicator(SCAppGeneralManager ref) {
|
||||
var size = (ref.giftByTab[widget.type]!.length) / 8;
|
||||
var list = <Widget>[];
|
||||
@ -490,3 +631,19 @@ class _GiftTabPageState extends State<GiftTabPage>
|
||||
return Row(mainAxisAlignment: MainAxisAlignment.center, children: list);
|
||||
}
|
||||
}
|
||||
|
||||
class _CpRelationBadgeSpec {
|
||||
const _CpRelationBadgeSpec({
|
||||
required this.label,
|
||||
required this.width,
|
||||
required this.color,
|
||||
required this.backgroundColor,
|
||||
required this.fontFamily,
|
||||
});
|
||||
|
||||
final String label;
|
||||
final double width;
|
||||
final Color color;
|
||||
final Color backgroundColor;
|
||||
final String fontFamily;
|
||||
}
|
||||
|
||||
@ -203,35 +203,17 @@ class _RoomFollowPageState
|
||||
),
|
||||
),
|
||||
SizedBox(width: 5.w),
|
||||
(roomRes
|
||||
.roomProfile
|
||||
?.extValues
|
||||
?.roomSetting
|
||||
?.password
|
||||
?.isEmpty ??
|
||||
false)
|
||||
roomRes.roomProfile?.extValues?.existsPassword != true
|
||||
? SCRoomLiveAudioIndicator(width: 14.w, height: 14.w)
|
||||
: Image.asset(
|
||||
"sc_images/index/sc_icon_room_suo.png",
|
||||
width: 20.w,
|
||||
height: 20.w,
|
||||
),
|
||||
(roomRes
|
||||
.roomProfile
|
||||
?.extValues
|
||||
?.roomSetting
|
||||
?.password
|
||||
?.isEmpty ??
|
||||
false)
|
||||
roomRes.roomProfile?.extValues?.existsPassword != true
|
||||
? SizedBox(width: 3.w)
|
||||
: Container(height: 10.w),
|
||||
(roomRes
|
||||
.roomProfile
|
||||
?.extValues
|
||||
?.roomSetting
|
||||
?.password
|
||||
?.isEmpty ??
|
||||
false)
|
||||
roomRes.roomProfile?.extValues?.existsPassword != true
|
||||
? text(
|
||||
roomRes.roomProfile?.displayMemberCount ?? "0",
|
||||
fontSize: 10.sp,
|
||||
@ -363,8 +345,8 @@ class _RoomFollowPageState
|
||||
previous.roomProfile?.roomGameIcon == next.roomProfile?.roomGameIcon &&
|
||||
previous.roomProfile?.displayMemberCount ==
|
||||
next.roomProfile?.displayMemberCount &&
|
||||
previous.roomProfile?.extValues?.roomSetting?.password ==
|
||||
next.roomProfile?.extValues?.roomSetting?.password;
|
||||
previous.roomProfile?.extValues?.existsPassword ==
|
||||
next.roomProfile?.extValues?.existsPassword;
|
||||
}
|
||||
|
||||
bool _sameRoomLists(List<FollowRoomRes> previous, List<FollowRoomRes> next) {
|
||||
|
||||
@ -204,35 +204,17 @@ class _SCRoomHistoryPageState
|
||||
),
|
||||
),
|
||||
SizedBox(width: 5.w),
|
||||
(roomRes
|
||||
.roomProfile
|
||||
?.extValues
|
||||
?.roomSetting
|
||||
?.password
|
||||
?.isEmpty ??
|
||||
false)
|
||||
roomRes.roomProfile?.extValues?.existsPassword != true
|
||||
? SCRoomLiveAudioIndicator(width: 14.w, height: 14.w)
|
||||
: Image.asset(
|
||||
"sc_images/index/sc_icon_room_suo.png",
|
||||
width: 20.w,
|
||||
height: 20.w,
|
||||
),
|
||||
(roomRes
|
||||
.roomProfile
|
||||
?.extValues
|
||||
?.roomSetting
|
||||
?.password
|
||||
?.isEmpty ??
|
||||
false)
|
||||
roomRes.roomProfile?.extValues?.existsPassword != true
|
||||
? SizedBox(width: 3.w)
|
||||
: Container(height: 10.w),
|
||||
(roomRes
|
||||
.roomProfile
|
||||
?.extValues
|
||||
?.roomSetting
|
||||
?.password
|
||||
?.isEmpty ??
|
||||
false)
|
||||
roomRes.roomProfile?.extValues?.existsPassword != true
|
||||
? text(
|
||||
roomRes.roomProfile?.displayMemberCount ?? "0",
|
||||
fontSize: 10.sp,
|
||||
@ -363,8 +345,8 @@ class _SCRoomHistoryPageState
|
||||
previous.roomProfile?.roomGameIcon == next.roomProfile?.roomGameIcon &&
|
||||
previous.roomProfile?.displayMemberCount ==
|
||||
next.roomProfile?.displayMemberCount &&
|
||||
previous.roomProfile?.extValues?.roomSetting?.password ==
|
||||
next.roomProfile?.extValues?.roomSetting?.password;
|
||||
previous.roomProfile?.extValues?.existsPassword ==
|
||||
next.roomProfile?.extValues?.existsPassword;
|
||||
}
|
||||
|
||||
bool _sameRoomLists(List<FollowRoomRes> previous, List<FollowRoomRes> next) {
|
||||
|
||||
@ -15,7 +15,9 @@ import 'package:provider/provider.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart';
|
||||
import 'package:yumi/shared/tools/sc_dialog_utils.dart';
|
||||
import 'package:yumi/services/general/sc_app_general_manager.dart';
|
||||
import 'package:yumi/services/auth/user_profile_manager.dart';
|
||||
import 'package:yumi/ui_kit/widgets/svga/sc_svga_asset_widget.dart';
|
||||
@ -44,11 +46,13 @@ class _SCIndexPageState extends State<SCIndexPage>
|
||||
final List<Widget> _pages = [];
|
||||
final Set<int> _builtPageIndexes = <int>{0};
|
||||
final List<BottomNavigationBarItem> _bottomItems = [];
|
||||
final ValueNotifier<int> _currentIndexNotifier = ValueNotifier<int>(0);
|
||||
final ValueNotifier<int> _taskClaimableCount = ValueNotifier<int>(0);
|
||||
SCAppGeneralManager? generalProvider;
|
||||
Locale? _lastLocale;
|
||||
bool _hasShownEntryDialogs = false;
|
||||
bool _hasShownRegisterRewardDialog = false;
|
||||
bool _hasRequestedFirstRechargeDialog = false;
|
||||
bool _isShowingDailySignInDialog = false;
|
||||
bool _showRegisterRewardAfterDailySignIn = false;
|
||||
bool _isOpeningFirstRegisterRoomGame = false;
|
||||
@ -66,15 +70,10 @@ class _SCIndexPageState extends State<SCIndexPage>
|
||||
_initializePages();
|
||||
unawaited(_loadTaskClaimableCount());
|
||||
generalProvider = Provider.of<SCAppGeneralManager>(context, listen: false);
|
||||
Provider.of<RtcProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).initializeRealTimeCommunicationManager(context);
|
||||
final rtcProvider = Provider.of<RtcProvider>(context, listen: false);
|
||||
rtcProvider.initializeRealTimeCommunicationManager(context);
|
||||
unawaited(
|
||||
Provider.of<RtcProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).prewarmRtcEngine().catchError((error, stackTrace) {}),
|
||||
rtcProvider.prewarmRtcEngine().catchError((error, stackTrace) {}),
|
||||
);
|
||||
Provider.of<RtmProvider>(context, listen: false).init(context);
|
||||
Provider.of<SocialChatUserProfileManager>(
|
||||
@ -89,9 +88,7 @@ class _SCIndexPageState extends State<SCIndexPage>
|
||||
WakelockPlus.enable();
|
||||
_showEntryDialogs();
|
||||
});
|
||||
if (DataPersistence.getLastTimeRoomId().isNotEmpty) {
|
||||
unawaited(DataPersistence.setLastTimeRoomId(""));
|
||||
}
|
||||
unawaited(rtcProvider.cleanupPersistedVoiceRoomSessionBeforeEntry());
|
||||
}
|
||||
|
||||
@override
|
||||
@ -100,6 +97,7 @@ class _SCIndexPageState extends State<SCIndexPage>
|
||||
routeObserver.unsubscribe(this);
|
||||
OverlayManager().setHomeRootTabsVisible(false);
|
||||
_registerRewardSubscription?.cancel();
|
||||
_currentIndexNotifier.dispose();
|
||||
_taskClaimableCount.dispose();
|
||||
WakelockPlus.disable();
|
||||
super.dispose();
|
||||
@ -277,7 +275,7 @@ class _SCIndexPageState extends State<SCIndexPage>
|
||||
),
|
||||
);
|
||||
_pages.add(SCMessagePage());
|
||||
_pages.add(MePage2());
|
||||
_pages.add(MePage2(currentIndexListenable: _currentIndexNotifier));
|
||||
}
|
||||
|
||||
Widget _buildCachedBody() {
|
||||
@ -304,6 +302,7 @@ class _SCIndexPageState extends State<SCIndexPage>
|
||||
}
|
||||
setState(() {
|
||||
_currentIndex = index;
|
||||
_currentIndexNotifier.value = index;
|
||||
_builtPageIndexes.add(index);
|
||||
});
|
||||
}
|
||||
@ -442,14 +441,27 @@ class _SCIndexPageState extends State<SCIndexPage>
|
||||
return;
|
||||
}
|
||||
|
||||
await _showDailySignInDialogIfNeeded();
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
await _showEntryPopupIfNeeded();
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
await _showFirstRechargeDialogIfNeeded();
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
await _openFirstRegisterRoomGameIfNeeded();
|
||||
}
|
||||
|
||||
Future<void> _showDailySignInDialogIfNeeded() async {
|
||||
final dialogData = await _loadDailySignInDialogData();
|
||||
if (!mounted || dialogData == null) {
|
||||
await _openFirstRegisterRoomGameIfNeeded();
|
||||
return;
|
||||
}
|
||||
_isShowingDailySignInDialog = true;
|
||||
@ -465,7 +477,6 @@ class _SCIndexPageState extends State<SCIndexPage>
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
await _openFirstRegisterRoomGameIfNeeded();
|
||||
}
|
||||
|
||||
Future<void> _showEntryPopupIfNeeded() async {
|
||||
@ -478,6 +489,18 @@ class _SCIndexPageState extends State<SCIndexPage>
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showFirstRechargeDialogIfNeeded() async {
|
||||
if (_hasRequestedFirstRechargeDialog ||
|
||||
SCGlobalConfig.isReview ||
|
||||
AccountStorage().getToken().isEmpty ||
|
||||
!mounted ||
|
||||
!(ModalRoute.of(context)?.isCurrent ?? false)) {
|
||||
return;
|
||||
}
|
||||
_hasRequestedFirstRechargeDialog = true;
|
||||
await SCDialogUtils.showFirstRechargeDialog(context, forceRefresh: true);
|
||||
}
|
||||
|
||||
Future<void> _waitForRegisterRewardSocketIfNeeded() async {
|
||||
if (!DataPersistence.getAwaitRegisterRewardSocket() ||
|
||||
DataPersistence.getPendingRegisterRewardDialog()) {
|
||||
|
||||
@ -14,6 +14,7 @@ import '../../../ui_kit/components/sc_tts.dart';
|
||||
import '../../../ui_kit/components/text/sc_text.dart';
|
||||
import '../../../ui_kit/widgets/id/sc_special_id_badge.dart';
|
||||
import '../../../shared/tools/sc_lk_dialog_util.dart';
|
||||
import '../../../ui_kit/widgets/badge/sc_user_badge_strip.dart';
|
||||
import '../../../ui_kit/widgets/room/room_user_info_card.dart';
|
||||
|
||||
///房间用户在线列表
|
||||
@ -43,7 +44,7 @@ class _RoomOnlinePageState
|
||||
if (dialogContext == null) {
|
||||
return;
|
||||
}
|
||||
showBottomInCenterDialog(dialogContext, RoomUserInfoCard(userId: userId));
|
||||
showBottomInBottomDialog(dialogContext, RoomUserInfoCard(userId: userId));
|
||||
});
|
||||
}
|
||||
|
||||
@ -107,84 +108,105 @@ class _RoomOnlinePageState
|
||||
_openUserCard(userInfo);
|
||||
},
|
||||
),
|
||||
SizedBox(width: 3.w),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
msgRoleTag(userInfo.roles ?? "", width: 20.w, height: 20.w),
|
||||
SizedBox(width: 3.w),
|
||||
socialchatNickNameText(
|
||||
textColor: Colors.black,
|
||||
maxWidth: 200.w,
|
||||
userInfo.userNickname ?? "",
|
||||
fontSize: 14.sp,
|
||||
type: userInfo.getVIP()?.name ?? "",
|
||||
needScroll:
|
||||
(userInfo.userNickname?.characters.length ?? 0) > 16,
|
||||
),
|
||||
getVIPBadge(
|
||||
userInfo.getVIP()?.name,
|
||||
width: 45.w,
|
||||
height: 25.w,
|
||||
),
|
||||
// ListView.separated(
|
||||
// scrollDirection: Axis.horizontal,
|
||||
// shrinkWrap: true,
|
||||
// itemCount: userInfo.wearBadge?.length ?? 0,
|
||||
// itemBuilder: (context, index) {
|
||||
// return netImage(
|
||||
// width: 25.w,
|
||||
// height: 25.w,
|
||||
// url: userInfo.wearBadge?[index].selectUrl ?? "",
|
||||
// );
|
||||
// },
|
||||
// separatorBuilder: (BuildContext context, int index) {
|
||||
// return SizedBox(width: 5.w);
|
||||
// },
|
||||
// ),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 3.w),
|
||||
GestureDetector(
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 3.w),
|
||||
child: SCSpecialIdBadge(
|
||||
idText: userInfo.getID(),
|
||||
showAnimated: userInfo.hasSpecialId(),
|
||||
assetPath: SCSpecialIdAssets.userId,
|
||||
animationWidth: 62.w,
|
||||
animationHeight: 24.w,
|
||||
showTextBesideAnimated: true,
|
||||
animatedTextSpacing: 0,
|
||||
showAnimatedGradientText:
|
||||
userInfo.shouldShowColoredSpecialIdText(),
|
||||
animationTextStyle: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 12.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
SizedBox(width: 8.w),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
msgRoleTag(
|
||||
userInfo.roles ?? "",
|
||||
width: 20.w,
|
||||
height: 20.w,
|
||||
),
|
||||
normalTextStyle: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 12.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
SizedBox(width: 3.w),
|
||||
socialchatNickNameText(
|
||||
textColor: Colors.black,
|
||||
maxWidth: 132.w,
|
||||
userInfo.userNickname ?? "",
|
||||
fontSize: 14.sp,
|
||||
type: userInfo.getVIP()?.name ?? "",
|
||||
needScroll:
|
||||
(userInfo.userNickname?.characters.length ?? 0) >
|
||||
16,
|
||||
),
|
||||
showCopyIcon: true,
|
||||
showCopyIconWhenAnimated: true,
|
||||
copyIconSize: 12.w,
|
||||
copyIconSpacing: 5.w,
|
||||
animationFit: BoxFit.contain,
|
||||
),
|
||||
getVIPBadge(
|
||||
userInfo.vipLevel ?? userInfo.getVIP()?.name,
|
||||
width: 45.w,
|
||||
height: 25.w,
|
||||
),
|
||||
SizedBox(width: 3.w),
|
||||
getWealthLevel(
|
||||
userInfo.wealthLevel ?? 0,
|
||||
width: 42.w,
|
||||
height: 20.w,
|
||||
fontSize: 9.sp,
|
||||
),
|
||||
SizedBox(width: 2.w),
|
||||
getUserLevel(
|
||||
userInfo.charmLevel ?? 0,
|
||||
width: 42.w,
|
||||
height: 20.w,
|
||||
fontSize: 9.sp,
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: userInfo.getID()));
|
||||
SCTts.show(
|
||||
SCAppLocalizations.of(context)!.copiedToClipboard,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
SizedBox(height: 3.w),
|
||||
GestureDetector(
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 3.w),
|
||||
child: SCSpecialIdBadge(
|
||||
idText: userInfo.getID(),
|
||||
showAnimated: userInfo.hasSpecialId(),
|
||||
assetPath: SCSpecialIdAssets.userId,
|
||||
animationWidth: 62.w,
|
||||
animationHeight: 24.w,
|
||||
showTextBesideAnimated: true,
|
||||
animatedTextSpacing: 0,
|
||||
showAnimatedGradientText:
|
||||
userInfo.shouldShowColoredSpecialIdText(),
|
||||
animationTextStyle: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 12.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
normalTextStyle: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 12.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
showCopyIcon: true,
|
||||
showCopyIconWhenAnimated: true,
|
||||
copyIconSize: 12.w,
|
||||
copyIconSpacing: 5.w,
|
||||
animationFit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: userInfo.getID()));
|
||||
SCTts.show(
|
||||
SCAppLocalizations.of(context)!.copiedToClipboard,
|
||||
);
|
||||
},
|
||||
),
|
||||
SizedBox(height: 2.w),
|
||||
SCUserBadgeStrip(
|
||||
userId: userInfo.id,
|
||||
fallbackBadges:
|
||||
userInfo.wearBadge
|
||||
?.where((item) => item.use ?? false)
|
||||
.toList() ??
|
||||
const <WearBadge>[],
|
||||
height: 23.w,
|
||||
badgeHeight: 21.w,
|
||||
longBadgeWidth: 51.w,
|
||||
spacing: 4.5.w,
|
||||
maxBadges: 8,
|
||||
reserveSpace: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@ -98,7 +98,7 @@ class _RoomGiftRankTabPageState
|
||||
// headdress: userInfo.userProfile?.getHeaddress()?.sourceUrl,
|
||||
),
|
||||
onTap: () {
|
||||
showBottomInCenterDialog(
|
||||
showBottomInBottomDialog(
|
||||
navigatorKey.currentState!.context,
|
||||
RoomUserInfoCard(userId: userProfile?.id),
|
||||
);
|
||||
|
||||
1202
lib/modules/room/rocket/room_rocket_rank_page.dart
Normal file
@ -225,7 +225,7 @@ class _SCSeatItemState extends State<SCSeatItem> with TickerProviderStateMixin {
|
||||
height: _seatLabelRowHeight.w,
|
||||
child: Center(
|
||||
child: text(
|
||||
"NO.${widget.index}",
|
||||
"NO.${widget.index.toInt() + 1}",
|
||||
fontSize: 10.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
|
||||
@ -7,6 +7,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||
import 'package:yumi/main.dart' show routeObserver;
|
||||
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
||||
import 'package:yumi/services/audio/rtc_manager.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/room_bottom_widget.dart';
|
||||
@ -20,15 +21,19 @@ import 'package:yumi/services/music/room_music_manager.dart';
|
||||
import 'package:yumi/services/audio/rtm_manager.dart';
|
||||
import 'package:yumi/shared/tools/sc_gift_vap_svga_manager.dart';
|
||||
import 'package:yumi/shared/tools/sc_lucky_gift_win_sound_player.dart';
|
||||
import 'package:yumi/shared/tools/sc_cp_gift_relation_utils.dart';
|
||||
import 'package:yumi/shared/tools/sc_network_image_utils.dart';
|
||||
import 'package:yumi/shared/tools/sc_path_utils.dart';
|
||||
import 'package:yumi/shared/tools/sc_room_effect_scheduler.dart';
|
||||
import 'package:yumi/shared/tools/sc_room_top_layer_guard.dart';
|
||||
import 'package:yumi/shared/data_sources/models/message/sc_floating_message.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/local/floating_screen_manager.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/anim/l_gift_animal_view.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/anim/room_gift_seat_flight_overlay.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/anim/room_entrance_screen.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/anim/room_entrance_widget.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/effect/luck_gift_nomor_anim_widget.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/effect/vapp_svga_layer_widget.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/room_head_widget.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/room_online_user_widget.dart';
|
||||
@ -51,7 +56,7 @@ class VoiceRoomPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _VoiceRoomPageState extends State<VoiceRoomPage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
with SingleTickerProviderStateMixin, RouteAware {
|
||||
static const int _chatTabCount = 3;
|
||||
static const Duration _luckyGiftComboWindow = Duration(seconds: 3);
|
||||
static const Duration _luckyGiftQueueDrainWindow = Duration(seconds: 3);
|
||||
@ -76,12 +81,16 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
||||
int _shownRoomStartupFailureToken = 0;
|
||||
RtcProvider? _rtcProvider;
|
||||
bool _roomProviderRebuildScheduled = false;
|
||||
bool _roomRouteVisibleScheduled = false;
|
||||
bool _roomVisualEffectsEnsureScheduled = false;
|
||||
PageRoute<dynamic>? _routeObserverRoute;
|
||||
SCRoomTopLayerGuardLease? _coveredRouteGuardLease;
|
||||
bool _roomRouteVisible = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabController = TabController(length: _chatTabCount, vsync: this);
|
||||
_enableRoomVisualEffects();
|
||||
_tabController.addListener(_handleTabChange);
|
||||
_subscription = eventBus.on<SCGiveRoomLuckPageDisposeEvent>().listen((
|
||||
event,
|
||||
@ -110,11 +119,28 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
||||
rtcProvider.addListener(_handleRoomProviderChanged);
|
||||
context.read<RoomMusicManager>().attachRtcProvider(rtcProvider);
|
||||
}
|
||||
_ensureRoomVisualEffectsEnabled();
|
||||
final route = ModalRoute.of(context);
|
||||
if (route is PageRoute<dynamic> && _routeObserverRoute != route) {
|
||||
if (_routeObserverRoute != null) {
|
||||
routeObserver.unsubscribe(this);
|
||||
}
|
||||
_routeObserverRoute = route;
|
||||
routeObserver.subscribe(this, route);
|
||||
if (route.isCurrent) {
|
||||
_scheduleRoomRouteVisible();
|
||||
} else {
|
||||
_handleRoomRouteCovered();
|
||||
}
|
||||
}
|
||||
_scheduleEnsureRoomVisualEffectsEnabled();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
routeObserver.unsubscribe(this);
|
||||
_routeObserverRoute = null;
|
||||
_releaseCoveredRouteGuard();
|
||||
_setRoomRouteVisible(false);
|
||||
_rtcProvider?.removeListener(_handleRoomProviderChanged);
|
||||
_rtcProvider = null;
|
||||
_roomProviderRebuildScheduled = false;
|
||||
@ -125,6 +151,74 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didPush() {
|
||||
_handleRoomRouteVisible();
|
||||
}
|
||||
|
||||
@override
|
||||
void didPopNext() {
|
||||
_handleRoomRouteVisible();
|
||||
}
|
||||
|
||||
@override
|
||||
void didPushNext() {
|
||||
_handleRoomRouteCovered();
|
||||
}
|
||||
|
||||
@override
|
||||
void didPop() {
|
||||
_handleRoomRouteCovered();
|
||||
_releaseCoveredRouteGuard();
|
||||
}
|
||||
|
||||
void _handleRoomRouteVisible() {
|
||||
_releaseCoveredRouteGuard();
|
||||
_setRoomRouteVisible(true);
|
||||
}
|
||||
|
||||
void _scheduleRoomRouteVisible() {
|
||||
if (_roomRouteVisibleScheduled) {
|
||||
return;
|
||||
}
|
||||
_roomRouteVisibleScheduled = true;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_roomRouteVisibleScheduled = false;
|
||||
if (!mounted || !(ModalRoute.of(context)?.isCurrent ?? false)) {
|
||||
return;
|
||||
}
|
||||
_handleRoomRouteVisible();
|
||||
_ensureRoomVisualEffectsEnabled();
|
||||
});
|
||||
}
|
||||
|
||||
void _handleRoomRouteCovered() {
|
||||
_setRoomRouteVisible(false);
|
||||
_acquireCoveredRouteGuard();
|
||||
_clearTransientRoomVisualEffects(reason: 'voice_room_route_covered');
|
||||
}
|
||||
|
||||
void _acquireCoveredRouteGuard() {
|
||||
_coveredRouteGuardLease ??= SCRoomTopLayerGuard().acquire(
|
||||
reason: 'voice_room_route_covered',
|
||||
);
|
||||
}
|
||||
|
||||
void _releaseCoveredRouteGuard() {
|
||||
_coveredRouteGuardLease?.release();
|
||||
_coveredRouteGuardLease = null;
|
||||
}
|
||||
|
||||
void _setRoomRouteVisible(bool visible) {
|
||||
if (_roomRouteVisible == visible) {
|
||||
return;
|
||||
}
|
||||
_roomRouteVisible = visible;
|
||||
final rtcProvider =
|
||||
_rtcProvider ?? Provider.of<RtcProvider>(context, listen: false);
|
||||
rtcProvider.setVoiceRoomRouteVisible(visible);
|
||||
}
|
||||
|
||||
void _handleTabChange() {
|
||||
if (!mounted) {
|
||||
return;
|
||||
@ -160,14 +254,55 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
||||
void _ensureRoomVisualEffectsEnabled() {
|
||||
final rtcProvider = Provider.of<RtcProvider>(context, listen: false);
|
||||
if (rtcProvider.currenRoom == null ||
|
||||
!_roomRouteVisible ||
|
||||
rtcProvider.roomVisualEffectsEnabled) {
|
||||
return;
|
||||
}
|
||||
_enableRoomVisualEffects();
|
||||
}
|
||||
|
||||
void _scheduleEnsureRoomVisualEffectsEnabled() {
|
||||
if (_roomVisualEffectsEnsureScheduled) {
|
||||
return;
|
||||
}
|
||||
_roomVisualEffectsEnsureScheduled = true;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_roomVisualEffectsEnsureScheduled = false;
|
||||
if (!mounted || !(ModalRoute.of(context)?.isCurrent ?? false)) {
|
||||
return;
|
||||
}
|
||||
_ensureRoomVisualEffectsEnabled();
|
||||
});
|
||||
}
|
||||
|
||||
void _clearTransientRoomVisualEffects({required String reason}) {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
final rtcProvider = Provider.of<RtcProvider>(context, listen: false);
|
||||
final currentRoomId =
|
||||
rtcProvider.currenRoom?.roomProfile?.roomProfile?.id?.trim() ?? '';
|
||||
|
||||
RoomEntranceHelper.clearQueue();
|
||||
Provider.of<GiftProvider>(context, listen: false).clearAllGiftData();
|
||||
Provider.of<GiftAnimationManager>(
|
||||
context,
|
||||
listen: false,
|
||||
).clearActiveAnimations();
|
||||
_clearLuckyGiftComboSessions();
|
||||
_clearGiftFlightBatchDedupeTimers();
|
||||
_clearGiftVisualBatchDedupeTimers();
|
||||
_giftSeatFlightController.clear();
|
||||
OverlayManager().removeRoom(roomId: currentRoomId);
|
||||
SCRoomEffectScheduler().clearDeferredTasks(reason: reason);
|
||||
SCGiftVapSvgaManager().stopPlayback();
|
||||
}
|
||||
|
||||
void _suspendRoomVisualEffects() {
|
||||
final rtcProvider = Provider.of<RtcProvider>(context, listen: false);
|
||||
final currentRoomId =
|
||||
rtcProvider.currenRoom?.roomProfile?.roomProfile?.id?.trim() ?? '';
|
||||
rtcProvider.setVoiceRoomRouteVisible(false);
|
||||
rtcProvider.setRoomVisualEffectsEnabled(false);
|
||||
|
||||
final rtmProvider = Provider.of<RtmProvider>(context, listen: false);
|
||||
@ -184,7 +319,7 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
||||
_clearGiftFlightBatchDedupeTimers();
|
||||
_clearGiftVisualBatchDedupeTimers();
|
||||
_giftSeatFlightController.clear();
|
||||
OverlayManager().removeRoom();
|
||||
OverlayManager().removeRoom(roomId: currentRoomId);
|
||||
SCRoomEffectScheduler().clearDeferredTasks(reason: 'voice_room_suspend');
|
||||
SCGiftVapSvgaManager().stopPlayback();
|
||||
unawaited(
|
||||
@ -252,6 +387,8 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
||||
return SCAppLocalizations.of(context)!.enterRoomFailedRetry;
|
||||
case RoomStartupFailureType.rtc:
|
||||
return SCAppLocalizations.of(context)!.voiceConnectionFailedRetry;
|
||||
case RoomStartupFailureType.kicked:
|
||||
return SCAppLocalizations.of(context)!.roomKickedEntryBlockedTips;
|
||||
case RoomStartupFailureType.entry:
|
||||
case RoomStartupFailureType.none:
|
||||
return SCAppLocalizations.of(context)!.operationFail;
|
||||
@ -365,6 +502,8 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
||||
// _buildPlayViews(),
|
||||
///幸运礼物中奖动画
|
||||
LuckGiftNomorAnimWidget(),
|
||||
_buildRoomGiftSvgaLayer(),
|
||||
_buildRoomGiftSeatFlightLayer(),
|
||||
const RoomGiftComboFloatingLayer(),
|
||||
],
|
||||
),
|
||||
@ -373,6 +512,43 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRoomGiftSvgaLayer() {
|
||||
if (!SCGlobalConfig.allowsHighCostAnimations) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Positioned.fill(
|
||||
child: Selector<RtcProvider, bool>(
|
||||
selector: (context, provider) => provider.shouldShowRoomVisualEffects,
|
||||
builder: (context, shouldShowRoomVisualEffects, child) {
|
||||
if (!shouldShowRoomVisualEffects) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return child!;
|
||||
},
|
||||
child: const VapPlusSvgaPlayer(tag: "room_gift"),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRoomGiftSeatFlightLayer() {
|
||||
return Positioned.fill(
|
||||
child: Selector<RtcProvider, bool>(
|
||||
selector: (context, provider) => provider.shouldShowRoomVisualEffects,
|
||||
builder: (context, shouldShowRoomVisualEffects, child) {
|
||||
if (!shouldShowRoomVisualEffects) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return RoomGiftSeatFlightOverlay(
|
||||
controller: _giftSeatFlightController,
|
||||
resolveTargetKey:
|
||||
(userId) =>
|
||||
context.read<RtcProvider>().getSeatGlobalKeyByIndex(userId),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
///消息
|
||||
Widget _buildChatView(String chatRoomKey) {
|
||||
return Expanded(
|
||||
@ -506,10 +682,8 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
||||
|
||||
///礼物上飘动画
|
||||
_floatingGiftListener(Msg msg) {
|
||||
if (!Provider.of<RtcProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).shouldShowRoomVisualEffects) {
|
||||
final rtcProvider = Provider.of<RtcProvider>(context, listen: false);
|
||||
if (!rtcProvider.shouldShowRoomVisualEffects) {
|
||||
return;
|
||||
}
|
||||
if (Provider.of<GiftProvider>(context, listen: false).hideLGiftAnimal) {
|
||||
@ -556,6 +730,7 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
||||
}
|
||||
|
||||
final giftPhoto = (msg.gift?.giftPhoto ?? "").trim();
|
||||
_enqueueCpGiftFloatingIfNeeded(msg);
|
||||
if (_supportsComboMilestoneEffects(msg)) {
|
||||
_handleComboMilestoneVisuals(msg, targetGroupKey);
|
||||
}
|
||||
@ -577,6 +752,48 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
||||
);
|
||||
}
|
||||
|
||||
void _enqueueCpGiftFloatingIfNeeded(Msg msg) {
|
||||
final gift = msg.gift;
|
||||
if (!scIsCpGift(gift) || gift == null) {
|
||||
return;
|
||||
}
|
||||
final quantity = msg.number ?? 0;
|
||||
if (quantity <= 0) {
|
||||
return;
|
||||
}
|
||||
final roomId = _resolveGiftFloatingRoomId(msg);
|
||||
OverlayManager().addMessage(
|
||||
SCFloatingMessage(
|
||||
type: 1,
|
||||
priority: 900,
|
||||
userId: msg.user?.id ?? "",
|
||||
toUserId: msg.toUser?.id ?? "",
|
||||
userAvatarUrl: msg.user?.userAvatar ?? "",
|
||||
toUserAvatarUrl: msg.toUser?.userAvatar ?? "",
|
||||
userName: msg.user?.userNickname ?? "",
|
||||
toUserName: msg.toUser?.userNickname ?? "",
|
||||
giftUrl: gift.giftPhoto ?? "",
|
||||
giftId: gift.id,
|
||||
giftTab: scCpRelationTypeForGift(gift),
|
||||
number: quantity,
|
||||
coins: quantity * (gift.giftCandy ?? 0),
|
||||
roomId: roomId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _resolveGiftFloatingRoomId(Msg msg) {
|
||||
final messageRoomId = (msg.msg ?? "").trim();
|
||||
if (messageRoomId.isNotEmpty) {
|
||||
return messageRoomId;
|
||||
}
|
||||
return Provider.of<RtcProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).currenRoom?.roomProfile?.roomProfile?.id ??
|
||||
"";
|
||||
}
|
||||
|
||||
void _luckyGiftRewardTickerListener(Msg msg) {
|
||||
if (!Provider.of<RtcProvider>(
|
||||
context,
|
||||
|
||||
@ -9,6 +9,7 @@ import 'package:yumi/modules/room/background/room_background_select_page.dart';
|
||||
import 'package:yumi/modules/room/background/room_background_upload_page.dart';
|
||||
import 'package:yumi/modules/room/edit/room_edit_page.dart';
|
||||
import 'package:yumi/modules/room/music/room_music_page.dart';
|
||||
import 'package:yumi/modules/room/rocket/room_rocket_rank_page.dart';
|
||||
import 'package:yumi/modules/room/them/room_theme_page.dart';
|
||||
import 'package:yumi/modules/room/voice_room_page.dart';
|
||||
import 'package:yumi/app/routes/sc_router_init.dart';
|
||||
@ -28,6 +29,7 @@ class VoiceRoomRoute implements SCIRouterProvider {
|
||||
static String roomBackgroundPreview = '/room/background/preview';
|
||||
static String roomBackgroundUpload = '/room/background/upload';
|
||||
static String roomMusic = '/room/music';
|
||||
static String roomRocketRank = '/room/rocket/rank';
|
||||
static int _voiceRoomRouteDepth = 0;
|
||||
|
||||
static bool get isVoiceRoomOpen => _voiceRoomRouteDepth > 0;
|
||||
@ -79,13 +81,15 @@ class VoiceRoomRoute implements SCIRouterProvider {
|
||||
final floatingGiftListener = rtmProvider.msgFloatingGiftListener;
|
||||
final luckyGiftRewardTickerListener =
|
||||
rtmProvider.msgLuckyGiftRewardTickerListener;
|
||||
final currentRoomId =
|
||||
rtcProvider.currenRoom?.roomProfile?.roomProfile?.id?.trim() ?? '';
|
||||
|
||||
rtcProvider.setRoomVisualEffectsEnabled(false);
|
||||
rtmProvider.msgFloatingGiftListener = null;
|
||||
rtmProvider.msgLuckyGiftRewardTickerListener = null;
|
||||
RoomEntranceHelper.clearQueue();
|
||||
giftAnimationManager.clearActiveAnimations();
|
||||
OverlayManager().removeRoom();
|
||||
OverlayManager().removeRoom(roomId: currentRoomId);
|
||||
SCRoomEffectScheduler().clearDeferredTasks(reason: 'room_background_route');
|
||||
SCGiftVapSvgaManager().stopPlayback();
|
||||
|
||||
@ -155,12 +159,14 @@ class VoiceRoomRoute implements SCIRouterProvider {
|
||||
static Future<T?> openRoomEdit<T>(
|
||||
BuildContext context, {
|
||||
required bool needRestCurrentRoomInfo,
|
||||
RoomEntryPreviewData? setupPreviewData,
|
||||
bool rootNavigator = true,
|
||||
}) {
|
||||
return Navigator.of(context, rootNavigator: rootNavigator).push<T>(
|
||||
_buildRoute<T>(
|
||||
RoomEditPage(
|
||||
needRestCurrentRoomInfo: needRestCurrentRoomInfo ? "true" : "false",
|
||||
setupPreviewData: setupPreviewData,
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -231,6 +237,33 @@ class VoiceRoomRoute implements SCIRouterProvider {
|
||||
);
|
||||
}
|
||||
|
||||
static Future<T?> openRoomRocketRank<T>(
|
||||
BuildContext context, {
|
||||
String? roomId,
|
||||
int initialLevel = 0,
|
||||
bool rootNavigator = false,
|
||||
}) {
|
||||
return Navigator.of(context, rootNavigator: rootNavigator).push<T>(
|
||||
_buildDarkRoute<T>(
|
||||
RoomRocketRankPage(roomId: roomId, initialLevel: initialLevel),
|
||||
settings: RouteSettings(name: roomRocketRank),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static Future<T?> openRoomRocketRankOn<T>(
|
||||
NavigatorState navigator, {
|
||||
String? roomId,
|
||||
int initialLevel = 0,
|
||||
}) {
|
||||
return navigator.push<T>(
|
||||
_buildDarkRoute<T>(
|
||||
RoomRocketRankPage(roomId: roomId, initialLevel: initialLevel),
|
||||
settings: RouteSettings(name: roomRocketRank),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initRouter(FluroRouter router) {
|
||||
router.define(
|
||||
@ -263,6 +296,10 @@ class VoiceRoomRoute implements SCIRouterProvider {
|
||||
roomMusic,
|
||||
handler: Handler(handlerFunc: (_, params) => const RoomMusicPage()),
|
||||
);
|
||||
router.define(
|
||||
roomRocketRank,
|
||||
handler: Handler(handlerFunc: (_, params) => const RoomRocketRankPage()),
|
||||
);
|
||||
router.define(
|
||||
roomEdit,
|
||||
handler: Handler(
|
||||
|
||||
@ -6,24 +6,24 @@ class BaishunLoadingView extends StatelessWidget {
|
||||
const BaishunLoadingView({
|
||||
super.key,
|
||||
this.message = 'Loading game...',
|
||||
this.backgroundColor = const Color(0xFF081915),
|
||||
});
|
||||
|
||||
final String message;
|
||||
final Color backgroundColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Positioned.fill(
|
||||
child: Container(
|
||||
color: Colors.black.withValues(alpha: 0.42),
|
||||
color: backgroundColor,
|
||||
alignment: Alignment.center,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 22.w, vertical: 18.w),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF102D28).withValues(alpha: 0.92),
|
||||
borderRadius: BorderRadius.circular(16.w),
|
||||
border: Border.all(
|
||||
color: Colors.white.withValues(alpha: 0.1),
|
||||
),
|
||||
border: Border.all(color: Colors.white.withValues(alpha: 0.1)),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
||||
@ -12,6 +12,7 @@ import 'package:yumi/modules/room_game/views/baishun_game_page.dart';
|
||||
import 'package:yumi/modules/room_game/views/leader_game_page.dart';
|
||||
import 'package:yumi/services/audio/rtc_manager.dart';
|
||||
import 'package:yumi/shared/tools/sc_lk_dialog_util.dart';
|
||||
import 'package:yumi/shared/tools/sc_room_top_layer_guard.dart';
|
||||
import 'package:yumi/ui_kit/components/custom_cached_image.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
||||
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
||||
@ -126,6 +127,7 @@ class _RoomGameListSheetState extends State<RoomGameListSheet> {
|
||||
_launchingGameId = game.gameId;
|
||||
});
|
||||
|
||||
SCRoomTopLayerGuardLease? gameDialogGuard;
|
||||
try {
|
||||
_log(
|
||||
'launch_request roomId=$_roomId gameId=${game.gameId} '
|
||||
@ -147,23 +149,38 @@ class _RoomGameListSheetState extends State<RoomGameListSheet> {
|
||||
return;
|
||||
}
|
||||
|
||||
gameDialogGuard = SCRoomTopLayerGuard().acquire(reason: 'room_game_page');
|
||||
Navigator.of(context).pop();
|
||||
await Future<void>.delayed(const Duration(milliseconds: 180));
|
||||
if (!widget.roomContext.mounted) {
|
||||
gameDialogGuard.release();
|
||||
gameDialogGuard = null;
|
||||
return;
|
||||
}
|
||||
final gamePage = _buildGamePage(game, launchModel);
|
||||
if (gamePage == null) {
|
||||
gameDialogGuard.release();
|
||||
gameDialogGuard = null;
|
||||
SCTts.show('This game provider is not wired yet');
|
||||
return;
|
||||
}
|
||||
showBottomInBottomDialog(
|
||||
widget.roomContext,
|
||||
gamePage,
|
||||
barrierColor: Colors.black54,
|
||||
barrierDismissible: true,
|
||||
);
|
||||
final activeGameDialogGuard = gameDialogGuard;
|
||||
gameDialogGuard = null;
|
||||
try {
|
||||
unawaited(
|
||||
showBottomInBottomDialog(
|
||||
widget.roomContext,
|
||||
gamePage,
|
||||
barrierColor: Colors.black54,
|
||||
barrierDismissible: true,
|
||||
).whenComplete(activeGameDialogGuard.release),
|
||||
);
|
||||
} catch (_) {
|
||||
activeGameDialogGuard.release();
|
||||
rethrow;
|
||||
}
|
||||
} catch (error) {
|
||||
gameDialogGuard?.release();
|
||||
_log('launch_error error=${_clip(error.toString(), 400)}');
|
||||
SCTts.show('Launch failed');
|
||||
} finally {
|
||||
|
||||
@ -535,17 +535,17 @@ class _SearchRoomListState extends State<SearchRoomList> {
|
||||
),
|
||||
),
|
||||
SizedBox(width: 5.w),
|
||||
(e.extValues?.roomSetting?.password?.isEmpty ?? false)
|
||||
e.extValues?.existsPassword != true
|
||||
? SCRoomLiveAudioIndicator(width: 14.w, height: 14.w)
|
||||
: Image.asset(
|
||||
"sc_images/index/sc_icon_room_suo.png",
|
||||
width: 20.w,
|
||||
height: 20.w,
|
||||
),
|
||||
(e.extValues?.roomSetting?.password?.isEmpty ?? false)
|
||||
e.extValues?.existsPassword != true
|
||||
? SizedBox(width: 3.w)
|
||||
: Container(height: 10.w),
|
||||
(e.extValues?.roomSetting?.password?.isEmpty ?? false)
|
||||
e.extValues?.existsPassword != true
|
||||
? text(
|
||||
e.displayMemberCount,
|
||||
fontSize: 10.sp,
|
||||
|
||||
@ -1,245 +1,156 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:yumi/ui_kit/components/appbar/socialchat_appbar.dart';
|
||||
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
|
||||
import 'package:yumi/services/localization/localization_manager.dart';
|
||||
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||
|
||||
///语言选择
|
||||
class LanguagePage extends StatefulWidget {
|
||||
@override
|
||||
_LanguagePageState createState() => _LanguagePageState();
|
||||
}
|
||||
|
||||
class _LanguagePageState extends State<LanguagePage> {
|
||||
int selectType = 1;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
String langCode = DataPersistence.getLang();
|
||||
if (langCode == "ar") {
|
||||
selectType = 1;
|
||||
}else if(langCode == "tr"){
|
||||
selectType = 2;
|
||||
}else if(langCode == "bn"){
|
||||
selectType = 3;
|
||||
} else {
|
||||
selectType = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
Image.asset(
|
||||
SCGlobalConfig.businessLogicStrategy.getLanguagePageBackgroundImage(),
|
||||
width: ScreenUtil().screenWidth,
|
||||
height: ScreenUtil().screenHeight,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
Scaffold(
|
||||
backgroundColor: SCGlobalConfig.businessLogicStrategy.getLanguagePageScaffoldBackgroundColor(),
|
||||
resizeToAvoidBottomInset: false,
|
||||
appBar: SocialChatStandardAppBar(
|
||||
title: SCAppLocalizations.of(context)!.language,
|
||||
actions: [],
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(width: 15.w),
|
||||
Expanded(
|
||||
child: text(
|
||||
"English",
|
||||
textColor: SCGlobalConfig.businessLogicStrategy.getLanguagePagePrimaryTextColor(),
|
||||
fontSize: 15.sp,
|
||||
),
|
||||
),
|
||||
Checkbox(
|
||||
value: selectType == 0,
|
||||
checkColor: SCGlobalConfig.businessLogicStrategy.getLanguagePageCheckColor(),
|
||||
activeColor: SCGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxActiveColor(),
|
||||
onChanged: (b) {
|
||||
if (selectType == 0) {
|
||||
selectType = 1;
|
||||
} else {
|
||||
selectType = 0;
|
||||
}
|
||||
setState(() {});
|
||||
if (b!) {
|
||||
Provider.of<LocalizationManager>(
|
||||
context,
|
||||
listen: false,
|
||||
).changeAppLanguage(const Locale('en', ''));
|
||||
}
|
||||
},
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(SCGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderRadius()),
|
||||
),
|
||||
side: WidgetStateBorderSide.resolveWith((states) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return BorderSide(
|
||||
color: SCGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderColor(),
|
||||
width: SCGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderWidth(),
|
||||
); // 选中时保留蓝色边框
|
||||
}
|
||||
return BorderSide(
|
||||
color: SCGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderColor(),
|
||||
width: SCGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderWidth(),
|
||||
); // 未选中时灰色边框
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(width: 15.w),
|
||||
Expanded(
|
||||
child: text(
|
||||
"العربية",
|
||||
textColor: SCGlobalConfig.businessLogicStrategy.getLanguagePagePrimaryTextColor(),
|
||||
fontSize: 15.sp,
|
||||
),
|
||||
),
|
||||
Checkbox(
|
||||
value: selectType == 1,
|
||||
checkColor: SCGlobalConfig.businessLogicStrategy.getLanguagePageCheckColor(),
|
||||
activeColor: SCGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxActiveColor(),
|
||||
onChanged: (b) {
|
||||
if (selectType == 1) {
|
||||
selectType = 0;
|
||||
} else {
|
||||
selectType = 1;
|
||||
}
|
||||
setState(() {});
|
||||
if (b!) {
|
||||
Provider.of<LocalizationManager>(
|
||||
context,
|
||||
listen: false,
|
||||
).changeAppLanguage(const Locale('ar', ''));
|
||||
}
|
||||
},
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(SCGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderRadius()),
|
||||
),
|
||||
side: WidgetStateBorderSide.resolveWith((states) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return BorderSide(
|
||||
color: SCGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderColor(),
|
||||
width: SCGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderWidth(),
|
||||
); // 选中时保留蓝色边框
|
||||
}
|
||||
return BorderSide(
|
||||
color: SCGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderColor(),
|
||||
width: SCGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderWidth(),
|
||||
); // 未选中时灰色边框
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(width: 15.w),
|
||||
Expanded(
|
||||
child: text(
|
||||
"Türkçe",
|
||||
textColor: SCGlobalConfig.businessLogicStrategy.getLanguagePagePrimaryTextColor(),
|
||||
fontSize: 15.sp,
|
||||
),
|
||||
),
|
||||
Checkbox(
|
||||
value: selectType == 2,
|
||||
checkColor: SCGlobalConfig.businessLogicStrategy.getLanguagePageCheckColor(),
|
||||
activeColor: SCGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxActiveColor(),
|
||||
onChanged: (b) {
|
||||
if (selectType == 2) {
|
||||
selectType = 0;
|
||||
} else {
|
||||
selectType = 2;
|
||||
}
|
||||
setState(() {});
|
||||
if (b!) {
|
||||
Provider.of<LocalizationManager>(
|
||||
context,
|
||||
listen: false,
|
||||
).changeAppLanguage(const Locale('tr', ''));
|
||||
}
|
||||
},
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(SCGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderRadius()),
|
||||
),
|
||||
side: WidgetStateBorderSide.resolveWith((states) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return BorderSide(
|
||||
color: SCGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderColor(),
|
||||
width: SCGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderWidth(),
|
||||
); // 选中时保留蓝色边框
|
||||
}
|
||||
return BorderSide(
|
||||
color: SCGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderColor(),
|
||||
width: SCGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderWidth(),
|
||||
); // 未选中时灰色边框
|
||||
}),
|
||||
),
|
||||
],
|
||||
), Row(
|
||||
children: [
|
||||
SizedBox(width: 15.w),
|
||||
Expanded(
|
||||
child: text(
|
||||
"বাংলা",
|
||||
textColor: SCGlobalConfig.businessLogicStrategy.getLanguagePagePrimaryTextColor(),
|
||||
fontSize: 15.sp,
|
||||
),
|
||||
),
|
||||
Checkbox(
|
||||
value: selectType == 3,
|
||||
checkColor: SCGlobalConfig.businessLogicStrategy.getLanguagePageCheckColor(),
|
||||
activeColor: SCGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxActiveColor(),
|
||||
onChanged: (b) {
|
||||
if (selectType == 3) {
|
||||
selectType = 0;
|
||||
} else {
|
||||
selectType = 3;
|
||||
}
|
||||
setState(() {});
|
||||
if (b!) {
|
||||
Provider.of<LocalizationManager>(
|
||||
context,
|
||||
listen: false,
|
||||
).changeAppLanguage(const Locale('bn', ''));
|
||||
}
|
||||
},
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(SCGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderRadius()),
|
||||
),
|
||||
side: WidgetStateBorderSide.resolveWith((states) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return BorderSide(
|
||||
color: SCGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderColor(),
|
||||
width: SCGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderWidth(),
|
||||
); // 选中时保留蓝色边框
|
||||
}
|
||||
return BorderSide(
|
||||
color: SCGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderColor(),
|
||||
width: SCGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderWidth(),
|
||||
); // 未选中时灰色边框
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
import 'package:yumi/services/localization/localization_manager.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
|
||||
import 'package:yumi/ui_kit/components/appbar/socialchat_appbar.dart';
|
||||
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
||||
|
||||
/// 语言选择
|
||||
class LanguagePage extends StatefulWidget {
|
||||
const LanguagePage({super.key});
|
||||
|
||||
@override
|
||||
State<LanguagePage> createState() => _LanguagePageState();
|
||||
}
|
||||
|
||||
class _LanguageOption {
|
||||
const _LanguageOption({required this.code, required this.label});
|
||||
|
||||
final String code;
|
||||
final String label;
|
||||
}
|
||||
|
||||
class _LanguagePageState extends State<LanguagePage> {
|
||||
static const List<_LanguageOption> _languageOptions = [
|
||||
_LanguageOption(code: 'en', label: 'English'),
|
||||
_LanguageOption(code: 'ar', label: 'العربية'),
|
||||
_LanguageOption(code: 'tr', label: 'Türkçe'),
|
||||
_LanguageOption(code: 'bn', label: 'বাংলা'),
|
||||
_LanguageOption(code: 'it', label: 'Italiano'),
|
||||
_LanguageOption(code: 'pt', label: 'Português'),
|
||||
_LanguageOption(code: 'fr', label: 'Français'),
|
||||
];
|
||||
|
||||
String _selectedLanguageCode = 'en';
|
||||
bool _didResolveInitialLanguage = false;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
if (!_didResolveInitialLanguage) {
|
||||
_selectedLanguageCode = _resolveCurrentLanguageCode();
|
||||
_didResolveInitialLanguage = true;
|
||||
}
|
||||
}
|
||||
|
||||
String _resolveCurrentLanguageCode() {
|
||||
final persistedLanguageCode = DataPersistence.getLang();
|
||||
if (SCAppLocalizations.isSupportedLanguageCode(persistedLanguageCode)) {
|
||||
return SCAppLocalizations.resolveSupportedLanguageCode(
|
||||
persistedLanguageCode,
|
||||
);
|
||||
}
|
||||
final currentLanguageCode =
|
||||
context.read<LocalizationManager>().locale?.languageCode ?? 'en';
|
||||
return SCAppLocalizations.resolveSupportedLanguageCode(currentLanguageCode);
|
||||
}
|
||||
|
||||
void _selectLanguage(String languageCode) {
|
||||
final resolvedLanguageCode =
|
||||
SCAppLocalizations.resolveSupportedLanguageCode(languageCode);
|
||||
if (_selectedLanguageCode == resolvedLanguageCode) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_selectedLanguageCode = resolvedLanguageCode;
|
||||
});
|
||||
context.read<LocalizationManager>().changeAppLanguage(
|
||||
Locale(resolvedLanguageCode, ''),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
Image.asset(
|
||||
SCGlobalConfig.businessLogicStrategy.getLanguagePageBackgroundImage(),
|
||||
width: ScreenUtil().screenWidth,
|
||||
height: ScreenUtil().screenHeight,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
Scaffold(
|
||||
backgroundColor:
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getLanguagePageScaffoldBackgroundColor(),
|
||||
resizeToAvoidBottomInset: false,
|
||||
appBar: SocialChatStandardAppBar(
|
||||
title: SCAppLocalizations.of(context)!.language,
|
||||
actions: [],
|
||||
),
|
||||
body: ListView.builder(
|
||||
padding: EdgeInsets.only(top: 4.w),
|
||||
itemCount: _languageOptions.length,
|
||||
itemBuilder: (context, index) {
|
||||
return _buildLanguageRow(_languageOptions[index]);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLanguageRow(_LanguageOption option) {
|
||||
final selected = _selectedLanguageCode == option.code;
|
||||
return InkWell(
|
||||
onTap: () => _selectLanguage(option.code),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(width: 15.w),
|
||||
Expanded(
|
||||
child: text(
|
||||
option.label,
|
||||
textColor:
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getLanguagePagePrimaryTextColor(),
|
||||
fontSize: 15.sp,
|
||||
),
|
||||
),
|
||||
Checkbox(
|
||||
value: selected,
|
||||
checkColor:
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getLanguagePageCheckColor(),
|
||||
activeColor:
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getLanguagePageCheckboxActiveColor(),
|
||||
onChanged: (checked) {
|
||||
if (checked == true) {
|
||||
_selectLanguage(option.code);
|
||||
}
|
||||
},
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getLanguagePageCheckboxBorderRadius(),
|
||||
),
|
||||
),
|
||||
side: WidgetStateBorderSide.resolveWith((states) {
|
||||
return BorderSide(
|
||||
color:
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getLanguagePageCheckboxBorderColor(),
|
||||
width:
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getLanguagePageCheckboxBorderWidth(),
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -178,7 +178,8 @@ class _SplashPageState extends State<SplashPage> {
|
||||
Image.asset(
|
||||
SCGlobalConfig.businessLogicStrategy.getSplashPageIcon(),
|
||||
width: 107.w,
|
||||
height: 159.w,
|
||||
height: 97.w,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
],
|
||||
);
|
||||
@ -448,13 +449,13 @@ class _SplashPageState extends State<SplashPage> {
|
||||
|
||||
Future<void> _goMainPage() async {
|
||||
try {
|
||||
await SCVersionUtils.checkReview();
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
var user = AccountStorage().getCurrentUser();
|
||||
var token = AccountStorage().getToken();
|
||||
if (user != null && token.isNotEmpty) {
|
||||
final canContinue = await SCVersionUtils.checkReview(context: context);
|
||||
if (!mounted || !canContinue) {
|
||||
return;
|
||||
}
|
||||
SCNavigatorUtils.push(context, SCRoutes.home, replace: true);
|
||||
return;
|
||||
}
|
||||
@ -464,6 +465,10 @@ class _SplashPageState extends State<SplashPage> {
|
||||
}
|
||||
if (restoredUser != null &&
|
||||
(restoredUser.token ?? "").trim().isNotEmpty) {
|
||||
final canContinue = await SCVersionUtils.checkReview(context: context);
|
||||
if (!mounted || !canContinue) {
|
||||
return;
|
||||
}
|
||||
SCNavigatorUtils.push(context, SCRoutes.home, replace: true);
|
||||
} else {
|
||||
SCNavigatorUtils.pushLoginIfNeeded(context, replace: true);
|
||||
|
||||
@ -1,17 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
||||
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:yumi/ui_kit/components/sc_page_list.dart';
|
||||
import 'package:yumi/shared/tools/sc_loading_manager.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_store_repository_imp.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/store_list_res.dart';
|
||||
import 'package:yumi/services/auth/user_profile_manager.dart';
|
||||
import 'package:yumi/ui_kit/widgets/store/props_store_chatbox_detail_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
|
||||
|
||||
@ -20,8 +13,10 @@ import '../../../shared/data_sources/models/enum/sc_props_type.dart';
|
||||
|
||||
///聊天气泡框
|
||||
class StoreChatboxPage extends SCPageList {
|
||||
const StoreChatboxPage({super.key});
|
||||
|
||||
@override
|
||||
_StoreChatboxPageState createState() => _StoreChatboxPageState();
|
||||
SCPageListState createState() => _StoreChatboxPageState();
|
||||
}
|
||||
|
||||
class _StoreChatboxPageState
|
||||
@ -41,7 +36,7 @@ class _StoreChatboxPageState
|
||||
crossAxisCount: gridViewCount,
|
||||
mainAxisSpacing: 12.w,
|
||||
crossAxisSpacing: 12.w,
|
||||
childAspectRatio: 0.78,
|
||||
childAspectRatio: 0.88,
|
||||
);
|
||||
loadData(1);
|
||||
}
|
||||
@ -59,106 +54,13 @@ class _StoreChatboxPageState
|
||||
|
||||
@override
|
||||
Widget buildItem(StoreListResBean res) {
|
||||
return GestureDetector(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
res.isSelecte
|
||||
? const Color(0xff18F2B1).withValues(alpha: 0.1)
|
||||
: SCGlobalConfig.businessLogicStrategy
|
||||
.getStoreItemBackgroundColor(),
|
||||
border: Border.all(
|
||||
color:
|
||||
res.isSelecte
|
||||
? SCGlobalConfig.businessLogicStrategy
|
||||
.getStoreItemSelectedBorderColor()
|
||||
: SCGlobalConfig.businessLogicStrategy
|
||||
.getStoreItemUnselectedBorderColor(),
|
||||
width: 1.w,
|
||||
),
|
||||
borderRadius: BorderRadius.all(Radius.circular(8.w)),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
SizedBox(height: 10.w),
|
||||
netImage(
|
||||
url: res.res.propsResources?.cover ?? "",
|
||||
height: 45.w,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
SizedBox(height: 3.w),
|
||||
buildStoreBagItemTitle(
|
||||
res.res.propsResources?.name ?? "",
|
||||
textColor: Colors.white,
|
||||
fontSize: 11.sp,
|
||||
),
|
||||
SizedBox(height: 3.w),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(width: 10.w),
|
||||
Expanded(
|
||||
child: Text.rich(
|
||||
textAlign: TextAlign.center,
|
||||
TextSpan(
|
||||
children: [
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: Image.asset(
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStoreItemGoldIcon(),
|
||||
width: 22.w,
|
||||
),
|
||||
),
|
||||
TextSpan(text: " "),
|
||||
disCount < 1
|
||||
? TextSpan(
|
||||
text: "${res.res.propsPrices![0].amount}",
|
||||
style: TextStyle(
|
||||
fontSize: 12.sp,
|
||||
color:
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStoreItemPriceTextColor(),
|
||||
fontWeight: FontWeight.w600,
|
||||
decoration: TextDecoration.lineThrough,
|
||||
),
|
||||
)
|
||||
: TextSpan(),
|
||||
disCount < 1 ? TextSpan(text: " ") : TextSpan(),
|
||||
TextSpan(
|
||||
text:
|
||||
"${((res.res.propsPrices![0].amount ?? 0) * disCount).toInt()}/${res.res.propsPrices![0].days} ${SCAppLocalizations.of(context)!.day}",
|
||||
style: TextStyle(
|
||||
fontSize: 12.sp,
|
||||
color:
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStoreItemPriceTextColor(),
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
strutStyle: StrutStyle(
|
||||
height: 1.3, // 行高倍数
|
||||
fontWeight: FontWeight.w500,
|
||||
forceStrutHeight: true, // 强制应用行高
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 10.w),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
_selectItem(res);
|
||||
_showDetail(res.res);
|
||||
},
|
||||
return SCStoreGridItemCard(
|
||||
res: res.res,
|
||||
discount: disCount,
|
||||
coverHeight: 45.w,
|
||||
previewWidth: 88.w,
|
||||
previewHeight: 58.w,
|
||||
onDetailsTap: () => _showDetail(res.res),
|
||||
);
|
||||
}
|
||||
|
||||
@ -196,40 +98,6 @@ class _StoreChatboxPageState
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _selectItem(StoreListResBean res) {
|
||||
for (var value in items) {
|
||||
value.isSelecte = false;
|
||||
}
|
||||
res.isSelecte = true;
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void _buy(StoreListResBean res) {
|
||||
if (res.res.propsPrices!.isEmpty) {
|
||||
return;
|
||||
}
|
||||
SCLoadingManager.show();
|
||||
num days = res.res.propsPrices![0].days ?? 0;
|
||||
SCStoreRepositoryImp()
|
||||
.storePurchasing(
|
||||
res.res.id ?? "",
|
||||
SCPropsType.CHAT_BUBBLE.name,
|
||||
SCCurrencyType.GOLD.name,
|
||||
"$days",
|
||||
)
|
||||
.then((value) {
|
||||
SCTts.show(SCAppLocalizations.of(context)!.purchaseIsSuccessful);
|
||||
Provider.of<SocialChatUserProfileManager>(
|
||||
context,
|
||||
listen: false,
|
||||
).updateBalance(value);
|
||||
SCLoadingManager.hide();
|
||||
})
|
||||
.catchError((e) {
|
||||
SCLoadingManager.hide();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class StoreListResBean {
|
||||
|
||||
107
lib/modules/store/data_card/store_data_card_page.dart
Normal file
@ -0,0 +1,107 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/store_list_res.dart';
|
||||
import 'package:yumi/shared/data_sources/models/enum/sc_currency_type.dart';
|
||||
import 'package:yumi/shared/data_sources/models/enum/sc_props_type.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_store_repository_imp.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_page_list.dart';
|
||||
import 'package:yumi/ui_kit/widgets/store/props_store_data_card_detail_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
|
||||
|
||||
class StoreDataCardPage extends SCPageList {
|
||||
const StoreDataCardPage({super.key});
|
||||
|
||||
@override
|
||||
StoreDataCardPageState createState() => StoreDataCardPageState();
|
||||
}
|
||||
|
||||
class StoreDataCardPageState
|
||||
extends SCPageListState<StoreDataCardListResBean, StoreDataCardPage> {
|
||||
double disCount = 1;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
enablePullUp = false;
|
||||
isGridView = true;
|
||||
gridViewCount = 3;
|
||||
padding = EdgeInsets.symmetric(horizontal: 6.w);
|
||||
backgroundColor = Colors.transparent;
|
||||
gridDelegate = SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: gridViewCount,
|
||||
mainAxisSpacing: 12.w,
|
||||
crossAxisSpacing: 12.w,
|
||||
childAspectRatio: 0.88,
|
||||
);
|
||||
loadData(1);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
body:
|
||||
items.isEmpty && isLoading
|
||||
? const SCStoreGridSkeleton()
|
||||
: buildList(context),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildItem(StoreDataCardListResBean item) {
|
||||
final res = item;
|
||||
return SCStoreGridItemCard(
|
||||
res: res.res,
|
||||
discount: disCount,
|
||||
previewKind: SCStoreItemPreviewKind.dataCard,
|
||||
coverWidth: 72.w,
|
||||
coverHeight: 50.w,
|
||||
previewWidth: 88.w,
|
||||
previewHeight: 58.w,
|
||||
coverFit: BoxFit.cover,
|
||||
previewFit: BoxFit.cover,
|
||||
coverBorderRadius: BorderRadius.circular(6.w),
|
||||
onDetailsTap: () => _showDetail(res.res),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
loadPage({
|
||||
required int page,
|
||||
required Function(List<StoreDataCardListResBean>) onSuccess,
|
||||
Function? onErr,
|
||||
}) async {
|
||||
try {
|
||||
final beans = <StoreDataCardListResBean>[];
|
||||
final storeList = await SCStoreRepositoryImp().storeList(
|
||||
SCCurrencyType.GOLD.name,
|
||||
SCPropsType.DATA_CARD.name,
|
||||
);
|
||||
for (final value in storeList) {
|
||||
beans.add(StoreDataCardListResBean(value, false));
|
||||
}
|
||||
onSuccess(beans);
|
||||
} catch (_) {
|
||||
onErr?.call();
|
||||
}
|
||||
}
|
||||
|
||||
void _showDetail(StoreListRes res) {
|
||||
SmartDialog.show(
|
||||
tag: "showPropsDetail",
|
||||
alignment: Alignment.bottomCenter,
|
||||
animationType: SmartAnimationType.fade,
|
||||
builder: (_) {
|
||||
return PropsStoreDataCardDetailDialog(res, disCount);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class StoreDataCardListResBean {
|
||||
StoreDataCardListResBean(this.res, this.isSelecte);
|
||||
|
||||
StoreListRes res;
|
||||
bool isSelecte;
|
||||
}
|
||||
@ -1,16 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
||||
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||
import 'package:yumi/shared/tools/sc_loading_manager.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_page_list.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_store_repository_imp.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/store_list_res.dart';
|
||||
import 'package:yumi/services/auth/user_profile_manager.dart';
|
||||
import 'package:yumi/ui_kit/widgets/store/props_store_headdress_detail_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
|
||||
|
||||
@ -19,8 +12,10 @@ import '../../../shared/data_sources/models/enum/sc_props_type.dart';
|
||||
|
||||
///头饰
|
||||
class StoreHeaddressPage extends SCPageList {
|
||||
const StoreHeaddressPage({super.key});
|
||||
|
||||
@override
|
||||
_StoreHeaddressPageState createState() => _StoreHeaddressPageState();
|
||||
SCPageListState createState() => _StoreHeaddressPageState();
|
||||
}
|
||||
|
||||
class _StoreHeaddressPageState
|
||||
@ -40,7 +35,7 @@ class _StoreHeaddressPageState
|
||||
crossAxisCount: gridViewCount,
|
||||
mainAxisSpacing: 12.w,
|
||||
crossAxisSpacing: 12.w,
|
||||
childAspectRatio: 0.78,
|
||||
childAspectRatio: 0.88,
|
||||
);
|
||||
loadData(1);
|
||||
}
|
||||
@ -58,106 +53,11 @@ class _StoreHeaddressPageState
|
||||
|
||||
@override
|
||||
Widget buildItem(StoreListResBean res) {
|
||||
return GestureDetector(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
res.isSelecte
|
||||
? const Color(0xff18F2B1).withValues(alpha: 0.1)
|
||||
: SCGlobalConfig.businessLogicStrategy
|
||||
.getStoreItemBackgroundColor(),
|
||||
border: Border.all(
|
||||
color:
|
||||
res.isSelecte
|
||||
? SCGlobalConfig.businessLogicStrategy
|
||||
.getStoreItemSelectedBorderColor()
|
||||
: SCGlobalConfig.businessLogicStrategy
|
||||
.getStoreItemUnselectedBorderColor(),
|
||||
width: 1.w,
|
||||
),
|
||||
borderRadius: BorderRadius.all(Radius.circular(8.w)),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
SizedBox(height: 10.w),
|
||||
netImage(
|
||||
url: res.res.propsResources?.cover ?? "",
|
||||
width: 55.w,
|
||||
height: 55.w,
|
||||
),
|
||||
SizedBox(height: 3.w),
|
||||
buildStoreBagItemTitle(
|
||||
res.res.propsResources?.name ?? "",
|
||||
textColor: Colors.white,
|
||||
fontSize: 11.sp,
|
||||
),
|
||||
SizedBox(height: 3.w),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(width: 10.w),
|
||||
Expanded(
|
||||
child: Text.rich(
|
||||
textAlign: TextAlign.center,
|
||||
TextSpan(
|
||||
children: [
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: Image.asset(
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStoreItemGoldIcon(),
|
||||
width: 22.w,
|
||||
),
|
||||
),
|
||||
TextSpan(text: " "),
|
||||
disCount < 1
|
||||
? TextSpan(
|
||||
text: "${res.res.propsPrices![0].amount}",
|
||||
style: TextStyle(
|
||||
fontSize: 12.sp,
|
||||
color:
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStoreItemPriceTextColor(),
|
||||
fontWeight: FontWeight.w600,
|
||||
decoration: TextDecoration.lineThrough,
|
||||
),
|
||||
)
|
||||
: TextSpan(),
|
||||
disCount < 1 ? TextSpan(text: " ") : TextSpan(),
|
||||
TextSpan(
|
||||
text:
|
||||
"${((res.res.propsPrices![0].amount ?? 0) * disCount).toInt()}/${res.res.propsPrices![0].days} ${SCAppLocalizations.of(context)!.day}",
|
||||
style: TextStyle(
|
||||
fontSize: 12.sp,
|
||||
color:
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStoreItemPriceTextColor(),
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
strutStyle: StrutStyle(
|
||||
height: 1.3, // 行高倍数
|
||||
fontWeight: FontWeight.w500,
|
||||
forceStrutHeight: true, // 强制应用行高
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 10.w),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
_selectItem(res);
|
||||
_showDetail(res.res);
|
||||
},
|
||||
return SCStoreGridItemCard(
|
||||
res: res.res,
|
||||
discount: disCount,
|
||||
previewKind: SCStoreItemPreviewKind.avatarFrame,
|
||||
onDetailsTap: () => _showDetail(res.res),
|
||||
);
|
||||
}
|
||||
|
||||
@ -195,40 +95,6 @@ class _StoreHeaddressPageState
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _selectItem(StoreListResBean res) {
|
||||
for (var value in items) {
|
||||
value.isSelecte = false;
|
||||
}
|
||||
res.isSelecte = true;
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void _buy(StoreListResBean res) {
|
||||
if (res.res.propsPrices!.isEmpty) {
|
||||
return;
|
||||
}
|
||||
SCLoadingManager.show();
|
||||
num days = res.res.propsPrices![0].days ?? 0;
|
||||
SCStoreRepositoryImp()
|
||||
.storePurchasing(
|
||||
res.res.id ?? "",
|
||||
SCPropsType.AVATAR_FRAME.name,
|
||||
SCCurrencyType.GOLD.name,
|
||||
"$days",
|
||||
)
|
||||
.then((value) {
|
||||
SCTts.show(SCAppLocalizations.of(context)!.purchaseIsSuccessful);
|
||||
Provider.of<SocialChatUserProfileManager>(
|
||||
context,
|
||||
listen: false,
|
||||
).updateBalance(value);
|
||||
SCLoadingManager.hide();
|
||||
})
|
||||
.catchError((e) {
|
||||
SCLoadingManager.hide();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class StoreListResBean {
|
||||
|
||||
@ -1,10 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_page_list.dart';
|
||||
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_store_repository_imp.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/store_list_res.dart';
|
||||
import 'package:yumi/ui_kit/widgets/store/props_store_mountains_detail_dialog.dart';
|
||||
@ -16,10 +13,10 @@ import '../../../shared/data_sources/models/enum/sc_props_type.dart';
|
||||
|
||||
///坐骑
|
||||
class StoreMountainsPage extends SCPageList {
|
||||
StoreMountainsPage();
|
||||
const StoreMountainsPage({super.key});
|
||||
|
||||
@override
|
||||
_StoreMountainsPageState createState() => _StoreMountainsPageState();
|
||||
SCPageListState createState() => _StoreMountainsPageState();
|
||||
}
|
||||
|
||||
class _StoreMountainsPageState
|
||||
@ -39,16 +36,11 @@ class _StoreMountainsPageState
|
||||
crossAxisCount: gridViewCount,
|
||||
mainAxisSpacing: 12.w,
|
||||
crossAxisSpacing: 12.w,
|
||||
childAspectRatio: 0.78,
|
||||
childAspectRatio: 0.88,
|
||||
);
|
||||
loadData(1);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@ -62,106 +54,10 @@ class _StoreMountainsPageState
|
||||
|
||||
@override
|
||||
Widget buildItem(StoreListResBean res) {
|
||||
return GestureDetector(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
res.isSelecte
|
||||
? const Color(0xff18F2B1).withValues(alpha: 0.1)
|
||||
: SCGlobalConfig.businessLogicStrategy
|
||||
.getStoreItemBackgroundColor(),
|
||||
border: Border.all(
|
||||
color:
|
||||
res.isSelecte
|
||||
? SCGlobalConfig.businessLogicStrategy
|
||||
.getStoreItemSelectedBorderColor()
|
||||
: SCGlobalConfig.businessLogicStrategy
|
||||
.getStoreItemUnselectedBorderColor(),
|
||||
width: 1.w,
|
||||
),
|
||||
borderRadius: BorderRadius.all(Radius.circular(8.w)),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
SizedBox(height: 10.w),
|
||||
netImage(
|
||||
url: res.res.propsResources?.cover ?? "",
|
||||
width: 55.w,
|
||||
height: 55.w,
|
||||
),
|
||||
SizedBox(height: 3.w),
|
||||
buildStoreBagItemTitle(
|
||||
res.res.propsResources?.name ?? "",
|
||||
textColor: Colors.white,
|
||||
fontSize: 11.sp,
|
||||
),
|
||||
SizedBox(height: 3.w),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(width: 10.w),
|
||||
Expanded(
|
||||
child: Text.rich(
|
||||
textAlign: TextAlign.center,
|
||||
TextSpan(
|
||||
children: [
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: Image.asset(
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStoreItemGoldIcon(),
|
||||
width: 22.w,
|
||||
),
|
||||
),
|
||||
TextSpan(text: " "),
|
||||
disCount < 1
|
||||
? TextSpan(
|
||||
text: "${res.res.propsPrices![0].amount}",
|
||||
style: TextStyle(
|
||||
fontSize: 12.sp,
|
||||
color:
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStoreItemPriceTextColor(),
|
||||
fontWeight: FontWeight.w600,
|
||||
decoration: TextDecoration.lineThrough,
|
||||
),
|
||||
)
|
||||
: TextSpan(),
|
||||
disCount < 1 ? TextSpan(text: " ") : TextSpan(),
|
||||
TextSpan(
|
||||
text:
|
||||
"${((res.res.propsPrices![0].amount ?? 0) * disCount).toInt()}/${res.res.propsPrices![0].days} ${SCAppLocalizations.of(context)!.day}",
|
||||
style: TextStyle(
|
||||
fontSize: 12.sp,
|
||||
color:
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStoreItemPriceTextColor(),
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
strutStyle: StrutStyle(
|
||||
height: 1.3, // 行高倍数
|
||||
fontWeight: FontWeight.w500,
|
||||
forceStrutHeight: true, // 强制应用行高
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 10.w),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
_selectItem(res);
|
||||
_showDetail(res.res);
|
||||
},
|
||||
return SCStoreGridItemCard(
|
||||
res: res.res,
|
||||
discount: disCount,
|
||||
onDetailsTap: () => _showDetail(res.res),
|
||||
);
|
||||
}
|
||||
|
||||
@ -199,12 +95,4 @@ class _StoreMountainsPageState
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _selectItem(StoreListResBean res) {
|
||||
for (var value in items) {
|
||||
value.isSelecte = false;
|
||||
}
|
||||
res.isSelecte = true;
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,22 +1,15 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
||||
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
||||
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||
import 'package:yumi/modules/store/store_route.dart';
|
||||
import 'package:yumi/modules/store/theme/store_theme_page.dart';
|
||||
import 'package:yumi/modules/store/data_card/store_data_card_page.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tancent_vap/utils/constant.dart';
|
||||
import 'package:tancent_vap/widgets/vap_view.dart';
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
import 'package:yumi/ui_kit/components/appbar/socialchat_appbar.dart';
|
||||
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||
import 'package:yumi/shared/tools/sc_path_utils.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/local/file_cache_manager.dart';
|
||||
import 'package:yumi/services/auth/user_profile_manager.dart';
|
||||
import 'package:yumi/ui_kit/widgets/headdress/headdress_widget.dart';
|
||||
import 'package:yumi/modules/wallet/wallet_route.dart';
|
||||
import 'package:yumi/modules/store/chatbox/store_chatbox_page.dart';
|
||||
import 'package:yumi/modules/store/headdress/store_headdress_page.dart';
|
||||
@ -25,8 +18,10 @@ import 'package:yumi/modules/store/mountains/store_mountains_page.dart';
|
||||
import '../../shared/business_logic/usecases/sc_fixed_width_tabIndicator.dart';
|
||||
|
||||
class StorePage extends StatefulWidget {
|
||||
const StorePage({super.key});
|
||||
|
||||
@override
|
||||
_StorePageState createState() => _StorePageState();
|
||||
State<StorePage> createState() => _StorePageState();
|
||||
}
|
||||
|
||||
class _StorePageState extends State<StorePage>
|
||||
@ -40,16 +35,11 @@ class _StorePageState extends State<StorePage>
|
||||
void initState() {
|
||||
super.initState();
|
||||
_pages.add(StoreHeaddressPage());
|
||||
_pages.add(
|
||||
StoreMountainsPage(),
|
||||
);
|
||||
_pages.add(
|
||||
StoreThemePage(),
|
||||
);
|
||||
_pages.add(StoreMountainsPage());
|
||||
_pages.add(const StoreDataCardPage());
|
||||
_pages.add(StoreChatboxPage());
|
||||
_tabController = TabController(length: _pages.length, vsync: this);
|
||||
_tabController.addListener(() {
|
||||
});
|
||||
_tabController.addListener(() {});
|
||||
Provider.of<SocialChatUserProfileManager>(context, listen: false).balance();
|
||||
}
|
||||
|
||||
@ -64,7 +54,7 @@ class _StorePageState extends State<StorePage>
|
||||
_tabs.clear();
|
||||
_tabs.add(Tab(text: SCAppLocalizations.of(context)!.headdress));
|
||||
_tabs.add(Tab(text: SCAppLocalizations.of(context)!.mountains));
|
||||
_tabs.add(Tab(text: SCAppLocalizations.of(context)!.theme));
|
||||
_tabs.add(const Tab(text: 'Profile Card'));
|
||||
_tabs.add(Tab(text: SCAppLocalizations.of(context)!.chatBox));
|
||||
return Stack(
|
||||
children: [
|
||||
@ -86,10 +76,10 @@ class _StorePageState extends State<StorePage>
|
||||
padding: EdgeInsets.all(5.w),
|
||||
margin: EdgeInsetsDirectional.only(
|
||||
end:
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStorePageShoppingBagMargin()
|
||||
.end
|
||||
.w,
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStorePageShoppingBagMargin()
|
||||
.end
|
||||
.w,
|
||||
),
|
||||
child: Image.asset(
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
@ -122,12 +112,12 @@ class _StorePageState extends State<StorePage>
|
||||
unselectedLabelStyle: SCGlobalConfig.businessLogicStrategy
|
||||
.getStorePageTabUnselectedLabelStyle()
|
||||
.copyWith(
|
||||
fontSize:
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStorePageTabUnselectedLabelStyle()
|
||||
.fontSize
|
||||
?.sp,
|
||||
),
|
||||
fontSize:
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStorePageTabUnselectedLabelStyle()
|
||||
.fontSize
|
||||
?.sp,
|
||||
),
|
||||
// indicatorPadding: EdgeInsets.symmetric(
|
||||
// vertical: 5.w,
|
||||
// horizontal: 15.w,
|
||||
@ -135,12 +125,12 @@ class _StorePageState extends State<StorePage>
|
||||
indicator: SCFixedWidthTabIndicator(
|
||||
width: 15.w,
|
||||
color:
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStorePageTabIndicatorColor(),
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStorePageTabIndicatorColor(),
|
||||
),
|
||||
dividerColor:
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStorePageTabDividerColor(),
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStorePageTabDividerColor(),
|
||||
controller: _tabController,
|
||||
tabs: _tabs,
|
||||
),
|
||||
@ -158,48 +148,48 @@ class _StorePageState extends State<StorePage>
|
||||
return GestureDetector(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(12.w),
|
||||
topRight: Radius.circular(12.w),
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(12.w),
|
||||
topRight: Radius.circular(12.w),
|
||||
),
|
||||
color: Color(0xff18F2B1).withValues(alpha: 0.1),
|
||||
),
|
||||
padding: EdgeInsets.only(bottom: 12.w, top: 12.w),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(width: 10.w),
|
||||
Image.asset(
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStorePageGoldIcon(),
|
||||
width: 25.w,
|
||||
height: 25.w,
|
||||
),
|
||||
color: Color(0xff18F2B1).withOpacity(0.1),
|
||||
SizedBox(width: 5.w),
|
||||
text(
|
||||
"${ref.myBalance}",
|
||||
fontSize: 14.sp,
|
||||
textColor:
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStorePageGoldTextColor(),
|
||||
),
|
||||
SizedBox(width: 4.w),
|
||||
Icon(
|
||||
Icons.chevron_right,
|
||||
size: 20.w,
|
||||
color:
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStorePageGoldIconColor(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
padding: EdgeInsets.only(bottom: 12.w,top: 12.w),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(width: 10.w),
|
||||
Image.asset(
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStorePageGoldIcon(),
|
||||
width: 25.w,
|
||||
height: 25.w,
|
||||
),
|
||||
SizedBox(width: 5.w),
|
||||
text(
|
||||
"${ref.myBalance}",
|
||||
fontSize: 14.sp,
|
||||
textColor:
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStorePageGoldTextColor(),
|
||||
),
|
||||
SizedBox(width: 4.w),
|
||||
Icon(
|
||||
Icons.chevron_right,
|
||||
size: 20.w,
|
||||
color:
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStorePageGoldIconColor(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
SCNavigatorUtils.push(
|
||||
context,
|
||||
WalletRoute.recharge,
|
||||
replace: false,
|
||||
);
|
||||
},
|
||||
onTap: () {
|
||||
SCNavigatorUtils.push(
|
||||
context,
|
||||
WalletRoute.recharge,
|
||||
replace: false,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@ -1,16 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_page_list.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
||||
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_store_repository_imp.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/store_list_res.dart';
|
||||
import 'package:yumi/services/auth/user_profile_manager.dart';
|
||||
import 'package:yumi/ui_kit/widgets/store/props_store_theme_detail_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
|
||||
import 'package:yumi/modules/store/headdress/store_headdress_page.dart';
|
||||
@ -20,10 +14,10 @@ import '../../../shared/data_sources/models/enum/sc_props_type.dart';
|
||||
|
||||
///房间主题
|
||||
class StoreThemePage extends SCPageList {
|
||||
StoreThemePage();
|
||||
const StoreThemePage({super.key});
|
||||
|
||||
@override
|
||||
_StoreThemePagePageState createState() => _StoreThemePagePageState();
|
||||
SCPageListState createState() => _StoreThemePagePageState();
|
||||
}
|
||||
|
||||
class _StoreThemePagePageState
|
||||
@ -43,7 +37,7 @@ class _StoreThemePagePageState
|
||||
crossAxisCount: gridViewCount,
|
||||
mainAxisSpacing: 12.w,
|
||||
crossAxisSpacing: 12.w,
|
||||
childAspectRatio: 0.78,
|
||||
childAspectRatio: 0.88,
|
||||
);
|
||||
loadData(1);
|
||||
}
|
||||
@ -61,107 +55,11 @@ class _StoreThemePagePageState
|
||||
|
||||
@override
|
||||
Widget buildItem(StoreListResBean res) {
|
||||
return GestureDetector(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
res.isSelecte
|
||||
? const Color(0xff18F2B1).withValues(alpha: 0.1)
|
||||
: SCGlobalConfig.businessLogicStrategy
|
||||
.getStoreItemBackgroundColor(),
|
||||
border: Border.all(
|
||||
color:
|
||||
res.isSelecte
|
||||
? SCGlobalConfig.businessLogicStrategy
|
||||
.getStoreItemSelectedBorderColor()
|
||||
: SCGlobalConfig.businessLogicStrategy
|
||||
.getStoreItemUnselectedBorderColor(),
|
||||
width: 1.w,
|
||||
),
|
||||
borderRadius: BorderRadius.all(Radius.circular(8.w)),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
SizedBox(height: 10.w),
|
||||
netImage(
|
||||
url: res.res.propsResources?.cover ?? "",
|
||||
width: 55.w,
|
||||
height: 55.w,
|
||||
borderRadius: BorderRadius.all(Radius.circular(8.w)),
|
||||
),
|
||||
SizedBox(height: 3.w),
|
||||
buildStoreBagItemTitle(
|
||||
res.res.propsResources?.name ?? "",
|
||||
textColor: Colors.white,
|
||||
fontSize: 11.sp,
|
||||
),
|
||||
SizedBox(height: 3.w),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(width: 10.w),
|
||||
Expanded(
|
||||
child: Text.rich(
|
||||
textAlign: TextAlign.center,
|
||||
TextSpan(
|
||||
children: [
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: Image.asset(
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStoreItemGoldIcon(),
|
||||
width: 22.w,
|
||||
),
|
||||
),
|
||||
TextSpan(text: " "),
|
||||
disCount < 1
|
||||
? TextSpan(
|
||||
text: "${res.res.propsPrices![0].amount}",
|
||||
style: TextStyle(
|
||||
fontSize: 12.sp,
|
||||
color:
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStoreItemPriceTextColor(),
|
||||
fontWeight: FontWeight.w600,
|
||||
decoration: TextDecoration.lineThrough,
|
||||
),
|
||||
)
|
||||
: TextSpan(),
|
||||
disCount < 1 ? TextSpan(text: " ") : TextSpan(),
|
||||
TextSpan(
|
||||
text:
|
||||
"${((res.res.propsPrices![0].amount ?? 0) * disCount).toInt()}/${res.res.propsPrices![0].days} ${SCAppLocalizations.of(context)!.day}",
|
||||
style: TextStyle(
|
||||
fontSize: 12.sp,
|
||||
color:
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStoreItemPriceTextColor(),
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
strutStyle: StrutStyle(
|
||||
height: 1.3, // 行高倍数
|
||||
fontWeight: FontWeight.w500,
|
||||
forceStrutHeight: true, // 强制应用行高
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 10.w),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
_selectItem(res);
|
||||
_showDetail(res.res);
|
||||
},
|
||||
return SCStoreGridItemCard(
|
||||
res: res.res,
|
||||
discount: disCount,
|
||||
coverBorderRadius: BorderRadius.all(Radius.circular(8.w)),
|
||||
onDetailsTap: () => _showDetail(res.res),
|
||||
);
|
||||
}
|
||||
|
||||
@ -199,35 +97,4 @@ class _StoreThemePagePageState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _selectItem(StoreListResBean res) {
|
||||
for (var value in items) {
|
||||
value.isSelecte = false;
|
||||
}
|
||||
res.isSelecte = true;
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void _buy(StoreListResBean res) {
|
||||
num days = 0;
|
||||
if (res.res.propsPrices!.length > 1) {
|
||||
days = res.res.propsPrices![1].days ?? 0;
|
||||
} else {
|
||||
days = res.res.propsPrices![0].days ?? 0;
|
||||
}
|
||||
SCStoreRepositoryImp()
|
||||
.storePurchasing(
|
||||
res.res.id ?? "",
|
||||
SCPropsType.THEME.name,
|
||||
SCCurrencyType.GOLD.name,
|
||||
"$days",
|
||||
)
|
||||
.then((value) {
|
||||
SCTts.show(SCAppLocalizations.of(context)!.purchaseIsSuccessful);
|
||||
Provider.of<SocialChatUserProfileManager>(
|
||||
context,
|
||||
listen: false,
|
||||
).updateBalance(value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,10 +53,14 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
||||
nickName = currentProfile?.userNickname ?? "";
|
||||
autograph = currentProfile?.autograph ?? "";
|
||||
hobby = currentProfile?.hobby ?? "";
|
||||
birthdayDate = _birthdayFromProfile(currentProfile);
|
||||
bornMonth = _formatTwoDigits(currentProfile?.bornMonth);
|
||||
bornDay = _formatTwoDigits(currentProfile?.bornDay);
|
||||
bornYear = _formatYear(currentProfile?.bornYear);
|
||||
birthdayDate =
|
||||
_birthdayFromProfile(currentProfile) ??
|
||||
_birthdayFromAge(currentProfile?.age);
|
||||
bornMonth = _formatTwoDigits(
|
||||
currentProfile?.bornMonth ?? birthdayDate?.month,
|
||||
);
|
||||
bornDay = _formatTwoDigits(currentProfile?.bornDay ?? birthdayDate?.day);
|
||||
bornYear = _formatYear(currentProfile?.bornYear ?? birthdayDate?.year);
|
||||
age = currentProfile?.age ?? _ageFromBirthday(birthdayDate);
|
||||
sex = currentProfile?.userSex;
|
||||
}
|
||||
@ -125,6 +129,15 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
||||
return (DateTime.now().year - date.year).abs();
|
||||
}
|
||||
|
||||
DateTime? _birthdayFromAge(num? age) {
|
||||
final ageValue = age?.toInt() ?? 0;
|
||||
if (ageValue <= 0) {
|
||||
return null;
|
||||
}
|
||||
final now = DateTime.now();
|
||||
return DateTime(now.year - ageValue, now.month, now.day);
|
||||
}
|
||||
|
||||
String _birthdayDisplayText() {
|
||||
if ((bornYear ?? "").isEmpty ||
|
||||
(bornMonth ?? "").isEmpty ||
|
||||
@ -150,10 +163,13 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
||||
autograph = profile.autograph ?? autograph;
|
||||
hobby = profile.hobby ?? hobby;
|
||||
sex = profile.userSex ?? sex;
|
||||
birthdayDate = _birthdayFromProfile(profile) ?? birthdayDate;
|
||||
bornYear = _formatYear(profile.bornYear);
|
||||
bornMonth = _formatTwoDigits(profile.bornMonth);
|
||||
bornDay = _formatTwoDigits(profile.bornDay);
|
||||
birthdayDate =
|
||||
_birthdayFromProfile(profile) ??
|
||||
_birthdayFromAge(profile.age) ??
|
||||
birthdayDate;
|
||||
bornYear = _formatYear(profile.bornYear ?? birthdayDate?.year);
|
||||
bornMonth = _formatTwoDigits(profile.bornMonth ?? birthdayDate?.month);
|
||||
bornDay = _formatTwoDigits(profile.bornDay ?? birthdayDate?.day);
|
||||
age = profile.age ?? _ageFromBirthday(birthdayDate) ?? age;
|
||||
}
|
||||
|
||||
@ -243,9 +259,7 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
||||
sex == 1
|
||||
? SCAppLocalizations.of(context)!.man
|
||||
: SCAppLocalizations.of(context)!.woman,
|
||||
() {
|
||||
_showSexDialog();
|
||||
},
|
||||
null,
|
||||
),
|
||||
|
||||
_buildItem(
|
||||
@ -522,6 +536,7 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
||||
}
|
||||
}
|
||||
|
||||
// ignore: unused_element
|
||||
void _showSexDialog() {
|
||||
showCenterDialog(
|
||||
context,
|
||||
@ -603,7 +618,7 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
||||
);
|
||||
}
|
||||
|
||||
_buildItem(String title, String value, Function() func) {
|
||||
_buildItem(String title, String value, Function()? func) {
|
||||
return SCDebounceWidget(
|
||||
child: Column(
|
||||
children: [
|
||||
@ -622,11 +637,12 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
||||
child: text(value, textColor: Colors.white, fontSize: 15.sp),
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.keyboard_arrow_right,
|
||||
size: 20.w,
|
||||
color: Colors.white70,
|
||||
),
|
||||
if (func != null)
|
||||
Icon(
|
||||
Icons.keyboard_arrow_right,
|
||||
size: 20.w,
|
||||
color: Colors.white70,
|
||||
),
|
||||
SizedBox(width: 8.w),
|
||||
],
|
||||
),
|
||||
@ -640,7 +656,7 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
func.call();
|
||||
func?.call();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -110,18 +110,26 @@ class _UserLevelPageState extends State<UserLevelPage> {
|
||||
],
|
||||
),
|
||||
SizedBox(height: 3.w),
|
||||
Stack(
|
||||
children: [
|
||||
Container(
|
||||
width: 185.w,
|
||||
height: 6.w,
|
||||
Stack(
|
||||
children: [
|
||||
Container(
|
||||
width: 185.w,
|
||||
height: 6.w,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: Colors.white24,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
color: Colors.white24,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: getLevelExperience(185.w),
|
||||
height: 6.w,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: SocialChatTheme.primaryLight,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 5.w),
|
||||
Row(
|
||||
children: [
|
||||
@ -150,19 +158,7 @@ class _UserLevelPageState extends State<UserLevelPage> {
|
||||
],
|
||||
),
|
||||
),
|
||||
text(
|
||||
SCAppLocalizations.of(context)!.medalAndAvatarFrameRewards,
|
||||
fontSize: 20.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
textColor: SocialChatTheme.primaryLight,
|
||||
),
|
||||
text(
|
||||
SCAppLocalizations.of(context)!.higherLevelFancierAvatarFrame,
|
||||
fontSize: 12.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
Image.asset("sc_images/level/sc_icon_user_level_center_bg_1.png"),
|
||||
Image.asset("sc_images/level/sc_icon_user_level_center_bg_1.png"),
|
||||
text(
|
||||
SCAppLocalizations.of(context)!.howToUpgrade,
|
||||
fontSize: 20.sp,
|
||||
@ -170,14 +166,15 @@ class _UserLevelPageState extends State<UserLevelPage> {
|
||||
textColor: SocialChatTheme.primaryLight,
|
||||
),
|
||||
SizedBox(height: 3),
|
||||
text(
|
||||
SCAppLocalizations.of(
|
||||
context,
|
||||
)!.spendCoinsToGainExperiencePoints,
|
||||
fontSize: 12.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
text(
|
||||
SCAppLocalizations.of(context)!.charmLevelUpgradeAnswer,
|
||||
fontSize: 12.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
textColor: Colors.white,
|
||||
maxLines: 2,
|
||||
maxWidth: 320.w,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 25.w),
|
||||
GestureDetector(
|
||||
child: Container(
|
||||
@ -205,12 +202,15 @@ class _UserLevelPageState extends State<UserLevelPage> {
|
||||
: Container();
|
||||
}
|
||||
|
||||
double getLevelExperience(double width) {
|
||||
int thatExp = SCStringUtils.convertToInteger(res?.thatExperience ?? "0");
|
||||
int nexExp = SCStringUtils.convertToInteger(res?.nextExperience ?? "0");
|
||||
int levelExp = thatExp + nexExp;
|
||||
return (thatExp / levelExp) * width;
|
||||
}
|
||||
double getLevelExperience(double width) {
|
||||
int thatExp = SCStringUtils.convertToInteger(res?.thatExperience ?? "0");
|
||||
int nexExp = SCStringUtils.convertToInteger(res?.nextExperience ?? "0");
|
||||
int levelExp = thatExp + nexExp;
|
||||
if (levelExp <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return (thatExp / levelExp) * width;
|
||||
}
|
||||
|
||||
num getLevelTotalExperience() {
|
||||
int thatExp = SCStringUtils.convertToInteger(res?.thatExperience ?? "0");
|
||||
|
||||
@ -110,18 +110,26 @@ class _WealthLevelPageState extends State<WealthLevelPage> {
|
||||
],
|
||||
),
|
||||
SizedBox(height: 3.w),
|
||||
Stack(
|
||||
children: [
|
||||
Container(
|
||||
width: 185.w,
|
||||
height: 6.w,
|
||||
Stack(
|
||||
children: [
|
||||
Container(
|
||||
width: 185.w,
|
||||
height: 6.w,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: Colors.white24,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
color: Colors.white24,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: getLevelExperience(185.w),
|
||||
height: 6.w,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: SocialChatTheme.primaryLight,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 5.w),
|
||||
Row(
|
||||
children: [
|
||||
@ -150,19 +158,7 @@ class _WealthLevelPageState extends State<WealthLevelPage> {
|
||||
],
|
||||
),
|
||||
),
|
||||
text(
|
||||
SCAppLocalizations.of(context)!.medalAndAvatarFrameRewards,
|
||||
fontSize: 20.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
textColor: SocialChatTheme.primaryLight,
|
||||
),
|
||||
text(
|
||||
SCAppLocalizations.of(context)!.higherLevelFancierAvatarFrame,
|
||||
fontSize: 12.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
Image.asset("sc_images/level/sc_icon_user_wealth_center_bg_1.png"),
|
||||
Image.asset("sc_images/level/sc_icon_user_wealth_center_bg_1.png"),
|
||||
text(
|
||||
SCAppLocalizations.of(context)!.howToUpgrade,
|
||||
fontSize: 20.sp,
|
||||
@ -170,14 +166,15 @@ class _WealthLevelPageState extends State<WealthLevelPage> {
|
||||
textColor: SocialChatTheme.primaryLight,
|
||||
),
|
||||
SizedBox(height: 3),
|
||||
text(
|
||||
SCAppLocalizations.of(
|
||||
context,
|
||||
)!.spendCoinsToGainExperiencePoints,
|
||||
fontSize: 12.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
text(
|
||||
SCAppLocalizations.of(context)!.richLevelUpgradeAnswer,
|
||||
fontSize: 12.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
textColor: Colors.white,
|
||||
maxLines: 2,
|
||||
maxWidth: 320.w,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 25.w),
|
||||
GestureDetector(
|
||||
child: Container(
|
||||
@ -205,12 +202,15 @@ class _WealthLevelPageState extends State<WealthLevelPage> {
|
||||
: Container();
|
||||
}
|
||||
|
||||
double getLevelExperience(double width) {
|
||||
int thatExp = SCStringUtils.convertToInteger(res?.thatExperience ?? "0");
|
||||
int nexExp = SCStringUtils.convertToInteger(res?.nextExperience ?? "0");
|
||||
int levelExp = thatExp + nexExp;
|
||||
return (thatExp / levelExp) * width;
|
||||
}
|
||||
double getLevelExperience(double width) {
|
||||
int thatExp = SCStringUtils.convertToInteger(res?.thatExperience ?? "0");
|
||||
int nexExp = SCStringUtils.convertToInteger(res?.nextExperience ?? "0");
|
||||
int levelExp = thatExp + nexExp;
|
||||
if (levelExp <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return (thatExp / levelExp) * width;
|
||||
}
|
||||
|
||||
num getLevelTotalExperience() {
|
||||
int thatExp = SCStringUtils.convertToInteger(res?.thatExperience ?? "0");
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||
@ -10,24 +13,30 @@ import 'package:yumi/modules/user/settings/settings_route.dart';
|
||||
import 'package:yumi/modules/user/vip/vip_route.dart';
|
||||
import 'package:yumi/modules/wallet/wallet_route.dart';
|
||||
import 'package:yumi/services/auth/user_profile_manager.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/sc_user_counter_res.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/sc_vip_res.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_vip_repository_imp.dart';
|
||||
import 'package:yumi/shared/tools/sc_lk_event_bus.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_debounce_widget.dart';
|
||||
import 'package:yumi/ui_kit/widgets/id/sc_special_id_badge.dart';
|
||||
import 'package:yumi/ui_kit/widgets/svga/sc_svga_asset_widget.dart';
|
||||
|
||||
class MePage2 extends StatefulWidget {
|
||||
const MePage2({super.key});
|
||||
const MePage2({super.key, this.currentIndexListenable, this.tabIndex = 3});
|
||||
|
||||
final ValueListenable<int>? currentIndexListenable;
|
||||
final int tabIndex;
|
||||
|
||||
@override
|
||||
State<MePage2> createState() => _MePage2State();
|
||||
}
|
||||
|
||||
class _MePage2State extends State<MePage2> {
|
||||
class _MePage2State extends State<MePage2> with WidgetsBindingObserver {
|
||||
static const Duration _counterStaleDuration = Duration(seconds: 30);
|
||||
static const String _inviteActivityUrl =
|
||||
'https://h5.haiyihy.com/app-invite/index.html';
|
||||
static const double _inviteActivityHorizontalScale = 750 / 662;
|
||||
@ -35,30 +44,147 @@ class _MePage2State extends State<MePage2> {
|
||||
Map<String, SCUserCounterRes> _counterMap = {};
|
||||
SCVipStatusRes? _vipStatus;
|
||||
bool _isVipStatusLoading = false;
|
||||
bool _isCounterLoading = false;
|
||||
bool _counterRefreshQueued = false;
|
||||
bool _counterReloadAfterCurrent = false;
|
||||
DateTime? _lastCounterLoadedAt;
|
||||
StreamSubscription<SCUserRelationChangedEvent>? _relationChangedSubscription;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
widget.currentIndexListenable?.addListener(_handleTabVisibilityChanged);
|
||||
_relationChangedSubscription = eventBus
|
||||
.on<SCUserRelationChangedEvent>()
|
||||
.listen(_handleUserRelationChanged);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_loadPageData();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant MePage2 oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.currentIndexListenable == widget.currentIndexListenable) {
|
||||
return;
|
||||
}
|
||||
oldWidget.currentIndexListenable?.removeListener(
|
||||
_handleTabVisibilityChanged,
|
||||
);
|
||||
widget.currentIndexListenable?.addListener(_handleTabVisibilityChanged);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.currentIndexListenable?.removeListener(_handleTabVisibilityChanged);
|
||||
_relationChangedSubscription?.cancel();
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
if (state == AppLifecycleState.resumed && _isCurrentTabVisible) {
|
||||
_refreshCounterIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
void _loadPageData() {
|
||||
final profileManager = Provider.of<SocialChatUserProfileManager>(
|
||||
context,
|
||||
listen: false,
|
||||
);
|
||||
profileManager.fetchUserProfileData();
|
||||
profileManager.getUserIdentity();
|
||||
profileManager.balance();
|
||||
_loadCounter();
|
||||
_loadVipStatus();
|
||||
}
|
||||
|
||||
Future<void> _loadCounter() async {
|
||||
bool get _isCurrentTabVisible {
|
||||
final currentIndex = widget.currentIndexListenable?.value;
|
||||
return currentIndex == null || currentIndex == widget.tabIndex;
|
||||
}
|
||||
|
||||
bool get _isCounterStale {
|
||||
final lastLoadedAt = _lastCounterLoadedAt;
|
||||
if (lastLoadedAt == null) {
|
||||
return true;
|
||||
}
|
||||
return DateTime.now().difference(lastLoadedAt) >= _counterStaleDuration;
|
||||
}
|
||||
|
||||
void _handleTabVisibilityChanged() {
|
||||
if (!_isCurrentTabVisible) {
|
||||
return;
|
||||
}
|
||||
_refreshCounterIfNeeded();
|
||||
}
|
||||
|
||||
void _refreshCounterIfNeeded() {
|
||||
if (_counterRefreshQueued || _isCounterStale) {
|
||||
unawaited(_loadCounter(force: true));
|
||||
}
|
||||
}
|
||||
|
||||
void _handleUserRelationChanged(SCUserRelationChangedEvent event) {
|
||||
final currentUserId =
|
||||
AccountStorage().getCurrentUser()?.userProfile?.id?.trim() ?? "";
|
||||
if (currentUserId.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final actorUserId = event.actorUserId.trim();
|
||||
final targetUserId = event.targetUserId.trim();
|
||||
final affectsCurrentUser =
|
||||
actorUserId == currentUserId || targetUserId == currentUserId;
|
||||
if (!affectsCurrentUser) {
|
||||
return;
|
||||
}
|
||||
|
||||
final delta = event.isFollowing ? 1 : -1;
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
if (actorUserId == currentUserId) {
|
||||
_applyCounterDelta("SUBSCRIPTION", delta);
|
||||
}
|
||||
if (targetUserId == currentUserId) {
|
||||
_applyCounterDelta("FANS", delta);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_counterRefreshQueued = true;
|
||||
if (_isCurrentTabVisible) {
|
||||
unawaited(_loadCounter(force: true));
|
||||
}
|
||||
}
|
||||
|
||||
void _applyCounterDelta(String type, int delta) {
|
||||
final currentCounter = _counterMap[type];
|
||||
final currentQuantity = int.tryParse(currentCounter?.quantity ?? "0") ?? 0;
|
||||
final nextQuantity = (currentQuantity + delta).clamp(0, 1 << 31);
|
||||
_counterMap = {
|
||||
..._counterMap,
|
||||
type: (currentCounter ?? SCUserCounterRes(counterType: type)).copyWith(
|
||||
quantity: nextQuantity.toString(),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
Future<void> _loadCounter({bool force = false}) async {
|
||||
if (_isCounterLoading) {
|
||||
if (force || _counterRefreshQueued) {
|
||||
_counterReloadAfterCurrent = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!force && !_counterRefreshQueued && !_isCounterStale) return;
|
||||
final userId = AccountStorage().getCurrentUser()?.userProfile?.id ?? "";
|
||||
if (userId.isEmpty) return;
|
||||
|
||||
_isCounterLoading = true;
|
||||
try {
|
||||
final userCounterList = await SCAccountRepository().userCounter(userId);
|
||||
if (!mounted) return;
|
||||
@ -68,8 +194,17 @@ class _MePage2State extends State<MePage2> {
|
||||
(counter) => MapEntry(counter.counterType ?? "", counter),
|
||||
),
|
||||
);
|
||||
_lastCounterLoadedAt = DateTime.now();
|
||||
_counterRefreshQueued = false;
|
||||
});
|
||||
} catch (_) {}
|
||||
} catch (_) {
|
||||
} finally {
|
||||
_isCounterLoading = false;
|
||||
if (_counterReloadAfterCurrent && mounted) {
|
||||
_counterReloadAfterCurrent = false;
|
||||
unawaited(_loadCounter(force: true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadVipStatus() async {
|
||||
@ -115,11 +250,36 @@ class _MePage2State extends State<MePage2> {
|
||||
status.active == false || status.levelInt <= 0
|
||||
? '0'
|
||||
: status.levelInt.toString();
|
||||
if (profile.vipLevel == nextVipLevel) return;
|
||||
|
||||
profileManager.syncCurrentUserProfile(
|
||||
final nextProfile = socialChatProfileWithAvatarFrameResource(
|
||||
profile.copyWith(vipLevel: nextVipLevel),
|
||||
_avatarFramePropsFromVipStatus(status),
|
||||
expireTime: _expireTimeFromVipStatus(status),
|
||||
);
|
||||
profileManager.syncCurrentUserProfile(nextProfile);
|
||||
}
|
||||
|
||||
PropsResources? _avatarFramePropsFromVipStatus(SCVipStatusRes status) {
|
||||
if (!status.isActive) {
|
||||
return null;
|
||||
}
|
||||
final resource = status.avatarFrame;
|
||||
return socialChatAvatarFrameProps(
|
||||
id: resource?.resourceId,
|
||||
name: resource?.name,
|
||||
sourceUrl: resource?.sourceResourceUrl,
|
||||
cover: resource?.coverUrl ?? resource?.cover,
|
||||
code: "VIP_AVATAR_FRAME",
|
||||
);
|
||||
}
|
||||
|
||||
String? _expireTimeFromVipStatus(SCVipStatusRes status) {
|
||||
final expireAt = status.expireAt?.trim();
|
||||
if (expireAt == null || expireAt.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
final normalized =
|
||||
expireAt.contains("T") ? expireAt : expireAt.replaceFirst(" ", "T");
|
||||
return DateTime.tryParse(normalized)?.millisecondsSinceEpoch.toString();
|
||||
}
|
||||
|
||||
void _refreshAfterVipReturn(SocialChatUserProfileManager profileManager) {
|
||||
@ -660,6 +820,16 @@ class _MePage2State extends State<MePage2> {
|
||||
final items = <_MenuRowData>[];
|
||||
final hasCurrentUser = userProfile.currentUserProfile != null;
|
||||
final isAgent = userProfile.userIdentity?.agent ?? false;
|
||||
final isYumiManager = userProfile.userIdentity?.yumiManager ?? false;
|
||||
if (kDebugMode) {
|
||||
final currentProfile = userProfile.currentUserProfile;
|
||||
debugPrint(
|
||||
'[MePage2] menu identity userId=${currentProfile?.id}, '
|
||||
'account=${currentProfile?.account}, hasCurrentUser=$hasCurrentUser, '
|
||||
'agent=${userProfile.userIdentity?.agent}, admin=${userProfile.userIdentity?.admin}, '
|
||||
'manager=${userProfile.userIdentity?.manager}, yumiManager=${userProfile.userIdentity?.yumiManager}',
|
||||
);
|
||||
}
|
||||
|
||||
// 1. 主播/代理入口
|
||||
if (hasCurrentUser) {
|
||||
@ -754,6 +924,27 @@ class _MePage2State extends State<MePage2> {
|
||||
),
|
||||
);
|
||||
}
|
||||
if (isYumiManager) {
|
||||
if (kDebugMode) {
|
||||
debugPrint('[MePage2] show Manager Center');
|
||||
}
|
||||
items.add(
|
||||
_MenuRowData(
|
||||
title: SCAppLocalizations.of(context)!.managerCenter,
|
||||
assetIcon: 'sc_images/index/sc_icon_admin_center.png',
|
||||
onTap: () {
|
||||
SCNavigatorUtils.push(
|
||||
context,
|
||||
"${SCMainRoute.webViewPage}?url=${Uri.encodeComponent(SCGlobalConfig.managerCenterUrl)}&showTitle=false",
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} else if (kDebugMode) {
|
||||
debugPrint(
|
||||
'[MePage2] hide Manager Center because yumiManager=$isYumiManager',
|
||||
);
|
||||
}
|
||||
return _buildMenuCard(items);
|
||||
}
|
||||
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
||||
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:yumi/ui_kit/components/dialog/dialog_base.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_page_list.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
||||
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||
@ -15,7 +13,6 @@ import 'package:yumi/shared/data_sources/sources/repositories/sc_store_repositor
|
||||
import 'package:yumi/shared/business_logic/models/res/bags_list_res.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
||||
import 'package:yumi/services/auth/user_profile_manager.dart';
|
||||
import 'package:yumi/ui_kit/widgets/bag/props_bag_chatbox_detail_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/countdown_timer.dart';
|
||||
import 'package:yumi/modules/store/store_route.dart';
|
||||
import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
|
||||
@ -63,7 +60,7 @@ class _BagsChatboxPageState
|
||||
Expanded(
|
||||
child:
|
||||
items.isEmpty && isLoading
|
||||
? const SCBagGridSkeleton()
|
||||
? const SCBagGridSkeleton(showTitle: false)
|
||||
: buildList(context),
|
||||
),
|
||||
selecteChatbox != null ? SizedBox(height: 10.w) : Container(),
|
||||
@ -155,52 +152,14 @@ class _BagsChatboxPageState
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
if (myChatbox?.id !=
|
||||
selecteChatbox?.propsResources?.id) {
|
||||
///佩戴
|
||||
SmartDialog.show(
|
||||
tag: "showConfirmDialog",
|
||||
alignment: Alignment.center,
|
||||
debounce: true,
|
||||
animationType: SmartAnimationType.fade,
|
||||
builder: (_) {
|
||||
return MsgDialog(
|
||||
title: SCAppLocalizations.of(context)!.tips,
|
||||
msg:
|
||||
SCAppLocalizations.of(
|
||||
context,
|
||||
)!.confirmUseTips,
|
||||
btnText:
|
||||
SCAppLocalizations.of(context)!.confirm,
|
||||
onEnsure: () {
|
||||
_use(selecteChatbox!, false);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
///卸下
|
||||
SmartDialog.show(
|
||||
tag: "showConfirmDialog",
|
||||
alignment: Alignment.center,
|
||||
debounce: true,
|
||||
animationType: SmartAnimationType.fade,
|
||||
builder: (_) {
|
||||
return MsgDialog(
|
||||
title: SCAppLocalizations.of(context)!.tips,
|
||||
msg:
|
||||
SCAppLocalizations.of(
|
||||
context,
|
||||
)!.confirmUnUseTips,
|
||||
btnText:
|
||||
SCAppLocalizations.of(context)!.confirm,
|
||||
onEnsure: () {
|
||||
_use(selecteChatbox!, true);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
final selected = selecteChatbox;
|
||||
if (selected == null) {
|
||||
return;
|
||||
}
|
||||
_use(
|
||||
selected,
|
||||
myChatbox?.id == selected.propsResources?.id,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
@ -234,19 +193,14 @@ class _BagsChatboxPageState
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(height: 25.w),
|
||||
netImage(
|
||||
url: res.propsResources?.cover ?? "",
|
||||
height: 35.w,
|
||||
width: 96.w,
|
||||
height: 52.w,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
SizedBox(height: 8.w),
|
||||
buildStoreBagItemTitle(
|
||||
res.propsResources?.name ?? "",
|
||||
textColor: Colors.black,
|
||||
fontSize: 12.sp,
|
||||
),
|
||||
],
|
||||
),
|
||||
PositionedDirectional(
|
||||
@ -281,33 +235,23 @@ class _BagsChatboxPageState
|
||||
),
|
||||
),
|
||||
),
|
||||
PositionedDirectional(
|
||||
bottom: 5.w,
|
||||
end: 5.w,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
selecteChatbox = res;
|
||||
setState(() {});
|
||||
_showDetail(res);
|
||||
},
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(5.w),
|
||||
child: Image.asset(
|
||||
"sc_images/store/sc_icon_shop_item_search.png",
|
||||
width: 18.w,
|
||||
height: 18.w,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
onTap: () async {
|
||||
selecteChatbox = res;
|
||||
setState(() {});
|
||||
if (myChatbox?.id != res.propsResources?.id) {
|
||||
final success = await _use(res, false);
|
||||
if (!success || !mounted) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
showStoreBagFullScreenResourcePreview(
|
||||
context,
|
||||
resource: res.propsResources,
|
||||
fit: BoxFit.contain,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -338,46 +282,42 @@ class _BagsChatboxPageState
|
||||
}
|
||||
}
|
||||
|
||||
void _showDetail(BagsListRes res) {
|
||||
SmartDialog.show(
|
||||
tag: "showPropsDetail",
|
||||
alignment: Alignment.center,
|
||||
animationType: SmartAnimationType.fade,
|
||||
builder: (_) {
|
||||
return PropsBagChatboxDetailDialog(res);
|
||||
},
|
||||
onDismiss: () {
|
||||
myChatbox = AccountStorage().getChatbox();
|
||||
setState(() {});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _use(BagsListRes res, bool unload) {
|
||||
Future<bool> _use(BagsListRes res, bool unload) async {
|
||||
SCLoadingManager.show(context: context);
|
||||
SCStoreRepositoryImp()
|
||||
.switchPropsUse(
|
||||
SCPropsType.CHAT_BUBBLE.name,
|
||||
res.propsResources?.id ?? "",
|
||||
unload,
|
||||
)
|
||||
.then((value) async {
|
||||
setState(() {});
|
||||
if (!unload) {
|
||||
myChatbox = res.propsResources;
|
||||
SCTts.show(SCAppLocalizations.of(context)!.successfulWear);
|
||||
} else {
|
||||
myChatbox = null;
|
||||
SCTts.show(SCAppLocalizations.of(context)!.successfullyUnloaded);
|
||||
}
|
||||
Provider.of<SocialChatUserProfileManager>(
|
||||
context,
|
||||
listen: false,
|
||||
).fetchUserProfileData(loadGuardCount: false);
|
||||
SCLoadingManager.hide();
|
||||
})
|
||||
.catchError((e) {
|
||||
SCLoadingManager.hide();
|
||||
});
|
||||
final localizations = SCAppLocalizations.of(context)!;
|
||||
final profileManager = Provider.of<SocialChatUserProfileManager>(
|
||||
context,
|
||||
listen: false,
|
||||
);
|
||||
try {
|
||||
await SCStoreRepositoryImp().switchPropsUse(
|
||||
SCPropsType.CHAT_BUBBLE.name,
|
||||
res.propsResources?.id ?? "",
|
||||
unload,
|
||||
);
|
||||
try {
|
||||
await profileManager.fetchUserProfileData(loadGuardCount: false);
|
||||
} catch (_) {}
|
||||
if (!mounted) {
|
||||
return false;
|
||||
}
|
||||
if (!unload) {
|
||||
myChatbox = res.propsResources;
|
||||
SCTts.show(localizations.successfulWear);
|
||||
} else {
|
||||
myChatbox = null;
|
||||
SCTts.show(localizations.successfullyUnloaded);
|
||||
}
|
||||
profileManager.syncCurrentUserChatBubble(
|
||||
unload ? null : res.propsResources,
|
||||
expireTime: res.expireTime?.toString(),
|
||||
);
|
||||
setState(() {});
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
} finally {
|
||||
SCLoadingManager.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
338
lib/modules/user/my_items/data_card/bags_data_card_page.dart
Normal file
@ -0,0 +1,338 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
import 'package:yumi/modules/store/store_route.dart';
|
||||
import 'package:yumi/services/auth/user_profile_manager.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/bags_list_res.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
||||
import 'package:yumi/shared/data_sources/models/enum/sc_props_type.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_store_repository_imp.dart';
|
||||
import 'package:yumi/shared/tools/sc_loading_manager.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_debounce_widget.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_page_list.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
||||
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
||||
import 'package:yumi/ui_kit/theme/socialchat_theme.dart';
|
||||
import 'package:yumi/ui_kit/widgets/countdown_timer.dart';
|
||||
import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
|
||||
|
||||
class BagsDataCardPage extends SCPageList {
|
||||
const BagsDataCardPage({super.key});
|
||||
|
||||
@override
|
||||
BagsDataCardPageState createState() => BagsDataCardPageState();
|
||||
}
|
||||
|
||||
class BagsDataCardPageState
|
||||
extends SCPageListState<BagsListRes, BagsDataCardPage> {
|
||||
PropsResources? myDataCard;
|
||||
BagsListRes? selectedDataCard;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
enablePullUp = false;
|
||||
isGridView = true;
|
||||
gridViewCount = 3;
|
||||
padding = EdgeInsets.symmetric(horizontal: 6.w);
|
||||
backgroundColor = Colors.transparent;
|
||||
gridDelegate = SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: gridViewCount,
|
||||
mainAxisSpacing: 12.w,
|
||||
crossAxisSpacing: 12.w,
|
||||
childAspectRatio: 0.88,
|
||||
);
|
||||
myDataCard = AccountStorage().getDataCard();
|
||||
loadData(1);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hasSelection = selectedDataCard != null;
|
||||
final selectedExpired =
|
||||
(selectedDataCard?.expireTime ?? 0) <
|
||||
DateTime.now().millisecondsSinceEpoch;
|
||||
final selectedInUse =
|
||||
myDataCard?.id == selectedDataCard?.propsResources?.id;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
body: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child:
|
||||
items.isEmpty && isLoading
|
||||
? const SCBagGridSkeleton(showTitle: false)
|
||||
: buildList(context),
|
||||
),
|
||||
hasSelection ? SizedBox(height: 10.w) : const SizedBox.shrink(),
|
||||
hasSelection
|
||||
? Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(12.w),
|
||||
topRight: Radius.circular(12.w),
|
||||
),
|
||||
color: const Color(0xff18F2B1).withValues(alpha: 0.1),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(height: 5.w),
|
||||
Row(
|
||||
spacing: 5.w,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
text(
|
||||
"${SCAppLocalizations.of(context)!.expirationTime}:",
|
||||
textColor: Colors.white,
|
||||
fontSize: 12.sp,
|
||||
),
|
||||
CountdownTimer(
|
||||
fontSize: 11.sp,
|
||||
color: SocialChatTheme.primaryLight,
|
||||
expiryDate: DateTime.fromMillisecondsSinceEpoch(
|
||||
selectedDataCard?.expireTime ?? 0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 10.w),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SCDebounceWidget(
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
width: 120.w,
|
||||
height: 35.w,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12.w),
|
||||
color: SocialChatTheme.primaryLight,
|
||||
),
|
||||
child: text(
|
||||
SCAppLocalizations.of(context)!.renewal,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14.sp,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
SCNavigatorUtils.push(
|
||||
context,
|
||||
StoreRoute.list,
|
||||
replace: false,
|
||||
);
|
||||
},
|
||||
),
|
||||
SizedBox(width: 10.w),
|
||||
SCDebounceWidget(
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
width: 120.w,
|
||||
height: 35.w,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12.w),
|
||||
color:
|
||||
selectedInUse
|
||||
? Colors.white
|
||||
: SocialChatTheme.primaryLight,
|
||||
),
|
||||
child: text(
|
||||
selectedExpired
|
||||
? SCAppLocalizations.of(context)!.renewal
|
||||
: selectedInUse
|
||||
? SCAppLocalizations.of(context)!.inUse
|
||||
: SCAppLocalizations.of(context)!.use,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14.sp,
|
||||
textColor:
|
||||
selectedInUse
|
||||
? const Color(0xffB1B1B1)
|
||||
: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
if (selectedExpired) {
|
||||
SCNavigatorUtils.push(
|
||||
context,
|
||||
StoreRoute.list,
|
||||
replace: false,
|
||||
);
|
||||
return;
|
||||
}
|
||||
final selected = selectedDataCard;
|
||||
if (selected == null) {
|
||||
return;
|
||||
}
|
||||
_use(selected, selectedInUse);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 10.w),
|
||||
],
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildItem(BagsListRes item) {
|
||||
final res = item;
|
||||
final selected =
|
||||
selectedDataCard?.propsResources?.id == res.propsResources?.id;
|
||||
return GestureDetector(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xff18F2B1).withValues(alpha: 0.1),
|
||||
border: Border.all(
|
||||
color: selected ? SocialChatTheme.primaryLight : Colors.transparent,
|
||||
width: 1.w,
|
||||
),
|
||||
borderRadius: BorderRadius.all(Radius.circular(8.w)),
|
||||
),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8.w),
|
||||
child: netImage(
|
||||
url: res.propsResources?.cover ?? "",
|
||||
width: 108.w,
|
||||
height: 75.w,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
PositionedDirectional(
|
||||
top: 0,
|
||||
start: 0,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 5.w),
|
||||
decoration: BoxDecoration(
|
||||
color: SocialChatTheme.primaryLight,
|
||||
borderRadius: BorderRadiusDirectional.only(
|
||||
topStart: Radius.circular(6.w),
|
||||
topEnd: Radius.circular(12.w),
|
||||
bottomEnd: Radius.circular(12.w),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
CountdownTimer(
|
||||
fontSize: 9.sp,
|
||||
color: Colors.white,
|
||||
expiryDate: DateTime.fromMillisecondsSinceEpoch(
|
||||
res.expireTime ?? 0,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 3.w),
|
||||
Image.asset(
|
||||
"sc_images/store/sc_icon_bag_clock.png",
|
||||
width: 22.w,
|
||||
height: 22.w,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () async {
|
||||
selectedDataCard = res;
|
||||
setState(() {});
|
||||
final expired =
|
||||
(res.expireTime ?? 0) < DateTime.now().millisecondsSinceEpoch;
|
||||
if (expired) {
|
||||
showStoreBagFullScreenResourcePreview(
|
||||
context,
|
||||
resource: res.propsResources,
|
||||
previewKind: SCStoreItemPreviewKind.dataCard,
|
||||
fit: BoxFit.contain,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (myDataCard?.id != res.propsResources?.id) {
|
||||
final success = await _use(res, false);
|
||||
if (!success || !mounted) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
showStoreBagFullScreenResourcePreview(
|
||||
context,
|
||||
resource: res.propsResources,
|
||||
previewKind: SCStoreItemPreviewKind.dataCard,
|
||||
fit: BoxFit.contain,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
loadPage({
|
||||
required int page,
|
||||
required Function(List<BagsListRes>) onSuccess,
|
||||
Function? onErr,
|
||||
}) async {
|
||||
try {
|
||||
final storeList = await SCStoreRepositoryImp().storeBackpack(
|
||||
AccountStorage().getCurrentUser()?.userProfile?.id ?? "",
|
||||
SCPropsType.DATA_CARD.name,
|
||||
);
|
||||
for (final item in storeList) {
|
||||
if (item.propsResources?.id == myDataCard?.id) {
|
||||
selectedDataCard = item;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
onSuccess(storeList);
|
||||
} catch (_) {
|
||||
onErr?.call();
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _use(BagsListRes res, bool unload) async {
|
||||
SCLoadingManager.show(context: context);
|
||||
final profileManager = Provider.of<SocialChatUserProfileManager>(
|
||||
context,
|
||||
listen: false,
|
||||
);
|
||||
final localizations = SCAppLocalizations.of(context)!;
|
||||
try {
|
||||
await SCStoreRepositoryImp().switchPropsUse(
|
||||
SCPropsType.DATA_CARD.name,
|
||||
res.propsResources?.id ?? "",
|
||||
unload,
|
||||
);
|
||||
if (!mounted) {
|
||||
return false;
|
||||
}
|
||||
if (!unload) {
|
||||
myDataCard = res.propsResources;
|
||||
SCTts.show(localizations.successfulWear);
|
||||
} else {
|
||||
myDataCard = null;
|
||||
SCTts.show(localizations.successfullyUnloaded);
|
||||
}
|
||||
setState(() {});
|
||||
profileManager.fetchUserProfileData(loadGuardCount: false);
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
} finally {
|
||||
SCLoadingManager.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
168
lib/modules/user/my_items/gift/bags_gift_page.dart
Normal file
@ -0,0 +1,168 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/gift_backpack_res.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_room_repository_imp.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_page_list.dart';
|
||||
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
||||
import 'package:yumi/ui_kit/theme/socialchat_theme.dart';
|
||||
import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
|
||||
|
||||
const double _kBagsGiftGridAspectRatio = 0.86;
|
||||
|
||||
///背包-礼物
|
||||
class BagsGiftPage extends SCPageList {
|
||||
const BagsGiftPage({super.key});
|
||||
|
||||
@override
|
||||
SCPageListState<SocialChatGiftBackpackRes, BagsGiftPage> createState() =>
|
||||
_BagsGiftPageState();
|
||||
}
|
||||
|
||||
class _BagsGiftPageState
|
||||
extends SCPageListState<SocialChatGiftBackpackRes, BagsGiftPage> {
|
||||
SocialChatGiftBackpackRes? selectedGift;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
enablePullUp = false;
|
||||
isGridView = true;
|
||||
gridViewCount = 3;
|
||||
padding = EdgeInsets.symmetric(horizontal: 6.w);
|
||||
backgroundColor = Colors.transparent;
|
||||
gridDelegate = SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: gridViewCount,
|
||||
mainAxisSpacing: 12.w,
|
||||
crossAxisSpacing: 12.w,
|
||||
childAspectRatio: _kBagsGiftGridAspectRatio,
|
||||
);
|
||||
loadData(1);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
body:
|
||||
items.isEmpty && isLoading
|
||||
? const SCBagGridSkeleton(showTitle: false)
|
||||
: buildList(context),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildItem(SocialChatGiftBackpackRes res) {
|
||||
final gift = res.giftConfig;
|
||||
final isSelected = selectedGift?.id == res.id;
|
||||
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
selectedGift = res;
|
||||
setState(() {});
|
||||
showStoreBagFullScreenResourcePreview(
|
||||
context,
|
||||
sourceUrl: gift?.giftSourceUrl,
|
||||
coverUrl: gift?.giftPhoto,
|
||||
fit: BoxFit.contain,
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xff18F2B1).withValues(alpha: 0.1),
|
||||
border: Border.all(
|
||||
color:
|
||||
isSelected ? SocialChatTheme.primaryLight : Colors.transparent,
|
||||
width: 1.w,
|
||||
),
|
||||
borderRadius: BorderRadius.all(Radius.circular(8.w)),
|
||||
),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
netImage(
|
||||
url: gift?.giftPhoto ?? "",
|
||||
width: 82.w,
|
||||
height: 82.w,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
SizedBox(height: 8.w),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Image.asset(
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getGiftPageGoldCoinIcon(),
|
||||
width: 14.w,
|
||||
height: 14.w,
|
||||
),
|
||||
SizedBox(width: 3.w),
|
||||
text(
|
||||
_formatNumber(gift?.giftCandy),
|
||||
fontSize: 10,
|
||||
lineHeight: 1,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
PositionedDirectional(
|
||||
top: 0,
|
||||
start: 0,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 2.w),
|
||||
decoration: BoxDecoration(
|
||||
color: SocialChatTheme.primaryLight,
|
||||
borderRadius: BorderRadiusDirectional.only(
|
||||
topStart: Radius.circular(6.w),
|
||||
topEnd: Radius.circular(12.w),
|
||||
bottomEnd: Radius.circular(12.w),
|
||||
),
|
||||
),
|
||||
child: text(
|
||||
"x${_formatNumber(res.quantity)}",
|
||||
fontSize: 10,
|
||||
lineHeight: 1,
|
||||
fontWeight: FontWeight.w600,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
loadPage({
|
||||
required int page,
|
||||
required Function(List<SocialChatGiftBackpackRes>) onSuccess,
|
||||
Function? onErr,
|
||||
}) async {
|
||||
try {
|
||||
final giftList = await SCChatRoomRepository().giftBackpack();
|
||||
onSuccess(giftList.where((item) => (item.quantity ?? 0) > 0).toList());
|
||||
} catch (e) {
|
||||
if (onErr != null) {
|
||||
onErr();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String _formatNumber(num? value) {
|
||||
if (value == null) {
|
||||
return "0";
|
||||
}
|
||||
if (value % 1 == 0) {
|
||||
return value.toInt().toString();
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||
import 'package:yumi/shared/tools/sc_loading_manager.dart';
|
||||
@ -11,13 +10,11 @@ import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
||||
import 'package:yumi/modules/store/store_route.dart';
|
||||
import 'package:yumi/services/auth/user_profile_manager.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_debounce_widget.dart';
|
||||
import 'package:yumi/ui_kit/components/dialog/dialog_base.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_page_list.dart';
|
||||
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
||||
import 'package:yumi/ui_kit/theme/socialchat_theme.dart';
|
||||
import 'package:yumi/ui_kit/widgets/bag/props_bag_headdress_detail_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/countdown_timer.dart';
|
||||
import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@ -63,7 +60,7 @@ class _BagsHeaddressPageState
|
||||
Expanded(
|
||||
child:
|
||||
items.isEmpty && isLoading
|
||||
? const SCBagGridSkeleton()
|
||||
? const SCBagGridSkeleton(showTitle: false)
|
||||
: buildList(context),
|
||||
),
|
||||
selecteHeaddress != null ? SizedBox(height: 10.w) : Container(),
|
||||
@ -155,52 +152,14 @@ class _BagsHeaddressPageState
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
if (myHeaddress?.id !=
|
||||
selecteHeaddress?.propsResources?.id) {
|
||||
///佩戴
|
||||
SmartDialog.show(
|
||||
tag: "showConfirmDialog",
|
||||
alignment: Alignment.center,
|
||||
debounce: true,
|
||||
animationType: SmartAnimationType.fade,
|
||||
builder: (_) {
|
||||
return MsgDialog(
|
||||
title: SCAppLocalizations.of(context)!.tips,
|
||||
msg:
|
||||
SCAppLocalizations.of(
|
||||
context,
|
||||
)!.confirmUseTips,
|
||||
btnText:
|
||||
SCAppLocalizations.of(context)!.confirm,
|
||||
onEnsure: () {
|
||||
_use(selecteHeaddress!, false);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
///卸下
|
||||
SmartDialog.show(
|
||||
tag: "showConfirmDialog",
|
||||
alignment: Alignment.center,
|
||||
debounce: true,
|
||||
animationType: SmartAnimationType.fade,
|
||||
builder: (_) {
|
||||
return MsgDialog(
|
||||
title: SCAppLocalizations.of(context)!.tips,
|
||||
msg:
|
||||
SCAppLocalizations.of(
|
||||
context,
|
||||
)!.confirmUnUseTips,
|
||||
btnText:
|
||||
SCAppLocalizations.of(context)!.confirm,
|
||||
onEnsure: () {
|
||||
_use(selecteHeaddress!, true);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
final selected = selecteHeaddress;
|
||||
if (selected == null) {
|
||||
return;
|
||||
}
|
||||
_use(
|
||||
selected,
|
||||
myHeaddress?.id == selected.propsResources?.id,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
@ -234,18 +193,13 @@ class _BagsHeaddressPageState
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(height: 25.w),
|
||||
netImage(
|
||||
url: res.propsResources?.cover ?? "",
|
||||
width: 55.w,
|
||||
height: 55.w,
|
||||
),
|
||||
SizedBox(height: 8.w),
|
||||
buildStoreBagItemTitle(
|
||||
res.propsResources?.name ?? "",
|
||||
textColor: Colors.white,
|
||||
fontSize: 12.sp,
|
||||
width: 82.w,
|
||||
height: 82.w,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -281,33 +235,24 @@ class _BagsHeaddressPageState
|
||||
),
|
||||
),
|
||||
),
|
||||
PositionedDirectional(
|
||||
bottom: 5.w,
|
||||
end: 5.w,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
selecteHeaddress = res;
|
||||
setState(() {});
|
||||
_showDetail(res);
|
||||
},
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(5.w),
|
||||
child: Image.asset(
|
||||
"sc_images/store/sc_icon_shop_item_search.png",
|
||||
width: 13.w,
|
||||
height: 13.w,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
onTap: () async {
|
||||
selecteHeaddress = res;
|
||||
setState(() {});
|
||||
if (myHeaddress?.id != res.propsResources?.id) {
|
||||
final success = await _use(res, false);
|
||||
if (!success || !mounted) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
showStoreBagFullScreenResourcePreview(
|
||||
context,
|
||||
resource: res.propsResources,
|
||||
previewKind: SCStoreItemPreviewKind.avatarFrame,
|
||||
fit: BoxFit.contain,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -338,46 +283,36 @@ class _BagsHeaddressPageState
|
||||
}
|
||||
}
|
||||
|
||||
void _showDetail(BagsListRes res) {
|
||||
SmartDialog.show(
|
||||
tag: "showPropsDetail",
|
||||
alignment: Alignment.center,
|
||||
animationType: SmartAnimationType.fade,
|
||||
builder: (_) {
|
||||
return PropsBagHeaddressDetailDialog(res);
|
||||
},
|
||||
onDismiss: () {
|
||||
myHeaddress = AccountStorage().getHeaddress();
|
||||
setState(() {});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _use(BagsListRes res, bool unload) {
|
||||
Future<bool> _use(BagsListRes res, bool unload) async {
|
||||
SCLoadingManager.show(context: context);
|
||||
SCStoreRepositoryImp()
|
||||
.switchPropsUse(
|
||||
SCPropsType.AVATAR_FRAME.name,
|
||||
res.propsResources?.id ?? "",
|
||||
unload,
|
||||
)
|
||||
.then((value) async {
|
||||
setState(() {});
|
||||
if (!unload) {
|
||||
myHeaddress = res.propsResources;
|
||||
SCTts.show(SCAppLocalizations.of(context)!.successfulWear);
|
||||
} else {
|
||||
myHeaddress = null;
|
||||
SCTts.show(SCAppLocalizations.of(context)!.successfullyUnloaded);
|
||||
}
|
||||
Provider.of<SocialChatUserProfileManager>(
|
||||
context,
|
||||
listen: false,
|
||||
).fetchUserProfileData(loadGuardCount: false);
|
||||
SCLoadingManager.hide();
|
||||
})
|
||||
.catchError((e) {
|
||||
SCLoadingManager.hide();
|
||||
});
|
||||
final localizations = SCAppLocalizations.of(context)!;
|
||||
final profileManager = Provider.of<SocialChatUserProfileManager>(
|
||||
context,
|
||||
listen: false,
|
||||
);
|
||||
try {
|
||||
await SCStoreRepositoryImp().switchPropsUse(
|
||||
SCPropsType.AVATAR_FRAME.name,
|
||||
res.propsResources?.id ?? "",
|
||||
unload,
|
||||
);
|
||||
if (!mounted) {
|
||||
return false;
|
||||
}
|
||||
if (!unload) {
|
||||
myHeaddress = res.propsResources;
|
||||
SCTts.show(localizations.successfulWear);
|
||||
} else {
|
||||
myHeaddress = null;
|
||||
SCTts.show(localizations.successfullyUnloaded);
|
||||
}
|
||||
setState(() {});
|
||||
profileManager.fetchUserProfileData(loadGuardCount: false);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
} finally {
|
||||
SCLoadingManager.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/bag/props_bag_mountains_detail_dialog.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
||||
@ -21,7 +19,6 @@ import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
|
||||
|
||||
import '../../../../shared/data_sources/models/enum/sc_props_type.dart';
|
||||
import '../../../../ui_kit/components/sc_debounce_widget.dart';
|
||||
import '../../../../ui_kit/components/dialog/dialog_base.dart';
|
||||
import '../../../../ui_kit/theme/socialchat_theme.dart';
|
||||
|
||||
///坐骑
|
||||
@ -69,7 +66,7 @@ class _BagsMountainsPageState
|
||||
Expanded(
|
||||
child:
|
||||
items.isEmpty && isLoading
|
||||
? const SCBagGridSkeleton()
|
||||
? const SCBagGridSkeleton(showTitle: false)
|
||||
: buildList(context),
|
||||
),
|
||||
selecteMountains != null ? SizedBox(height: 10.w) : Container(),
|
||||
@ -161,52 +158,14 @@ class _BagsMountainsPageState
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
if (myMountains?.id !=
|
||||
selecteMountains?.propsResources?.id) {
|
||||
///佩戴
|
||||
SmartDialog.show(
|
||||
tag: "showConfirmDialog",
|
||||
alignment: Alignment.center,
|
||||
debounce: true,
|
||||
animationType: SmartAnimationType.fade,
|
||||
builder: (_) {
|
||||
return MsgDialog(
|
||||
title: SCAppLocalizations.of(context)!.tips,
|
||||
msg:
|
||||
SCAppLocalizations.of(
|
||||
context,
|
||||
)!.confirmUseTips,
|
||||
btnText:
|
||||
SCAppLocalizations.of(context)!.confirm,
|
||||
onEnsure: () {
|
||||
_use(selecteMountains!, false);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
///卸下
|
||||
SmartDialog.show(
|
||||
tag: "showConfirmDialog",
|
||||
alignment: Alignment.center,
|
||||
debounce: true,
|
||||
animationType: SmartAnimationType.fade,
|
||||
builder: (_) {
|
||||
return MsgDialog(
|
||||
title: SCAppLocalizations.of(context)!.tips,
|
||||
msg:
|
||||
SCAppLocalizations.of(
|
||||
context,
|
||||
)!.confirmUnUseTips,
|
||||
btnText:
|
||||
SCAppLocalizations.of(context)!.confirm,
|
||||
onEnsure: () {
|
||||
_use(selecteMountains!, true);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
final selected = selecteMountains;
|
||||
if (selected == null) {
|
||||
return;
|
||||
}
|
||||
_use(
|
||||
selected,
|
||||
myMountains?.id == selected.propsResources?.id,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
@ -240,18 +199,13 @@ class _BagsMountainsPageState
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(height: 25.w),
|
||||
netImage(
|
||||
url: res.propsResources?.cover ?? "",
|
||||
width: 55.w,
|
||||
height: 55.w,
|
||||
),
|
||||
SizedBox(height: 8.w),
|
||||
buildStoreBagItemTitle(
|
||||
res.propsResources?.name ?? "",
|
||||
textColor: Colors.white,
|
||||
fontSize: 12.sp,
|
||||
width: 82.w,
|
||||
height: 82.w,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -290,9 +244,20 @@ class _BagsMountainsPageState
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
onTap: () async {
|
||||
selecteMountains = res;
|
||||
setState(() {});
|
||||
if (myMountains?.id != res.propsResources?.id) {
|
||||
final success = await _use(res, false);
|
||||
if (!success || !mounted) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
showStoreBagFullScreenResourcePreview(
|
||||
context,
|
||||
resource: res.propsResources,
|
||||
fit: BoxFit.contain,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -323,46 +288,36 @@ class _BagsMountainsPageState
|
||||
}
|
||||
}
|
||||
|
||||
void _showDetail(BagsListRes res) {
|
||||
SmartDialog.show(
|
||||
tag: "showPropsDetail",
|
||||
alignment: Alignment.center,
|
||||
animationType: SmartAnimationType.fade,
|
||||
builder: (_) {
|
||||
return PropsBagMountainsDetailDialog(res);
|
||||
},
|
||||
onDismiss: () {
|
||||
myMountains = AccountStorage().getMountains();
|
||||
setState(() {});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _use(BagsListRes res, bool unload) {
|
||||
Future<bool> _use(BagsListRes res, bool unload) async {
|
||||
SCLoadingManager.show(context: context);
|
||||
SCStoreRepositoryImp()
|
||||
.switchPropsUse(
|
||||
SCPropsType.RIDE.name,
|
||||
res.propsResources?.id ?? "",
|
||||
unload,
|
||||
)
|
||||
.then((value) async {
|
||||
setState(() {});
|
||||
if (!unload) {
|
||||
myMountains = res.propsResources;
|
||||
SCTts.show(SCAppLocalizations.of(context)!.successfulWear);
|
||||
} else {
|
||||
myMountains = null;
|
||||
SCTts.show(SCAppLocalizations.of(context)!.successfullyUnloaded);
|
||||
}
|
||||
Provider.of<SocialChatUserProfileManager>(
|
||||
context,
|
||||
listen: false,
|
||||
).fetchUserProfileData(loadGuardCount: false);
|
||||
SCLoadingManager.hide();
|
||||
})
|
||||
.catchError((e) {
|
||||
SCLoadingManager.hide();
|
||||
});
|
||||
final localizations = SCAppLocalizations.of(context)!;
|
||||
final profileManager = Provider.of<SocialChatUserProfileManager>(
|
||||
context,
|
||||
listen: false,
|
||||
);
|
||||
try {
|
||||
await SCStoreRepositoryImp().switchPropsUse(
|
||||
SCPropsType.RIDE.name,
|
||||
res.propsResources?.id ?? "",
|
||||
unload,
|
||||
);
|
||||
if (!mounted) {
|
||||
return false;
|
||||
}
|
||||
if (!unload) {
|
||||
myMountains = res.propsResources;
|
||||
SCTts.show(localizations.successfulWear);
|
||||
} else {
|
||||
myMountains = null;
|
||||
SCTts.show(localizations.successfullyUnloaded);
|
||||
}
|
||||
setState(() {});
|
||||
profileManager.fetchUserProfileData(loadGuardCount: false);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
} finally {
|
||||
SCLoadingManager.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,142 +1,141 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:yumi/modules/user/my_items/theme/bags_tab_theme_page.dart';
|
||||
import 'package:tancent_vap/utils/constant.dart';
|
||||
import 'package:tancent_vap/widgets/vap_view.dart';
|
||||
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
import 'package:yumi/ui_kit/components/appbar/socialchat_appbar.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
||||
import 'package:yumi/ui_kit/theme/socialchat_theme.dart';
|
||||
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||
import 'package:yumi/shared/tools/sc_path_utils.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/local/file_cache_manager.dart';
|
||||
import 'package:yumi/ui_kit/widgets/headdress/headdress_widget.dart';
|
||||
import 'package:yumi/modules/store/store_route.dart';
|
||||
import 'package:yumi/modules/user/my_items/chatbox/bags_chatbox_page.dart';
|
||||
import 'package:yumi/modules/user/my_items/headdress/bags_headdress_page.dart';
|
||||
import 'package:yumi/modules/user/my_items/mountains/bags_mountains_page.dart';
|
||||
|
||||
import '../../../app/constants/sc_global_config.dart';
|
||||
import '../../../shared/business_logic/usecases/sc_fixed_width_tabIndicator.dart';
|
||||
|
||||
class MyItemsPage extends StatefulWidget {
|
||||
@override
|
||||
_MyItemsPageState createState() => _MyItemsPageState();
|
||||
}
|
||||
|
||||
class _MyItemsPageState extends State<MyItemsPage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late TabController _tabController;
|
||||
final List<Widget> _pages = [];
|
||||
final List<Widget> _tabs = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_pages.add(BagsHeaddressPage());
|
||||
_pages.add(
|
||||
BagsMountainsPage(),
|
||||
);
|
||||
_pages.add(
|
||||
BagsTabThemePage(),
|
||||
);
|
||||
_pages.add(BagsChatboxPage());
|
||||
_tabController = TabController(length: _pages.length, vsync: this);
|
||||
_tabController.addListener(() {
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_tabs.clear();
|
||||
_tabs.add(Tab(text: SCAppLocalizations.of(context)!.headdress));
|
||||
_tabs.add(Tab(text: SCAppLocalizations.of(context)!.mountains));
|
||||
_tabs.add(Tab(text: SCAppLocalizations.of(context)!.theme));
|
||||
_tabs.add(Tab(text: SCAppLocalizations.of(context)!.chatBox));
|
||||
return Stack(
|
||||
children: [
|
||||
Image.asset(
|
||||
SCGlobalConfig.businessLogicStrategy.getLanguagePageBackgroundImage(),
|
||||
width: ScreenUtil().screenWidth,
|
||||
height: ScreenUtil().screenHeight,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
resizeToAvoidBottomInset: false,
|
||||
appBar: SocialChatStandardAppBar(
|
||||
title: SCAppLocalizations.of(context)!.myItems,
|
||||
actions: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(5.w),
|
||||
margin: EdgeInsetsDirectional.only(end: 10.w),
|
||||
child: Image.asset(
|
||||
"sc_images/store/sc_icon_bag_shop.png",
|
||||
width: 20.w,
|
||||
height: 20.w,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
SCNavigatorUtils.push(context, StoreRoute.list, replace: true);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SafeArea(top:false,child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
TabBar(
|
||||
tabAlignment: TabAlignment.center,
|
||||
isScrollable: true,
|
||||
labelColor: Colors.white,
|
||||
unselectedLabelStyle: SCGlobalConfig.businessLogicStrategy
|
||||
.getStorePageTabUnselectedLabelStyle()
|
||||
.copyWith(
|
||||
fontSize:
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStorePageTabUnselectedLabelStyle()
|
||||
.fontSize
|
||||
?.sp,
|
||||
),
|
||||
// indicatorPadding: EdgeInsets.symmetric(
|
||||
// vertical: 5.w,
|
||||
// horizontal: 15.w,
|
||||
// ),
|
||||
indicator: SCFixedWidthTabIndicator(
|
||||
width: 15.w,
|
||||
color:
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStorePageTabIndicatorColor(),
|
||||
),
|
||||
dividerColor:
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStorePageTabDividerColor(),
|
||||
controller: _tabController,
|
||||
tabs: _tabs,
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 5.w),
|
||||
Expanded(
|
||||
child: TabBarView(controller: _tabController, children: _pages),
|
||||
),
|
||||
],
|
||||
),),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
import 'package:yumi/ui_kit/components/appbar/socialchat_appbar.dart';
|
||||
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||
import 'package:yumi/modules/store/store_route.dart';
|
||||
import 'package:yumi/modules/user/my_items/chatbox/bags_chatbox_page.dart';
|
||||
import 'package:yumi/modules/user/my_items/data_card/bags_data_card_page.dart';
|
||||
import 'package:yumi/modules/user/my_items/gift/bags_gift_page.dart';
|
||||
import 'package:yumi/modules/user/my_items/headdress/bags_headdress_page.dart';
|
||||
import 'package:yumi/modules/user/my_items/mountains/bags_mountains_page.dart';
|
||||
|
||||
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||
import 'package:yumi/shared/business_logic/usecases/sc_fixed_width_tabIndicator.dart';
|
||||
|
||||
class MyItemsPage extends StatefulWidget {
|
||||
const MyItemsPage({super.key});
|
||||
|
||||
@override
|
||||
State<MyItemsPage> createState() => _MyItemsPageState();
|
||||
}
|
||||
|
||||
class _MyItemsPageState extends State<MyItemsPage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late TabController _tabController;
|
||||
final List<Widget> _pages = [];
|
||||
final List<Widget> _tabs = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_pages.add(BagsHeaddressPage());
|
||||
_pages.add(BagsMountainsPage());
|
||||
_pages.add(BagsGiftPage());
|
||||
_pages.add(BagsChatboxPage());
|
||||
_pages.add(const BagsDataCardPage());
|
||||
_tabController = TabController(length: _pages.length, vsync: this);
|
||||
_tabController.addListener(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tabController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_tabs.clear();
|
||||
_tabs.add(Tab(text: SCAppLocalizations.of(context)!.headdress));
|
||||
_tabs.add(Tab(text: SCAppLocalizations.of(context)!.mountains));
|
||||
_tabs.add(Tab(text: SCAppLocalizations.of(context)!.gift));
|
||||
_tabs.add(Tab(text: SCAppLocalizations.of(context)!.chatBox));
|
||||
_tabs.add(const Tab(text: 'Profile Card'));
|
||||
return Stack(
|
||||
children: [
|
||||
Image.asset(
|
||||
SCGlobalConfig.businessLogicStrategy.getLanguagePageBackgroundImage(),
|
||||
width: ScreenUtil().screenWidth,
|
||||
height: ScreenUtil().screenHeight,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
resizeToAvoidBottomInset: false,
|
||||
appBar: SocialChatStandardAppBar(
|
||||
title: SCAppLocalizations.of(context)!.myItems,
|
||||
actions: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(5.w),
|
||||
margin: EdgeInsetsDirectional.only(end: 10.w),
|
||||
child: Image.asset(
|
||||
"sc_images/store/sc_icon_bag_shop.png",
|
||||
width: 20.w,
|
||||
height: 20.w,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
SCNavigatorUtils.push(
|
||||
context,
|
||||
StoreRoute.list,
|
||||
replace: true,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: TabBar(
|
||||
tabAlignment: TabAlignment.center,
|
||||
isScrollable: true,
|
||||
labelColor: Colors.white,
|
||||
unselectedLabelStyle: SCGlobalConfig.businessLogicStrategy
|
||||
.getStorePageTabUnselectedLabelStyle()
|
||||
.copyWith(
|
||||
fontSize:
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStorePageTabUnselectedLabelStyle()
|
||||
.fontSize
|
||||
?.sp,
|
||||
),
|
||||
// indicatorPadding: EdgeInsets.symmetric(
|
||||
// vertical: 5.w,
|
||||
// horizontal: 15.w,
|
||||
// ),
|
||||
indicator: SCFixedWidthTabIndicator(
|
||||
width: 15.w,
|
||||
color:
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStorePageTabIndicatorColor(),
|
||||
),
|
||||
dividerColor:
|
||||
SCGlobalConfig.businessLogicStrategy
|
||||
.getStorePageTabDividerColor(),
|
||||
controller: _tabController,
|
||||
tabs: _tabs,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5.w),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: _pages,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,7 +9,6 @@ import 'package:yumi/ui_kit/theme/socialchat_theme.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_page_list.dart';
|
||||
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_store_repository_imp.dart';
|
||||
import 'package:yumi/ui_kit/widgets/bag/props_bag_theme_detail_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/countdown_timer.dart';
|
||||
import 'package:yumi/modules/store/store_route.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@ -20,7 +19,6 @@ import '../../../../shared/business_logic/models/res/sc_room_theme_list_res.dart
|
||||
import '../../../../services/audio/rtc_manager.dart';
|
||||
import '../../../../services/audio/rtm_manager.dart';
|
||||
import '../../../../ui_kit/components/sc_debounce_widget.dart';
|
||||
import '../../../../ui_kit/components/dialog/dialog_base.dart';
|
||||
import '../../../../ui_kit/components/sc_tts.dart';
|
||||
import '../../../../ui_kit/widgets/room/room_msg_item.dart';
|
||||
|
||||
@ -149,51 +147,11 @@ class _BagsThemePageState
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
if (!(selecteTheme?.useTheme ?? false)) {
|
||||
///佩戴
|
||||
SmartDialog.show(
|
||||
tag: "showConfirmDialog",
|
||||
alignment: Alignment.center,
|
||||
debounce: true,
|
||||
animationType: SmartAnimationType.fade,
|
||||
builder: (_) {
|
||||
return MsgDialog(
|
||||
title: SCAppLocalizations.of(context)!.tips,
|
||||
msg:
|
||||
SCAppLocalizations.of(
|
||||
context,
|
||||
)!.confirmUseTips,
|
||||
btnText:
|
||||
SCAppLocalizations.of(context)!.confirm,
|
||||
onEnsure: () {
|
||||
_use(selecteTheme!, false);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
///卸下
|
||||
SmartDialog.show(
|
||||
tag: "showConfirmDialog",
|
||||
alignment: Alignment.center,
|
||||
debounce: true,
|
||||
animationType: SmartAnimationType.fade,
|
||||
builder: (_) {
|
||||
return MsgDialog(
|
||||
title: SCAppLocalizations.of(context)!.tips,
|
||||
msg:
|
||||
SCAppLocalizations.of(
|
||||
context,
|
||||
)!.confirmUnUseTips,
|
||||
btnText:
|
||||
SCAppLocalizations.of(context)!.confirm,
|
||||
onEnsure: () {
|
||||
_use(selecteTheme!, true);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
final selected = selecteTheme;
|
||||
if (selected == null) {
|
||||
return;
|
||||
}
|
||||
_use(selected, selected.useTheme ?? false);
|
||||
},
|
||||
),
|
||||
],
|
||||
@ -227,15 +185,14 @@ class _BagsThemePageState
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(height: 25.w),
|
||||
netImage(
|
||||
url: res.themeBack ?? "",
|
||||
width: 55.w,
|
||||
height: 55.w,
|
||||
width: 82.w,
|
||||
height: 82.w,
|
||||
borderRadius: BorderRadius.all(Radius.circular(8.w)),
|
||||
),
|
||||
SizedBox(height: 8.w),
|
||||
],
|
||||
),
|
||||
PositionedDirectional(
|
||||
@ -273,9 +230,21 @@ class _BagsThemePageState
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
onTap: () async {
|
||||
selecteTheme = res;
|
||||
setState(() {});
|
||||
if (!(res.useTheme ?? false)) {
|
||||
final success = await _use(res, false);
|
||||
if (!success || !mounted) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
showStoreBagFullScreenResourcePreview(
|
||||
context,
|
||||
sourceUrl: res.themeBack,
|
||||
coverUrl: res.themeBack,
|
||||
fit: BoxFit.contain,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -303,62 +272,46 @@ class _BagsThemePageState
|
||||
}
|
||||
}
|
||||
|
||||
void _use(SCRoomThemeListRes res, bool unload) {
|
||||
Future<bool> _use(SCRoomThemeListRes res, bool unload) async {
|
||||
SCLoadingManager.show();
|
||||
SCStoreRepositoryImp()
|
||||
.themeSwitchUse(res.id ?? "", unload)
|
||||
.then((value) async {
|
||||
SCLoadingManager.hide();
|
||||
SmartDialog.dismiss(tag: "showPropsDetail");
|
||||
if (!unload) {
|
||||
SCTts.show(SCAppLocalizations.of(context)!.successfulWear);
|
||||
Provider.of<RealTimeCommunicationManager>(
|
||||
context!,
|
||||
listen: false,
|
||||
).updateRoomBG(res);
|
||||
} else {
|
||||
Provider.of<RealTimeCommunicationManager>(
|
||||
context!,
|
||||
listen: false,
|
||||
).updateRoomBG(SCRoomThemeListRes());
|
||||
selecteTheme = null;
|
||||
SCTts.show(SCAppLocalizations.of(context)!.successfullyUnloaded);
|
||||
}
|
||||
loadData(1);
|
||||
|
||||
///发送一条消息更新远端房间信息
|
||||
String? groupId =
|
||||
Provider.of<RealTimeCommunicationManager>(
|
||||
context,
|
||||
listen: false,
|
||||
).currenRoom?.roomProfile?.roomProfile?.roomAccount;
|
||||
if (groupId != null) {
|
||||
Msg msg = Msg(
|
||||
groupId: groupId,
|
||||
msg: unload ? "" : jsonEncode(res.toJson()),
|
||||
type: SCRoomMsgType.roomBGUpdate,
|
||||
);
|
||||
Provider.of<RtmProvider>(
|
||||
context!,
|
||||
listen: false,
|
||||
).dispatchMessage(msg, addLocal: false);
|
||||
}
|
||||
})
|
||||
.catchError((e) {
|
||||
SCLoadingManager.hide();
|
||||
});
|
||||
}
|
||||
|
||||
void _showDetail(SCRoomThemeListRes res) {
|
||||
SmartDialog.show(
|
||||
tag: "showPropsDetail",
|
||||
alignment: Alignment.center,
|
||||
animationType: SmartAnimationType.fade,
|
||||
builder: (_) {
|
||||
return PropsBagThemeDetailDialog(res, () {
|
||||
loadData(1);
|
||||
});
|
||||
},
|
||||
final localizations = SCAppLocalizations.of(context)!;
|
||||
final rtcManager = Provider.of<RealTimeCommunicationManager>(
|
||||
context,
|
||||
listen: false,
|
||||
);
|
||||
final rtmProvider = Provider.of<RtmProvider>(context, listen: false);
|
||||
try {
|
||||
await SCStoreRepositoryImp().themeSwitchUse(res.id ?? "", unload);
|
||||
if (!mounted) {
|
||||
return false;
|
||||
}
|
||||
SmartDialog.dismiss(tag: "showPropsDetail");
|
||||
if (!unload) {
|
||||
SCTts.show(localizations.successfulWear);
|
||||
rtcManager.updateRoomBG(res);
|
||||
} else {
|
||||
rtcManager.updateRoomBG(SCRoomThemeListRes());
|
||||
selecteTheme = null;
|
||||
SCTts.show(localizations.successfullyUnloaded);
|
||||
}
|
||||
loadData(1);
|
||||
|
||||
///发送一条消息更新远端房间信息
|
||||
String? groupId =
|
||||
rtcManager.currenRoom?.roomProfile?.roomProfile?.roomAccount;
|
||||
if (groupId != null) {
|
||||
Msg msg = Msg(
|
||||
groupId: groupId,
|
||||
msg: unload ? "" : jsonEncode(res.toJson()),
|
||||
type: SCRoomMsgType.roomBGUpdate,
|
||||
);
|
||||
rtmProvider.dispatchMessage(msg, addLocal: false);
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
} finally {
|
||||
SCLoadingManager.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_svga/flutter_svga.dart';
|
||||
@ -564,7 +566,6 @@ class _GiftWallSvgaEffectOverlay extends StatefulWidget {
|
||||
class _GiftWallSvgaEffectOverlayState extends State<_GiftWallSvgaEffectOverlay>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final SVGAAnimationController _controller;
|
||||
MovieEntity? _retainedMovieEntity;
|
||||
bool _loaded = false;
|
||||
bool _finished = false;
|
||||
|
||||
@ -584,15 +585,9 @@ class _GiftWallSvgaEffectOverlayState extends State<_GiftWallSvgaEffectOverlay>
|
||||
|
||||
Future<void> _loadAndPlay() async {
|
||||
try {
|
||||
final movieEntity = await SCGiftVapSvgaManager().acquireSvgaEntity(
|
||||
widget.sourceUrl,
|
||||
);
|
||||
if (!mounted || _finished) {
|
||||
SCGiftVapSvgaManager().releaseSvgaEntity(movieEntity);
|
||||
return;
|
||||
}
|
||||
final movieEntity = await _loadMovieEntity(widget.sourceUrl);
|
||||
if (!mounted || _finished) return;
|
||||
setState(() {
|
||||
_retainedMovieEntity = movieEntity;
|
||||
_loaded = true;
|
||||
_controller.videoItem = movieEntity;
|
||||
});
|
||||
@ -604,6 +599,28 @@ class _GiftWallSvgaEffectOverlayState extends State<_GiftWallSvgaEffectOverlay>
|
||||
}
|
||||
}
|
||||
|
||||
Future<MovieEntity> _loadMovieEntity(String sourceUrl) async {
|
||||
final cache = SCGiftVapSvgaManager().videoItemCache;
|
||||
final cached = cache[sourceUrl];
|
||||
if (cached != null) return cached;
|
||||
|
||||
final pathType = SCPathUtils.getPathType(sourceUrl);
|
||||
late final MovieEntity movieEntity;
|
||||
if (pathType == PathType.network) {
|
||||
movieEntity = await SVGAParser.shared.decodeFromURL(sourceUrl);
|
||||
} else if (pathType == PathType.asset) {
|
||||
movieEntity = await SVGAParser.shared.decodeFromAssets(sourceUrl);
|
||||
} else if (pathType == PathType.file) {
|
||||
final bytes = await File(sourceUrl).readAsBytes();
|
||||
movieEntity = await SVGAParser.shared.decodeFromBuffer(bytes);
|
||||
} else {
|
||||
throw Exception("Unsupported SVGA source: $sourceUrl");
|
||||
}
|
||||
movieEntity.autorelease = false;
|
||||
cache[sourceUrl] = movieEntity;
|
||||
return movieEntity;
|
||||
}
|
||||
|
||||
void _finish() {
|
||||
if (_finished) return;
|
||||
_finished = true;
|
||||
@ -614,8 +631,6 @@ class _GiftWallSvgaEffectOverlayState extends State<_GiftWallSvgaEffectOverlay>
|
||||
void dispose() {
|
||||
_controller.removeStatusListener(_handleStatusChanged);
|
||||
_controller.dispose();
|
||||
SCGiftVapSvgaManager().releaseSvgaEntity(_retainedMovieEntity);
|
||||
_retainedMovieEntity = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/sc_user_badge_res.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
||||
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
||||
|
||||
class ProfileWallOfHonorsPage extends StatefulWidget {
|
||||
@ -15,13 +20,49 @@ class ProfileWallOfHonorsPage extends StatefulWidget {
|
||||
|
||||
class _ProfileWallOfHonorsPageState extends State<ProfileWallOfHonorsPage> {
|
||||
static const Color _profileBg = Color(0xff072121);
|
||||
static const Color _mutedText = Color(0xff8F9A98);
|
||||
|
||||
int _selectedTabIndex = 0;
|
||||
final int _honorCount = 0;
|
||||
final int _eventCount = 0;
|
||||
bool _loading = true;
|
||||
SCUserBadgeRes _badges = SCUserBadgeRes.empty();
|
||||
|
||||
int get _totalCount => _honorCount + _eventCount;
|
||||
List<WearBadge> get _wallBadges => _badges.honorWallBadges;
|
||||
|
||||
int get _totalCount => _wallBadges.length;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadBadges();
|
||||
}
|
||||
|
||||
Future<void> _loadBadges() async {
|
||||
final userId = widget.tageId.trim();
|
||||
if (userId.isEmpty) {
|
||||
setState(() {
|
||||
_loading = false;
|
||||
_badges = SCUserBadgeRes.empty();
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final currentUserId =
|
||||
AccountStorage().getCurrentUser()?.userProfile?.id?.trim() ?? "";
|
||||
final result =
|
||||
currentUserId.isNotEmpty && currentUserId == userId
|
||||
? await SCAccountRepository().currentUserBadges()
|
||||
: await SCAccountRepository().otherUserBadges(userId);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_badges = result;
|
||||
_loading = false;
|
||||
});
|
||||
} catch (_) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_badges = SCUserBadgeRes.empty(userId: userId);
|
||||
_loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildTopBar() {
|
||||
return SizedBox(
|
||||
@ -61,54 +102,6 @@ class _ProfileWallOfHonorsPageState extends State<ProfileWallOfHonorsPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTabs() {
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
height: 82.w,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: _buildTab(index: 0, label: "Honors($_honorCount)")),
|
||||
Expanded(child: _buildTab(index: 1, label: "Event($_eventCount)")),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTab({required int index, required String label}) {
|
||||
final selected = _selectedTabIndex == index;
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
if (_selectedTabIndex == index) return;
|
||||
setState(() {
|
||||
_selectedTabIndex = index;
|
||||
});
|
||||
},
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
text(
|
||||
label,
|
||||
fontSize: 20,
|
||||
textColor: selected ? Colors.white : _mutedText,
|
||||
fontWeight: FontWeight.w400,
|
||||
maxLines: 1,
|
||||
),
|
||||
SizedBox(height: 9.w),
|
||||
AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 160),
|
||||
width: selected ? 28.w : 0,
|
||||
height: 4.w,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(4.w),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyState() {
|
||||
return SliverFillRemaining(
|
||||
hasScrollBody: false,
|
||||
@ -131,6 +124,70 @@ class _ProfileWallOfHonorsPageState extends State<ProfileWallOfHonorsPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoadingState() {
|
||||
return const SliverFillRemaining(
|
||||
hasScrollBody: false,
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 28,
|
||||
height: 28,
|
||||
child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBadgeGrid() {
|
||||
final list = _wallBadges;
|
||||
if (list.isEmpty) {
|
||||
return _buildEmptyState();
|
||||
}
|
||||
return SliverPadding(
|
||||
padding: EdgeInsets.fromLTRB(16.w, 4.w, 16.w, 32.w),
|
||||
sliver: SliverGrid(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) => _buildBadgeItem(list[index]),
|
||||
childCount: list.length,
|
||||
),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
mainAxisExtent: 96.w,
|
||||
mainAxisSpacing: 14.w,
|
||||
crossAxisSpacing: 12.w,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBadgeItem(WearBadge badge) {
|
||||
final url = _badgeUrl(badge);
|
||||
return Center(
|
||||
child: Container(
|
||||
width: 86.w,
|
||||
height: 86.w,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xff0E332D),
|
||||
borderRadius: BorderRadius.circular(8.w),
|
||||
border: Border.all(
|
||||
color: const Color(0xff18F2B1).withValues(alpha: 0.24),
|
||||
),
|
||||
),
|
||||
child: netImage(
|
||||
url: url,
|
||||
width: 68.w,
|
||||
height: 68.w,
|
||||
fit: BoxFit.contain,
|
||||
noDefaultImg: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _badgeUrl(WearBadge badge) {
|
||||
return (badge.selectUrl ?? "").trim();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@ -140,10 +197,8 @@ class _ProfileWallOfHonorsPageState extends State<ProfileWallOfHonorsPage> {
|
||||
child: CustomScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: Column(children: [_buildTopBar(), _buildTabs()]),
|
||||
),
|
||||
_buildEmptyState(),
|
||||
SliverToBoxAdapter(child: _buildTopBar()),
|
||||
_loading ? _buildLoadingState() : _buildBadgeGrid(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@ -13,6 +13,8 @@ import 'package:yumi/shared/tools/sc_loading_manager.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_debounce_widget.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
||||
import 'package:yumi/ui_kit/widgets/props/sc_data_card_resource_view.dart';
|
||||
import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
|
||||
|
||||
class VipDetailPage extends StatefulWidget {
|
||||
const VipDetailPage({super.key});
|
||||
@ -58,7 +60,6 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
final status = results[1] as SCVipStatusRes;
|
||||
final resolvedStatus = _resolveStatus(status, home.state);
|
||||
final selectedLevel = _resolveInitialLevel(home, resolvedStatus);
|
||||
_logLoadedVipData(home, resolvedStatus, selectedLevel);
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
@ -141,11 +142,6 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
_selectedLevel = level;
|
||||
_preview = null;
|
||||
});
|
||||
_logSelectedLevelResources(
|
||||
level,
|
||||
config: _levelConfigFor(level),
|
||||
status: _status,
|
||||
);
|
||||
_loadPreviewForLevel(level);
|
||||
}
|
||||
|
||||
@ -201,6 +197,7 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
await _loadVipData(showLoading: false);
|
||||
}
|
||||
} catch (error) {
|
||||
debugPrint('VIP purchase failed: $error');
|
||||
} finally {
|
||||
SCLoadingManager.hide();
|
||||
if (mounted) {
|
||||
@ -214,56 +211,6 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
return 'vip-buy-$userId-${DateTime.now().millisecondsSinceEpoch}';
|
||||
}
|
||||
|
||||
void _logLoadedVipData(
|
||||
SCVipHomeRes home,
|
||||
SCVipStatusRes? status,
|
||||
int selectedLevel,
|
||||
) {
|
||||
if (home.configured != true) {}
|
||||
if (home.levels.isEmpty) {}
|
||||
_logSelectedLevelResources(
|
||||
selectedLevel,
|
||||
config: _levelConfigFromHome(home, selectedLevel),
|
||||
status: status,
|
||||
);
|
||||
}
|
||||
|
||||
void _logSelectedLevelResources(
|
||||
int level, {
|
||||
required SCVipLevelConfigRes? config,
|
||||
required SCVipStatusRes? status,
|
||||
}) {
|
||||
final state = status?.levelInt == level ? status : null;
|
||||
if (config == null && state == null) {
|
||||
return;
|
||||
}
|
||||
final badge = _preferResource(config?.badge, state?.badge);
|
||||
final avatarFrame = _preferResource(
|
||||
config?.avatarFrame,
|
||||
state?.avatarFrame,
|
||||
);
|
||||
final entryEffect = _preferResource(
|
||||
config?.entryEffect,
|
||||
state?.entryEffect,
|
||||
);
|
||||
final chatBubble = _preferResource(config?.chatBubble, state?.chatBubble);
|
||||
final floatPicture = _preferResource(
|
||||
config?.floatPicture,
|
||||
state?.floatPicture,
|
||||
);
|
||||
}
|
||||
|
||||
String _resourceLogValue(SCVipResourceRes? resource) {
|
||||
if (!_hasResource(resource)) {
|
||||
return 'null';
|
||||
}
|
||||
return '{id:${resource?.resourceId},name:${resource?.name},'
|
||||
'hasPreviewUrl:${resource?.hasPreviewUrl},'
|
||||
'hasSourceUrl:${resource?.hasSourceUrl},'
|
||||
'previewUrl:${resource?.previewUrl},'
|
||||
'sourceUrl:${resource?.sourceResourceUrl}}';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@ -524,6 +471,7 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
|
||||
Widget _buildLevelCard() {
|
||||
final assetLevel = _assetLevel;
|
||||
final effectImage = _selectedEffectImage;
|
||||
return Center(
|
||||
child: SizedBox(
|
||||
width: 351.w,
|
||||
@ -532,18 +480,20 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8.w),
|
||||
border: Border.all(
|
||||
color: const Color(0xFFEBCB76),
|
||||
width: 1.w,
|
||||
),
|
||||
image: DecorationImage(
|
||||
image: AssetImage(
|
||||
'sc_images/vip/sc_vip_card_bg_$assetLevel.png',
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8.w),
|
||||
child: _buildVipCardBackground(assetLevel: assetLevel),
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: IgnorePointer(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8.w),
|
||||
border: Border.all(
|
||||
color: const Color(0xFFEBCB76),
|
||||
width: 1.w,
|
||||
),
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -572,22 +522,32 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
PositionedDirectional(
|
||||
end: 24.w,
|
||||
top: -36.w,
|
||||
child: _buildResourceImage(
|
||||
resource: _selectedBadge,
|
||||
width: 135.w,
|
||||
height: 135.w,
|
||||
fallbackIcon: Icons.workspace_premium_outlined,
|
||||
if (_hasResourceUrl(effectImage))
|
||||
PositionedDirectional(
|
||||
end: 24.w,
|
||||
top: -36.w,
|
||||
child: _buildDynamicResourceVisual(
|
||||
resource: effectImage,
|
||||
width: 135.w,
|
||||
height: 135.w,
|
||||
fallbackIcon: Icons.workspace_premium_outlined,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildVipCardBackground({required int assetLevel}) {
|
||||
return Image.asset(
|
||||
'sc_images/vip/sc_vip_card_bg_$assetLevel.png',
|
||||
fit: BoxFit.fill,
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLevelDivider() {
|
||||
final ornamentLayout = _ornamentLayoutForLevel(_assetLevel);
|
||||
return SizedBox(
|
||||
@ -658,19 +618,27 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
}
|
||||
|
||||
Widget _buildDecorationGrid(List<_VipFeatureData> items) {
|
||||
return Wrap(
|
||||
spacing: 10.w,
|
||||
runSpacing: 10.w,
|
||||
children:
|
||||
items
|
||||
.map(
|
||||
(item) => _buildFeatureTile(
|
||||
item,
|
||||
width: item.wide ? 206.w : 98.w,
|
||||
height: 100.w,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final spacing = 10.w;
|
||||
final maxWidth =
|
||||
constraints.maxWidth.isFinite ? constraints.maxWidth : 314.w;
|
||||
final itemWidth = (maxWidth - spacing * 2) / 3;
|
||||
return Wrap(
|
||||
spacing: spacing,
|
||||
runSpacing: spacing,
|
||||
children:
|
||||
items
|
||||
.map(
|
||||
(item) => _buildFeatureTile(
|
||||
item,
|
||||
width: itemWidth,
|
||||
height: 100.w,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -713,9 +681,14 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
);
|
||||
}
|
||||
|
||||
SCVipResourceRes? get _selectedBadge => _selectedResource(
|
||||
fromConfig: (config) => config.badge,
|
||||
fromState: (status) => status.badge,
|
||||
SCVipResourceRes? get _selectedLongBadge => _selectedResource(
|
||||
fromConfig: (config) => config.longBadge,
|
||||
fromState: (status) => status.longBadge,
|
||||
);
|
||||
|
||||
SCVipResourceRes? get _selectedShortBadge => _selectedResource(
|
||||
fromConfig: (config) => config.shortBadge,
|
||||
fromState: (status) => status.shortBadge,
|
||||
);
|
||||
|
||||
SCVipResourceRes? get _selectedAvatarFrame => _selectedResource(
|
||||
@ -738,6 +711,16 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
fromState: (status) => status.floatPicture,
|
||||
);
|
||||
|
||||
SCVipResourceRes? get _selectedBackgroundCard => _selectedResource(
|
||||
fromConfig: (config) => config.backgroundCard,
|
||||
fromState: (status) => status.backgroundCard,
|
||||
);
|
||||
|
||||
SCVipResourceRes? get _selectedEffectImage => _selectedResource(
|
||||
fromConfig: (config) => config.effectImage,
|
||||
fromState: (status) => status.effectImage,
|
||||
);
|
||||
|
||||
SCVipResourceRes? _selectedResource({
|
||||
required SCVipResourceRes? Function(SCVipLevelConfigRes config) fromConfig,
|
||||
required SCVipResourceRes? Function(SCVipStatusRes status) fromState,
|
||||
@ -768,8 +751,14 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
final items = <_VipFeatureData>[];
|
||||
_addResourceItem(
|
||||
items,
|
||||
resource: _selectedBadge,
|
||||
fallbackTitle: 'VIP Badge',
|
||||
resource: _selectedLongBadge,
|
||||
fallbackTitle: 'Long Badge',
|
||||
fallbackIcon: Icons.workspace_premium_outlined,
|
||||
);
|
||||
_addResourceItem(
|
||||
items,
|
||||
resource: _selectedShortBadge,
|
||||
fallbackTitle: 'Short Badge',
|
||||
fallbackIcon: Icons.workspace_premium_outlined,
|
||||
);
|
||||
_addResourceItem(
|
||||
@ -777,6 +766,7 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
resource: _selectedAvatarFrame,
|
||||
fallbackTitle: 'Avatar Frame',
|
||||
fallbackIcon: Icons.person_pin_circle_outlined,
|
||||
previewKind: SCStoreItemPreviewKind.avatarFrame,
|
||||
);
|
||||
_addResourceItem(
|
||||
items,
|
||||
@ -795,14 +785,21 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
resource: _selectedEntryEffect,
|
||||
fallbackTitle: 'Entry Effect',
|
||||
fallbackIcon: Icons.airline_seat_recline_extra,
|
||||
wide: true,
|
||||
);
|
||||
_addResourceItem(
|
||||
items,
|
||||
resource: _selectedBackgroundCard,
|
||||
fallbackTitle: 'Profile Card',
|
||||
fallbackIcon: Icons.badge_outlined,
|
||||
previewKind: SCStoreItemPreviewKind.dataCard,
|
||||
previewFit: BoxFit.cover,
|
||||
);
|
||||
return items;
|
||||
}
|
||||
|
||||
List<_VipFeatureData> get _vipPrivilegeItems {
|
||||
// The VIP home API currently returns resource perks only:
|
||||
// badge/avatarFrame/entryEffect/chatBubble/floatPicture.
|
||||
// badges/avatarFrame/entryEffect/chatBubble/floatPicture/backgroundCard.
|
||||
// Do not render demo-only functional privileges without backend data.
|
||||
return const <_VipFeatureData>[];
|
||||
}
|
||||
@ -813,6 +810,9 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
required String fallbackTitle,
|
||||
IconData? fallbackIcon,
|
||||
bool wide = false,
|
||||
SCStoreItemPreviewKind previewKind = SCStoreItemPreviewKind.resource,
|
||||
BoxFit previewFit = BoxFit.contain,
|
||||
BoxFit tileFit = BoxFit.contain,
|
||||
}) {
|
||||
if (!_hasResource(resource)) {
|
||||
return;
|
||||
@ -822,8 +822,12 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
_resourceTitle(resource, fallbackTitle),
|
||||
null,
|
||||
icon: fallbackIcon,
|
||||
resourceUrl: resource?.previewUrl,
|
||||
sourceUrl: resource?.sourceResourceUrl,
|
||||
coverUrl: _resourceCoverUrl(resource),
|
||||
wide: wide,
|
||||
previewKind: previewKind,
|
||||
previewFit: previewFit,
|
||||
tileFit: tileFit,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -844,6 +848,52 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
return fallbackTitle;
|
||||
}
|
||||
|
||||
String? _resourceCoverUrl(SCVipResourceRes? resource) {
|
||||
final coverUrl = resource?.coverUrl?.trim();
|
||||
if (coverUrl != null && coverUrl.isNotEmpty) {
|
||||
return coverUrl;
|
||||
}
|
||||
final cover = resource?.cover?.trim();
|
||||
if (cover != null && cover.isNotEmpty) {
|
||||
return cover;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Widget _buildDynamicResourceVisual({
|
||||
required SCVipResourceRes? resource,
|
||||
required double width,
|
||||
required double height,
|
||||
required IconData fallbackIcon,
|
||||
}) {
|
||||
final sourceUrl = resource?.sourceResourceUrl?.trim();
|
||||
final previewUrl = resource?.previewUrl?.trim();
|
||||
final hasSource = sourceUrl != null && sourceUrl.isNotEmpty;
|
||||
final hasPreview = previewUrl != null && previewUrl.isNotEmpty;
|
||||
if (hasSource || hasPreview) {
|
||||
return SCDataCardResourceView(
|
||||
sourceUrl: sourceUrl,
|
||||
coverUrl: previewUrl,
|
||||
width: width,
|
||||
height: height,
|
||||
fit: BoxFit.contain,
|
||||
fallback: _buildResourceImage(
|
||||
resource: resource,
|
||||
width: width,
|
||||
height: height,
|
||||
fallbackIcon: fallbackIcon,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return _buildResourceImage(
|
||||
resource: resource,
|
||||
width: width,
|
||||
height: height,
|
||||
fallbackIcon: fallbackIcon,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildResourceImage({
|
||||
required SCVipResourceRes? resource,
|
||||
required double width,
|
||||
@ -910,7 +960,7 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
}
|
||||
|
||||
Widget _buildFeatureIcon(_VipFeatureData item, {required double size}) {
|
||||
final url = item.resourceUrl?.trim();
|
||||
final url = item.coverUrl?.trim();
|
||||
if (url != null && url.isNotEmpty) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(6.w),
|
||||
@ -937,15 +987,15 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
}
|
||||
|
||||
Widget _buildFeatureVisual(_VipFeatureData item, {required bool isWide}) {
|
||||
final url = item.resourceUrl?.trim();
|
||||
if (url != null && url.isNotEmpty) {
|
||||
final coverUrl = item.coverUrl?.trim();
|
||||
if (coverUrl != null && coverUrl.isNotEmpty) {
|
||||
return netImage(
|
||||
url: url,
|
||||
fit: BoxFit.contain,
|
||||
url: coverUrl,
|
||||
fit: item.tileFit,
|
||||
noDefaultImg: true,
|
||||
errorWidget:
|
||||
item.assetPath != null
|
||||
? Image.asset(item.assetPath!, fit: BoxFit.contain)
|
||||
? Image.asset(item.assetPath!, fit: item.tileFit)
|
||||
: isWide
|
||||
? Center(child: _buildWideRewardPlaceholder())
|
||||
: _buildRewardPlaceholder(item.icon ?? Icons.auto_awesome),
|
||||
@ -1026,32 +1076,32 @@ class _VipDetailPageState extends State<VipDetailPage> {
|
||||
required double height,
|
||||
}) {
|
||||
final isWide = item.wide;
|
||||
return Container(
|
||||
width: width,
|
||||
height: height,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6.w),
|
||||
border: Border.all(color: const Color(0xFFEBCB76), width: 1.w),
|
||||
color: Colors.black.withValues(alpha: 0.38),
|
||||
),
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 8.w),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(child: _buildFeatureVisual(item, isWide: isWide)),
|
||||
SizedBox(height: 5.w),
|
||||
Text(
|
||||
item.title,
|
||||
maxLines: 2,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12.sp,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap:
|
||||
item.canPreview
|
||||
? () => showStoreBagFullScreenResourcePreview(
|
||||
context,
|
||||
sourceUrl: item.sourceUrl,
|
||||
coverUrl: item.coverUrl,
|
||||
previewKind: item.previewKind,
|
||||
fit: item.previewFit,
|
||||
)
|
||||
: null,
|
||||
child: Semantics(
|
||||
label: item.title,
|
||||
button: item.canPreview,
|
||||
child: Container(
|
||||
width: width,
|
||||
height: height,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6.w),
|
||||
border: Border.all(color: const Color(0xFFEBCB76), width: 1.w),
|
||||
color: Colors.black.withValues(alpha: 0.38),
|
||||
),
|
||||
],
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 8.w),
|
||||
child: Center(child: _buildFeatureVisual(item, isWide: isWide)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -1384,15 +1434,27 @@ class _VipFeatureData {
|
||||
this.title,
|
||||
this.assetPath, {
|
||||
this.icon,
|
||||
this.resourceUrl,
|
||||
this.sourceUrl,
|
||||
this.coverUrl,
|
||||
this.wide = false,
|
||||
this.previewKind = SCStoreItemPreviewKind.resource,
|
||||
this.previewFit = BoxFit.contain,
|
||||
this.tileFit = BoxFit.contain,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String? assetPath;
|
||||
final IconData? icon;
|
||||
final String? resourceUrl;
|
||||
final String? sourceUrl;
|
||||
final String? coverUrl;
|
||||
final bool wide;
|
||||
final SCStoreItemPreviewKind previewKind;
|
||||
final BoxFit previewFit;
|
||||
final BoxFit tileFit;
|
||||
|
||||
bool get canPreview =>
|
||||
(sourceUrl?.trim().isNotEmpty ?? false) ||
|
||||
(coverUrl?.trim().isNotEmpty ?? false);
|
||||
}
|
||||
|
||||
class _VipActionButtonConfig {
|
||||
|
||||