diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7fc522b..fa6f24e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -15,8 +15,8 @@ android { applicationId = "com.bunbeauty.fakelivestream" minSdk = 27 targetSdk = 34 - versionCode = 152 - versionName = "1.5.2" + versionCode = 160 + versionName = "1.6.0" multiDexEnabled = true testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" @@ -65,12 +65,12 @@ android { dependencies { implementation("com.android.support:multidex:1.0.3") - implementation("androidx.core:core-ktx:1.12.0") + implementation("androidx.core:core-ktx:1.13.0") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") // Compose - implementation("androidx.activity:activity-compose:1.8.2") - implementation(platform("androidx.compose:compose-bom:2023.08.00")) + implementation("androidx.activity:activity-compose:1.9.0") + implementation(platform("androidx.compose:compose-bom:2024.04.01")) implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui-graphics") implementation("androidx.compose.ui:ui-tooling-preview") @@ -87,9 +87,9 @@ dependencies { implementation("io.coil-kt:coil-compose:2.5.0") // Camera - implementation("androidx.camera:camera-camera2:1.3.2") - implementation("androidx.camera:camera-lifecycle:1.3.2") - implementation("androidx.camera:camera-view:1.3.2") + implementation("androidx.camera:camera-camera2:1.3.3") + implementation("androidx.camera:camera-lifecycle:1.3.3") + implementation("androidx.camera:camera-view:1.3.3") // ViewModel implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0") @@ -103,8 +103,8 @@ dependencies { implementation("com.google.accompanist:accompanist-permissions:0.34.0") // Media3 - implementation("androidx.media3:media3-ui:1.3.0") - implementation("androidx.media3:media3-exoplayer:1.3.0") + implementation("androidx.media3:media3-ui:1.3.1") + implementation("androidx.media3:media3-exoplayer:1.3.1") // Firebase implementation(platform("com.google.firebase:firebase-bom:32.7.3")) @@ -116,6 +116,9 @@ dependencies { // Image Cropping implementation("com.vanniktech:android-image-cropper:4.5.0") + + // Immutable collections + implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.7") } kapt { diff --git a/app/src/main/java/com/bunbeauty/fakelivestream/common/presentation/BaseViewModel.kt b/app/src/main/java/com/bunbeauty/fakelivestream/common/presentation/BaseViewModel.kt index 5f1bdf6..63615cd 100644 --- a/app/src/main/java/com/bunbeauty/fakelivestream/common/presentation/BaseViewModel.kt +++ b/app/src/main/java/com/bunbeauty/fakelivestream/common/presentation/BaseViewModel.kt @@ -16,6 +16,8 @@ abstract class BaseViewModel( ) : ViewModel() { protected val mutableState = MutableStateFlow(initState()) + protected val currentState: S + get() = mutableState.value val state: StateFlow = mutableState.asStateFlow() private val mutableEvent = Channel() diff --git a/app/src/main/java/com/bunbeauty/fakelivestream/common/ui/components/bottomsheet/FakeLiveBottomSheet.kt b/app/src/main/java/com/bunbeauty/fakelivestream/common/ui/components/bottomsheet/FakeLiveBottomSheet.kt index dd32158..511d632 100644 --- a/app/src/main/java/com/bunbeauty/fakelivestream/common/ui/components/bottomsheet/FakeLiveBottomSheet.kt +++ b/app/src/main/java/com/bunbeauty/fakelivestream/common/ui/components/bottomsheet/FakeLiveBottomSheet.kt @@ -48,7 +48,11 @@ fun FakeLiveBottomSheet( windowInsets = windowInsets, modifier = modifier ) { - Column(modifier = Modifier.navigationBarsPadding()) { + Column( + modifier = Modifier + .fillMaxWidth() + .navigationBarsPadding() + ) { content() } } diff --git a/app/src/main/java/com/bunbeauty/fakelivestream/common/ui/components/bottomsheet/FakeLiveBottomSheetContent.kt b/app/src/main/java/com/bunbeauty/fakelivestream/common/ui/components/bottomsheet/FakeLiveBottomSheetContent.kt new file mode 100644 index 0000000..ee22ad7 --- /dev/null +++ b/app/src/main/java/com/bunbeauty/fakelivestream/common/ui/components/bottomsheet/FakeLiveBottomSheetContent.kt @@ -0,0 +1,42 @@ +package com.bunbeauty.fakelivestream.common.ui.components.bottomsheet + +import androidx.annotation.StringRes +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Divider +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.bunbeauty.fakelivestream.common.ui.theme.FakeLiveStreamTheme + +@Composable +fun ColumnScope.FakeLiveBottomSheetContent( + @StringRes titleResId: Int, + content: @Composable () -> Unit +) { + Text( + modifier = Modifier + .fillMaxWidth() + .padding( + top = 16.dp, + bottom = 12.dp + ), + text = stringResource(titleResId), + color = FakeLiveStreamTheme.colors.onSurface, + style = FakeLiveStreamTheme.typography.titleMedium, + textAlign = TextAlign.Center + ) + Divider( + modifier = Modifier.fillMaxWidth(), + thickness = 0.5.dp, + color = FakeLiveStreamTheme.colors.border + ) + Column(modifier = Modifier.padding(horizontal = 16.dp)) { + content() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bunbeauty/fakelivestream/common/ui/theme/Color.kt b/app/src/main/java/com/bunbeauty/fakelivestream/common/ui/theme/Color.kt index 4f1d6d1..84fc2ef 100644 --- a/app/src/main/java/com/bunbeauty/fakelivestream/common/ui/theme/Color.kt +++ b/app/src/main/java/com/bunbeauty/fakelivestream/common/ui/theme/Color.kt @@ -7,7 +7,8 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.graphics.Color val White = Color(0xFFFFFFFF) -val Black = Color(0xFF000000) +val Black200 = Color(0xFF000000) +val Black100 = Color(0xFF1A1A1A) val Gray400 = Color(0xFF262626) val Gray300 = Color(0xFF6D6D6D) val Gray200 = Color(0xFF888888) @@ -21,10 +22,12 @@ val Amber = Color(0xFFFFBF00) @Stable class ColorScheme( interactive: Color, + important: Color, icon: Color, iconVariant: Color, surface: Color, surfaceVariant: Color, + selectedSurface: Color, onSurface: Color, onSurfaceVariant: Color, background: Color, @@ -37,6 +40,9 @@ class ColorScheme( var interactive by mutableStateOf(interactive) internal set + var important by mutableStateOf(important) + internal set + var icon by mutableStateOf(icon) internal set @@ -49,6 +55,9 @@ class ColorScheme( var surfaceVariant by mutableStateOf(surfaceVariant) internal set + var selectedSurface by mutableStateOf(selectedSurface) + internal set + var onSurface by mutableStateOf(onSurface) internal set @@ -73,10 +82,12 @@ class ColorScheme( fun copy( interactive: Color = this.interactive, + important: Color = this.important, icon: Color = this.icon, iconVariant: Color = this.iconVariant, surface: Color = this.surface, surfaceVariant: Color = this.surfaceVariant, + selectedSurface: Color = this.selectedSurface, onSurface: Color = this.onSurface, onSurfaceVariant: Color = this.onSurfaceVariant, background: Color = this.background, @@ -86,10 +97,12 @@ class ColorScheme( instagram: InstagramColors = this.instagram, ): ColorScheme = ColorScheme( interactive = interactive, + important = important, icon = icon, iconVariant = iconVariant, surface = surface, surfaceVariant = surfaceVariant, + selectedSurface = selectedSurface, onSurface = onSurface, onSurfaceVariant = onSurfaceVariant, background = background, diff --git a/app/src/main/java/com/bunbeauty/fakelivestream/common/ui/theme/Theme.kt b/app/src/main/java/com/bunbeauty/fakelivestream/common/ui/theme/Theme.kt index 1da2961..ba8386a 100644 --- a/app/src/main/java/com/bunbeauty/fakelivestream/common/ui/theme/Theme.kt +++ b/app/src/main/java/com/bunbeauty/fakelivestream/common/ui/theme/Theme.kt @@ -10,15 +10,17 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.staticCompositionLocalOf private val DarkColorScheme = ColorScheme( + important = Scarlet, interactive = Blue, icon = White, iconVariant = Gray200, - surface = Black, + surface = Black200, surfaceVariant = Gray400, + selectedSurface = Black100, onSurface = White, onSurfaceVariant = Gray200, background = White, - onBackground = Black, + onBackground = Black200, border = Gray300, borderVariant = Gray100, instagram = InstagramColors( @@ -31,14 +33,16 @@ private val DarkColorScheme = ColorScheme( private val LightColorScheme = ColorScheme( interactive = Blue, + important = Scarlet, icon = White, iconVariant = Gray200, - surface = Black, + surface = Black200, surfaceVariant = Gray400, + selectedSurface = Black100, onSurface = White, onSurfaceVariant = Gray200, background = White, - onBackground = Black, + onBackground = Black200, border = Gray300, borderVariant = Gray100, instagram = InstagramColors( diff --git a/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/data/user/UserRepository.kt b/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/data/user/UserRepository.kt index 0aa3c78..8d58dd2 100644 --- a/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/data/user/UserRepository.kt +++ b/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/data/user/UserRepository.kt @@ -3,19 +3,19 @@ package com.bunbeauty.fakelivestream.features.stream.data.user import com.bunbeauty.fakelivestream.features.stream.data.comment.PictureStore import javax.inject.Inject import javax.inject.Singleton -import kotlin.random.Random @Singleton class UserRepository @Inject constructor() { - private val pictureStore = PictureStore() + private val commentPictureStore = PictureStore() + private val questionPictureStore = PictureStore() - fun getPictureName(): String? { - return if (Random.nextInt(10) == 0) { - null - } else { - pictureStore.getNext() - } + fun getCommentPictureName(): String { + return commentPictureStore.getNext() + } + + fun getQuestionPictureName(): String { + return questionPictureStore.getNext() } } \ No newline at end of file diff --git a/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/domain/GetCommentsDelayUseCase.kt b/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/domain/GetCommentsDelayUseCase.kt new file mode 100644 index 0000000..f022b2f --- /dev/null +++ b/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/domain/GetCommentsDelayUseCase.kt @@ -0,0 +1,16 @@ +package com.bunbeauty.fakelivestream.features.stream.domain + +import javax.inject.Inject +import kotlin.random.Random + +class GetCommentsDelayUseCase @Inject constructor() { + + operator fun invoke(viewersCount: Int): Long { + return if (viewersCount < 1000) { + Random.nextLong(1_000, 2_000) + } else { + Random.nextLong(500, 1_000) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/domain/GetCommentsUseCase.kt b/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/domain/GetCommentsUseCase.kt index 0095081..c1366ea 100644 --- a/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/domain/GetCommentsUseCase.kt +++ b/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/domain/GetCommentsUseCase.kt @@ -3,6 +3,7 @@ package com.bunbeauty.fakelivestream.features.stream.domain import com.bunbeauty.fakelivestream.features.stream.data.user.UserRepository import com.bunbeauty.fakelivestream.features.stream.domain.model.Comment import javax.inject.Inject +import kotlin.random.Random class GetCommentsUseCase @Inject constructor( private val userRepository: UserRepository, @@ -10,10 +11,22 @@ class GetCommentsUseCase @Inject constructor( private val getRandomUsernameUseCase: GetRandomUsernameUseCase, ) { - operator fun invoke(count: Int): List { + operator fun invoke(viewersCount: Int): List { + val count = if (viewersCount < 1000) { + 1 + } else { + val maxCommentCount = (viewersCount / 1_000).coerceIn(2, 5) + Random.nextInt(1, maxCommentCount) + } + return List(count) { + val picture = if (Random.nextInt(10) == 0) { + null + } else { + userRepository.getCommentPictureName() + } Comment( - picture = userRepository.getPictureName(), + picture = picture, username = getRandomUsernameUseCase(), text = getRandomCommentText(), ) diff --git a/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/domain/GetQuestionUseCase.kt b/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/domain/GetQuestionUseCase.kt new file mode 100644 index 0000000..72c0b5e --- /dev/null +++ b/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/domain/GetQuestionUseCase.kt @@ -0,0 +1,29 @@ +package com.bunbeauty.fakelivestream.features.stream.domain + +import com.bunbeauty.fakelivestream.features.stream.data.user.UserRepository +import com.bunbeauty.fakelivestream.features.stream.domain.model.Question +import java.util.UUID +import javax.inject.Inject + +private const val QUESTION_LIMIT = 3 + +class GetQuestionUseCase @Inject constructor( + private val userRepository: UserRepository, + private val getRandomCommentText: GetRandomCommentTextUseCase, + private val getRandomUsernameUseCase: GetRandomUsernameUseCase, +) { + + operator fun invoke(questionCount: Int): Question? { + if (questionCount >= QUESTION_LIMIT) { + return null + } + + return Question( + uuid = UUID.randomUUID().toString(), + picture = userRepository.getQuestionPictureName(), + username = getRandomUsernameUseCase(), + text = getRandomCommentText(type = CommentType.QUESTION), + ) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/domain/GetRandomCommentTextUseCase.kt b/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/domain/GetRandomCommentTextUseCase.kt index dc6bfa8..9d4b62d 100644 --- a/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/domain/GetRandomCommentTextUseCase.kt +++ b/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/domain/GetRandomCommentTextUseCase.kt @@ -9,11 +9,18 @@ class GetRandomCommentTextUseCase @Inject constructor( private val commentRepository: CommentRepository, ) { + operator fun invoke(type: CommentType): String { + return buildTextComment( + type = type, + withEmoji = false + ) + } + operator fun invoke(): String { return when (Random.nextInt(CommentType.entries.size + 2)) { 0 -> buildEmojiString(maxCount = 5) 1 -> buildSymbolString() - else -> buildTextComment() + else -> buildTextComment(type = randomType()) } } @@ -30,8 +37,10 @@ class GetRandomCommentTextUseCase @Inject constructor( } } - private fun buildTextComment(): String { - val type = randomType() + private fun buildTextComment( + type: CommentType, + withEmoji: Boolean = true, + ): String { var comment = commentRepository.getNextComment(type) comment = when (type) { CommentType.ONE_LETTER, @@ -57,7 +66,7 @@ class GetRandomCommentTextUseCase @Inject constructor( } } - return if (Random.nextBoolean()) { + return if (withEmoji && Random.nextBoolean()) { comment.addRandomEmoji() } else { comment diff --git a/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/domain/model/Question.kt b/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/domain/model/Question.kt new file mode 100644 index 0000000..e2982ea --- /dev/null +++ b/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/domain/model/Question.kt @@ -0,0 +1,8 @@ +package com.bunbeauty.fakelivestream.features.stream.domain.model + +data class Question( + val uuid: String, + val picture: String?, + val username: String, + val text: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/presentation/Stream.kt b/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/presentation/Stream.kt index ed8fd00..b42ceab 100644 --- a/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/presentation/Stream.kt +++ b/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/presentation/Stream.kt @@ -3,6 +3,7 @@ package com.bunbeauty.fakelivestream.features.stream.presentation import android.net.Uri import com.bunbeauty.fakelivestream.common.presentation.Base import com.bunbeauty.fakelivestream.features.stream.domain.model.Comment +import com.bunbeauty.fakelivestream.features.stream.domain.model.Question interface Stream { @@ -12,32 +13,59 @@ interface Stream { val viewersCount: Int, val comments: List, val reactionCount: Int, + val questionState: QuestionState, val startStreamTimeMillis: Long, val isCameraEnabled: Boolean, val isCameraFront: Boolean, val showJoinRequests: Boolean, val showInvite: Boolean, - val showQuestions: Boolean, val showDirect: Boolean, - ): Base.State + ) : Base.State - sealed interface Action: Base.Action { - data object CameraClick: Action - data object SwitchCameraClick: Action - data object ShowJoinRequests: Action - data object HideJoinRequests: Action - data object ShowInvite: Action - data object HideInvite: Action - data object ShowQuestions: Action - data object HideQuestions: Action - data object ShowDirect: Action - data object HideDirect: Action - data object Start: Action - data object Stop: Action - data object FinishStreamClick: Action + data class QuestionState( + val show: Boolean, + val questions: List, + val unreadQuestionCount: Int?, + val currentQuestionToAnswer: SelectableQuestion?, + ) { + val isEmpty: Boolean = questions.isEmpty() + val notAnsweredQuestions = questions.filterNot { question -> + question.isAnswered + } + val answeredQuestions = questions.filter { question -> + question.isAnswered + } + val selectedQuestion: SelectableQuestion? = questions.find { question -> + question.isSelected + } } - sealed interface Event: Base.Event { - data class GoBack(val durationInSeconds: Int): Event + data class SelectableQuestion( + val question: Question, + val isAnswered: Boolean, + val isSelected: Boolean, + ) + + sealed interface Action : Base.Action { + data object CameraClick : Action + data object SwitchCameraClick : Action + data object ShowJoinRequests : Action + data object HideJoinRequests : Action + data object ShowInvite : Action + data object HideInvite : Action + data object ShowQuestions : Action + data object HideQuestions : Action + data class ClickQuestion(val uuid: String) : Action + data class DeleteQuestion(val uuid: String) : Action + data object CloseCurrentQuestion : Action + data object ShowDirect : Action + data object HideDirect : Action + data object Start : Action + data object Stop : Action + data object FinishStreamClick : Action + } + + sealed interface Event : Base.Event { + data class GoBack(val durationInSeconds: Int) : Event } } \ No newline at end of file diff --git a/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/presentation/StreamViewModel.kt b/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/presentation/StreamViewModel.kt index 7a2ace6..6e2281b 100644 --- a/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/presentation/StreamViewModel.kt +++ b/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/presentation/StreamViewModel.kt @@ -8,9 +8,11 @@ import com.bunbeauty.fakelivestream.features.domain.GetImageUriFlowUseCase import com.bunbeauty.fakelivestream.features.domain.GetUsernameUseCase import com.bunbeauty.fakelivestream.features.domain.GetViewerCountUseCase import com.bunbeauty.fakelivestream.features.stream.CameraUtil +import com.bunbeauty.fakelivestream.features.stream.domain.GetCommentsDelayUseCase import com.bunbeauty.fakelivestream.features.stream.domain.GetCommentsUseCase +import com.bunbeauty.fakelivestream.features.stream.domain.GetQuestionUseCase +import com.bunbeauty.fakelivestream.features.stream.domain.model.Question import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch @@ -23,7 +25,9 @@ class StreamViewModel @Inject constructor( private val getImageUriFlowUseCase: GetImageUriFlowUseCase, private val getUsernameUseCase: GetUsernameUseCase, private val getViewerCountUseCase: GetViewerCountUseCase, - private val getComments: GetCommentsUseCase, + private val getCommentsUseCase: GetCommentsUseCase, + private val getCommentsDelayUseCase: GetCommentsDelayUseCase, + private val getQuestionUseCase: GetQuestionUseCase, private val analyticsManager: AnalyticsManager, private val cameraUtil: CameraUtil, ) : BaseViewModel( @@ -34,21 +38,31 @@ class StreamViewModel @Inject constructor( viewersCount = 0, comments = emptyList(), reactionCount = 0, + questionState = Stream.QuestionState( + show = false, + questions = emptyList(), + unreadQuestionCount = null, + currentQuestionToAnswer = null, + ), startStreamTimeMillis = System.currentTimeMillis(), isCameraEnabled = cameraUtil.hasCamera(), isCameraFront = cameraUtil.hasFrontCamera(), showJoinRequests = false, showInvite = false, - showQuestions = false, showDirect = false, ) } ) { init { - getAvatar() - getUsername() - getViewerCount() + setupAvatar() + setupUsername() + setupViewerCount() + + startGenerateReactions() + startGenerateViewersCount() + startGenerateComments() + startGenerateQuestions() } override fun onAction(action: Stream.Action) { @@ -68,6 +82,7 @@ class StreamViewModel @Inject constructor( } } } + Stream.Action.CameraClick -> { if (cameraUtil.hasCamera()) { setState { @@ -75,6 +90,7 @@ class StreamViewModel @Inject constructor( } } } + Stream.Action.ShowJoinRequests -> { setState { copy(showJoinRequests = true) @@ -101,13 +117,71 @@ class StreamViewModel @Inject constructor( Stream.Action.ShowQuestions -> { setState { - copy(showQuestions = true) + copy( + questionState = questionState.copy( + show = true, + unreadQuestionCount = null + ) + ) } } Stream.Action.HideQuestions -> { setState { - copy(showQuestions = false) + copy( + questionState = questionState.copy( + show = false, + currentQuestionToAnswer = questionState.selectedQuestion + ) + ) + } + } + + is Stream.Action.ClickQuestion -> { + setState { + copy( + questionState = questionState.copy( + questions = questionState.questions.map { question -> + question.copy( + isSelected = if (question.question.uuid == action.uuid) { + !question.isSelected + } else { + false + }, + isAnswered = question.isAnswered || + (question.question.uuid == questionState.currentQuestionToAnswer?.question?.uuid) + ) + }, + currentQuestionToAnswer = null, + ) + ) + } + } + + is Stream.Action.DeleteQuestion -> { + setState { + copy( + questionState = questionState.copy( + questions = questionState.questions.filter { question -> + question.question.uuid != action.uuid + } + ) + ) + } + } + + Stream.Action.CloseCurrentQuestion -> { + setState { + copy( + questionState = questionState.copy( + questions = questionState.questions.map { question -> + question.copy( + isSelected = false, + isAnswered = question.isAnswered || question.isSelected + ) + } + ) + ) } } @@ -142,52 +216,46 @@ class StreamViewModel @Inject constructor( } } - private fun getAvatar() { + private fun setupAvatar() { setState { copy(imageUri = getImageUriFlowUseCase().firstOrNull()?.toUri()) } } - private fun getUsername() { + private fun setupUsername() { setState { copy(username = getUsernameUseCase()) } } - private fun getViewerCount() { + private fun setupViewerCount() { viewModelScope.launch { - val viewerCount = getViewerCountUseCase() setState { - copy(viewersCount = viewerCount.min) + copy(viewersCount = getViewerCountUseCase().min) } - - startGenerateReactions(viewerCount = viewerCount.min) - startGenerateViewersCount( - min = viewerCount.min, - max = viewerCount.max - ) - startGenerateComments() } } - private fun startGenerateReactions(viewerCount: Int) { + private fun startGenerateReactions() { viewModelScope.launch { delay(5_000) + val viewerCount = getViewerCountUseCase() setState { copy( - reactionCount = min(10, viewerCount / 100 + 1) + reactionCount = min(10, viewerCount.min / 100 + 1) ) } } } - private fun startGenerateViewersCount(min: Int, max: Int) { - viewModelScope.launch(Dispatchers.Default) { + private fun startGenerateViewersCount() { + viewModelScope.launch { delay(1_000) - val onePercent = (max - min) / 100 + val viewerCount = getViewerCountUseCase() + val onePercent = (viewerCount.max - viewerCount.min) / 100 val step = min(onePercent, Random.nextInt(200, 800)) - val median = min + (max - min) / 2 + val median = viewerCount.min + (viewerCount.max - viewerCount.min) / 2 while (true) { val delayMillis = Random.nextLong(2_000, 4_000) delay(delayMillis) @@ -215,21 +283,8 @@ class StreamViewModel @Inject constructor( private fun startGenerateComments() { viewModelScope.launch { while (true) { - val viewersCount = mutableState.value.viewersCount - val commentCount = if (viewersCount < 1000) { - 1 - } else { - val maxCommentCount = (viewersCount / 1_000).coerceIn(2, 5) - Random.nextInt(1, maxCommentCount) - } - - val newComments = getComments(count = commentCount) - - val delayMillis = if (viewersCount < 1000) { - Random.nextLong(1_000, 2_000) - } else { - Random.nextLong(500, 1_000) - } + val newComments = getCommentsUseCase(viewersCount = currentState.viewersCount) + val delayMillis = getCommentsDelayUseCase(viewersCount = currentState.viewersCount) delay(delayMillis) setState { @@ -241,6 +296,41 @@ class StreamViewModel @Inject constructor( } } + private fun startGenerateQuestions() { + viewModelScope.launch { + delay(5_000) + while (true) { + val questionCount = currentState.questionState.notAnsweredQuestions.size + val newQuestion = getQuestionUseCase(questionCount = questionCount) + if (newQuestion != null) { + handleNewQuestion(question = newQuestion) + } + + delay(Random.nextLong(20_000, 50_000)) + } + } + } + + private fun handleNewQuestion(question: Question) { + val newQuestion = Stream.SelectableQuestion( + question = question, + isSelected = false, + isAnswered = false, + ) + setState { + copy( + questionState = questionState.copy( + questions = questionState.questions + newQuestion, + unreadQuestionCount = if (questionState.show) { + null + } else { + (questionState.unreadQuestionCount ?: 0) + 1 + } + ), + ) + } + } + private fun getStreamDurationInSeconds(): Int { val start = mutableState.value.startStreamTimeMillis val finish = System.currentTimeMillis() diff --git a/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/view/StreamScreen.kt b/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/view/StreamScreen.kt index 684a7ac..c5daaad 100644 --- a/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/view/StreamScreen.kt +++ b/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/view/StreamScreen.kt @@ -19,6 +19,7 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Badge import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold import androidx.compose.material3.Text @@ -32,10 +33,13 @@ import androidx.compose.runtime.setValue 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.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextOverflow.Companion.Ellipsis +import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.Lifecycle @@ -43,6 +47,13 @@ import androidx.lifecycle.compose.LifecycleEventEffect import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavHostController import com.bunbeauty.fakelivestream.R +import com.bunbeauty.fakelivestream.common.ui.LocalePreview +import com.bunbeauty.fakelivestream.common.ui.blurTop +import com.bunbeauty.fakelivestream.common.ui.clickableWithoutIndication +import com.bunbeauty.fakelivestream.common.ui.components.CachedImage +import com.bunbeauty.fakelivestream.common.ui.components.ImageSource +import com.bunbeauty.fakelivestream.common.ui.theme.FakeLiveStreamTheme +import com.bunbeauty.fakelivestream.common.ui.theme.bold import com.bunbeauty.fakelivestream.features.stream.presentation.Stream import com.bunbeauty.fakelivestream.features.stream.presentation.StreamViewModel import com.bunbeauty.fakelivestream.features.stream.view.ui.AnimatedReaction @@ -50,13 +61,10 @@ import com.bunbeauty.fakelivestream.features.stream.view.ui.AvatarImage import com.bunbeauty.fakelivestream.features.stream.view.ui.CameraComponent import com.bunbeauty.fakelivestream.features.stream.view.ui.EmptyBottomSheet import com.bunbeauty.fakelivestream.features.stream.view.ui.FiltersRow +import com.bunbeauty.fakelivestream.features.stream.view.ui.QuestionState +import com.bunbeauty.fakelivestream.features.stream.view.ui.QuestionUi +import com.bunbeauty.fakelivestream.features.stream.view.ui.QuestionsBottomSheet import com.bunbeauty.fakelivestream.features.stream.view.ui.VideoComponent -import com.bunbeauty.fakelivestream.common.ui.LocalePreview -import com.bunbeauty.fakelivestream.common.ui.blurTop -import com.bunbeauty.fakelivestream.common.ui.clickableWithoutIndication -import com.bunbeauty.fakelivestream.common.ui.components.CachedImage -import com.bunbeauty.fakelivestream.common.ui.components.ImageSource -import com.bunbeauty.fakelivestream.common.ui.theme.FakeLiveStreamTheme import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -200,10 +208,16 @@ fun StreamContent( } ) } - Comments( + Column( modifier = Modifier.align(Alignment.BottomStart), - comments = state.comments, - ) + ) { + Comments(comments = state.comments) + CurrentQuestion( + modifier = Modifier.padding(top = 16.dp), + question = state.selectedQuestion, + onAction = onAction + ) + } } } @@ -217,7 +231,10 @@ fun StreamContent( } } Column { - BottomPanel(onAction = onAction) + BottomPanel( + unreadQuestionCount = state.unreadQuestionCount, + onAction = onAction + ) if (showFilters) { FiltersRow() } @@ -239,10 +256,9 @@ fun StreamContent( ) QuestionsBottomSheet( - show = state.showQuestions, - onDismiss = { - onAction(Stream.Action.HideQuestions) - } + show = state.questionState != QuestionState.Hidden, + questionState = state.questionState, + onAction = onAction, ) DirectBottomSheet( @@ -419,6 +435,70 @@ private fun Comments( } } +@Composable +private fun CurrentQuestion( + question: QuestionUi?, + onAction: (Stream.Action) -> Unit, + modifier: Modifier = Modifier, +) { + question ?: return + + Box( + modifier = modifier + .clip(RoundedCornerShape(8.dp)) + .background(FakeLiveStreamTheme.colors.surface.copy(alpha = 0.8f)) + ) { + Row( + modifier = Modifier.padding(12.dp), + horizontalArrangement = spacedBy(12.dp), + ) { + CachedImage( + modifier = Modifier + .size(32.dp) + .clip(CircleShape), + imageSource = question.picture, + cacheKey = question.username, + contentDescription = "Question avatar", + ) + Column(modifier = Modifier.weight(1f)) { + Text( + text = stringResource(R.string.stream_question_title), + color = FakeLiveStreamTheme.colors.onSurface, + style = FakeLiveStreamTheme.typography.titleSmall, + ) + + val usernameStyle = FakeLiveStreamTheme.typography.titleSmall + val annotatedString = remember(question) { + buildAnnotatedString { + withStyle(style = usernameStyle.toSpanStyle()) { + append("${question.username} ") + } + append(question.text) + } + } + Text( + modifier = Modifier.padding(top = 2.dp), + text = annotatedString, + color = FakeLiveStreamTheme.colors.onSurface, + style = FakeLiveStreamTheme.typography.bodySmall, + ) + } + } + Icon( + modifier = Modifier + .align(Alignment.TopEnd) + .padding(10.dp) + .size(12.dp) + .clickableWithoutIndication { + onAction(Stream.Action.CloseCurrentQuestion) + }, + imageVector = ImageVector.vectorResource(R.drawable.ic_close), + contentDescription = "Close", + tint = FakeLiveStreamTheme.colors.icon, + ) + } +} + @Composable private fun CommentItem( comment: CommentUi, @@ -453,6 +533,7 @@ private fun CommentItem( @Composable private fun BottomPanel( + unreadQuestionCount: Int?, onAction: (Stream.Action) -> Unit, modifier: Modifier = Modifier, ) { @@ -511,15 +592,32 @@ private fun BottomPanel( iconResId = R.drawable.ic_invite, contentDescription = "Invite", ) - ActionIcon( - modifier = Modifier.clickableWithoutIndication( - onClick = { - onAction(Stream.Action.ShowQuestions) + Box { + ActionIcon( + modifier = Modifier + .padding(end = 4.dp) + .clickableWithoutIndication( + onClick = { + onAction(Stream.Action.ShowQuestions) + } + ), + iconResId = R.drawable.ic_question, + contentDescription = "Question", + ) + unreadQuestionCount?.let { + Badge( + modifier = Modifier.align(Alignment.TopEnd), + containerColor = FakeLiveStreamTheme.colors.important, + contentColor = FakeLiveStreamTheme.colors.onSurface + ) { + Text( + text = unreadQuestionCount.toString(), + color = FakeLiveStreamTheme.colors.onSurface, + style = FakeLiveStreamTheme.typography.bodySmall.bold + ) } - ), - iconResId = R.drawable.ic_question, - contentDescription = "Question", - ) + } + } ActionIcon( modifier = Modifier.clickableWithoutIndication( onClick = { @@ -576,21 +674,6 @@ private fun InviteBottomSheet( } } -@Composable -private fun QuestionsBottomSheet( - show: Boolean, - onDismiss: () -> Unit, -) { - if (show) { - EmptyBottomSheet( - onDismissRequest = onDismiss, - titleResId = R.string.stream_questions_title, - bodyResId = R.string.stream_questions_body, - descriptionResId = R.string.stream_questions_description, - ) - } -} - @Composable private fun DirectBottomSheet( show: Boolean, @@ -610,41 +693,51 @@ private fun DirectBottomSheet( @Composable private fun StreamScreenPreview() { FakeLiveStreamTheme { - StreamContent( - state = ViewState( - image = ImageSource.ResId(R.drawable.img_default_avatar), - username = "long_user_name", - viewersCount = ViewersCountUi.Thousands( - thousands = "10", - hundreds = "4", - ), - comments = listOf( - CommentUi( - picture = ImageSource.ResId(R.drawable.img_default_avatar), - username = "username1", - text = "Text 1", + Box(modifier = Modifier.background(Color.White)) { + StreamContent( + state = ViewState( + image = ImageSource.ResId(R.drawable.img_default_avatar), + username = "long_user_name", + viewersCount = ViewersCountUi.Thousands( + thousands = "10", + hundreds = "4", ), - CommentUi( - picture = ImageSource.ResId(R.drawable.img_default_avatar), - username = "username2", - text = "Text 2", + comments = listOf( + CommentUi( + picture = ImageSource.ResId(R.drawable.img_default_avatar), + username = "username1", + text = "Text 1", + ), + CommentUi( + picture = ImageSource.ResId(R.drawable.img_default_avatar), + username = "username2", + text = "Text 2", + ), + CommentUi( + picture = ImageSource.ResId(R.drawable.img_default_avatar), + username = "username3", + text = "Text 3", + ), ), - CommentUi( + reactionCount = 10, + mode = Mode.CAMERA, + isCameraEnabled = true, + isCameraFront = true, + showJoinRequests = false, + showInvite = false, + questionState = QuestionState.Hidden, + unreadQuestionCount = 1, + selectedQuestion = QuestionUi( + uuid = "", picture = ImageSource.ResId(R.drawable.img_default_avatar), - username = "username3", - text = "Text 3", + username = "username", + text = "Question?", + isSelected = true, ), + showDirect = false, ), - reactionCount = 10, - mode = Mode.CAMERA, - isCameraEnabled = true, - isCameraFront = true, - showJoinRequests = false, - showInvite = false, - showQuestions = false, - showDirect = false, - ), - onAction = {}, - ) + onAction = {}, + ) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/view/StreamStateMapper.kt b/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/view/StreamStateMapper.kt index 3032f24..e841ea8 100644 --- a/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/view/StreamStateMapper.kt +++ b/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/view/StreamStateMapper.kt @@ -2,8 +2,12 @@ package com.bunbeauty.fakelivestream.features.stream.view import com.bunbeauty.fakelivestream.BuildConfig import com.bunbeauty.fakelivestream.R -import com.bunbeauty.fakelivestream.features.stream.presentation.Stream import com.bunbeauty.fakelivestream.common.ui.components.ImageSource +import com.bunbeauty.fakelivestream.features.stream.domain.model.Comment +import com.bunbeauty.fakelivestream.features.stream.presentation.Stream +import com.bunbeauty.fakelivestream.features.stream.view.ui.QuestionState +import com.bunbeauty.fakelivestream.features.stream.view.ui.QuestionUi +import kotlinx.collections.immutable.toImmutableList fun Stream.State.toViewState(): ViewState { return ViewState( @@ -24,15 +28,7 @@ fun Stream.State.toViewState(): ViewState { ) }, comments = comments.map { comment -> - CommentUi( - picture = if (comment.picture == null) { - ImageSource.ResId(R.drawable.img_default_avatar) - } else { - ImageSource.ResName(comment.picture) - }, - username = comment.username, - text = comment.text, - ) + comment.toCommentUi() }, reactionCount = reactionCount, mode = if (BuildConfig.SHOW_CAMERA) Mode.CAMERA else Mode.VIDEO, @@ -40,7 +36,50 @@ fun Stream.State.toViewState(): ViewState { isCameraFront = isCameraFront, showJoinRequests = showJoinRequests, showInvite = showInvite, - showQuestions = showQuestions, + questionState = if (questionState.show) { + if (questionState.isEmpty) { + QuestionState.Empty + } else { + QuestionState.NotEmpty( + notAnsweredQuestions = questionState.notAnsweredQuestions.map { question -> + question.toQuestionUi() + }.toImmutableList(), + answeredQuestions = questionState.answeredQuestions.map { question -> + question.toQuestionUi() + }.toImmutableList(), + ) + } + } else { + QuestionState.Hidden + }, + unreadQuestionCount = questionState.unreadQuestionCount, + selectedQuestion = questionState.selectedQuestion?.toQuestionUi(), showDirect = showDirect, ) +} + +private fun Comment.toCommentUi(): CommentUi { + return CommentUi( + picture = if (picture == null) { + ImageSource.ResId(R.drawable.img_default_avatar) + } else { + ImageSource.ResName(picture) + }, + username = username, + text = text, + ) +} + +private fun Stream.SelectableQuestion.toQuestionUi(): QuestionUi { + return QuestionUi( + uuid = question.uuid, + picture = if (question.picture == null) { + ImageSource.ResId(R.drawable.img_default_avatar) + } else { + ImageSource.ResName(question.picture) + }, + username = question.username, + text = question.text, + isSelected = isSelected, + ) } \ No newline at end of file diff --git a/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/view/ViewState.kt b/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/view/ViewState.kt index 2f26632..dcdda30 100644 --- a/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/view/ViewState.kt +++ b/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/view/ViewState.kt @@ -2,6 +2,8 @@ package com.bunbeauty.fakelivestream.features.stream.view import androidx.compose.runtime.Immutable import com.bunbeauty.fakelivestream.common.ui.components.ImageSource +import com.bunbeauty.fakelivestream.features.stream.view.ui.QuestionState +import com.bunbeauty.fakelivestream.features.stream.view.ui.QuestionUi @Immutable data class ViewState( @@ -15,7 +17,9 @@ data class ViewState( val isCameraFront: Boolean, val showJoinRequests: Boolean, val showInvite: Boolean, - val showQuestions: Boolean, + val questionState: QuestionState, + val unreadQuestionCount: Int?, + val selectedQuestion: QuestionUi?, val showDirect: Boolean, ) diff --git a/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/view/ui/EmptyBottomSheet.kt b/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/view/ui/EmptyBottomSheet.kt index 631e59d..c1389d7 100644 --- a/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/view/ui/EmptyBottomSheet.kt +++ b/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/view/ui/EmptyBottomSheet.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material.Divider import androidx.compose.material.Text import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable @@ -16,6 +15,7 @@ import androidx.compose.ui.unit.dp import com.bunbeauty.fakelivestream.R import com.bunbeauty.fakelivestream.common.ui.LocalePreview import com.bunbeauty.fakelivestream.common.ui.components.bottomsheet.FakeLiveBottomSheet +import com.bunbeauty.fakelivestream.common.ui.components.bottomsheet.FakeLiveBottomSheetContent import com.bunbeauty.fakelivestream.common.ui.theme.FakeLiveStreamTheme @OptIn(ExperimentalMaterial3Api::class) @@ -31,7 +31,7 @@ fun EmptyBottomSheet( modifier = modifier, onDismissRequest = onDismissRequest ) { - Content( + EmptyBottomSheetContent( titleResId = titleResId, bodyResId = bodyResId, descriptionResId = descriptionResId, @@ -40,59 +40,53 @@ fun EmptyBottomSheet( } @Composable -private fun ColumnScope.Content( +private fun ColumnScope.EmptyBottomSheetContent( @StringRes titleResId: Int, @StringRes bodyResId: Int, @StringRes descriptionResId: Int, +) { + FakeLiveBottomSheetContent(titleResId = titleResId) { + EmptyBottomSheetContent( + bodyResId = bodyResId, + descriptionResId = descriptionResId, + ) + } +} + +@Composable +fun ColumnScope.EmptyBottomSheetContent( + @StringRes bodyResId: Int, + @StringRes descriptionResId: Int, ) { Text( modifier = Modifier - .fillMaxWidth() - .padding( - top = 16.dp, - bottom = 12.dp - ), - text = stringResource(titleResId), + .padding(top = 100.dp) + .fillMaxWidth(), + text = stringResource(bodyResId), color = FakeLiveStreamTheme.colors.onSurface, - style = FakeLiveStreamTheme.typography.titleMedium, + style = FakeLiveStreamTheme.typography.titleLarge, textAlign = TextAlign.Center ) - Divider( - modifier = Modifier.fillMaxWidth(), - thickness = 0.5.dp, - color = FakeLiveStreamTheme.colors.border + Text( + modifier = Modifier + .padding( + top = 16.dp, + bottom = 100.dp + ) + .fillMaxWidth(), + text = stringResource(descriptionResId), + color = FakeLiveStreamTheme.colors.onSurfaceVariant, + style = FakeLiveStreamTheme.typography.bodyMedium, + textAlign = TextAlign.Center ) - Column(modifier = Modifier.padding(16.dp)) { - Text( - modifier = Modifier - .padding(top = 100.dp) - .fillMaxWidth(), - text = stringResource(bodyResId), - color = FakeLiveStreamTheme.colors.onSurface, - style = FakeLiveStreamTheme.typography.titleLarge, - textAlign = TextAlign.Center - ) - Text( - modifier = Modifier - .padding( - top = 16.dp, - bottom = 100.dp - ) - .fillMaxWidth(), - text = stringResource(descriptionResId), - color = FakeLiveStreamTheme.colors.onSurfaceVariant, - style = FakeLiveStreamTheme.typography.bodyMedium, - textAlign = TextAlign.Center - ) - } } @LocalePreview @Composable -fun EmptyBottomSheetPreview() { +private fun EmptyBottomSheetPreview() { FakeLiveStreamTheme { Column { - Content( + EmptyBottomSheetContent( titleResId = R.string.stream_questions_title, bodyResId = R.string.stream_questions_body, descriptionResId = R.string.stream_questions_description, diff --git a/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/view/ui/QuestionBottomSheet.kt b/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/view/ui/QuestionBottomSheet.kt new file mode 100644 index 0000000..ed4cdcd --- /dev/null +++ b/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/view/ui/QuestionBottomSheet.kt @@ -0,0 +1,306 @@ +package com.bunbeauty.fakelivestream.features.stream.view.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.remember +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.res.stringResource +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.unit.dp +import com.bunbeauty.fakelivestream.R +import com.bunbeauty.fakelivestream.common.ui.LocalePreview +import com.bunbeauty.fakelivestream.common.ui.clickableWithoutIndication +import com.bunbeauty.fakelivestream.common.ui.components.CachedImage +import com.bunbeauty.fakelivestream.common.ui.components.ImageSource +import com.bunbeauty.fakelivestream.common.ui.components.bottomsheet.FakeLiveBottomSheet +import com.bunbeauty.fakelivestream.common.ui.components.bottomsheet.FakeLiveBottomSheetContent +import com.bunbeauty.fakelivestream.common.ui.theme.FakeLiveStreamTheme +import com.bunbeauty.fakelivestream.common.ui.theme.bold +import com.bunbeauty.fakelivestream.features.stream.presentation.Stream +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +@Immutable +sealed interface QuestionState { + @Immutable + data object Hidden : QuestionState + + @Immutable + data object Empty : QuestionState + + @Immutable + data class NotEmpty( + val notAnsweredQuestions: ImmutableList, + val answeredQuestions: ImmutableList, + ) : QuestionState +} + +@Immutable +data class QuestionUi( + val uuid: String, + val picture: ImageSource<*>, + val username: String, + val text: String, + val isSelected: Boolean, +) + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun QuestionsBottomSheet( + show: Boolean, + questionState: QuestionState, + onAction: (Stream.Action) -> Unit, + modifier: Modifier = Modifier, +) { + if (show) { + FakeLiveBottomSheet( + modifier = modifier, + onDismissRequest = { + onAction(Stream.Action.HideQuestions) + } + ) { + QuestionsContent( + questionState = questionState, + onAction = onAction, + ) + } + } +} + +@Composable +private fun ColumnScope.QuestionsContent( + questionState: QuestionState, + onAction: (Stream.Action) -> Unit +) { + FakeLiveBottomSheetContent(titleResId = R.string.stream_questions_title) { + when (questionState) { + QuestionState.Empty -> { + EmptyBottomSheetContent( + bodyResId = R.string.stream_questions_body, + descriptionResId = R.string.stream_questions_description, + ) + } + + is QuestionState.NotEmpty -> { + LazyColumn( + modifier = Modifier + .fillMaxWidth() + .height(320.dp), + verticalArrangement = spacedBy(8.dp), + contentPadding = PaddingValues(vertical = 16.dp) + ) { + if (questionState.notAnsweredQuestions.isNotEmpty()) { + item(key = "tapToAnswerText") { + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(R.string.stream_questions_tap_to_answer), + color = FakeLiveStreamTheme.colors.onSurface, + style = FakeLiveStreamTheme.typography.titleMedium, + ) + } + item(key = "everyoneWatchingText") { + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(R.string.stream_questions_everyone_watching), + color = FakeLiveStreamTheme.colors.onSurfaceVariant, + style = FakeLiveStreamTheme.typography.bodyMedium, + ) + } + items( + items = questionState.notAnsweredQuestions, + key = { question -> question.uuid } + ) { question -> + QuestionItem( + question = question, + onAction = onAction, + ) + } + } + if (questionState.answeredQuestions.isNotEmpty()) { + item(key = "answeredQuestionsText") { + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(R.string.stream_questions_answered), + color = FakeLiveStreamTheme.colors.onSurface, + style = FakeLiveStreamTheme.typography.titleMedium, + ) + } + items( + items = questionState.answeredQuestions, + key = { question -> question.uuid } + ) { question -> + QuestionItem( + question = question, + onAction = onAction, + ) + } + } + } + } + + else -> Unit + } + } +} + +@Composable +private fun QuestionItem( + question: QuestionUi, + onAction: (Stream.Action) -> Unit, + modifier: Modifier = Modifier +) { + val borderColor = if (question.isSelected) { + FakeLiveStreamTheme.colors.interactive + } else { + FakeLiveStreamTheme.colors.border + } + val backgroundColor = if (question.isSelected) { + FakeLiveStreamTheme.colors.selectedSurface + } else { + Color.Transparent + } + Row( + modifier = modifier + .fillMaxWidth() + .border( + width = 1.dp, + color = borderColor, + shape = RoundedCornerShape(2.dp) + ) + .background( + color = backgroundColor, + shape = RoundedCornerShape(2.dp) + ) + .padding(horizontal = 8.dp) + .padding(top = 12.dp, bottom = 16.dp) + .clickableWithoutIndication { + onAction( + Stream.Action.ClickQuestion( + uuid = question.uuid + ) + ) + }, + verticalAlignment = Alignment.CenterVertically + ) { + CachedImage( + modifier = Modifier + .size(32.dp) + .clip(CircleShape), + imageSource = question.picture, + cacheKey = question.username, + contentDescription = "Comment avatar", + ) + Column( + modifier = Modifier.padding(start = 12.dp), + verticalArrangement = spacedBy(4.dp) + ) { + val usernameStyle = FakeLiveStreamTheme.typography.titleSmall + val annotatedString = remember(question) { + buildAnnotatedString { + withStyle(style = usernameStyle.toSpanStyle()) { + append("${question.username} ") + } + append(question.text) + } + } + Text( + modifier = Modifier.padding(top = 4.dp), + text = annotatedString, + color = FakeLiveStreamTheme.colors.onSurface, + style = FakeLiveStreamTheme.typography.bodySmall, + ) + Row(horizontalArrangement = spacedBy(16.dp)) { + Text( + modifier = Modifier.clickableWithoutIndication { + onAction(Stream.Action.DeleteQuestion(question.uuid)) + }, + text = stringResource(R.string.stream_questions_delete), + color = FakeLiveStreamTheme.colors.onSurfaceVariant, + style = FakeLiveStreamTheme.typography.bodySmall.bold, + ) + Text( + modifier = Modifier.clickableWithoutIndication { + onAction(Stream.Action.DeleteQuestion(question.uuid)) + }, + text = stringResource(R.string.stream_questions_report), + color = FakeLiveStreamTheme.colors.onSurfaceVariant, + style = FakeLiveStreamTheme.typography.bodySmall.bold, + ) + } + } + } +} + +@LocalePreview +@Composable +private fun EmptyQuestionsBottomSheetPreview() { + FakeLiveStreamTheme { + Column { + QuestionsContent( + questionState = QuestionState.Empty, + onAction = {} + ) + } + } +} + +@LocalePreview +@Composable +private fun NotEmptyQuestionsBottomSheetPreview() { + FakeLiveStreamTheme { + Column { + QuestionsContent( + questionState = QuestionState.NotEmpty( + notAnsweredQuestions = persistentListOf( + QuestionUi( + uuid = "1", + picture = ImageSource.ResId(R.drawable.a1), + username = "user.name", + text = "short question", + isSelected = true, + ), + QuestionUi( + uuid = "2", + picture = ImageSource.ResId(R.drawable.a2), + username = "user.name2", + text = "Looooooooooooooooooooooooooooooooooooo ooooo ooo ooong question", + + isSelected = false, + ), + ), + answeredQuestions = persistentListOf( + QuestionUi( + uuid = "3", + picture = ImageSource.ResId(R.drawable.a3), + username = "user.name3", + text = "short question", + isSelected = false, + ), + ) + ), + onAction = {}, + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/view/ui/VideoComponent.kt b/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/view/ui/VideoComponent.kt index 6b846da..8efd286 100644 --- a/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/view/ui/VideoComponent.kt +++ b/app/src/main/java/com/bunbeauty/fakelivestream/features/stream/view/ui/VideoComponent.kt @@ -1,5 +1,7 @@ package com.bunbeauty.fakelivestream.features.stream.view.ui +import android.content.ContentResolver +import android.net.Uri import androidx.annotation.OptIn import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect @@ -9,18 +11,21 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.viewinterop.AndroidView import androidx.media3.common.MediaItem import androidx.media3.common.util.UnstableApi -import androidx.media3.datasource.RawResourceDataSource import androidx.media3.exoplayer.ExoPlayer import androidx.media3.ui.PlayerView import com.bunbeauty.fakelivestream.R -@OptIn(UnstableApi::class) @Composable +@OptIn(UnstableApi::class) +@Composable fun VideoComponent( modifier: Modifier = Modifier ) { val localContext = LocalContext.current val exoPlayer = remember { - val videoUri = RawResourceDataSource.buildRawResourceUri(R.raw.video) + val videoUri = Uri.Builder() + .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) + .path(R.raw.video.toString()) + .build() ExoPlayer.Builder(localContext).build() .apply { setMediaItem(MediaItem.fromUri(videoUri)) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 5b6ac4a..c99306f 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -10,6 +10,8 @@ AO VIVO %s.%sK Adicione um comentário… + Pergunta + Permitir que %s acesse sua câmera Isso permite iniciar o Ao Vivo. Você pode alterar isso a qualquer momento nas configurações do seu dispositivo. @@ -38,6 +40,11 @@ Perguntas Sem perguntas ainda Qualquer pessoa assistindo pode enviar e ver perguntas aqui. + Toque em uma pergunta para responder + Perguntas respondidas + Todos que assistem verão na tela. + Eliminar + Denunciar Direto Direto não disponível @@ -186,4 +193,100 @@ Relatável e autêntico, seu conteúdo parece uma conexão genuína + + Como foi seu dia? + Como você está? + Como está seu humor? + O que te inspira? + O que você gosta de fazer no seu tempo livre? + Quais são seus hobbies? + Qual foi a última série que você assistiu? + Qual foi o último evento ou ocasião que você se lembra? + O que você mais gosta no seu trabalho/estudos? + Como foi seu fim de semana? + Qual é o seu superpoder? + Que habilidade você gostaria de aprender em breve? + Qual é sua série ou filme favorito no momento? + Você ouviu algo legal recentemente? + Que tipo de música você ouve? + O que você faz à noite? + Em que jogos você gosta de jogar? + O que você vai fazer depois da transmissão? + Cinema ou sofá em casa? + Qual foi o último fato interessante que você aprendeu? + Há algo sobre o qual você sonha? + Você já foi a um concerto? + Sobre o que você sonha? + Você está esperando algum lançamento? + Em qual filme você gostaria de atuar? + Recomende um livro + Recomende uma série + Recomende um filme divertido + Recomende um filme gentil + Para onde você gostaria de viajar? + Que livro você acha que todos deveriam ler? + Onde você gostaria de morar? + Como você relaxa? + O que te motiva? + Qual filme ou livro teve o maior impacto em você? + Se você pudesse escolher qualquer profissão sem se preocupar com dinheiro, o que seria? + Há algo que você sempre quis tentar? + Qual é a sua estação do ano favorita? + Que evento deixou a maior marca em sua vida? + Quais três coisas você levaria para uma ilha deserta? + Você gostaria de visitar o passado? + Você gosta de cozinhar? + Você prefere dar ou receber presentes? + O que fazer em um dia chuvoso? + Se você pudesse mudar um evento em sua vida, qual seria? + Descreva o dia perfeito + Qual é a sua memória mais feliz da infância? + O que mais te motiva todos os dias? + O que te motiva a acordar pela manhã? + Se você pudesse obter a resposta para qualquer pergunta, qual seria? + O que é mais importante na vida? + O que você queria ser quando crescesse? + Qual é o seu feriado favorito? + Qual é o prato mais incomum que você já experimentou? + Qual país ou cultura você acha mais interessante e por quê? + Você tem um lugar favorito? + Você tem metas para o ano? + Se você pudesse escolher qualquer talento, qual você gostaria de ter? + Você tem um talismã? + Qual gênero de música ou filmes você considera mais subestimado? + Qual é o seu aplicativo favorito? + Qual é o seu site favorito? + Você gosta de correr riscos? + Você tem animais de estimação? + Descreva-se em três palavras + Você prefere o verão ou o inverno? + Que língua você gostaria de saber? Por quê? + Qual é o seu lugar favorito? + Você tem plantas em casa? + O que você mais gosta no lugar onde mora agora? + Fale sobre seus hobbies + Se você pudesse conhecer qualquer personagem de um livro/filme, quem seria? + Que conselho você gostaria de dar a todas as pessoas na Terra? + Você prefere aventura ou relaxamento? + Você é uma pessoa matutina ou noturna? + Qual foi o melhor presente que você já recebeu? + O que você pensa sobre viagens espaciais? + Café ou chá? + Livros ou filmes? + Cães ou gatos? + Pizza ou sushi? + Filmes em casa ou no cinema? + Você gosta de festas? + Você prefere filmes ou séries? + Quais gêneros de filmes você gosta? + Quais gêneros musicais você gosta? + Você prefere clima quente ou frio? + Você prefere lazer ativo ou relaxamento? + Você prefere doce ou salgado? + Você prefere planejar com antecedência ou viver espontaneamente? + Jogos de tabuleiro ou videogames? + Você prefere passar tempo em companhia ou sozinho? + Você gosta de café? + + \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index fd34b83..afae9dd 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -9,6 +9,8 @@ ПРЯМОЙ ЭФИР %s,%s тыс. Добавьте комментарий… + Вопрос + Разрешите %s to доступ к камере Это позволит начть стрим. Выбор в любое время можно изменить в настройках устройства. @@ -37,6 +39,11 @@ Вопросы Вопросов пока нет Все зрители могут отправлять и видеть здесь вопросы. + Нажмите на вопрос, чтобы ответить + Отвеченные вопросы + Все, кто смотрит, увидят его на экране. + Удалить + Пожаловаться Поделитесь Поделиться недоступно @@ -227,133 +234,100 @@ Спасибо за ответы на вопросы зрителей, это делает стрим еще интереснее - - Как ты выбираешь тему для своего следующего видео? - Есть ли у тебя любимый персонаж или тема, которую ты часто включаешь в свои видео? - Какую камеру ты используешь для съемок? - Каким образом ты поддерживаешь вовлеченность зрителей? - Сколько времени ты тратишь на монтаж одного видео? - Есть ли у тебя какие-то традиции перед началом съемок? - Какие темы тебе интересно было бы рассмотреть в будущих видео? - Как ты выбираешь музыку для своих видео? - Как часто ты взаимодействуешь с комментариями зрителей? - Какие советы по монтажу ты мог бы поделиться с новичками? - Как ты относишься к трендам в мире контент-создания? - Есть ли у тебя любимый момент из всех своих видео? - Каким образом ты формируешь идеи для своих роликов? - Что, по-твоему, является ключевым для успешного контент-создания? - Как ты поддерживаешь баланс между креативностью и регулярностью публикаций? - Какую роль играют твои зрители в формировании контента? - Есть ли у тебя кинематографические вдохновители или образцы? - Как ты принимаешь решение о сотрудничестве с другими контент-создателями? - Что, по-твоему, является наиболее трудным в процессе создания контента? - Какие темы ты бы хотел рассмотреть в будущем? - Какую часть процесса создания контента ты находишь наиболее увлекательной? - Какое оборудование ты рекомендовал бы начинающим контент-создателям? - Есть ли у тебя любимая реакция или комментарий зрителей? - Каким образом ты избегаешь рутины в своем творчестве? - Как важна для тебя обратная связь от зрителей? - Есть ли у тебя любимый период в истории твоего канала? - Какие темы вызывают у тебя наибольший энтузиазм? - Как ты выбираешь заголовки для своих видео? - Какие сложности ты испытываешь в процессе создания контента? - Как ты поддерживаешь креативный настрой в трудные периоды? - Что, по-твоему, делает твой контент уникальным? - Какие изменения ты заметил в своем стиле с момента начала канала? - Есть ли у тебя любимый проект, который ты особенно любишь? - Какой совет ты бы дал себе на старте своего творческого пути? - Какую роль социальные сети играют в продвижении твоего контента? - Как ты выбираешь образ для видео? Есть ли у тебя какие-то символы или эмблемы? - Есть ли у тебя личные традиции в связи с праздниками, которые ты отмечаешь в контенте? - Какое место или обстановка являются наиболее вдохновляющими для твоего творчества? - Какие технологии ты используешь для взаимодействия со своей аудиторией вне платформы контента? - Каким образом ты измеряешь успех своих видео? Есть ли у тебя какие-то ключевые показатели? - Есть ли у тебя предпочтения по форматам видео? Например, интервью, обзоры, рассказы и т.д. - Какие темы или вызовы ты считаешь наиболее важными для обсуждения в сообществе контент-создателей? - Как ты подбираешь музыку для своих видео? Есть ли у тебя предпочтения в жанрах? - Есть ли у тебя какие-то необычные источники вдохновения для создания контента? - Какую реакцию от зрителей ты считаешь наиболее ценной? - Каким образом ты взаимодействуешь с сообществом своих зрителей вне платформы? - Какую тему или вызов ты считаешь самым интересным для дискуссии среди контент-создателей? - Как ты решаешь, какой контент будет наиболее интересен для твоей аудитории? - Каким образом ты планируешь свой контент? Используешь ли какие-то инструменты или систему? - Есть ли у тебя планы на развитие своего контента в будущем? - Твои зрители влияют на выбор темы? - Какой формат видео тебе нравится больше всего? - Сколько времени уходит на подготовку контента? - Как часто меняешь стиль монтажа? - Любимый способ взаимодействия с фанатами? - Что вдохновляет тебя кроме контента? - Есть ли у тебя регулярный график публикаций? - Как ты решаешь, что видео готово к публикации? - Что делаешь, чтобы перезарядить творческие энергии? - Ты предпочитаешь скрипт или импровизацию в видео? - Самый неожиданный вопрос от зрителей? - Есть ли у тебя свои личные мемы из видео? - Какую книгу или фильм ты рекомендуешь сейчас? - Какие темы тебе приходится избегать в контенте? - Твой самый успешный контент — это сюрприз или план? - Есть ли у тебя мантра или лозунг в творчестве? - Ты предпочитаешь снимать днем или ночью? - Что важнее: контент или вовлеченность аудитории? - Сколько времени занимает создание одного видео? - Твои друзья или семья участвуют в видеоинтересах? - Ты сначала придумываешь название или контент? - Как ты борешься с креативным стагнацией? - Что ты думаешь о тематических стримах? - Какие твои планы на следующий месяц? - Твои зрители влияют на твой стиль? - Что больше всего ценится в сообществе? - Ты больше любишь снимать один или в компании? - Какие инструменты ты используешь для аналитики? - Какую роль играет юмор в твоих видео? - Сколько времени уходит на ответы в комментариях? - Ты предпочитаешь темные или светлые темы? - Какие твои рекорды или достижения в контенте? - Какие тренды тебе не нравятся в мире контент-создания? - Твой самый большой творческий провал? - Как часто ты пересматриваешь свои старые видео? - Есть ли у тебя ритуалы перед началом стрима? - Что тебе нравится больше всего в процессе монтажа? - Ты делаешь паузы между проектами или работаешь постоянно? - Что было бы твоим идеальным видеоинструментом? - Какие темы ты избегаешь в своих видео? - Твой самый удачный эксперимент в контенте? - Как ты относишься к анонимной критике? - Как часто проверяешь комментарии под видео? - Ты делаешь влоги на ходу или сценаризируешь? - Есть ли у тебя неизведанные форматы видео, которые хотелось бы попробовать? - Что тебя мотивирует делать контент каждый день? - Как ты выбираешь обложку для своих видео? - Твой самый большой творческий вызов на данный момент? - Твои зрители предлагают идеи для видео? - Какова твоя цель на текущий год в контент-создании? - Твоя самая популярная тема видео? - Любимая камера для съемок? - Как часто стримишь в неделю? - Что тебя вдохновляет для контента? - Какова твоя цель на текущий месяц? - Самый интересный комментарий от зрителя? - Любимый вид контента для производства? - Твой секрет успешного взаимодействия с подписчиками? - Есть ли у тебя темы, которых ты избегаешь? - Твои зрители влияют на изменения в контенте? - Лучший момент, который ты запомнил из стрима? - Твоя основная идея за кадром? - Как часто ты вносишь новые элементы в контент? - Как ты выбираешь музыку для своих видео? - Твой самый ценный опыт в контент-создании? - Какова твоя мечта относительно своего контента? - Самый необычный запрос от зрителей? - Какие темы тебе интересны за пределами твоего канала? - Твой лучший совет для новичков в контент-создании? - Сколько времени уходит на ответы в комментариях? - Где? - Как? - Каво? - Каког? - Поч? - Почему? + + Как день? + Как дела? + Как настроение? + Что тебя вдохновляет? + Чем ты любишь заниматься в свободное время? + Какие у тебя хобби? + Какой последний сериал смотрел? + Какое последнее мероприятие или событие тебе запомнилось? + Что тебе больше всего нравится в вашей работе/учебе? + Как выходные? + Какая твоя супер способность? + Какой навык хотел бы освоить в ближайшем будущем? + Какой твой любимый сериал или фильм сейчас? + Слушал что-нибудь крутое недавно? + Какую музыку ты слушаешь? + Чем ты занимаешься по вечерам? + В какие игры ты любишь играть? + Что будешь делать после стрима? + Кинотеатр или дома на диване? + Какой последний интересный факт ты узнал? + Есть ли что-то, о чем ты мечтаешь? + Ты когда-нибудь был на концерте? + О чем ты мечтаешь? + Ждешь какой-нибудь релиз? + В каком фильме ты бы хотел сняться? + Посоветуй книгу + Посоветуй сериальчик + Посоветуй веселый фильм + Посоветуй добрый фильм + Куда бы ты отправился в путешествие? + Какую книгу ты считаешь должен прочитать каждый? + Где бы ты хотел жить? + Как ты расслабляешься? + Что тебя мотивирует? + Какой фильм или книга оказали на тебя самое большое впечатление? + Если бы ты мог выбрать любую профессию, не беспокоясь о деньгах, что бы это было? + Есть ли что-то, что ты всегда хотел попробовать? + Любимое время года + Какое событие оставило в твоей жизни наибольший след? + Какие три вещи ты бы взял с собой на необитаемый остров? + Хотел бы побывать в прошлом? + Ты любишь готовить? + Ты больше любить дарить или получать подарки? + Что делать в дождливый день? + Если бы ты мог изменить одно событие в своей жизни, что бы это было? + Опиши идеальный день + Какое твоё самое счастливое воспоминание из детства? + Что тебя больше всего мотивирует каждый день? + Что мотивирует тебя вставать по утрам? + Если бы ты мог получить ответ на любой вопрос, какой бы вопрос ты задал? + Что самое главное в жизни? + Кем ты хотел стать, когда вырастешь? + Какой твой любимый праздник? + Какое самое необычное блюдо ты когда-либо пробовал? + Какую страну или культуру ты находишь наиболее интересной и почему? + У тебя есть любимое место? + Есть цели на год? + Если бы ты мог выбрать любой талант, какой бы ты хотел иметь? + У тебя есть талисман? + Какой жанр музыки или фильмов ты считаешь наиболее недооценённым? + Какое твое любимое приложение? + Какой твой любимый сайт? + Ты любишь рисковать? + У тебя есть домашние животные? + Опиши себя тремя словами + Ты больше любишь лето или зиму? + Какой язык ты хотел бы знать? Почему? + Какое твое любимое место? + У тебя есть домашние растения? + Что тебе больше всего нравится в том месте, где ты живешь сейчас? + Расскажи про свои хобби + Если бы ты мог встретить любого персонажа из книги/фильма, кто бы это был? + Какой совет ты хотел бы дать каждому человеку на Земле? + Что ты предпочитаешь: приключение или отдых? + Ты жаворонок или сова? + Какой лучший подарок ты когда-либо получал? + Что ты думаешь о космических путешествиях? + Кофе или чай? + Книги или фильмы? + Собаки или кошки? + Пицца или суши? + Фильмы дома или в кино? + Ты любишь вечеринки? + Тебе больше нравятся фильмы или сериалы? + Какие жанры фильмов тебе нравятся? + Какие музыкальные жанры ты любишь? + Ты больше любишь горячий или холодный климат? + Ты любишь больше активный отдых или релакс? + Предпочитаешь ли ты сладкое или соленое? + Предпочитаешь ли ты планировать заранее или жить спонтанно? + Настольные игры или видеоигры? + Любишь ли ты больше проводить время в компании или наедине? + Ты любишь кофе? \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7a4f7e8..045ac2b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -10,6 +10,8 @@ LIVE %s.%sK Add comment… + Question + Allow %s to access your camera This lets you start the Live. You can change this anytime in your device settings. @@ -38,6 +40,11 @@ Questions No Questions Yet Anyone watching can send and see questions here. + Tap a Question to Answer + Answered questions + Everyone watching will see it on screen. + Delete + Report Direct Direct not available @@ -214,57 +221,100 @@ Relatable and authentic, your content feels like a genuine connection - - What inspired your latest video? - How do you stay creative and motivated? - Any exciting projects or collaborations? - Favorite part about creating content? - Behind-the-scenes moment from your latest video? - How do you handle creative challenges? - Future content topic or theme you\'re eager about? - Tips for aspiring content creators? - How do you engage with your audience? - Most rewarding aspect of being a content creator? - Any favorite tools or apps for content creation? - What\'s your go-to source of inspiration? - How do you balance creativity with consistency? - What\'s the most memorable comment you\'ve received? - Any upcoming events or milestones to celebrate? - How has your content evolved over time? - Do you have a specific routine before filming? - Any advice for overcoming creative blocks? - What platforms do you find most engaging for your content? - How do you decide on the topics for your videos? - Can you share a funny or unexpected moment during filming? - What\'s your favorite aspect of interacting with your audience? - How do you approach balancing work and personal life? - Do you have a dream project you\'d love to work on? - What\'s the most challenging part of content creation for you? - How do you choose music for your videos? - Is there a specific goal you\'re working towards currently? - Do you prefer scripted or spontaneous content? - How do you stay updated on trends in your content niche? - What impact do you hope your content has on viewers? - Your favorite video you\'ve ever made? - Preferred editing software? - How often do you post content? - Top tip for new creators? - Ideal collaboration partner? - Favorite camera for filming? - Biggest lesson in content creation? - Best moment with your audience? - Current favorite content trend? - Next big goal for your channel? - Your quick fix for creative blocks? - Top source for content ideas? - Preferred filming location? - Your favorite video genre to create? - How do you unwind after filming? - Most used social media platform? - Preferred video length? - Biggest challenge you\'ve overcome? - Any content creation rituals? - Favorite part of the editing process? + + How\'s your day? + How are you doing? + How are you feeling? + What inspires you? + What do you like to do in your free time? + What hobbies do you have? + What was the last series you watched? + What was the last event or occasion you remember? + What do you like most about your work/studies? + How was your weekend? + What\'s your superpower? + What skill would you like to learn in the near future? + What\'s your favorite series or movie right now? + Have you listened to anything cool recently? + What music do you listen to? + What do you do in the evenings? + What games do you like to play? + What will you do after the stream? + Cinema or couch at home? + What\'s the last interesting fact you learned? + Is there something you dream about? + Have you ever been to a concert? + What do you dream about? + Are you waiting for any releases? + In which movie would you like to act? + Recommend a book + Recommend a series + Recommend a fun movie + Recommend a kind movie + Where would you go on a trip? + What book do you think everyone should read? + Where would you like to live? + How do you relax? + What motivates you? + Which movie or book had the greatest impact on you? + If you could choose any profession without worrying about money, what would it be? + Is there something you\'ve always wanted to try? + Favorite season? + What event left the biggest mark on your life? + What three things would you take with you to a deserted island? + Would you like to visit the past? + Do you like to cook? + Do you prefer giving or receiving gifts? + What to do on a rainy day? + If you could change one event in your life, what would it be? + Describe the perfect day + What is your happiest childhood memory? + What motivates you the most every day? + What motivates you to get up in the morning? + If you could get an answer to any question, what would you ask? + What\'s the most important thing in life? + What did you want to be when you grew up? + What\'s your favorite holiday? + What\'s the most unusual dish you\'ve ever tried? + Which country or culture do you find most interesting and why? + Do you have a favorite place? + Do you have goals for the year? + If you could choose any talent, what would you want? + Do you have a talisman? + What genre of music or movies do you think is most underrated? + What\'s your favorite app? + What\'s your favorite website? + Do you like to take risks? + Do you have pets? + Describe yourself in three words + Do you prefer summer or winter? + What language would you like to know? Why? + What is your favorite place? + Do you have house plants? + What do you like most about the place where you live now? + Tell about your hobbies + If you could meet any character from a book/movie, who would it be? + What advice would you give to every person on Earth? + Do you prefer adventure or relaxation? + Are you a morning person or a night owl? + What\'s the best gift you\'ve ever received? + What do you think about space travel? + Coffee or tea? + Books or movies? + Dogs or cats? + Pizza or sushi? + Movies at home or in the cinema? + Do you like parties? + Do you prefer movies or series? + What movie genres do you like? + What music genres do you like? + Do you prefer hot or cold weather? + Do you prefer active leisure or relaxation? + Do you prefer sweet or salty? + Do you prefer to plan in advance or live spontaneously? + Board games or video games? + Do you prefer to spend time in company or alone? + Do you like coffee? \ No newline at end of file diff --git a/app/src/video/res/raw/video.MP4 b/app/src/video/res/raw/video.MP4 new file mode 100644 index 0000000..ab0cb17 Binary files /dev/null and b/app/src/video/res/raw/video.MP4 differ diff --git a/app/src/video/res/raw/video.mp4 b/app/src/video/res/raw/video.mp4 deleted file mode 100644 index 9414a2a..0000000 Binary files a/app/src/video/res/raw/video.mp4 and /dev/null differ