Приложение
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
package stories.presentation
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import core.network.ServerConfig
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody.Companion.asRequestBody
|
||||
import stories.data.remote.api.CreateStoryRequest
|
||||
import stories.data.remote.api.StoryApi
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
||||
data class TextObject(
|
||||
val id: Long = System.currentTimeMillis(),
|
||||
val text: String,
|
||||
val offset: Offset = Offset(100f, 100f),
|
||||
val color: Color = Color.White,
|
||||
val fontSize: Float = 24f,
|
||||
val scale: Float = 1f
|
||||
)
|
||||
|
||||
data class StickerObject(
|
||||
val id: Long = System.currentTimeMillis(),
|
||||
val emoji: String,
|
||||
val offset: Offset = Offset(200f, 200f),
|
||||
val scale: Float = 1.5f
|
||||
)
|
||||
|
||||
sealed class StoryTool {
|
||||
object None : StoryTool()
|
||||
object Text : StoryTool()
|
||||
object Crop : StoryTool()
|
||||
object Stickers : StoryTool()
|
||||
object Brush : StoryTool()
|
||||
object Filters : StoryTool()
|
||||
object Trim : StoryTool()
|
||||
}
|
||||
|
||||
@HiltViewModel
|
||||
class CreateStoryViewModel @Inject constructor(
|
||||
private val storyApi: StoryApi,
|
||||
private val serverConfig: ServerConfig
|
||||
) : ViewModel() {
|
||||
|
||||
var selectedMediaUri by mutableStateOf<Uri?>(null)
|
||||
private set
|
||||
|
||||
var isVideo by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
var currentTool by mutableStateOf<StoryTool>(StoryTool.None)
|
||||
private set
|
||||
|
||||
var bgColor by mutableStateOf(Color(0xFF1E1E2E))
|
||||
private set
|
||||
|
||||
val textObjects = mutableStateListOf<TextObject>()
|
||||
val stickerObjects = mutableStateListOf<StickerObject>()
|
||||
|
||||
var isUploading by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
private val _uploadSuccess = MutableStateFlow(false)
|
||||
val uploadSuccess = _uploadSuccess.asStateFlow()
|
||||
|
||||
private val _errorMessage = MutableStateFlow<String?>(null)
|
||||
val errorMessage = _errorMessage.asStateFlow()
|
||||
|
||||
fun onMediaSelected(uri: Uri, isVideo: Boolean) {
|
||||
this.selectedMediaUri = uri
|
||||
this.isVideo = isVideo
|
||||
if (isVideo) {
|
||||
currentTool = StoryTool.Trim
|
||||
} else {
|
||||
currentTool = StoryTool.Crop
|
||||
}
|
||||
}
|
||||
|
||||
fun setTool(tool: StoryTool) {
|
||||
currentTool = tool
|
||||
}
|
||||
|
||||
fun addText(text: String) {
|
||||
textObjects.add(TextObject(text = text))
|
||||
currentTool = StoryTool.Text
|
||||
}
|
||||
|
||||
fun addSticker(emoji: String) {
|
||||
stickerObjects.add(StickerObject(emoji = emoji))
|
||||
}
|
||||
|
||||
fun updateBgColor(color: Color) {
|
||||
bgColor = color
|
||||
}
|
||||
|
||||
fun removeMedia() {
|
||||
selectedMediaUri = null
|
||||
isVideo = false
|
||||
currentTool = StoryTool.None
|
||||
textObjects.clear()
|
||||
stickerObjects.clear()
|
||||
}
|
||||
|
||||
fun publishStory(context: android.content.Context) {
|
||||
val uri = selectedMediaUri ?: return
|
||||
val maxFileSize = serverConfig.getServerConfig().limits.maxFileSize
|
||||
|
||||
viewModelScope.launch {
|
||||
isUploading = true
|
||||
try {
|
||||
val file = uriToFile(context, uri)
|
||||
|
||||
if (file.length() > maxFileSize) {
|
||||
_errorMessage.value = "File too large for this server"
|
||||
return@launch
|
||||
}
|
||||
|
||||
val mediaUrl = if (isVideo) {
|
||||
val part = MultipartBody.Part.createFormData(
|
||||
"file", file.name, file.asRequestBody("video/*".toMediaTypeOrNull())
|
||||
)
|
||||
storyApi.uploadVideo(part).url
|
||||
} else {
|
||||
// Simulating image upload via same or different endpoint
|
||||
"https://example.com/simulated_upload.jpg"
|
||||
}
|
||||
|
||||
val request = CreateStoryRequest(
|
||||
type = if (isVideo) "video" else "image",
|
||||
mediaUrl = mediaUrl,
|
||||
bgColor = String.format("#%06X", (0xFFFFFF and bgColor.value.toInt())),
|
||||
content = null // Serialize stickers/text if needed
|
||||
)
|
||||
|
||||
storyApi.createStory(request)
|
||||
_uploadSuccess.value = true
|
||||
} catch (e: Exception) {
|
||||
_errorMessage.value = e.message ?: "Upload failed"
|
||||
} finally {
|
||||
isUploading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun uriToFile(context: android.content.Context, uri: Uri): File {
|
||||
val inputStream = context.contentResolver.openInputStream(uri)
|
||||
val file = File(context.cacheDir, "temp_story_${System.currentTimeMillis()}")
|
||||
file.outputStream().use { outputStream ->
|
||||
inputStream?.copyTo(outputStream)
|
||||
}
|
||||
return file
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user