Per-stream visibility: isPublic field, Room migration 6→7, publish toggle in CreatePlan

This commit is contained in:
2026-03-03 21:37:20 +01:00
parent ec1b84994b
commit b235eabd40
9 changed files with 57 additions and 6 deletions

View File

@@ -16,7 +16,7 @@ import com.omixlab.lckcontrol.data.local.entity.StreamPlanEntity
StreamPlanEntity::class,
StreamDestinationEntity::class,
],
version = 6,
version = 7,
exportSchema = false,
)
abstract class LckDatabase : RoomDatabase() {
@@ -116,5 +116,11 @@ abstract class LckDatabase : RoomDatabase() {
db.execSQL("ALTER TABLE linked_accounts ADD COLUMN streamKey TEXT")
}
}
val MIGRATION_6_7 = object : Migration(6, 7) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE stream_plans ADD COLUMN isPublic INTEGER NOT NULL DEFAULT 1")
}
}
}
}

View File

@@ -10,5 +10,6 @@ data class StreamPlanEntity(
val status: String = "DRAFT",
val executionMode: String = "IN_GAME",
val gameId: String = "",
val isPublic: Boolean = true,
val createdAt: Long = System.currentTimeMillis(),
)

View File

@@ -101,6 +101,7 @@ data class CreateStreamPlanRequest(
val name: String,
val executionMode: String? = null,
val gameId: String? = null,
val isPublic: Boolean? = null,
val destinations: List<CreateDestinationRequest>,
)
@@ -109,6 +110,7 @@ data class UpdateStreamPlanRequest(
val name: String? = null,
val executionMode: String? = null,
val gameId: String? = null,
val isPublic: Boolean? = null,
val destinations: List<CreateDestinationRequest>? = null,
)
@@ -131,6 +133,7 @@ data class StreamPlanResponse(
val status: String,
val executionMode: String? = null,
val gameId: String? = null,
val isPublic: Boolean = true,
val createdAt: String,
val updatedAt: String,
val destinations: List<StreamDestinationResponse>,

View File

@@ -48,11 +48,13 @@ class StreamPlanRepository @Inject constructor(
destinations: List<StreamDestination>,
executionMode: String = "IN_GAME",
gameId: String = "",
isPublic: Boolean = true,
): StreamPlan {
val request = CreateStreamPlanRequest(
name = name,
executionMode = executionMode,
gameId = gameId.ifBlank { null },
isPublic = isPublic,
destinations = destinations.map { dest ->
CreateDestinationRequest(
linkedAccountId = dest.linkedAccountId.ifBlank { null },
@@ -78,11 +80,13 @@ class StreamPlanRepository @Inject constructor(
destinations: List<StreamDestination>,
executionMode: String,
gameId: String,
isPublic: Boolean = true,
): StreamPlan {
val request = UpdateStreamPlanRequest(
name = name,
executionMode = executionMode,
gameId = gameId.ifBlank { null },
isPublic = isPublic,
destinations = destinations.map { dest ->
CreateDestinationRequest(
linkedAccountId = dest.linkedAccountId.ifBlank { null },
@@ -146,6 +150,7 @@ class StreamPlanRepository @Inject constructor(
status = remote.status,
executionMode = remote.executionMode ?: "IN_GAME",
gameId = remote.gameId ?: "",
isPublic = remote.isPublic,
)
val destEntities = remote.destinations.map { d ->
StreamDestinationEntity(
@@ -173,6 +178,7 @@ class StreamPlanRepository @Inject constructor(
status = plan.status,
executionMode = plan.executionMode,
gameId = plan.gameId,
isPublic = plan.isPublic,
destinations = destinations.map { it.toStreamDestination() },
)

View File

@@ -20,7 +20,7 @@ object DatabaseModule {
@Singleton
fun provideDatabase(@ApplicationContext context: Context): LckDatabase =
Room.databaseBuilder(context, LckDatabase::class.java, "lck_control.db")
.addMigrations(LckDatabase.MIGRATION_1_2, LckDatabase.MIGRATION_2_3, LckDatabase.MIGRATION_3_4, LckDatabase.MIGRATION_4_5, LckDatabase.MIGRATION_5_6)
.addMigrations(LckDatabase.MIGRATION_1_2, LckDatabase.MIGRATION_2_3, LckDatabase.MIGRATION_3_4, LckDatabase.MIGRATION_4_5, LckDatabase.MIGRATION_5_6, LckDatabase.MIGRATION_6_7)
.build()
@Provides

View File

@@ -206,9 +206,9 @@ fun DashboardScreen(
verticalAlignment = Alignment.CenterVertically,
) {
Column(modifier = Modifier.weight(1f)) {
Text("Show on Portal", style = MaterialTheme.typography.titleSmall)
Text("Public Profile", style = MaterialTheme.typography.titleSmall)
Text(
"Allow others to discover your live streams",
"Allow others to find your profile",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)

View File

@@ -30,6 +30,7 @@ import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
@@ -38,6 +39,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
@@ -56,6 +58,7 @@ fun CreatePlanScreen(
val planName by viewModel.planName.collectAsStateWithLifecycle()
val executionMode by viewModel.executionMode.collectAsStateWithLifecycle()
val gameId by viewModel.gameId.collectAsStateWithLifecycle()
val isPublic by viewModel.isPublic.collectAsStateWithLifecycle()
val connectedClients by viewModel.connectedClients.collectAsStateWithLifecycle()
val destinations by viewModel.destinations.collectAsStateWithLifecycle()
val linkedAccounts by viewModel.linkedAccounts.collectAsStateWithLifecycle()
@@ -101,6 +104,27 @@ fun CreatePlanScreen(
)
}
item {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Column(modifier = Modifier.weight(1f)) {
Text("Publish to Portal", style = MaterialTheme.typography.titleSmall)
Text(
"Show this stream in the public feed",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
Switch(
checked = isPublic,
onCheckedChange = viewModel::setIsPublic,
)
}
}
item {
Spacer(Modifier.height(8.dp))
Text("Execution Mode", style = MaterialTheme.typography.titleMedium)

View File

@@ -66,6 +66,9 @@ class CreatePlanViewModel @Inject constructor(
private val _gameId = MutableStateFlow("")
val gameId: StateFlow<String> = _gameId.asStateFlow()
private val _isPublic = MutableStateFlow(true)
val isPublic: StateFlow<Boolean> = _isPublic.asStateFlow()
private val _connectedClients = MutableStateFlow<List<ConnectedClientInfo>>(emptyList())
val connectedClients: StateFlow<List<ConnectedClientInfo>> = _connectedClients.asStateFlow()
@@ -134,6 +137,7 @@ class CreatePlanViewModel @Inject constructor(
_planName.value = plan.name
_executionMode.value = plan.executionMode
_gameId.value = plan.gameId
_isPublic.value = plan.isPublic
// Wait for linked accounts to load for label resolution
val accounts = accountRepository.getAccounts()
@@ -172,6 +176,10 @@ class CreatePlanViewModel @Inject constructor(
_gameId.value = gameId
}
fun setIsPublic(isPublic: Boolean) {
_isPublic.value = isPublic
}
fun addDestination() {
_destinations.value = _destinations.value + DestinationInput()
}
@@ -243,9 +251,9 @@ class CreatePlanViewModel @Inject constructor(
}
}
val plan = if (isEditMode) {
streamPlanRepository.updatePlan(editingPlanId!!, name, streamDests, _executionMode.value, _gameId.value)
streamPlanRepository.updatePlan(editingPlanId!!, name, streamDests, _executionMode.value, _gameId.value, _isPublic.value)
} else {
streamPlanRepository.createPlan(name, streamDests, _executionMode.value, _gameId.value)
streamPlanRepository.createPlan(name, streamDests, _executionMode.value, _gameId.value, _isPublic.value)
}
onSaved(plan.planId)
} catch (e: Exception) {

View File

@@ -10,6 +10,7 @@ data class StreamPlan(
val destinations: List<StreamDestination> = emptyList(),
val executionMode: String = "IN_GAME",
val gameId: String = "",
val isPublic: Boolean = true,
) : Parcelable {
constructor(parcel: Parcel) : this(
@@ -19,6 +20,7 @@ data class StreamPlan(
destinations = parcel.createTypedArrayList(StreamDestination.CREATOR) ?: emptyList(),
executionMode = if (parcel.dataAvail() > 0) parcel.readString() ?: "IN_GAME" else "IN_GAME",
gameId = if (parcel.dataAvail() > 0) parcel.readString() ?: "" else "",
isPublic = if (parcel.dataAvail() > 0) parcel.readInt() == 1 else true,
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
@@ -28,6 +30,7 @@ data class StreamPlan(
parcel.writeTypedList(destinations)
parcel.writeString(executionMode)
parcel.writeString(gameId)
parcel.writeInt(if (isPublic) 1 else 0)
}
override fun describeContents(): Int = 0