Skip to content

Commit

Permalink
#17 in progress - commemorative
Browse files Browse the repository at this point in the history
  • Loading branch information
mjureczko committed Apr 19, 2024
1 parent 1abbe44 commit 1b7bc04
Show file tree
Hide file tree
Showing 19 changed files with 382 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import pl.marianjureczko.poszukiwacz.R
import pl.marianjureczko.poszukiwacz.activity.facebook.FacebookInputData
import pl.marianjureczko.poszukiwacz.databinding.ActivityCommemorativeBinding
import pl.marianjureczko.poszukiwacz.model.TreasuresProgress
import pl.marianjureczko.poszukiwacz.shared.ActivityWithAdsAndBackButton
Expand Down Expand Up @@ -53,7 +52,7 @@ class CommemorativeActivity : ActivityWithAdsAndBackButton() {
rotatePhoto(input.photoAbsolutePath, model.commemorativePhotoUri())
}
binding.doPhotoBtn.setOnClickListener {
doPhotoLauncher.launch(photoHelper.createCommemorativePhotoTempUri())
doPhotoLauncher.launch(photoHelper.getCommemorativePhotoTempUri())
}
setContentView(binding.root)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package pl.marianjureczko.poszukiwacz.activity.commemorative.n

import android.annotation.SuppressLint
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
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.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Scaffold
import androidx.compose.material.ScaffoldState
import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavController
import pl.marianjureczko.poszukiwacz.App
import pl.marianjureczko.poszukiwacz.R
import pl.marianjureczko.poszukiwacz.activity.searching.n.CommemorativeSharedState
import pl.marianjureczko.poszukiwacz.activity.searching.n.CommemorativeSharedViewModel
import pl.marianjureczko.poszukiwacz.activity.searching.n.SharedViewModel
import pl.marianjureczko.poszukiwacz.ui.components.AdvertBanner
import pl.marianjureczko.poszukiwacz.ui.components.TopBar
import pl.marianjureczko.poszukiwacz.ui.shareViewModelStoreOwner
import pl.marianjureczko.poszukiwacz.ui.theme.SecondaryBackground

@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
@Composable
fun CommemorativeScreen(
navController: NavController,
navBackStackEntry: NavBackStackEntry,
onClickOnGuide: () -> Unit,
) {
val scaffoldState: ScaffoldState = rememberScaffoldState()
Scaffold(
scaffoldState = scaffoldState,
topBar = { TopBar(navController, onClickOnGuide) },
content = {
CommemorativeScreenBody(
navController,
shareViewModelStoreOwner(navBackStackEntry, navController),
scaffoldState
)
}
)
}

@Composable
fun CommemorativeScreenBody(
navController: NavController,
viewModelStoreOwner: NavBackStackEntry,
scaffoldState: ScaffoldState
) {
val sharedViewModel: CommemorativeSharedViewModel = getViewModel(viewModelStoreOwner)
val sharedState = sharedViewModel.state.value as CommemorativeSharedState
val localViewModel: CommemorativeViewModel = hiltViewModel()
val localState: CommemorativeState = localViewModel.state.value
localViewModel.setPhotoPath(
sharedState.treasuresProgress.commemorativePhotosByTreasuresDescriptionIds[localState.treasureDesId]!!
)
Column(Modifier.background(SecondaryBackground)) {
Spacer(
modifier = Modifier
.weight(0.01f)
.background(Color.Transparent)
)
if (localState.photoPath != null) {
val photo: Bitmap = BitmapFactory.decodeFile(localState.photoPath)
val aspectRatio = photo.width.toFloat() / photo.height.toFloat()
Box(
modifier = Modifier
.fillMaxSize()
.weight(0.9f),
contentAlignment = Alignment.BottomEnd
) {
Image(
painter = BitmapPainter(photo.asImageBitmap()),
contentDescription = "Photo from the hunt",
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
.align(Alignment.Center)
.aspectRatio(aspectRatio),
contentScale = ContentScale.FillBounds,
)
DoPhotoButton(sharedViewModel, sharedState, localState)
Image(
painter = painterResource(id = R.drawable.rotate_arc),
contentDescription = "Rotate commemorative photo",
modifier = Modifier
.height(50.dp)
.offset(x = (-10).dp, y = (-70).dp)
.clickable { localViewModel.rotatePhoto() },
contentScale = ContentScale.Inside
)
}
}
Spacer(
modifier = Modifier
.weight(0.01f)
.background(Color.Transparent)
)
AdvertBanner()
}
}

