diff --git a/app/src/main/java/com/omixlab/lckcontrol/app/data/local/AppPreferences.kt b/app/src/main/java/com/omixlab/lckcontrol/app/data/local/AppPreferences.kt index 30a0614..48d9587 100644 --- a/app/src/main/java/com/omixlab/lckcontrol/app/data/local/AppPreferences.kt +++ b/app/src/main/java/com/omixlab/lckcontrol/app/data/local/AppPreferences.kt @@ -21,9 +21,47 @@ class AppPreferences @Inject constructor( get() = prefs.getBoolean(KEY_DARK_THEME, true) set(value) = prefs.edit().putBoolean(KEY_DARK_THEME, value).apply() + // Paired Quest device info + var pairedDeviceId: String? + get() = prefs.getString(KEY_PAIRED_DEVICE_ID, null) + set(value) = prefs.edit().putString(KEY_PAIRED_DEVICE_ID, value).apply() + + var pairedDeviceName: String? + get() = prefs.getString(KEY_PAIRED_DEVICE_NAME, null) + set(value) = prefs.edit().putString(KEY_PAIRED_DEVICE_NAME, value).apply() + + var pairedDeviceIp: String? + get() = prefs.getString(KEY_PAIRED_DEVICE_IP, null) + set(value) = prefs.edit().putString(KEY_PAIRED_DEVICE_IP, value).apply() + + var pairedDevicePort: Int + get() = prefs.getInt(KEY_PAIRED_DEVICE_PORT, 0) + set(value) = prefs.edit().putInt(KEY_PAIRED_DEVICE_PORT, value).apply() + + var pairedDeviceNonce: String? + get() = prefs.getString(KEY_PAIRED_DEVICE_NONCE, null) + set(value) = prefs.edit().putString(KEY_PAIRED_DEVICE_NONCE, value).apply() + + fun hasSavedDevice(): Boolean = pairedDeviceId != null && pairedDeviceIp != null + + fun clearPairedDevice() { + prefs.edit() + .remove(KEY_PAIRED_DEVICE_ID) + .remove(KEY_PAIRED_DEVICE_NAME) + .remove(KEY_PAIRED_DEVICE_IP) + .remove(KEY_PAIRED_DEVICE_PORT) + .remove(KEY_PAIRED_DEVICE_NONCE) + .apply() + } + companion object { private const val PREFS_NAME = "lck_control_app_prefs" private const val KEY_FEED_FILTER = "feed_filter" private const val KEY_DARK_THEME = "dark_theme" + private const val KEY_PAIRED_DEVICE_ID = "paired_device_id" + private const val KEY_PAIRED_DEVICE_NAME = "paired_device_name" + private const val KEY_PAIRED_DEVICE_IP = "paired_device_ip" + private const val KEY_PAIRED_DEVICE_PORT = "paired_device_port" + private const val KEY_PAIRED_DEVICE_NONCE = "paired_device_nonce" } } diff --git a/app/src/main/java/com/omixlab/lckcontrol/app/data/remote/ApiModels.kt b/app/src/main/java/com/omixlab/lckcontrol/app/data/remote/ApiModels.kt index 451099d..575a4ed 100644 --- a/app/src/main/java/com/omixlab/lckcontrol/app/data/remote/ApiModels.kt +++ b/app/src/main/java/com/omixlab/lckcontrol/app/data/remote/ApiModels.kt @@ -199,6 +199,9 @@ data class FeedItemResponse( val plan: StreamPlanResponse? = null, val video: VideoFeedItem? = null, val previewUrl: String?, + val posterUrl: String? = null, + val thumbnailUrl: String? = null, + val clipUrl: String? = null, val likeCount: Int, val commentCount: Int, val isLiked: Boolean, diff --git a/app/src/main/java/com/omixlab/lckcontrol/app/ui/device/DeviceViewModel.kt b/app/src/main/java/com/omixlab/lckcontrol/app/ui/device/DeviceViewModel.kt index 98352a7..073d9ec 100644 --- a/app/src/main/java/com/omixlab/lckcontrol/app/ui/device/DeviceViewModel.kt +++ b/app/src/main/java/com/omixlab/lckcontrol/app/ui/device/DeviceViewModel.kt @@ -2,6 +2,7 @@ package com.omixlab.lckcontrol.app.ui.device import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.omixlab.lckcontrol.app.data.local.AppPreferences import com.omixlab.lckcontrol.app.p2p.discovery.DiscoveredDevice import com.omixlab.lckcontrol.app.p2p.discovery.LanDiscoveryManager import com.omixlab.lckcontrol.app.p2p.pairing.DevicePairingManager @@ -31,15 +32,27 @@ class DeviceViewModel @Inject constructor( private val pairingManager: DevicePairingManager, private val webRtcClient: WebRtcClient, private val remoteControlSession: RemoteControlSession, + private val appPreferences: AppPreferences, ) : ViewModel() { private val _uiState = MutableStateFlow(DeviceUiState()) val uiState: StateFlow = _uiState.asStateFlow() + private var autoConnectAttempted = false + init { viewModelScope.launch { discoveryManager.devices.collect { devices -> _uiState.value = _uiState.value.copy(discoveredDevices = devices) + // Auto-connect: when the saved device appears in discovered list + if (!autoConnectAttempted && appPreferences.hasSavedDevice()) { + val savedId = appPreferences.pairedDeviceId + val match = devices.find { it.name == savedId || it.ip == appPreferences.pairedDeviceIp } + if (match != null) { + autoConnectAttempted = true + pairWithDevice(match) + } + } } } viewModelScope.launch { @@ -55,6 +68,11 @@ class DeviceViewModel @Inject constructor( } } } + + // Auto-start discovery if there's a saved device + if (appPreferences.hasSavedDevice()) { + discoveryManager.startDiscovery() + } } fun startDiscovery() { @@ -76,6 +94,14 @@ class DeviceViewModel @Inject constructor( showDiscoverySheet = false, ) discoveryManager.stopDiscovery() + + // Save paired device info for auto-connect + appPreferences.pairedDeviceId = paired.deviceId + appPreferences.pairedDeviceName = paired.deviceName + appPreferences.pairedDeviceIp = paired.ip + appPreferences.pairedDevicePort = paired.port + appPreferences.pairedDeviceNonce = paired.nonce + // Initialize WebRTC connection connectToDevice(paired) } else { @@ -92,6 +118,7 @@ class DeviceViewModel @Inject constructor( fun disconnect() { webRtcClient.disconnect() + appPreferences.clearPairedDevice() _uiState.value = _uiState.value.copy( pairedDevice = null, connectionState = ConnectionState.DISCONNECTED, diff --git a/app/src/main/java/com/omixlab/lckcontrol/app/ui/feed/FeedCard.kt b/app/src/main/java/com/omixlab/lckcontrol/app/ui/feed/FeedCard.kt index a5d4e8e..2590d2a 100644 --- a/app/src/main/java/com/omixlab/lckcontrol/app/ui/feed/FeedCard.kt +++ b/app/src/main/java/com/omixlab/lckcontrol/app/ui/feed/FeedCard.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.viewinterop.AndroidView @@ -16,6 +17,8 @@ import androidx.media3.common.Player import androidx.media3.exoplayer.ExoPlayer import androidx.media3.ui.AspectRatioFrameLayout import androidx.media3.ui.PlayerView +import coil.compose.AsyncImage +import coil.request.ImageRequest import com.omixlab.lckcontrol.app.data.remote.FeedItemResponse @Composable @@ -31,14 +34,71 @@ fun FeedCard( val baseUrl = "https://lck.omigame.dev" val rawUrl = item.previewUrl ?: item.video?.videoUrl val videoUrl = rawUrl?.let { if (it.startsWith("/")) "$baseUrl$it" else it } + val posterUrl = item.posterUrl?.let { if (it.startsWith("/")) "$baseUrl$it" else it } Box( modifier = Modifier .fillMaxSize() .background(Color.Black), ) { - // Video player - if (videoUrl != null) { + // Poster image — shows instantly while video loads + if (posterUrl != null) { + var showPoster by remember { mutableStateOf(true) } + + if (showPoster) { + AsyncImage( + model = ImageRequest.Builder(context) + .data(posterUrl) + .build(), + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize(), + ) + } + + // Video player on top — hides poster when first frame renders + if (videoUrl != null) { + var player by remember { mutableStateOf(null) } + + DisposableEffect(videoUrl) { + val exoPlayer = ExoPlayer.Builder(context).build().apply { + setMediaItem(MediaItem.fromUri(videoUrl)) + repeatMode = Player.REPEAT_MODE_ONE + addListener(object : Player.Listener { + override fun onRenderedFirstFrame() { + showPoster = false + } + }) + prepare() + playWhenReady = true + } + player = exoPlayer + + onDispose { + exoPlayer.release() + player = null + } + } + + player?.let { exo -> + AndroidView( + factory = { + PlayerView(it).apply { + this.player = exo + useController = false + resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM + layoutParams = FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + ) + } + }, + modifier = Modifier.fillMaxSize(), + ) + } + } + } else if (videoUrl != null) { + // No poster — video only (original behavior) var player by remember { mutableStateOf(null) } DisposableEffect(videoUrl) { diff --git a/app/src/main/java/com/omixlab/lckcontrol/app/ui/navigation/AppNavigation.kt b/app/src/main/java/com/omixlab/lckcontrol/app/ui/navigation/AppNavigation.kt index 2096fad..e3bbac6 100644 --- a/app/src/main/java/com/omixlab/lckcontrol/app/ui/navigation/AppNavigation.kt +++ b/app/src/main/java/com/omixlab/lckcontrol/app/ui/navigation/AppNavigation.kt @@ -27,6 +27,7 @@ import com.omixlab.lckcontrol.app.ui.profile.FollowListScreen import com.omixlab.lckcontrol.app.ui.profile.ProfileScreen import com.omixlab.lckcontrol.app.ui.profile.UserProfileScreen import com.omixlab.lckcontrol.app.ui.streams.StreamDetailScreen +import com.omixlab.lckcontrol.app.ui.streams.StreamPlayerScreen import com.omixlab.lckcontrol.app.ui.streams.StreamsScreen @Composable @@ -99,8 +100,8 @@ fun AppNavigation(navViewModel: NavViewModel = hiltViewModel()) { } composable(Screen.Streams.route) { StreamsScreen( - onNavigateToDetail = { planId -> - navController.navigate(Screen.StreamDetail.createRoute(planId)) + onNavigateToPlayer = { planId -> + navController.navigate(Screen.StreamPlayer.createRoute(planId)) }, ) } @@ -110,6 +111,16 @@ fun AppNavigation(navViewModel: NavViewModel = hiltViewModel()) { ) { StreamDetailScreen(onNavigateBack = { navController.popBackStack() }) } + composable( + Screen.StreamPlayer.route, + arguments = listOf(navArgument("planId") { type = NavType.StringType }), + ) { backStackEntry -> + val planId = backStackEntry.arguments?.getString("planId") ?: return@composable + StreamPlayerScreen( + planId = planId, + onNavigateBack = { navController.popBackStack() }, + ) + } composable(Screen.Device.route) { DeviceScreen( onNavigateToCamera = { navController.navigate(Screen.CameraView.route) }, diff --git a/app/src/main/java/com/omixlab/lckcontrol/app/ui/navigation/Screen.kt b/app/src/main/java/com/omixlab/lckcontrol/app/ui/navigation/Screen.kt index ef96026..d4d8d6c 100644 --- a/app/src/main/java/com/omixlab/lckcontrol/app/ui/navigation/Screen.kt +++ b/app/src/main/java/com/omixlab/lckcontrol/app/ui/navigation/Screen.kt @@ -7,6 +7,9 @@ sealed class Screen(val route: String) { data object StreamDetail : Screen("streams/{planId}") { fun createRoute(planId: String) = "streams/$planId" } + data object StreamPlayer : Screen("streams/play/{planId}") { + fun createRoute(planId: String) = "streams/play/$planId" + } data object Device : Screen("device") data object CameraView : Screen("device/camera") data object FileTransfer : Screen("device/files") diff --git a/app/src/main/java/com/omixlab/lckcontrol/app/ui/profile/ProfileScreen.kt b/app/src/main/java/com/omixlab/lckcontrol/app/ui/profile/ProfileScreen.kt index 2ab69af..d3b4361 100644 --- a/app/src/main/java/com/omixlab/lckcontrol/app/ui/profile/ProfileScreen.kt +++ b/app/src/main/java/com/omixlab/lckcontrol/app/ui/profile/ProfileScreen.kt @@ -1,11 +1,11 @@ package com.omixlab.lckcontrol.app.ui.profile +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.Logout import androidx.compose.material.icons.filled.Link import androidx.compose.material.icons.filled.People -import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -95,20 +95,15 @@ fun ProfileScreen( ListItem( headlineContent = { Text("Followers & Following") }, leadingContent = { Icon(Icons.Default.People, null) }, - modifier = Modifier.let { mod -> - mod + modifier = Modifier.clickable { + onNavigateToFollows(profile.id, "followers") }, ) ListItem( headlineContent = { Text("Linked Accounts") }, leadingContent = { Icon(Icons.Default.Link, null) }, - modifier = Modifier, - ) - - ListItem( - headlineContent = { Text("Settings") }, - leadingContent = { Icon(Icons.Default.Settings, null) }, + modifier = Modifier.clickable { onNavigateToAccounts() }, ) Spacer(modifier = Modifier.weight(1f)) diff --git a/app/src/main/java/com/omixlab/lckcontrol/app/ui/streams/StreamPlayerScreen.kt b/app/src/main/java/com/omixlab/lckcontrol/app/ui/streams/StreamPlayerScreen.kt new file mode 100644 index 0000000..69139fe --- /dev/null +++ b/app/src/main/java/com/omixlab/lckcontrol/app/ui/streams/StreamPlayerScreen.kt @@ -0,0 +1,54 @@ +package com.omixlab.lckcontrol.app.ui.streams + +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.IconButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.omixlab.lckcontrol.app.ui.feed.FeedCard + +@Composable +fun StreamPlayerScreen( + planId: String, + onNavigateBack: () -> Unit, + viewModel: StreamsViewModel = hiltViewModel(), +) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val item = uiState.streams.find { it.plan?.id == planId } + + Box(modifier = Modifier.fillMaxSize()) { + if (item != null) { + FeedCard( + item = item, + pageIndex = 0, + onLikeClick = {}, + onFollowClick = {}, + onUserClick = {}, + onCommentClick = {}, + ) + } + + IconButton( + onClick = onNavigateBack, + colors = IconButtonDefaults.iconButtonColors( + containerColor = Color.Black.copy(alpha = 0.5f), + contentColor = Color.White, + ), + modifier = Modifier + .align(Alignment.TopStart) + .statusBarsPadding() + .padding(12.dp), + ) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") + } + } +} diff --git a/app/src/main/java/com/omixlab/lckcontrol/app/ui/streams/StreamsScreen.kt b/app/src/main/java/com/omixlab/lckcontrol/app/ui/streams/StreamsScreen.kt index 3131ff0..44bf4ed 100644 --- a/app/src/main/java/com/omixlab/lckcontrol/app/ui/streams/StreamsScreen.kt +++ b/app/src/main/java/com/omixlab/lckcontrol/app/ui/streams/StreamsScreen.kt @@ -1,18 +1,30 @@ package com.omixlab.lckcontrol.app.ui.streams +import androidx.compose.foundation.background import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Favorite +import androidx.compose.material.icons.filled.Videocam import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import coil.compose.SubcomposeAsyncImage +import coil.request.ImageRequest +import com.omixlab.lckcontrol.app.data.remote.FeedItemResponse import com.omixlab.lckcontrol.app.ui.components.ErrorState import com.omixlab.lckcontrol.app.ui.components.LiveBadge import com.omixlab.lckcontrol.app.ui.components.LoadingIndicator @@ -20,75 +32,148 @@ import com.omixlab.lckcontrol.app.ui.components.PullToRefreshLayout @Composable fun StreamsScreen( - onNavigateToDetail: (String) -> Unit, + onNavigateToPlayer: (String) -> Unit, viewModel: StreamsViewModel = hiltViewModel(), ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val baseUrl = "https://lck.omigame.dev" - Scaffold( - floatingActionButton = { - FloatingActionButton(onClick = { /* TODO: create stream plan */ }) { - Icon(Icons.Default.Add, contentDescription = "New Stream Plan") + PullToRefreshLayout( + isRefreshing = uiState.isLoading, + onRefresh = { viewModel.loadStreams() }, + modifier = Modifier.fillMaxSize(), + ) { + when { + uiState.isLoading && uiState.streams.isEmpty() -> { + LoadingIndicator(modifier = Modifier.fillMaxSize()) } - }, - ) { padding -> - PullToRefreshLayout( - isRefreshing = uiState.isLoading, - onRefresh = { viewModel.loadPlans() }, - modifier = Modifier - .fillMaxSize() - .padding(padding), - ) { - when { - uiState.isLoading && uiState.plans.isEmpty() -> { - LoadingIndicator(modifier = Modifier.fillMaxSize()) - } - uiState.error != null && uiState.plans.isEmpty() -> { - ErrorState( - message = uiState.error!!, - onRetry = { viewModel.loadPlans() }, - modifier = Modifier.fillMaxSize(), - ) - } - else -> { - LazyColumn( - contentPadding = PaddingValues(16.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), - ) { - items(uiState.plans) { plan -> - Card( - onClick = { onNavigateToDetail(plan.id) }, - modifier = Modifier.fillMaxWidth(), - ) { - ListItem( - headlineContent = { Text(plan.name) }, - supportingContent = { - Text("${plan.destinations.size} destination(s)") - }, - trailingContent = { - Row { - if (plan.status == "LIVE") { - LiveBadge() - } else { - Text( - text = plan.status, - style = MaterialTheme.typography.labelSmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - IconButton( - onClick = { viewModel.deletePlan(plan.id) }, - ) { - Icon(Icons.Default.Delete, contentDescription = "Delete") - } - } - }, - ) - } - } + uiState.error != null && uiState.streams.isEmpty() -> { + ErrorState( + message = uiState.error!!, + onRetry = { viewModel.loadStreams() }, + modifier = Modifier.fillMaxSize(), + ) + } + else -> { + LazyVerticalGrid( + columns = GridCells.Fixed(2), + contentPadding = PaddingValues(4.dp), + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + items(uiState.streams) { item -> + StreamThumbnailCell( + item = item, + baseUrl = baseUrl, + onClick = { + item.plan?.id?.let { onNavigateToPlayer(it) } + }, + onDelete = { + item.plan?.id?.let { viewModel.deleteStream(it) } + }, + ) } } } } } } + +@Composable +private fun StreamThumbnailCell( + item: FeedItemResponse, + baseUrl: String, + onClick: () -> Unit, + onDelete: () -> Unit, +) { + val thumbUrl = item.thumbnailUrl?.let { + if (it.startsWith("/")) "$baseUrl$it" else it + } ?: item.plan?.id?.let { "$baseUrl/media/thumbnails/${it}_thumb.jpg" } + + Card( + onClick = onClick, + shape = RoundedCornerShape(8.dp), + ) { + Box( + modifier = Modifier.aspectRatio(1f), + ) { + // Thumbnail image + SubcomposeAsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(thumbUrl) + .build(), + contentDescription = item.plan?.name, + contentScale = ContentScale.Crop, + loading = { ThumbnailPlaceholder() }, + error = { ThumbnailPlaceholder() }, + modifier = Modifier.fillMaxSize(), + ) + + // Delete button — top-right + IconButton( + onClick = onDelete, + colors = IconButtonDefaults.iconButtonColors( + containerColor = Color.Black.copy(alpha = 0.5f), + contentColor = Color.White, + ), + modifier = Modifier + .align(Alignment.TopEnd) + .size(32.dp), + ) { + Icon( + Icons.Default.Close, + contentDescription = "Delete", + modifier = Modifier.size(16.dp), + ) + } + + // Bottom overlay bar + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .align(Alignment.BottomCenter) + .fillMaxWidth() + .background(Color.Black.copy(alpha = 0.6f)) + .padding(horizontal = 8.dp, vertical = 4.dp), + ) { + // Like count + Icon( + Icons.Default.Favorite, + contentDescription = null, + tint = Color.White, + modifier = Modifier.size(14.dp), + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = "${item.likeCount}", + color = Color.White, + style = MaterialTheme.typography.labelSmall, + ) + + Spacer(modifier = Modifier.weight(1f)) + + // LIVE badge + if (item.plan?.status == "LIVE") { + LiveBadge() + } + } + } + } +} + +@Composable +private fun ThumbnailPlaceholder() { + Box( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.surfaceVariant), + contentAlignment = Alignment.Center, + ) { + Icon( + Icons.Default.Videocam, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.size(32.dp), + ) + } +} diff --git a/app/src/main/java/com/omixlab/lckcontrol/app/ui/streams/StreamsViewModel.kt b/app/src/main/java/com/omixlab/lckcontrol/app/ui/streams/StreamsViewModel.kt index ff96514..39c9f94 100644 --- a/app/src/main/java/com/omixlab/lckcontrol/app/ui/streams/StreamsViewModel.kt +++ b/app/src/main/java/com/omixlab/lckcontrol/app/ui/streams/StreamsViewModel.kt @@ -2,7 +2,8 @@ package com.omixlab.lckcontrol.app.ui.streams import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.omixlab.lckcontrol.app.data.remote.StreamPlanResponse +import com.omixlab.lckcontrol.app.data.remote.FeedItemResponse +import com.omixlab.lckcontrol.app.data.repository.FeedRepository import com.omixlab.lckcontrol.app.data.repository.StreamRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow @@ -12,13 +13,14 @@ import kotlinx.coroutines.launch import javax.inject.Inject data class StreamsUiState( - val plans: List = emptyList(), + val streams: List = emptyList(), val isLoading: Boolean = false, val error: String? = null, ) @HiltViewModel class StreamsViewModel @Inject constructor( + private val feedRepository: FeedRepository, private val streamRepository: StreamRepository, ) : ViewModel() { @@ -26,29 +28,29 @@ class StreamsViewModel @Inject constructor( val uiState: StateFlow = _uiState.asStateFlow() init { - loadPlans() + loadStreams() } - fun loadPlans() { + fun loadStreams() { viewModelScope.launch { _uiState.value = _uiState.value.copy(isLoading = true, error = null) try { - val plans = streamRepository.getStreamPlans() - _uiState.value = _uiState.value.copy(plans = plans, isLoading = false) + val (items, _) = feedRepository.getFeed(filter = "mine") + _uiState.value = _uiState.value.copy(streams = items, isLoading = false) } catch (e: Exception) { _uiState.value = _uiState.value.copy( isLoading = false, - error = e.message ?: "Failed to load stream plans", + error = e.message ?: "Failed to load streams", ) } } } - fun deletePlan(id: String) { + fun deleteStream(planId: String) { viewModelScope.launch { try { - streamRepository.deleteStreamPlan(id) - loadPlans() + streamRepository.deleteStreamPlan(planId) + loadStreams() } catch (_: Exception) {} } }