feat: thêm tính năng tải frame, presets, background từ cloud
This commit is contained in:
@@ -17,7 +17,8 @@
|
||||
android:icon="@android:mipmap/sym_def_app_icon"
|
||||
android:label="Retro Photobooth"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
android:theme="@style/Theme.MaterialComponents.DayNight.NoActionBar"
|
||||
android:usesCleartextTraffic="true">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
|
||||
@@ -41,10 +41,18 @@ class BackgroundOptionAdapter(
|
||||
|
||||
if (item.assetPath == null) {
|
||||
holder.imgBgThumb.setImageResource(R.drawable.ic_block_white)
|
||||
} else if (item.id == "MORE") {
|
||||
holder.imgBgThumb.setImageResource(R.drawable.ic_more)
|
||||
holder.txtBgName.text = "Tải thêm"
|
||||
} else {
|
||||
try {
|
||||
val inputStream = holder.itemView.context.assets.open(item.assetPath)
|
||||
val bitmap = BitmapFactory.decodeStream(inputStream)
|
||||
val localFile = java.io.File(holder.itemView.context.filesDir, item.assetPath)
|
||||
val bitmap = if (localFile.exists()) {
|
||||
BitmapFactory.decodeFile(localFile.absolutePath)
|
||||
} else {
|
||||
val inputStream = holder.itemView.context.assets.open(item.assetPath)
|
||||
BitmapFactory.decodeStream(inputStream)
|
||||
}
|
||||
holder.imgBgThumb.setImageBitmap(bitmap)
|
||||
} catch (e: Exception) {
|
||||
holder.imgBgThumb.setImageDrawable(null)
|
||||
@@ -71,4 +79,14 @@ class BackgroundOptionAdapter(
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = items.size
|
||||
|
||||
fun selectItem(itemId: String) {
|
||||
val index = items.indexOfFirst { it.id == itemId }
|
||||
if (index != -1 && index != selectedPosition) {
|
||||
val oldPos = selectedPosition
|
||||
selectedPosition = index
|
||||
notifyItemChanged(oldPos)
|
||||
notifyItemChanged(selectedPosition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,15 +37,23 @@ class FrameItemAdapter(
|
||||
if (item.id == "DEFAULT") {
|
||||
holder.imgFrameThumb.setImageResource(R.drawable.ic_block_white)
|
||||
holder.txtFrameName.text = holder.itemView.context.getString(R.string.default_frame)
|
||||
} else if (item.id == "MORE") {
|
||||
holder.imgFrameThumb.setImageResource(R.drawable.ic_more)
|
||||
holder.txtFrameName.text = "Tải thêm"
|
||||
} else {
|
||||
try {
|
||||
val path = if (item.imageFileName.startsWith("frames/")) {
|
||||
item.imageFileName
|
||||
val localFile = java.io.File(holder.itemView.context.filesDir, item.imageFileName)
|
||||
val bitmap = if (localFile.exists()) {
|
||||
BitmapFactory.decodeFile(localFile.absolutePath)
|
||||
} else {
|
||||
"frames/${item.imageFileName}"
|
||||
val path = if (item.imageFileName.startsWith("frames/")) {
|
||||
item.imageFileName
|
||||
} else {
|
||||
"frames/${item.imageFileName}"
|
||||
}
|
||||
val inputStream = holder.itemView.context.assets.open(path)
|
||||
BitmapFactory.decodeStream(inputStream)
|
||||
}
|
||||
val inputStream = holder.itemView.context.assets.open(path)
|
||||
val bitmap = BitmapFactory.decodeStream(inputStream)
|
||||
holder.imgFrameThumb.setImageBitmap(bitmap)
|
||||
} catch (e: Exception) {
|
||||
holder.imgFrameThumb.setImageDrawable(null)
|
||||
@@ -78,4 +86,14 @@ class FrameItemAdapter(
|
||||
selectedPosition = 0 // Reset to default when switching stack
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun selectItem(itemId: String) {
|
||||
val index = items.indexOfFirst { it.id == itemId }
|
||||
if (index != -1 && index != selectedPosition) {
|
||||
val oldPos = selectedPosition
|
||||
selectedPosition = index
|
||||
notifyItemChanged(oldPos)
|
||||
notifyItemChanged(selectedPosition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,11 +88,17 @@ class MainActivity : AppCompatActivity() {
|
||||
private var currentBackgroundMode = BackgroundMode.DEFAULT
|
||||
|
||||
private fun updateBackgroundMode() {
|
||||
val oldMode = currentBackgroundMode
|
||||
currentBackgroundMode = when {
|
||||
selectedBgAssetPath != null || blurIntensity > 0f -> BackgroundMode.AI_BLUR
|
||||
isPortraitBlurModeOpen -> BackgroundMode.HARDWARE_DOF
|
||||
else -> BackgroundMode.DEFAULT
|
||||
}
|
||||
if (currentBackgroundMode == BackgroundMode.AI_BLUR && oldMode != BackgroundMode.AI_BLUR) {
|
||||
// Tắt focus API: Hủy bỏ lấy nét thấu kính vật lý và reset class focus
|
||||
userSelectedClassFocus = -1
|
||||
cameraControl?.cancelFocusAndMetering()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getImageSegmenter(): ImageSegmenter? {
|
||||
@@ -162,7 +168,7 @@ class MainActivity : AppCompatActivity() {
|
||||
allFrames = listOf(
|
||||
FrameItem("DEFAULT", "Mặc định", ""),
|
||||
FrameItem("instax_mini_single", "Instax Mini", "frames/instaxframe.png")
|
||||
)
|
||||
) + loadDownloadedFrames() + listOf(FrameItem("MORE", "Tải thêm", ""))
|
||||
|
||||
// Initialize default Color Presets
|
||||
val defaultPresets = listOf(
|
||||
@@ -209,7 +215,18 @@ class MainActivity : AppCompatActivity() {
|
||||
)
|
||||
|
||||
// Load custom user saved presets from filesDir
|
||||
colorPresets = defaultPresets + loadCustomPresets()
|
||||
colorPresets = defaultPresets + loadCustomPresets() + listOf(
|
||||
ColorPreset(
|
||||
id = "MORE",
|
||||
name = "Tải thêm",
|
||||
themeCategory = "None",
|
||||
isEditable = false,
|
||||
basic = BasicAdjustments(0f, 0f, 0f, 0f, 0f, 0f),
|
||||
advanced = AdvancedEffects(0f, 0f, 0f),
|
||||
toneCurve = ToneCurveEmulation(0f, 0f, 0f, 0f),
|
||||
grain = FilmGrain(0f, 0f)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun loadCustomPresets(): List<ColorPreset> {
|
||||
@@ -276,13 +293,21 @@ class MainActivity : AppCompatActivity() {
|
||||
binding.rvHorizontalTimeline.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
|
||||
|
||||
frameItemAdapter = FrameItemAdapter(allFrames) { frame ->
|
||||
setFrameOverlay(frame.imageFileName)
|
||||
updateFrameModeIcon(frame)
|
||||
if (frame.id == "MORE") {
|
||||
openOnlineStoreDialog("frames")
|
||||
} else {
|
||||
setFrameOverlay(frame.imageFileName)
|
||||
updateFrameModeIcon(frame)
|
||||
}
|
||||
}
|
||||
|
||||
presetAdapter = PresetAdapter(colorPresets, { preset ->
|
||||
applyPresetFilter(preset)
|
||||
updatePresetModeIcon(preset)
|
||||
if (preset.id == "MORE") {
|
||||
openOnlineStoreDialog("presets")
|
||||
} else {
|
||||
applyPresetFilter(preset)
|
||||
updatePresetModeIcon(preset)
|
||||
}
|
||||
}, { preset ->
|
||||
openAdvancedSliders(preset)
|
||||
})
|
||||
@@ -323,10 +348,14 @@ class MainActivity : AppCompatActivity() {
|
||||
BackgroundOption("BG_01", "Nền 1", "backgrounds/background-01.png"),
|
||||
BackgroundOption("BG_02", "Nền 2", "backgrounds/background-02.png"),
|
||||
BackgroundOption("BG_03", "Nền 3", "backgrounds/background-03.png")
|
||||
)
|
||||
) + loadDownloadedBackgrounds() + listOf(BackgroundOption("MORE", "Tải thêm", "MORE"))
|
||||
backgroundOptionAdapter = BackgroundOptionAdapter(backgroundOptions) { option ->
|
||||
selectedBgAssetPath = option.assetPath
|
||||
updateBackgroundMode()
|
||||
if (option.id == "MORE") {
|
||||
openOnlineStoreDialog("backgrounds")
|
||||
} else {
|
||||
selectedBgAssetPath = option.assetPath
|
||||
updateBackgroundMode()
|
||||
}
|
||||
}
|
||||
binding.rvBackgroundOptions.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
|
||||
binding.rvBackgroundOptions.adapter = backgroundOptionAdapter
|
||||
@@ -352,8 +381,13 @@ class MainActivity : AppCompatActivity() {
|
||||
currentSelectedFramePath = null
|
||||
} else {
|
||||
try {
|
||||
val inputStream = assets.open(fileName)
|
||||
val bitmap = android.graphics.BitmapFactory.decodeStream(inputStream)
|
||||
val localFile = java.io.File(filesDir, fileName)
|
||||
val bitmap = if (localFile.exists()) {
|
||||
android.graphics.BitmapFactory.decodeFile(localFile.absolutePath)
|
||||
} else {
|
||||
val inputStream = assets.open(fileName)
|
||||
android.graphics.BitmapFactory.decodeStream(inputStream)
|
||||
}
|
||||
binding.imgFrameOverlay.setImageBitmap(bitmap)
|
||||
currentSelectedFramePath = fileName
|
||||
} catch (e: Exception) {
|
||||
@@ -747,18 +781,17 @@ class MainActivity : AppCompatActivity() {
|
||||
val percentX = (event.x / v.width).coerceIn(0f, 1f)
|
||||
val percentY = (event.y / v.height).coerceIn(0f, 1f)
|
||||
|
||||
// Cập nhật Class ID lấy nét từ điểm chạm
|
||||
val mask = cachedCategoryMask
|
||||
val mW = cachedMaskWidth
|
||||
val mH = cachedMaskHeight
|
||||
if (mask != null && mW > 0 && mH > 0) {
|
||||
val maskX = (percentX * mW).toInt().coerceIn(0, mW - 1)
|
||||
val maskY = (percentY * mH).toInt().coerceIn(0, mH - 1)
|
||||
userSelectedClassFocus = mask[maskY * mW + maskX].toInt()
|
||||
}
|
||||
|
||||
// 1. Gửi lệnh Focus vật lý và đo sáng qua CameraX API (chỉ khi không sử dụng tính năng AI tách/xóa nền)
|
||||
// Cập nhật Class ID lấy nét từ điểm chạm (chỉ khi không sử dụng tính năng AI tách/xóa nền)
|
||||
if (currentBackgroundMode != BackgroundMode.AI_BLUR) {
|
||||
val mask = cachedCategoryMask
|
||||
val mW = cachedMaskWidth
|
||||
val mH = cachedMaskHeight
|
||||
if (mask != null && mW > 0 && mH > 0) {
|
||||
val maskX = (percentX * mW).toInt().coerceIn(0, mW - 1)
|
||||
val maskY = (percentY * mH).toInt().coerceIn(0, mH - 1)
|
||||
userSelectedClassFocus = mask[maskY * mW + maskX].toInt()
|
||||
}
|
||||
|
||||
val cameraCtrl = this@MainActivity.cameraControl
|
||||
if (cameraCtrl != null) {
|
||||
try {
|
||||
@@ -787,6 +820,10 @@ class MainActivity : AppCompatActivity() {
|
||||
.setDuration(900)
|
||||
.withEndAction { focusRing.visibility = android.view.View.GONE }
|
||||
.start()
|
||||
} else {
|
||||
userSelectedClassFocus = -1
|
||||
// Đảm bảo hủy bỏ lấy nét thấu kính vật lý để camera tự động lấy nét toàn cảnh rộng (AF liên tục)
|
||||
cameraControl?.cancelFocusAndMetering()
|
||||
}
|
||||
|
||||
v.performClick()
|
||||
@@ -1287,6 +1324,11 @@ class MainActivity : AppCompatActivity() {
|
||||
cameraControl = camera.cameraControl
|
||||
cameraInfo = camera.cameraInfo
|
||||
|
||||
if (currentBackgroundMode == BackgroundMode.AI_BLUR) {
|
||||
userSelectedClassFocus = -1
|
||||
cameraControl?.cancelFocusAndMetering()
|
||||
}
|
||||
|
||||
// Reset zoom to 1.0x on start/switch
|
||||
applyZoom(1.0f)
|
||||
} catch (xc: Exception) {
|
||||
@@ -1770,40 +1812,17 @@ class MainActivity : AppCompatActivity() {
|
||||
cachedMaskHeight = h
|
||||
categoryMaskBuffer.rewind()
|
||||
|
||||
// Create and blur alpha mask with Touch-to-Focus class selection
|
||||
val focusClassId = if (userSelectedClassFocus >= 0) userSelectedClassFocus else -1
|
||||
// Create and blur alpha mask (keep entire person sharp: all classes > 0)
|
||||
val alphaMask = FloatArray(w * h)
|
||||
for (i in 0 until (w * h)) {
|
||||
val classId = if (i < bufferCapacity) rawBytes[i].toInt() else 0
|
||||
alphaMask[i] = if (focusClassId >= 0) {
|
||||
// Người dùng đã chạm chọn: chỉ lớp đúng class ID mới nét (Semantic Focus Plane)
|
||||
if (classId == focusClassId) 1.0f else 0.0f
|
||||
} else {
|
||||
// Mặc định: Giữ nét toàn bộ Người (ID 1) và Phụ kiện cận cảnh (ID 2)
|
||||
if (classId == 1 || classId == 2) 1.0f else 0.0f
|
||||
}
|
||||
alphaMask[i] = if (classId > 0) 1.0f else 0.0f
|
||||
}
|
||||
blurAlphaMaskBox(alphaMask, w, h, radius = 3)
|
||||
|
||||
// Prepare background (camera preview or custom background asset)
|
||||
val baseBg = if (selectedBgAssetPath != null) {
|
||||
try {
|
||||
val bgInputStream = assets.open(selectedBgAssetPath!!)
|
||||
val decodedBg = android.graphics.BitmapFactory.decodeStream(bgInputStream)
|
||||
if (decodedBg != null) {
|
||||
val tempScaled = android.graphics.Bitmap.createScaledBitmap(decodedBg, w, h, true)
|
||||
if (tempScaled != decodedBg) decodedBg.recycle()
|
||||
tempScaled
|
||||
} else {
|
||||
val solidBg = android.graphics.Bitmap.createBitmap(w, h, android.graphics.Bitmap.Config.ARGB_8888)
|
||||
solidBg.eraseColor(android.graphics.Color.BLACK)
|
||||
solidBg
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
val solidBg = android.graphics.Bitmap.createBitmap(w, h, android.graphics.Bitmap.Config.ARGB_8888)
|
||||
solidBg.eraseColor(android.graphics.Color.BLACK)
|
||||
solidBg
|
||||
}
|
||||
loadBackgroundBitmap(selectedBgAssetPath!!, w, h)
|
||||
} else {
|
||||
capturedImage
|
||||
}
|
||||
@@ -1885,40 +1904,17 @@ class MainActivity : AppCompatActivity() {
|
||||
val h = categoryMask.height
|
||||
val bufferCapacity = categoryMaskBuffer.capacity()
|
||||
|
||||
// Create and blur alpha mask with Touch-to-Focus class selection
|
||||
val focusClassId = if (userSelectedClassFocus >= 0) userSelectedClassFocus else -1
|
||||
// Create and blur alpha mask (keep entire person sharp: all classes > 0)
|
||||
val alphaMask = FloatArray(w * h)
|
||||
for (i in 0 until (w * h)) {
|
||||
val classId = if (i < bufferCapacity) categoryMaskBuffer.get(i).toInt() else 0
|
||||
alphaMask[i] = if (focusClassId >= 0) {
|
||||
// Người dùng đã chạm chọn: chỉ lớp đúng class ID mới nét (Semantic Focus Plane)
|
||||
if (classId == focusClassId) 1.0f else 0.0f
|
||||
} else {
|
||||
// Mặc định: Giữ nét toàn bộ Người (ID 1) và Phụ kiện cận cảnh (ID 2)
|
||||
if (classId == 1 || classId == 2) 1.0f else 0.0f
|
||||
}
|
||||
alphaMask[i] = if (classId > 0) 1.0f else 0.0f
|
||||
}
|
||||
blurAlphaMaskBox(alphaMask, w, h, radius = 5)
|
||||
|
||||
// Prepare background (camera preview or custom background asset)
|
||||
val baseBg = if (selectedBgAssetPath != null) {
|
||||
try {
|
||||
val bgInputStream = assets.open(selectedBgAssetPath!!)
|
||||
val decodedBg = android.graphics.BitmapFactory.decodeStream(bgInputStream)
|
||||
if (decodedBg != null) {
|
||||
val tempScaled = android.graphics.Bitmap.createScaledBitmap(decodedBg, w, h, true)
|
||||
if (tempScaled != decodedBg) decodedBg.recycle()
|
||||
tempScaled
|
||||
} else {
|
||||
val solidBg = android.graphics.Bitmap.createBitmap(w, h, android.graphics.Bitmap.Config.ARGB_8888)
|
||||
solidBg.eraseColor(android.graphics.Color.BLACK)
|
||||
solidBg
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
val solidBg = android.graphics.Bitmap.createBitmap(w, h, android.graphics.Bitmap.Config.ARGB_8888)
|
||||
solidBg.eraseColor(android.graphics.Color.BLACK)
|
||||
solidBg
|
||||
}
|
||||
loadBackgroundBitmap(selectedBgAssetPath!!, w, h)
|
||||
} else {
|
||||
// Triệt tiêu Ghost Shadow (Bóng ma rế viền) bằng giãn cơ thể ẩn (Inpainting Dilate)
|
||||
val cleanBg = android.graphics.Bitmap.createBitmap(w, h, android.graphics.Bitmap.Config.ARGB_8888)
|
||||
@@ -2317,11 +2313,11 @@ class MainActivity : AppCompatActivity() {
|
||||
val h = capturedImage.height
|
||||
val alphaMask = FloatArray(w * h)
|
||||
|
||||
// Giả lập mặt nạ bằng hình bầu dục lấy nét mềm ở trung tâm (mô phỏng chân dung)
|
||||
// Giả lập mặt nạ bằng hình bầu dục lấy nét mềm ở trung tâm (Tối ưu hóa đẩy tâm lên cao và mở rộng bán kính để giữ nét hoàn toàn cho khuôn mặt và đầu tóc người chụp)
|
||||
val cx = w * 0.5f
|
||||
val cy = h * 0.52f
|
||||
val rx = w * 0.28f
|
||||
val ry = h * 0.38f
|
||||
val cy = h * 0.40f
|
||||
val rx = w * 0.40f
|
||||
val ry = h * 0.50f
|
||||
for (y in 0 until h) {
|
||||
val dy = (y - cy) / ry
|
||||
val rowOffset = y * w
|
||||
@@ -2339,23 +2335,7 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
// TẠO HÌNH NỀN (Background Canvas)
|
||||
val baseBg = if (selectedBgAssetPath != null) {
|
||||
try {
|
||||
val bgInputStream = assets.open(selectedBgAssetPath!!)
|
||||
val decodedBg = android.graphics.BitmapFactory.decodeStream(bgInputStream)
|
||||
if (decodedBg != null) {
|
||||
val tempScaled = android.graphics.Bitmap.createScaledBitmap(decodedBg, w, h, true)
|
||||
if (tempScaled != decodedBg) decodedBg.recycle()
|
||||
tempScaled
|
||||
} else {
|
||||
val solidBg = android.graphics.Bitmap.createBitmap(w, h, android.graphics.Bitmap.Config.ARGB_8888)
|
||||
solidBg.eraseColor(android.graphics.Color.BLACK)
|
||||
solidBg
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
val solidBg = android.graphics.Bitmap.createBitmap(w, h, android.graphics.Bitmap.Config.ARGB_8888)
|
||||
solidBg.eraseColor(android.graphics.Color.BLACK)
|
||||
solidBg
|
||||
}
|
||||
loadBackgroundBitmap(selectedBgAssetPath!!, w, h)
|
||||
} else {
|
||||
// Triệt tiêu Ghost Shadow bằng Inpainting Dilate
|
||||
val cleanBg = android.graphics.Bitmap.createBitmap(w, h, android.graphics.Bitmap.Config.ARGB_8888)
|
||||
@@ -2428,6 +2408,372 @@ class MainActivity : AppCompatActivity() {
|
||||
return outputBitmap
|
||||
}
|
||||
|
||||
private fun loadBackgroundBitmap(path: String, w: Int, h: Int): android.graphics.Bitmap {
|
||||
try {
|
||||
val localFile = java.io.File(filesDir, path)
|
||||
val decodedBg = if (localFile.exists()) {
|
||||
android.graphics.BitmapFactory.decodeFile(localFile.absolutePath)
|
||||
} else {
|
||||
val bgInputStream = assets.open(path)
|
||||
android.graphics.BitmapFactory.decodeStream(bgInputStream)
|
||||
}
|
||||
if (decodedBg != null) {
|
||||
val tempScaled = android.graphics.Bitmap.createScaledBitmap(decodedBg, w, h, true)
|
||||
if (tempScaled != decodedBg) decodedBg.recycle()
|
||||
return tempScaled
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
val solidBg = android.graphics.Bitmap.createBitmap(w, h, android.graphics.Bitmap.Config.ARGB_8888)
|
||||
solidBg.eraseColor(android.graphics.Color.BLACK)
|
||||
return solidBg
|
||||
}
|
||||
|
||||
private fun loadDownloadedFrames(): List<FrameItem> {
|
||||
val list = mutableListOf<FrameItem>()
|
||||
val dir = java.io.File(filesDir, "downloaded_frames")
|
||||
if (dir.exists() && dir.isDirectory) {
|
||||
dir.listFiles()?.forEach { file ->
|
||||
if (file.isFile && file.name.endsWith(".png")) {
|
||||
val id = file.nameWithoutExtension
|
||||
val name = id.replace("_", " ").replaceFirstChar { if (it.isLowerCase()) it.titlecase(java.util.Locale.getDefault()) else it.toString() }
|
||||
list.add(FrameItem(id, name, "downloaded_frames/${file.name}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
private fun loadDownloadedBackgrounds(): List<BackgroundOption> {
|
||||
val list = mutableListOf<BackgroundOption>()
|
||||
val dir = java.io.File(filesDir, "downloaded_backgrounds")
|
||||
if (dir.exists() && dir.isDirectory) {
|
||||
dir.listFiles()?.forEach { file ->
|
||||
if (file.isFile && (file.name.endsWith(".jpg") || file.name.endsWith(".png") || file.name.endsWith(".jpeg"))) {
|
||||
val id = file.nameWithoutExtension
|
||||
val name = id.replace("_", " ").replaceFirstChar { if (it.isLowerCase()) it.titlecase(java.util.Locale.getDefault()) else it.toString() }
|
||||
list.add(BackgroundOption(id, name, "downloaded_backgrounds/${file.name}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
private var storeDialog: androidx.appcompat.app.AlertDialog? = null
|
||||
|
||||
private fun getServerUrl(): String {
|
||||
val prefs = getSharedPreferences("app_prefs", MODE_PRIVATE)
|
||||
return prefs.getString("server_url", "http://10.0.2.2:8080") ?: "http://10.0.2.2:8080"
|
||||
}
|
||||
|
||||
private fun saveServerUrl(url: String) {
|
||||
val prefs = getSharedPreferences("app_prefs", MODE_PRIVATE)
|
||||
prefs.edit().putString("server_url", url).apply()
|
||||
}
|
||||
|
||||
private fun openOnlineStoreDialog(defaultTab: String = "frames") {
|
||||
val dialogView = layoutInflater.inflate(R.layout.dialog_online_store, null)
|
||||
val etServerUrl = dialogView.findViewById<android.widget.EditText>(R.id.etServerUrl)
|
||||
val btnConnectServer = dialogView.findViewById<android.widget.Button>(R.id.btnConnectServer)
|
||||
val btnTabFrames = dialogView.findViewById<android.widget.Button>(R.id.btnTabFrames)
|
||||
val btnTabBackgrounds = dialogView.findViewById<android.widget.Button>(R.id.btnTabBackgrounds)
|
||||
val btnTabPresets = dialogView.findViewById<android.widget.Button>(R.id.btnTabPresets)
|
||||
val rvStoreAssets = dialogView.findViewById<androidx.recyclerview.widget.RecyclerView>(R.id.rvStoreAssets)
|
||||
val progressLoadingStore = dialogView.findViewById<android.widget.ProgressBar>(R.id.progressLoadingStore)
|
||||
val layoutStoreError = dialogView.findViewById<android.widget.LinearLayout>(R.id.layoutStoreError)
|
||||
val btnStoreRetry = dialogView.findViewById<android.widget.Button>(R.id.btnStoreRetry)
|
||||
val btnStoreClose = dialogView.findViewById<android.widget.ImageButton>(R.id.btnStoreClose)
|
||||
|
||||
etServerUrl.setText(getServerUrl())
|
||||
|
||||
var currentTab = defaultTab
|
||||
var allFetchedItems = listOf<OnlineStoreItem>()
|
||||
|
||||
val storeAdapter = StoreItemAdapter(listOf(), filesDir) { storeItem ->
|
||||
if (storeItem.downloadState == DownloadState.DOWNLOADED) {
|
||||
applyDownloadedAsset(storeItem)
|
||||
storeDialog?.dismiss()
|
||||
} else if (storeItem.downloadState == DownloadState.NOT_DOWNLOADED) {
|
||||
downloadAsset(storeItem, rvStoreAssets.adapter as StoreItemAdapter)
|
||||
}
|
||||
}
|
||||
rvStoreAssets.layoutManager = LinearLayoutManager(this)
|
||||
rvStoreAssets.adapter = storeAdapter
|
||||
|
||||
fun updateTabButtons() {
|
||||
val primaryColor = ContextCompat.getColor(this, R.color.theme_primary)
|
||||
val cardColor = ContextCompat.getColor(this, R.color.theme_item_card_bg)
|
||||
val darkColor = ContextCompat.getColor(this, R.color.theme_panel_dark)
|
||||
val whiteColor = ContextCompat.getColor(this, R.color.theme_white)
|
||||
|
||||
btnTabFrames.setBackgroundColor(if (currentTab == "frames") primaryColor else cardColor)
|
||||
btnTabFrames.setTextColor(if (currentTab == "frames") darkColor else whiteColor)
|
||||
|
||||
btnTabBackgrounds.setBackgroundColor(if (currentTab == "backgrounds") primaryColor else cardColor)
|
||||
btnTabBackgrounds.setTextColor(if (currentTab == "backgrounds") darkColor else whiteColor)
|
||||
|
||||
btnTabPresets.setBackgroundColor(if (currentTab == "presets") primaryColor else cardColor)
|
||||
btnTabPresets.setTextColor(if (currentTab == "presets") darkColor else whiteColor)
|
||||
|
||||
val filtered = allFetchedItems.filter { it.category == currentTab }
|
||||
storeAdapter.updateItems(filtered)
|
||||
}
|
||||
|
||||
fun fetchManifest() {
|
||||
val urlStr = etServerUrl.text.toString().trim()
|
||||
if (urlStr.isEmpty()) {
|
||||
Toast.makeText(this, "Vui lòng nhập địa chỉ server", Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
saveServerUrl(urlStr)
|
||||
|
||||
progressLoadingStore.visibility = android.view.View.VISIBLE
|
||||
rvStoreAssets.visibility = android.view.View.GONE
|
||||
layoutStoreError.visibility = android.view.View.GONE
|
||||
|
||||
Thread {
|
||||
try {
|
||||
val url = java.net.URL("$urlStr/manifest.json")
|
||||
val connection = url.openConnection() as java.net.HttpURLConnection
|
||||
connection.connectTimeout = 5000
|
||||
connection.readTimeout = 5000
|
||||
val content = connection.inputStream.bufferedReader().use { it.readText() }
|
||||
val json = org.json.JSONObject(content)
|
||||
|
||||
val itemsList = mutableListOf<OnlineStoreItem>()
|
||||
|
||||
if (json.has("frames")) {
|
||||
val arr = json.getJSONArray("frames")
|
||||
for (i in 0 until arr.length()) {
|
||||
val obj = arr.getJSONObject(i)
|
||||
itemsList.add(OnlineStoreItem(
|
||||
id = obj.getString("id"),
|
||||
name = obj.getString("name"),
|
||||
url = obj.getString("url"),
|
||||
version = obj.optInt("version", 1),
|
||||
category = "frames"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
if (json.has("backgrounds")) {
|
||||
val arr = json.getJSONArray("backgrounds")
|
||||
for (i in 0 until arr.length()) {
|
||||
val obj = arr.getJSONObject(i)
|
||||
itemsList.add(OnlineStoreItem(
|
||||
id = obj.getString("id"),
|
||||
name = obj.getString("name"),
|
||||
url = obj.getString("url"),
|
||||
version = obj.optInt("version", 1),
|
||||
category = "backgrounds"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
if (json.has("presets")) {
|
||||
val arr = json.getJSONArray("presets")
|
||||
for (i in 0 until arr.length()) {
|
||||
val obj = arr.getJSONObject(i)
|
||||
itemsList.add(OnlineStoreItem(
|
||||
id = obj.getString("id"),
|
||||
name = obj.getString("name"),
|
||||
url = obj.getString("url"),
|
||||
version = obj.optInt("version", 1),
|
||||
category = "presets"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
runOnUiThread {
|
||||
allFetchedItems = itemsList
|
||||
progressLoadingStore.visibility = android.view.View.GONE
|
||||
rvStoreAssets.visibility = android.view.View.VISIBLE
|
||||
updateTabButtons()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
runOnUiThread {
|
||||
progressLoadingStore.visibility = android.view.View.GONE
|
||||
layoutStoreError.visibility = android.view.View.VISIBLE
|
||||
Toast.makeText(this, "Vui lòng kết nối mạng để tải thêm tài nguyên", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
btnConnectServer.setOnClickListener { fetchManifest() }
|
||||
btnStoreRetry.setOnClickListener { fetchManifest() }
|
||||
btnTabFrames.setOnClickListener {
|
||||
currentTab = "frames"
|
||||
updateTabButtons()
|
||||
}
|
||||
btnTabBackgrounds.setOnClickListener {
|
||||
currentTab = "backgrounds"
|
||||
updateTabButtons()
|
||||
}
|
||||
btnTabPresets.setOnClickListener {
|
||||
currentTab = "presets"
|
||||
updateTabButtons()
|
||||
}
|
||||
btnStoreClose.setOnClickListener { storeDialog?.dismiss() }
|
||||
|
||||
fetchManifest()
|
||||
|
||||
storeDialog = androidx.appcompat.app.AlertDialog.Builder(this)
|
||||
.setView(dialogView)
|
||||
.create()
|
||||
storeDialog?.window?.setBackgroundDrawableResource(android.R.color.transparent)
|
||||
storeDialog?.show()
|
||||
}
|
||||
|
||||
private fun downloadAsset(item: OnlineStoreItem, adapter: StoreItemAdapter) {
|
||||
item.downloadState = DownloadState.DOWNLOADING
|
||||
adapter.notifyDataSetChanged()
|
||||
|
||||
Thread {
|
||||
try {
|
||||
val url = java.net.URL(item.url)
|
||||
val targetDir = when (item.category) {
|
||||
"frames" -> java.io.File(filesDir, "downloaded_frames")
|
||||
"backgrounds" -> java.io.File(filesDir, "downloaded_backgrounds")
|
||||
else -> filesDir
|
||||
}
|
||||
if (!targetDir.exists()) targetDir.mkdirs()
|
||||
|
||||
val ext = item.url.substringAfterLast(".", "png")
|
||||
val fileName = if (item.category == "presets") "${item.id}.json" else "${item.id}.$ext"
|
||||
val outputFile = java.io.File(targetDir, fileName)
|
||||
|
||||
url.openStream().use { input ->
|
||||
outputFile.outputStream().use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
|
||||
runOnUiThread {
|
||||
item.downloadState = DownloadState.DOWNLOADED
|
||||
adapter.notifyDataSetChanged()
|
||||
Toast.makeText(this, "Tải thành công: ${item.name}", Toast.LENGTH_SHORT).show()
|
||||
reloadLocalAssets()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
runOnUiThread {
|
||||
item.downloadState = DownloadState.NOT_DOWNLOADED
|
||||
adapter.notifyDataSetChanged()
|
||||
Toast.makeText(this, "Tải thất bại: ${e.message}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
private fun reloadLocalAssets() {
|
||||
allFrames = listOf(
|
||||
FrameItem("DEFAULT", "Mặc định", ""),
|
||||
FrameItem("instax_mini_single", "Instax Mini", "frames/instaxframe.png")
|
||||
) + loadDownloadedFrames() + listOf(FrameItem("MORE", "Tải thêm", ""))
|
||||
frameItemAdapter.updateItems(allFrames)
|
||||
|
||||
backgroundOptions = listOf(
|
||||
BackgroundOption("NONE", "Mặc định", null),
|
||||
BackgroundOption("BG_01", "Nền 1", "backgrounds/background-01.png"),
|
||||
BackgroundOption("BG_02", "Nền 2", "backgrounds/background-02.png"),
|
||||
BackgroundOption("BG_03", "Nền 3", "backgrounds/background-03.png")
|
||||
) + loadDownloadedBackgrounds() + listOf(BackgroundOption("MORE", "Tải thêm", "MORE"))
|
||||
backgroundOptionAdapter = BackgroundOptionAdapter(backgroundOptions) { option ->
|
||||
if (option.id == "MORE") {
|
||||
openOnlineStoreDialog("backgrounds")
|
||||
} else {
|
||||
selectedBgAssetPath = option.assetPath
|
||||
updateBackgroundMode()
|
||||
}
|
||||
}
|
||||
binding.rvBackgroundOptions.adapter = backgroundOptionAdapter
|
||||
|
||||
val defaultPresets = listOf(
|
||||
ColorPreset(
|
||||
id = "DEFAULT",
|
||||
name = "Mặc định",
|
||||
themeCategory = "None",
|
||||
isEditable = false,
|
||||
basic = BasicAdjustments(0f, 0f, 0f, 0f, 0f, 0f),
|
||||
advanced = AdvancedEffects(0f, 0f, 0f),
|
||||
toneCurve = ToneCurveEmulation(0f, 0f, 0f, 0f),
|
||||
grain = FilmGrain(0f, 0f)
|
||||
),
|
||||
ColorPreset(
|
||||
id = "instax_faded_warm_01",
|
||||
name = "Instax Nắng Chiều",
|
||||
themeCategory = "Retro Vintage",
|
||||
isEditable = true,
|
||||
basic = BasicAdjustments(0.05f, -0.15f, -0.20f, 0.25f, 0.18f, -0.02f),
|
||||
advanced = AdvancedEffects(-0.20f, -0.15f, 0.35f),
|
||||
toneCurve = ToneCurveEmulation(0.12f, 0.0f, 0.04f, 0.08f),
|
||||
grain = FilmGrain(0.28f, 0.15f)
|
||||
),
|
||||
ColorPreset(
|
||||
id = "instax_bw_cool",
|
||||
name = "Instax Trắng Đen",
|
||||
themeCategory = "Monochrome",
|
||||
isEditable = true,
|
||||
basic = BasicAdjustments(0.0f, 0.10f, -1.0f, 0.0f, -0.05f, 0.0f),
|
||||
advanced = AdvancedEffects(0.05f, 0.0f, 0.20f),
|
||||
toneCurve = ToneCurveEmulation(0.08f, 0.0f, 0.0f, 0.0f),
|
||||
grain = FilmGrain(0.35f, 0.18f)
|
||||
),
|
||||
ColorPreset(
|
||||
id = "instax_cool_summer",
|
||||
name = "Mùa Hè Dịu Mát",
|
||||
themeCategory = "Summer",
|
||||
isEditable = true,
|
||||
basic = BasicAdjustments(0.02f, -0.05f, -0.10f, 0.15f, -0.18f, 0.05f),
|
||||
advanced = AdvancedEffects(-0.10f, -0.05f, 0.15f),
|
||||
toneCurve = ToneCurveEmulation(0.05f, 0.02f, 0.05f, 0.10f),
|
||||
grain = FilmGrain(0.15f, 0.12f)
|
||||
)
|
||||
)
|
||||
colorPresets = defaultPresets + loadCustomPresets() + listOf(
|
||||
ColorPreset(
|
||||
id = "MORE",
|
||||
name = "Tải thêm",
|
||||
themeCategory = "None",
|
||||
isEditable = false,
|
||||
basic = BasicAdjustments(0f, 0f, 0f, 0f, 0f, 0f),
|
||||
advanced = AdvancedEffects(0f, 0f, 0f),
|
||||
toneCurve = ToneCurveEmulation(0f, 0f, 0f, 0f),
|
||||
grain = FilmGrain(0f, 0f)
|
||||
)
|
||||
)
|
||||
presetAdapter.updatePresets(colorPresets)
|
||||
}
|
||||
|
||||
private fun applyDownloadedAsset(item: OnlineStoreItem) {
|
||||
when (item.category) {
|
||||
"frames" -> {
|
||||
val ext = item.url.substringAfterLast(".", "png")
|
||||
val path = "downloaded_frames/${item.id}.$ext"
|
||||
setFrameOverlay(path)
|
||||
frameItemAdapter.selectItem(item.id)
|
||||
}
|
||||
"backgrounds" -> {
|
||||
val ext = item.url.substringAfterLast(".", "png")
|
||||
val path = "downloaded_backgrounds/${item.id}.$ext"
|
||||
selectedBgAssetPath = path
|
||||
updateBackgroundMode()
|
||||
backgroundOptionAdapter.selectItem(item.id)
|
||||
}
|
||||
"presets" -> {
|
||||
val targetPreset = colorPresets.find { it.id == item.id }
|
||||
if (targetPreset != null) {
|
||||
applyPresetFilter(targetPreset)
|
||||
updatePresetModeIcon(targetPreset)
|
||||
presetAdapter.selectItem(item.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
cameraExecutor.shutdown()
|
||||
|
||||
@@ -39,6 +39,9 @@ class PresetAdapter(
|
||||
if (preset.id == "DEFAULT") {
|
||||
holder.viewPresetColor.setBackgroundResource(R.drawable.ic_block_white)
|
||||
holder.txtPresetName.text = holder.itemView.context.getString(R.string.default_frame)
|
||||
} else if (preset.id == "MORE") {
|
||||
holder.viewPresetColor.setBackgroundResource(R.drawable.ic_more)
|
||||
holder.txtPresetName.text = "Tải thêm"
|
||||
} else {
|
||||
val colorHex = when (preset.id) {
|
||||
"instax_faded_warm_01" -> "#FFF57C00"
|
||||
@@ -125,4 +128,14 @@ class PresetAdapter(
|
||||
notifyItemChanged(previousSelected)
|
||||
notifyItemChanged(selectedPosition)
|
||||
}
|
||||
|
||||
fun selectItem(presetId: String) {
|
||||
val index = presets.indexOfFirst { it.id == presetId }
|
||||
if (index != -1 && index != selectedPosition) {
|
||||
val oldPos = selectedPosition
|
||||
selectedPosition = index
|
||||
notifyItemChanged(oldPos)
|
||||
notifyItemChanged(selectedPosition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.photobooth.app
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import java.io.File
|
||||
|
||||
data class OnlineStoreItem(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val url: String,
|
||||
val version: Int,
|
||||
val category: String, // "frames", "backgrounds", "presets"
|
||||
var downloadState: DownloadState = DownloadState.NOT_DOWNLOADED
|
||||
)
|
||||
|
||||
enum class DownloadState {
|
||||
NOT_DOWNLOADED,
|
||||
DOWNLOADING,
|
||||
DOWNLOADED
|
||||
}
|
||||
|
||||
class StoreItemAdapter(
|
||||
private var items: List<OnlineStoreItem>,
|
||||
private val filesDir: File,
|
||||
private val onActionClick: (OnlineStoreItem) -> Unit
|
||||
) : RecyclerView.Adapter<StoreItemAdapter.ViewHolder>() {
|
||||
|
||||
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||
val imgThumb: ImageView = view.findViewById(R.id.imgStoreAssetThumb)
|
||||
val txtName: TextView = view.findViewById(R.id.txtStoreAssetName)
|
||||
val btnAction: Button = view.findViewById(R.id.btnStoreAssetAction)
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_online_asset, parent, false)
|
||||
return ViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item = items[position]
|
||||
holder.txtName.text = item.name
|
||||
|
||||
// Set icon based on category
|
||||
when (item.category) {
|
||||
"frames" -> holder.imgThumb.setImageResource(R.drawable.ic_default_frame_thumbnail)
|
||||
"backgrounds" -> holder.imgThumb.setImageResource(R.drawable.ic_image_gallery)
|
||||
"presets" -> holder.imgThumb.setImageResource(R.drawable.ic_default_preset_thumbnail)
|
||||
else -> holder.imgThumb.setImageResource(R.drawable.ic_image_gallery)
|
||||
}
|
||||
|
||||
// Determine if downloaded
|
||||
val isDownloaded = isAssetDownloaded(item)
|
||||
if (isDownloaded) {
|
||||
item.downloadState = DownloadState.DOWNLOADED
|
||||
} else if (item.downloadState != DownloadState.DOWNLOADING) {
|
||||
item.downloadState = DownloadState.NOT_DOWNLOADED
|
||||
}
|
||||
|
||||
// Update button appearance based on state
|
||||
when (item.downloadState) {
|
||||
DownloadState.NOT_DOWNLOADED -> {
|
||||
holder.btnAction.text = "Tải về"
|
||||
holder.btnAction.isEnabled = true
|
||||
holder.btnAction.setBackgroundColor(
|
||||
androidx.core.content.ContextCompat.getColor(holder.itemView.context, R.color.theme_primary)
|
||||
)
|
||||
}
|
||||
DownloadState.DOWNLOADING -> {
|
||||
holder.btnAction.text = "Đang tải..."
|
||||
holder.btnAction.isEnabled = false
|
||||
holder.btnAction.setBackgroundColor(
|
||||
androidx.core.content.ContextCompat.getColor(holder.itemView.context, R.color.theme_item_card_bg)
|
||||
)
|
||||
}
|
||||
DownloadState.DOWNLOADED -> {
|
||||
holder.btnAction.text = "Áp dụng"
|
||||
holder.btnAction.isEnabled = true
|
||||
holder.btnAction.setBackgroundColor(
|
||||
android.graphics.Color.parseColor("#4CAF50") // Green for Apply
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
holder.btnAction.setOnClickListener {
|
||||
onActionClick(item)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = items.size
|
||||
|
||||
fun updateItems(newItems: List<OnlineStoreItem>) {
|
||||
items = newItems
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
private fun isAssetDownloaded(item: OnlineStoreItem): Boolean {
|
||||
return when (item.category) {
|
||||
"frames" -> {
|
||||
val ext = item.url.substringAfterLast(".", "png")
|
||||
val localFile = File(filesDir, "downloaded_frames/${item.id}.$ext")
|
||||
localFile.exists()
|
||||
}
|
||||
"backgrounds" -> {
|
||||
val ext = item.url.substringAfterLast(".", "png")
|
||||
val localFile = File(filesDir, "downloaded_backgrounds/${item.id}.$ext")
|
||||
localFile.exists()
|
||||
}
|
||||
"presets" -> {
|
||||
val localFile = File(filesDir, "${item.id}.json")
|
||||
localFile.exists()
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M6,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,168 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/theme_sub_bar_bg"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- Header Section -->
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvStoreTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:text="Cửa Hàng Tài Nguyên"
|
||||
android:textColor="@color/theme_white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnStoreClose"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:src="@drawable/ic_close_white"
|
||||
android:contentDescription="Close Store" />
|
||||
</RelativeLayout>
|
||||
|
||||
<!-- Editable Cloud Server URL Input -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/theme_item_card_bg"
|
||||
android:padding="8dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etServerUrl"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:hint="Địa chỉ Cloud Server"
|
||||
android:textColorHint="#88FFFFFF"
|
||||
android:textColor="@color/theme_white"
|
||||
android:textSize="14sp"
|
||||
android:inputType="textUri"
|
||||
android:background="@android:color/transparent"
|
||||
android:text="http://10.0.2.2:8080" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnConnectServer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Kết nối"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@color/theme_panel_dark"
|
||||
android:backgroundTint="@color/theme_primary"
|
||||
android:paddingHorizontal="12dp"
|
||||
android:minHeight="36dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Custom Category Tabs -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:weightSum="3">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnTabFrames"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:text="Khung ảnh"
|
||||
android:textSize="12sp"
|
||||
android:backgroundTint="@color/theme_primary"
|
||||
android:textColor="@color/theme_panel_dark"
|
||||
android:paddingHorizontal="2dp"
|
||||
android:minHeight="40dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnTabBackgrounds"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginHorizontal="2dp"
|
||||
android:text="Phông nền"
|
||||
android:textSize="12sp"
|
||||
android:backgroundTint="@color/theme_item_card_bg"
|
||||
android:textColor="@color/theme_white"
|
||||
android:paddingHorizontal="2dp"
|
||||
android:minHeight="40dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnTabPresets"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="4dp"
|
||||
android:text="Bộ màu"
|
||||
android:textSize="12sp"
|
||||
android:backgroundTint="@color/theme_item_card_bg"
|
||||
android:textColor="@color/theme_white"
|
||||
android:paddingHorizontal="2dp"
|
||||
android:minHeight="40dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- List & Loading Container -->
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="250dp">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvStoreAssets"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="visible" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressLoadingStore"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminateTint="@color/theme_primary"
|
||||
android:visibility="gone" />
|
||||
|
||||
<!-- Error Layout -->
|
||||
<LinearLayout
|
||||
android:id="@+id/layoutStoreError"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:visibility="gone"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Vui lòng kết nối mạng để tải thêm tài nguyên"
|
||||
android:textColor="#E57373"
|
||||
android:textSize="14sp"
|
||||
android:gravity="center"
|
||||
android:layout_marginBottom="12dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnStoreRetry"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Thử lại"
|
||||
android:textColor="@color/theme_panel_dark"
|
||||
android:backgroundTint="@color/theme_primary" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:background="@color/theme_item_card_bg"
|
||||
android:layout_marginVertical="4dp"
|
||||
android:padding="8dp">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
app:cardCornerRadius="4dp"
|
||||
app:strokeWidth="1dp"
|
||||
app:strokeColor="#44FFFFFF"
|
||||
android:layout_marginEnd="12dp"
|
||||
app:cardBackgroundColor="@color/theme_panel_dark">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgStoreAssetThumb"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/ic_image_gallery" />
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtStoreAssetName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Asset Name"
|
||||
android:textColor="@color/theme_white"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnStoreAssetAction"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Tải về"
|
||||
android:textSize="11sp"
|
||||
android:textColor="@color/theme_panel_dark"
|
||||
android:backgroundTint="@color/theme_primary"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:minHeight="36dp"
|
||||
android:minWidth="90dp" />
|
||||
|
||||
</LinearLayout>
|
||||
Reference in New Issue
Block a user