Per-stream visibility: isPublic field, Room migration 6→7, publish toggle in CreatePlan
This commit is contained in:
@@ -16,7 +16,7 @@ import com.omixlab.lckcontrol.data.local.entity.StreamPlanEntity
|
|||||||
StreamPlanEntity::class,
|
StreamPlanEntity::class,
|
||||||
StreamDestinationEntity::class,
|
StreamDestinationEntity::class,
|
||||||
],
|
],
|
||||||
version = 6,
|
version = 7,
|
||||||
exportSchema = false,
|
exportSchema = false,
|
||||||
)
|
)
|
||||||
abstract class LckDatabase : RoomDatabase() {
|
abstract class LckDatabase : RoomDatabase() {
|
||||||
@@ -116,5 +116,11 @@ abstract class LckDatabase : RoomDatabase() {
|
|||||||
db.execSQL("ALTER TABLE linked_accounts ADD COLUMN streamKey TEXT")
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,5 +10,6 @@ data class StreamPlanEntity(
|
|||||||
val status: String = "DRAFT",
|
val status: String = "DRAFT",
|
||||||
val executionMode: String = "IN_GAME",
|
val executionMode: String = "IN_GAME",
|
||||||
val gameId: String = "",
|
val gameId: String = "",
|
||||||
|
val isPublic: Boolean = true,
|
||||||
val createdAt: Long = System.currentTimeMillis(),
|
val createdAt: Long = System.currentTimeMillis(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ data class CreateStreamPlanRequest(
|
|||||||
val name: String,
|
val name: String,
|
||||||
val executionMode: String? = null,
|
val executionMode: String? = null,
|
||||||
val gameId: String? = null,
|
val gameId: String? = null,
|
||||||
|
val isPublic: Boolean? = null,
|
||||||
val destinations: List<CreateDestinationRequest>,
|
val destinations: List<CreateDestinationRequest>,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -109,6 +110,7 @@ data class UpdateStreamPlanRequest(
|
|||||||
val name: String? = null,
|
val name: String? = null,
|
||||||
val executionMode: String? = null,
|
val executionMode: String? = null,
|
||||||
val gameId: String? = null,
|
val gameId: String? = null,
|
||||||
|
val isPublic: Boolean? = null,
|
||||||
val destinations: List<CreateDestinationRequest>? = null,
|
val destinations: List<CreateDestinationRequest>? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -131,6 +133,7 @@ data class StreamPlanResponse(
|
|||||||
val status: String,
|
val status: String,
|
||||||
val executionMode: String? = null,
|
val executionMode: String? = null,
|
||||||
val gameId: String? = null,
|
val gameId: String? = null,
|
||||||
|
val isPublic: Boolean = true,
|
||||||
val createdAt: String,
|
val createdAt: String,
|
||||||
val updatedAt: String,
|
val updatedAt: String,
|
||||||
val destinations: List<StreamDestinationResponse>,
|
val destinations: List<StreamDestinationResponse>,
|
||||||
|
|||||||
@@ -48,11 +48,13 @@ class StreamPlanRepository @Inject constructor(
|
|||||||
destinations: List<StreamDestination>,
|
destinations: List<StreamDestination>,
|
||||||
executionMode: String = "IN_GAME",
|
executionMode: String = "IN_GAME",
|
||||||
gameId: String = "",
|
gameId: String = "",
|
||||||
|
isPublic: Boolean = true,
|
||||||
): StreamPlan {
|
): StreamPlan {
|
||||||
val request = CreateStreamPlanRequest(
|
val request = CreateStreamPlanRequest(
|
||||||
name = name,
|
name = name,
|
||||||
executionMode = executionMode,
|
executionMode = executionMode,
|
||||||
gameId = gameId.ifBlank { null },
|
gameId = gameId.ifBlank { null },
|
||||||
|
isPublic = isPublic,
|
||||||
destinations = destinations.map { dest ->
|
destinations = destinations.map { dest ->
|
||||||
CreateDestinationRequest(
|
CreateDestinationRequest(
|
||||||
linkedAccountId = dest.linkedAccountId.ifBlank { null },
|
linkedAccountId = dest.linkedAccountId.ifBlank { null },
|
||||||
@@ -78,11 +80,13 @@ class StreamPlanRepository @Inject constructor(
|
|||||||
destinations: List<StreamDestination>,
|
destinations: List<StreamDestination>,
|
||||||
executionMode: String,
|
executionMode: String,
|
||||||
gameId: String,
|
gameId: String,
|
||||||
|
isPublic: Boolean = true,
|
||||||
): StreamPlan {
|
): StreamPlan {
|
||||||
val request = UpdateStreamPlanRequest(
|
val request = UpdateStreamPlanRequest(
|
||||||
name = name,
|
name = name,
|
||||||
executionMode = executionMode,
|
executionMode = executionMode,
|
||||||
gameId = gameId.ifBlank { null },
|
gameId = gameId.ifBlank { null },
|
||||||
|
isPublic = isPublic,
|
||||||
destinations = destinations.map { dest ->
|
destinations = destinations.map { dest ->
|
||||||
CreateDestinationRequest(
|
CreateDestinationRequest(
|
||||||
linkedAccountId = dest.linkedAccountId.ifBlank { null },
|
linkedAccountId = dest.linkedAccountId.ifBlank { null },
|
||||||
@@ -146,6 +150,7 @@ class StreamPlanRepository @Inject constructor(
|
|||||||
status = remote.status,
|
status = remote.status,
|
||||||
executionMode = remote.executionMode ?: "IN_GAME",
|
executionMode = remote.executionMode ?: "IN_GAME",
|
||||||
gameId = remote.gameId ?: "",
|
gameId = remote.gameId ?: "",
|
||||||
|
isPublic = remote.isPublic,
|
||||||
)
|
)
|
||||||
val destEntities = remote.destinations.map { d ->
|
val destEntities = remote.destinations.map { d ->
|
||||||
StreamDestinationEntity(
|
StreamDestinationEntity(
|
||||||
@@ -173,6 +178,7 @@ class StreamPlanRepository @Inject constructor(
|
|||||||
status = plan.status,
|
status = plan.status,
|
||||||
executionMode = plan.executionMode,
|
executionMode = plan.executionMode,
|
||||||
gameId = plan.gameId,
|
gameId = plan.gameId,
|
||||||
|
isPublic = plan.isPublic,
|
||||||
destinations = destinations.map { it.toStreamDestination() },
|
destinations = destinations.map { it.toStreamDestination() },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ object DatabaseModule {
|
|||||||
@Singleton
|
@Singleton
|
||||||
fun provideDatabase(@ApplicationContext context: Context): LckDatabase =
|
fun provideDatabase(@ApplicationContext context: Context): LckDatabase =
|
||||||
Room.databaseBuilder(context, LckDatabase::class.java, "lck_control.db")
|
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()
|
.build()
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|||||||
@@ -206,9 +206,9 @@ fun DashboardScreen(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Text("Show on Portal", style = MaterialTheme.typography.titleSmall)
|
Text("Public Profile", style = MaterialTheme.typography.titleSmall)
|
||||||
Text(
|
Text(
|
||||||
"Allow others to discover your live streams",
|
"Allow others to find your profile",
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import androidx.compose.material3.OutlinedTextField
|
|||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -38,6 +39,7 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
@@ -56,6 +58,7 @@ fun CreatePlanScreen(
|
|||||||
val planName by viewModel.planName.collectAsStateWithLifecycle()
|
val planName by viewModel.planName.collectAsStateWithLifecycle()
|
||||||
val executionMode by viewModel.executionMode.collectAsStateWithLifecycle()
|
val executionMode by viewModel.executionMode.collectAsStateWithLifecycle()
|
||||||
val gameId by viewModel.gameId.collectAsStateWithLifecycle()
|
val gameId by viewModel.gameId.collectAsStateWithLifecycle()
|
||||||
|
val isPublic by viewModel.isPublic.collectAsStateWithLifecycle()
|
||||||
val connectedClients by viewModel.connectedClients.collectAsStateWithLifecycle()
|
val connectedClients by viewModel.connectedClients.collectAsStateWithLifecycle()
|
||||||
val destinations by viewModel.destinations.collectAsStateWithLifecycle()
|
val destinations by viewModel.destinations.collectAsStateWithLifecycle()
|
||||||
val linkedAccounts by viewModel.linkedAccounts.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 {
|
item {
|
||||||
Spacer(Modifier.height(8.dp))
|
Spacer(Modifier.height(8.dp))
|
||||||
Text("Execution Mode", style = MaterialTheme.typography.titleMedium)
|
Text("Execution Mode", style = MaterialTheme.typography.titleMedium)
|
||||||
|
|||||||
@@ -66,6 +66,9 @@ class CreatePlanViewModel @Inject constructor(
|
|||||||
private val _gameId = MutableStateFlow("")
|
private val _gameId = MutableStateFlow("")
|
||||||
val gameId: StateFlow<String> = _gameId.asStateFlow()
|
val gameId: StateFlow<String> = _gameId.asStateFlow()
|
||||||
|
|
||||||
|
private val _isPublic = MutableStateFlow(true)
|
||||||
|
val isPublic: StateFlow<Boolean> = _isPublic.asStateFlow()
|
||||||
|
|
||||||
private val _connectedClients = MutableStateFlow<List<ConnectedClientInfo>>(emptyList())
|
private val _connectedClients = MutableStateFlow<List<ConnectedClientInfo>>(emptyList())
|
||||||
val connectedClients: StateFlow<List<ConnectedClientInfo>> = _connectedClients.asStateFlow()
|
val connectedClients: StateFlow<List<ConnectedClientInfo>> = _connectedClients.asStateFlow()
|
||||||
|
|
||||||
@@ -134,6 +137,7 @@ class CreatePlanViewModel @Inject constructor(
|
|||||||
_planName.value = plan.name
|
_planName.value = plan.name
|
||||||
_executionMode.value = plan.executionMode
|
_executionMode.value = plan.executionMode
|
||||||
_gameId.value = plan.gameId
|
_gameId.value = plan.gameId
|
||||||
|
_isPublic.value = plan.isPublic
|
||||||
|
|
||||||
// Wait for linked accounts to load for label resolution
|
// Wait for linked accounts to load for label resolution
|
||||||
val accounts = accountRepository.getAccounts()
|
val accounts = accountRepository.getAccounts()
|
||||||
@@ -172,6 +176,10 @@ class CreatePlanViewModel @Inject constructor(
|
|||||||
_gameId.value = gameId
|
_gameId.value = gameId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setIsPublic(isPublic: Boolean) {
|
||||||
|
_isPublic.value = isPublic
|
||||||
|
}
|
||||||
|
|
||||||
fun addDestination() {
|
fun addDestination() {
|
||||||
_destinations.value = _destinations.value + DestinationInput()
|
_destinations.value = _destinations.value + DestinationInput()
|
||||||
}
|
}
|
||||||
@@ -243,9 +251,9 @@ class CreatePlanViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val plan = if (isEditMode) {
|
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 {
|
} else {
|
||||||
streamPlanRepository.createPlan(name, streamDests, _executionMode.value, _gameId.value)
|
streamPlanRepository.createPlan(name, streamDests, _executionMode.value, _gameId.value, _isPublic.value)
|
||||||
}
|
}
|
||||||
onSaved(plan.planId)
|
onSaved(plan.planId)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ data class StreamPlan(
|
|||||||
val destinations: List<StreamDestination> = emptyList(),
|
val destinations: List<StreamDestination> = emptyList(),
|
||||||
val executionMode: String = "IN_GAME",
|
val executionMode: String = "IN_GAME",
|
||||||
val gameId: String = "",
|
val gameId: String = "",
|
||||||
|
val isPublic: Boolean = true,
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
constructor(parcel: Parcel) : this(
|
constructor(parcel: Parcel) : this(
|
||||||
@@ -19,6 +20,7 @@ data class StreamPlan(
|
|||||||
destinations = parcel.createTypedArrayList(StreamDestination.CREATOR) ?: emptyList(),
|
destinations = parcel.createTypedArrayList(StreamDestination.CREATOR) ?: emptyList(),
|
||||||
executionMode = if (parcel.dataAvail() > 0) parcel.readString() ?: "IN_GAME" else "IN_GAME",
|
executionMode = if (parcel.dataAvail() > 0) parcel.readString() ?: "IN_GAME" else "IN_GAME",
|
||||||
gameId = if (parcel.dataAvail() > 0) parcel.readString() ?: "" else "",
|
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) {
|
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||||
@@ -28,6 +30,7 @@ data class StreamPlan(
|
|||||||
parcel.writeTypedList(destinations)
|
parcel.writeTypedList(destinations)
|
||||||
parcel.writeString(executionMode)
|
parcel.writeString(executionMode)
|
||||||
parcel.writeString(gameId)
|
parcel.writeString(gameId)
|
||||||
|
parcel.writeInt(if (isPublic) 1 else 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun describeContents(): Int = 0
|
override fun describeContents(): Int = 0
|
||||||
|
|||||||
Reference in New Issue
Block a user