Skip to content

Commit

Permalink
#17 in progress - result, with movie
Browse files Browse the repository at this point in the history
  • Loading branch information
mjureczko committed May 2, 2024
1 parent c50842a commit d116c52
Show file tree
Hide file tree
Showing 9 changed files with 317 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import android.content.res.AssetManager
import pl.marianjureczko.poszukiwacz.shared.StorageHelper
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream

class CustomInitializerForRoute(
val storageHelper: StorageHelper,
Expand All @@ -13,33 +12,35 @@ class CustomInitializerForRoute(
companion object {
const val routeName = "custom"
}

private val markerFile = "copied_marker.txt"
private val pathToDestination = storageHelper.pathToRoutesDir() + File.separator
fun copyRouteToLocalStorage() {
if (!isAlreadyCopied()) {
copyRouteDefinition()
copyPhotosAndSounds()
copyMedia()
markIsCopied()
}
}

private fun copyRouteDefinition() {
val inputStream = assetManager.open("$routeName.xml")
copy(inputStream, storageHelper.getRouteFile(routeName))
copy("$routeName.xml", storageHelper.getRouteFile(routeName))
}

private fun copyPhotosAndSounds() {
private fun copyMedia() {
val route = storageHelper.loadRoute(routeName)
route.treasures.forEach { td ->
td.photoFileName?.let { photoFileName ->
val inputToPhoto = assetManager.open(photoFileName)
copy(inputToPhoto, File(pathToDestination + photoFileName))
td.photoFileName = pathToDestination + photoFileName
td.photoFileName = copy(photoFileName, File(pathToDestination + photoFileName))
}
td.tipFileName?.let { soundFileName ->
val inputToSound = assetManager.open(soundFileName)
copy(inputToSound, File(pathToDestination + soundFileName))
td.tipFileName = pathToDestination + soundFileName
td.tipFileName = copy(soundFileName, File(pathToDestination + soundFileName))
}
td.movieFileName?.let { movieFileName ->
td.movieFileName = copy(movieFileName, File(pathToDestination + movieFileName))
}
td.subtitlesFileName?.let { subtitlesFileName ->
td.subtitlesFileName = copy(subtitlesFileName, File(pathToDestination + subtitlesFileName))
}
}
storageHelper.save(route)
Expand All @@ -53,12 +54,14 @@ class CustomInitializerForRoute(
File(pathToDestination + markerFile).createNewFile()
}

private fun copy(inputStream: InputStream, outputFile: File) {
private fun copy(assetFileName: String, outputFile: File): String {
val inputStream = assetManager.open(assetFileName)
val outputStream = FileOutputStream(outputFile)
inputStream.use { input ->
outputStream.use { output ->
input.copyTo(output)
}
}
return pathToDestination + assetFileName
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,37 @@
package pl.marianjureczko.poszukiwacz.activity.result.n

import android.annotation.SuppressLint
import android.media.MediaPlayer.TrackInfo
import android.widget.VideoView
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavController
Expand All @@ -26,6 +44,8 @@ import pl.marianjureczko.poszukiwacz.ui.shareViewModelStoreOwner
import pl.marianjureczko.poszukiwacz.ui.theme.FANCY_FONT
import pl.marianjureczko.poszukiwacz.ui.theme.SecondaryBackground

private const val SUBTITLES_MIME_TYPE = "application/x-subrip"

@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
@Composable
fun ResultScreen(
Expand All @@ -44,32 +64,26 @@ fun ResultScreen(
@Composable
fun ResultScreenBody(viewModelStoreOwner: NavBackStackEntry) {
val localViewModel: ResultViewModel = hiltViewModel()
val localState = localViewModel.state.value
val localState: ResultState = localViewModel.state.value
val sharedViewModel: ResultSharedViewModel = getViewModel(viewModelStoreOwner)
sharedViewModel.resultPresented()
val snackbarCoroutineScope = rememberCoroutineScope()
Column(Modifier.background(SecondaryBackground)) {
if (localState.resultType == ResultType.TREASURE) {
//TODO
Spacer(
modifier = Modifier
.weight(0.01f)
.background(Color.Transparent)
)
if (localState.resultType == ResultType.TREASURE && localState.moviePath != null) {
Movie(
localState.isPlayVisible,
localViewModel,
localState.moviePath,
localState.subtitlesLine,
localState.subtitlesPath
) { localViewModel.setSubtitlesLine(it) }
} else {
Spacer(
modifier = Modifier
.weight(0.01f)
.background(Color.Transparent)
)
val text = if (localState.resultType == ResultType.NOT_A_TREASURE) {
stringResource(R.string.not_a_treasure_msg)
} else {
stringResource(R.string.treasure_already_taken_msg)
}
Text(
fontSize = 60.sp,
fontWeight = FontWeight.Bold,
fontFamily = FANCY_FONT,
color = Color.Gray,
textAlign = TextAlign.Center,
text = text
)
Message(localState)
}
Spacer(
modifier = Modifier
Expand All @@ -80,6 +94,136 @@ fun ResultScreenBody(viewModelStoreOwner: NavBackStackEntry) {
}
}

@Composable
private fun Message(localState: ResultState) {
val text = if (localState.resultType == ResultType.NOT_A_TREASURE) {
stringResource(R.string.not_a_treasure_msg)
} else {
stringResource(R.string.treasure_already_taken_msg)
}
Text(
fontSize = 60.sp,
fontWeight = FontWeight.Bold,
fontFamily = FANCY_FONT,
color = Color.Gray,
textAlign = TextAlign.Center,
text = text
)
}

@Composable
@Preview(showBackground = true, apiLevel = 31)
private fun Movie(
isPlayVisible: Boolean = true,
movieController: MovieController = object : MovieController {
override fun onPlay() {}
override fun onPause() {}
override fun onMovieFinished() {}
},
moviePath: String = "/data/user/0/pl.marianjureczko.poszukiwacz.kalinowice/files/treasures_lists/kalinowice_01.mp4",
subtitlesLine: String? = null,
subtitlesPath: String? = null,
updateSubtitlesLine: (String) -> Unit = {}
) {
Column(
modifier = Modifier.fillMaxHeight(0.95F)
) {
Box(
modifier = Modifier
.fillMaxSize()
.weight(0.9f),
contentAlignment = Alignment.Center
) {
val context = LocalContext.current
val videoView: VideoView = remember { VideoView(context) }
Video(videoView, subtitlesPath, updateSubtitlesLine, movieController, moviePath)
PlayButton(isPlayVisible, videoView, movieController)
subtitlesLine?.let {
Text(
fontSize = 45.sp,
fontFamily = FANCY_FONT,
color = Color.White,
textAlign = TextAlign.Center,
text = it,
modifier = Modifier.align(Alignment.BottomCenter),
style = TextStyle(shadow = Shadow(color = Color.Black, blurRadius = 12f))
)
}
}
}
}

@Composable
private fun Video(
videoView: VideoView,
subtitlesPath: String?,
updateSubtitlesLine: (String) -> Unit,
movieController: MovieController,
moviePath: String
) {
videoView.setOnPreparedListener { mediaPlayer ->
subtitlesPath?.let {
mediaPlayer.addTimedTextSource(subtitlesPath, SUBTITLES_MIME_TYPE)
val textTrackIndex: Int = findTrackIndexFor(
TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT, mediaPlayer.getTrackInfo()
)
if (textTrackIndex >= 0) {
mediaPlayer.selectTrack(textTrackIndex);
}
mediaPlayer.setOnTimedTextListener { _, text -> updateSubtitlesLine(text.text) }
}
}
AndroidView(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 10.dp, vertical = 10.dp)
.clickable {
videoView.pause()
movieController.onPause()
},
factory = { _ ->
videoView.apply {
setVideoPath(moviePath)
setOnCompletionListener {
movieController.onMovieFinished()
videoView.seekTo(0)
}
videoView.seekTo(1)
}
})
}

@Composable
private fun PlayButton(
isPlayVisible: Boolean,
videoView: VideoView,
movieController: MovieController
) {
if (isPlayVisible) {
Image(
painterResource(R.drawable.play),
modifier = Modifier
.padding(10.dp)
.clickable {
videoView.start()
movieController.onPlay()
},
contentDescription = "Play the movie",
contentScale = ContentScale.Inside,
)
}
}

private fun findTrackIndexFor(mediaTrackType: Int, trackInfo: Array<TrackInfo>): Int {
val index = -1
for (i in trackInfo.indices) {
if (trackInfo[i].trackType == mediaTrackType) {
return i
}
}
return index
}

@Composable
private fun getViewModel(viewModelStoreOwner: NavBackStackEntry): ResultSharedViewModel {
val viewModelDoNotInline: SharedViewModel = hiltViewModel(viewModelStoreOwner)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ data class ResultState(
val resultType: ResultType,
val treasureType: TreasureType?,
val amount: Int?,
val moviePath: String?
val moviePath: String?,
val subtitlesLine: String?,
val subtitlesPath: String?,
val localesWithSubtitles: Boolean = false,
val isPlayVisible: Boolean = true
)

enum class ResultType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,62 @@ import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import pl.marianjureczko.poszukiwacz.activity.main.CustomInitializerForRoute
import pl.marianjureczko.poszukiwacz.shared.StorageHelper
import java.util.Locale
import javax.inject.Inject

const val PARAMETER_RESULT_TYPE = "result_type"
const val PARAMETER_TREASURE_ID = "treasure_id"
const val NOTHING_FOUND_TREASURE_ID = -1

interface MovieController {
fun onPlay()
fun onPause()
fun onMovieFinished()
}

@HiltViewModel
class ResultViewModel @Inject constructor(
private val stateHandle: SavedStateHandle,
) : ViewModel() {
private val storageHelper: StorageHelper
) : ViewModel(), MovieController {

private var _state: MutableState<ResultState> = mutableStateOf(createState())

val state: State<ResultState>
get() = _state

override fun onPlay() {
_state.value = _state.value.copy(isPlayVisible = false)
}

override fun onPause() {
_state.value = _state.value.copy(isPlayVisible = true)
}

override fun onMovieFinished() {
_state.value = _state.value.copy(isPlayVisible = true)
}

fun setSubtitlesLine(line: String?) {
_state.value = _state.value.copy(subtitlesLine = line)
}

private fun createState(): ResultState {
var moviePath: String? = null
var subtitlesPath: String? = null
//TODO: conditional for custom version
val treasureDescId = stateHandle.get<Int>(PARAMETER_TREASURE_ID) ?: NOTHING_FOUND_TREASURE_ID
if (NOTHING_FOUND_TREASURE_ID != treasureDescId) {
val treasureDescription = storageHelper.loadRoute(CustomInitializerForRoute.routeName).treasures
.find { it.id == treasureDescId }
moviePath = treasureDescription?.movieFileName
subtitlesPath = treasureDescription?.subtitlesFileName
}
val localesWithSubtitles = !"pl".equals(Locale.getDefault().language, true)
val resultType = stateHandle.get<ResultType>(PARAMETER_RESULT_TYPE) ?: ResultType.NOT_A_TREASURE
return ResultState(resultType, null, null, null)
return ResultState(resultType, null, null, moviePath, null, subtitlesPath, localesWithSubtitles)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ data class TreasureDescription(
@field:Element(required = false) var qrCode: String?,
@field:Element(required = false) var tipFileName: String?,
@field:Element(required = false) var photoFileName: String?,
@field:Element(required = false) var movieFileName: String?
@field:Element(required = false) var movieFileName: String?,
@field:Element(required = false) var subtitlesFileName: String?
) : Serializable {
constructor(id: Int, latitude: Double, longitude: Double) : this(id, latitude, longitude, null, null, null, null)
constructor() : this(0, 0.0, 0.0, null, null, null, null)
constructor(id: Int, latitude: Double, longitude: Double) : this(id, latitude, longitude, null, null, null, null, null)
constructor() : this(0, 0.0, 0.0, null, null, null, null, null)

fun prettyName(): String = "[$id] $latitude $longitude"

Expand Down
Loading

0 comments on commit d116c52

Please sign in to comment.