@Composable
private fun DoPhotoButton(
sharedViewModel: CommemorativeSharedViewModel,
sharedState: CommemorativeSharedState,
localState: CommemorativeState
) {
val successMsg = stringResource(R.string.photo_replaced)
val failureMsg = stringResource(R.string.photo_not_replaced)
val cameraLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.TakePicture(),
onResult = { success ->
if (success) {
Toast.makeText(App.getAppContext(), successMsg, Toast.LENGTH_SHORT).show()
sharedViewModel.handleDoCommemorativePhotoResult(
sharedState.route.treasures.find { it.id == localState.treasureDesId }!!
)()
} else {
Toast.makeText(App.getAppContext(), failureMsg, Toast.LENGTH_SHORT).show()
}
}
)
Image(
painter = painterResource(id = R.drawable.camera_do_photo),
contentDescription = "Do a new commemorative photo",
modifier = Modifier
.height(50.dp)
.offset(x = (-10).dp, y = (-10).dp)
.clickable { cameraLauncher.launch(localState.tempPhotoFileLocation) },
contentScale = ContentScale.Inside
)
}

@Composable
private fun getViewModel(viewModelStoreOwner: NavBackStackEntry): CommemorativeSharedViewModel {
val viewModelDoNotInline: SharedViewModel = hiltViewModel(viewModelStoreOwner)
return viewModelDoNotInline
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package pl.marianjureczko.poszukiwacz.activity.commemorative.n

import android.net.Uri

data class CommemorativeState(
val treasureDesId: Int,
val tempPhotoFileLocation: Uri,
val photoPath: String?
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package pl.marianjureczko.poszukiwacz.activity.commemorative.n

import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import pl.marianjureczko.poszukiwacz.shared.PhotoHelper
import javax.inject.Inject

const val PARAMETER_TREASURE_DESCRIPTION_ID = "treasure_description_id"

@HiltViewModel
class CommemorativeViewModel @Inject constructor(
private val stateHandle: SavedStateHandle,
private val photoHelper: PhotoHelper
) : ViewModel() {
private val TAG = javaClass.simpleName
private var _state: MutableState<CommemorativeState> = mutableStateOf(createState())

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

fun setPhotoPath(photoPath: String) {
if (photoPath != state.value.photoPath) {
_state.value = _state.value.copy(
photoPath = photoPath
)
}
}

fun rotatePhoto() {
if(state.value.photoPath != null) {
viewModelScope.launch {
PhotoHelper.rotateGraphicClockwise(state.value.photoPath!!) {
// refresh view
_state.value = _state.value.copy()
}
}
}
}

private fun createState(): CommemorativeState {
return CommemorativeState(stateHandle.get<Int>(
PARAMETER_TREASURE_DESCRIPTION_ID)!!,
photoHelper.getCommemorativePhotoTempUri(),
null)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import dagger.hilt.android.AndroidEntryPoint
import pl.marianjureczko.poszukiwacz.R
import pl.marianjureczko.poszukiwacz.activity.commemorative.n.CommemorativeScreen
import pl.marianjureczko.poszukiwacz.activity.commemorative.n.PARAMETER_TREASURE_DESCRIPTION_ID
import pl.marianjureczko.poszukiwacz.activity.map.n.MapScreen
import pl.marianjureczko.poszukiwacz.activity.map.n.PARAMETER_ROUTE_NAME_2
import pl.marianjureczko.poszukiwacz.activity.photo.n.PARAMETER_TIP_PHOTO
Expand All @@ -40,6 +42,8 @@ val RESULTS_PATH = "result"
val RESULTS_ROUTE = "$RESULTS_PATH/{$PARAMETER_RESULT_TYPE}/{$PARAMETER_TREASURE_ID}"
val SELECTOR_PATH = "selector"
val SELECTOR_ROUTE = "$SELECTOR_PATH/{$PARAMETER_JUST_FOUND_TREASURE}"
val COMMEMORATIVE_PATH = "commemorative"
val COMMEMORATIVE_ROUTE = "$COMMEMORATIVE_PATH/{$PARAMETER_TREASURE_DESCRIPTION_ID}"

/**
* Routes creation and selection activity
Expand Down Expand Up @@ -123,7 +127,7 @@ private fun ComposeRoot(settings: Settings, resources: Resources, onClickGuide:
navArgument(PARAMETER_RESULT_TYPE) { type = NavType.EnumType(ResultType::class.java) },
navArgument(PARAMETER_TREASURE_ID) { type = NavType.IntType }
)
) { navBackStackEntry -> ResultScreen(navController, navBackStackEntry, resources, onClickGuide) }
) { navBackStackEntry -> ResultScreen(navController, navBackStackEntry, onClickGuide) }
composable(
route = "tipPhoto/{$PARAMETER_TIP_PHOTO}",
arguments = listOf(navArgument(PARAMETER_TIP_PHOTO) { type = NavType.StringType })
Expand All @@ -144,7 +148,12 @@ private fun ComposeRoot(settings: Settings, resources: Resources, onClickGuide:
navBackStackEntry,
resources,
onClickGuide,
goToResult = { treasureId -> navController.navigate("$RESULTS_PATH/${ResultType.TREASURE}/$treasureId") }
goToResult = { treasureId -> navController.navigate("$RESULTS_PATH/${ResultType.TREASURE}/$treasureId") },
goToCommemorative = {treasureId -> navController.navigate("$COMMEMORATIVE_PATH/$treasureId")}
) }
composable(
route = COMMEMORATIVE_ROUTE,
arguments = listOf(navArgument(PARAMETER_TREASURE_DESCRIPTION_ID) { type = NavType.IntType })
) { navBackStackEntry -> CommemorativeScreen(navController, navBackStackEntry, onClickGuide) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.activity.ComponentActivity
import pl.marianjureczko.poszukiwacz.permissions.ActivityRequirements
import pl.marianjureczko.poszukiwacz.permissions.PermissionListener
import pl.marianjureczko.poszukiwacz.permissions.PermissionManager
import pl.marianjureczko.poszukiwacz.permissions.RequirementsForDoingPhoto
import pl.marianjureczko.poszukiwacz.permissions.RequirementsForNavigation

abstract class PermissionActivity : ComponentActivity() {
Expand Down Expand Up @@ -42,6 +43,9 @@ abstract class PermissionActivity : ComponentActivity() {
}
permissionManager = PermissionManager(permissionListener)
assurePermissionsAreGranted(RequirementsForNavigation, true)
//TODO: exitOnDenied==true and then exitOnDenied==false presumably leads to race condition
assurePermissionsAreGranted(RequirementsForDoingPhoto, false)
// assurePermissionsAreGranted(RequirementsForExternalStorage, false)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class ResultActivity : ActivityWithAdsAndBackButton() {
} else {
binding.doPhoto.setImageResource(R.drawable.camera_do_photo)
binding.doPhoto.setOnClickListener {
doPhotoLauncher.launch(photoHelper.createCommemorativePhotoTempUri())
doPhotoLauncher.launch(photoHelper.getCommemorativePhotoTempUri())
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package pl.marianjureczko.poszukiwacz.activity.result.n

import android.annotation.SuppressLint
import android.content.res.Resources
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
Expand All @@ -11,6 +10,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.sp
Expand All @@ -31,19 +31,18 @@ import pl.marianjureczko.poszukiwacz.ui.theme.SecondaryBackground
fun ResultScreen(
navController: NavController,
navBackStackEntry: NavBackStackEntry,
resources: Resources,
onClickOnGuide: () -> Unit
) {
Scaffold(
topBar = { TopBar(navController, onClickOnGuide) },
content = {
ResultScreenBody(resources, shareViewModelStoreOwner(navBackStackEntry, navController))
ResultScreenBody(shareViewModelStoreOwner(navBackStackEntry, navController))
}
)
}

@Composable
fun ResultScreenBody(resources: Resources, viewModelStoreOwner: NavBackStackEntry) {
fun ResultScreenBody(viewModelStoreOwner: NavBackStackEntry) {
val localViewModel: ResultViewModel = hiltViewModel()
val localState = localViewModel.state.value
val sharedViewModel: ResultSharedViewModel = getViewModel(viewModelStoreOwner)
Expand All @@ -59,9 +58,9 @@ fun ResultScreenBody(resources: Resources, viewModelStoreOwner: NavBackStackEntr
.background(Color.Transparent)
)
val text = if (localState.resultType == ResultType.NOT_A_TREASURE) {
resources.getString(R.string.not_a_treasure_msg)
stringResource(R.string.not_a_treasure_msg)
} else {
resources.getString(R.string.treasure_already_taken_msg)
stringResource(R.string.treasure_already_taken_msg)
}
Text(
fontSize = 60.sp,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,12 @@ import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
import com.google.android.gms.location.LocationServices
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlin.coroutines.suspendCoroutine


class LocationFetcher(val context: Context, val dispatcher: CoroutineDispatcher) {
class LocationFetcher(val context: Context) {

private val TAG = javaClass.simpleName
private lateinit var fusedLocationClient: FusedLocationProviderClient
Expand All @@ -32,7 +31,7 @@ class LocationFetcher(val context: Context, val dispatcher: CoroutineDispatcher)

fun startFetching(interval: Long, viewModelScope: CoroutineScope, updateLocationCallback: (Location) -> Unit) {
this.updateLocationCallback = updateLocationCallback
viewModelScope.launch(dispatcher) {
viewModelScope.launch {
requestLocation(interval)
}
}
Expand Down
Loading

0 comments on commit 1b7bc04

Please sign in to comment.