diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 545f375..9431e03 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -56,7 +56,9 @@ + android:exported="false" + android:theme="@style/FullscreenTheme" + /> + android:label="@string/photo_tip" + android:theme="@style/FullscreenTheme" + /> + android:theme="@style/AppTheme" /> () { - companion object { - private val xmlHelper = XmlHelper() - } - override fun createIntent(context: Context, input: FacebookInputData): Intent { return Intent(context, FacebookActivity::class.java).apply { - putExtra(FacebookActivity.TREASURE_PROGRESS, xmlHelper.writeToString(input.progress)) + putExtra(FacebookActivity.INPUT, input) } } diff --git a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/facebook/FacebookViewModel.kt b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/facebook/FacebookViewModel.kt index 684d647..386068e 100644 --- a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/facebook/FacebookViewModel.kt +++ b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/facebook/FacebookViewModel.kt @@ -4,6 +4,7 @@ import android.content.Context import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import pl.marianjureczko.poszukiwacz.R +import pl.marianjureczko.poszukiwacz.model.HunterPath import pl.marianjureczko.poszukiwacz.model.Route import pl.marianjureczko.poszukiwacz.model.TreasuresProgress import pl.marianjureczko.poszukiwacz.shared.StorageHelper @@ -12,13 +13,16 @@ class FacebookViewModel(private val state: SavedStateHandle) : ViewModel() { private val TAG = javaClass.simpleName lateinit var progress: TreasuresProgress private set + var hunterPath: HunterPath? = null + private set lateinit var elements: List private set lateinit var route: Route - fun initialize(progress: TreasuresProgress, context: Context) { + fun initialize(context: Context, hunterPath: HunterPath?, progress: TreasuresProgress) { this.progress = progress + this.hunterPath = hunterPath val elements = mutableListOf() elements.add(ElementDescription(Type.TREASURES_SUMMARY, true, context.getString(R.string.collected_treasures))) val treasure = context.getString(R.string.treasure) diff --git a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/facebook/ReportMap.kt b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/facebook/ReportMap.kt index 43c2816..1810a1a 100644 --- a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/facebook/ReportMap.kt +++ b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/facebook/ReportMap.kt @@ -62,19 +62,22 @@ class ReportMap( } private fun drawRoute(snapshot: MapSnapshotInterface, mapCanvas: Canvas) { - val locations = model.progress.hunterPath.pathAsCoordinates().toList() - if (locations.size > 1) { - var previousXY = snapshot.screenCoordinate(Point.fromLngLat(locations[0].longitude, locations[0].latitude)) - locations.asSequence() - .drop(1) - .forEach { - val xy = snapshot.screenCoordinate(Point.fromLngLat(it.longitude, it.latitude)) - mapCanvas.drawLine( - previousXY.x.toFloat(), previousXY.y.toFloat(), xy.x.toFloat(), xy.y.toFloat(), - Paint().apply { color = Color.RED } - ) - previousXY = xy - } + model.hunterPath?.let { + val locations = it.pathAsCoordinates().toList() + if (locations.size > 1) { + var previousXY = + snapshot.screenCoordinate(Point.fromLngLat(locations[0].longitude, locations[0].latitude)) + locations.asSequence() + .drop(1) + .forEach { + val xy = snapshot.screenCoordinate(Point.fromLngLat(it.longitude, it.latitude)) + mapCanvas.drawLine( + previousXY.x.toFloat(), previousXY.y.toFloat(), xy.x.toFloat(), xy.y.toFloat(), + Paint().apply { color = Color.RED } + ) + previousXY = xy + } + } } } diff --git a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/facebook/ReportMapSummary.kt b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/facebook/ReportMapSummary.kt index 4eb7da5..ebb080b 100644 --- a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/facebook/ReportMapSummary.kt +++ b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/facebook/ReportMapSummary.kt @@ -29,9 +29,9 @@ class ReportMapSummary( val textPaint = ReportCommons.getTextPaint(font, Paint.Align.LEFT) var textY = currentTop + 50 var x = ReportCommons.REPORT_MARGIN - canvas.drawText(distanceText(context, model.progress.hunterPath), x, textY, textPaint) + canvas.drawText(distanceText(context, model.hunterPath), x, textY, textPaint) textY += 40 - canvas.drawText(timeText(context, model.progress.hunterPath), x, textY, textPaint) + canvas.drawText(timeText(context, model.hunterPath), x, textY, textPaint) } } @@ -42,17 +42,18 @@ class ReportMapSummary( context.getResources().getConfiguration().locale } - private fun distanceText(context: Context, hunterPath: HunterPath): String { - val formattedDistance = "%.2f".format(hunterPath.pathLengthInKm()) + private fun distanceText(context: Context, hunterPath: HunterPath?): String { + val distance = hunterPath?.pathLengthInKm() ?: 0.0 + val formattedDistance = "%.2f".format(distance) return "${context.getString(R.string.walked_route)} $formattedDistance km." } - private fun timeText(context: Context, hunterPath: HunterPath): String { + private fun timeText(context: Context, hunterPath: HunterPath?): String { val loc = Locale("en", "US") val timeFormat = DateFormat.getTimeInstance(DateFormat.SHORT, loc) val dateFormat: DateFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, loc) - hunterPath.getStartTime()?.let { start -> + hunterPath?.getStartTime()?.let { start -> val startDate = dateFormat.format(start) val startTime = timeFormat.format(start) hunterPath.getEndTime()?.let { end -> diff --git a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/main/MainActivity.kt b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/main/MainActivity.kt index 1c6e000..aaac273 100644 --- a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/main/MainActivity.kt +++ b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/main/MainActivity.kt @@ -23,7 +23,7 @@ import pl.marianjureczko.poszukiwacz.shared.StorageHelper class MainActivity : PermissionActivity() { private val TAG = javaClass.simpleName - override fun getCurrentTreasuresProgress(): TreasuresProgress? { + override fun getTreasureProgress(): TreasuresProgress? { return null } diff --git a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/map/MapActivity.kt b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/map/MapActivity.kt index 81ae7a3..b8263f8 100644 --- a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/map/MapActivity.kt +++ b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/map/MapActivity.kt @@ -41,7 +41,7 @@ class MapActivity : ActivityWithAdsAndBackButton() { setUpAds(binding.adView) } - override fun getCurrentTreasuresProgress(): TreasuresProgress? = model.progress + override fun getTreasureProgress(): TreasuresProgress? = model.progress override fun onResume() { super.onResume() diff --git a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/photo/PhotoActivity.kt b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/photo/PhotoActivity.kt index ff617ff..c299ed9 100644 --- a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/photo/PhotoActivity.kt +++ b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/photo/PhotoActivity.kt @@ -33,7 +33,7 @@ class PhotoActivity : ActivityWithAdsAndBackButton() { setUpAds(binding.adView) } - override fun getCurrentTreasuresProgress(): TreasuresProgress = + override fun getTreasureProgress(): TreasuresProgress = model.progress } \ No newline at end of file diff --git a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/result/ResultActivity.kt b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/result/ResultActivity.kt index 2b1f02c..bed83bc 100644 --- a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/result/ResultActivity.kt +++ b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/result/ResultActivity.kt @@ -2,20 +2,49 @@ package pl.marianjureczko.poszukiwacz.activity.result import android.app.Activity import android.content.pm.ActivityInfo +import android.net.Uri import android.os.Bundle import android.view.View +import android.widget.Toast +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.activity.viewModels +import androidx.core.view.isVisible import pl.marianjureczko.poszukiwacz.R +import pl.marianjureczko.poszukiwacz.activity.commemorative.CommemorativeContract +import pl.marianjureczko.poszukiwacz.activity.commemorative.CommemorativeInputData import pl.marianjureczko.poszukiwacz.databinding.ActivityResultBinding import pl.marianjureczko.poszukiwacz.model.TreasuresProgress import pl.marianjureczko.poszukiwacz.shared.ActivityWithAdsAndBackButton +import pl.marianjureczko.poszukiwacz.shared.PhotoHelper +import pl.marianjureczko.poszukiwacz.shared.StorageHelper class ResultActivity : ActivityWithAdsAndBackButton() { companion object { - const val RESULT = "pl.marianjureczko.poszukiwacz.activity.result"; + const val RESULT_IN = "pl.marianjureczko.poszukiwacz.activity.result_in" + const val RESULT_OUT = "pl.marianjureczko.poszukiwacz.activity.result_out" } + private val model: ResultActivityViewModel by viewModels() private lateinit var binding: ActivityResultBinding + private val storageHelper = StorageHelper(this) + private val photoHelper = PhotoHelper(this, storageHelper) + + private val showCommemorativeLauncher: ActivityResultLauncher = + registerForActivityResult(CommemorativeContract()) {} + + //TODO t: copied from CommemorativeActivity + private val doPhotoLauncher: ActivityResultLauncher = + registerForActivityResult(ActivityResultContracts.TakePicture()) { result -> + if (result) { + model.addCommemorativePhoto(storageHelper, photoHelper.moveCommemorativePhotoToPermanentLocation()) + configureDoPhotoButton() + Toast.makeText(this, R.string.photo_saved, Toast.LENGTH_SHORT).show() + } else { + Toast.makeText(this, R.string.photo_not_replaced, Toast.LENGTH_SHORT).show() + } + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -24,24 +53,51 @@ class ResultActivity : ActivityWithAdsAndBackButton() { binding = ActivityResultBinding.inflate(layoutInflater) requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - val input = intent.getSerializableExtra(RESULT) as ResultActivityData - if (input.isError()) { - binding.resultDescription.text = input.error + val input = intent.getSerializableExtra(RESULT_IN) as ResultActivityInput + model.initialize(this, storageHelper, input.treasure, input.progress, input.treasureDescription) + + configureView() + setContentView(binding.root) + setUpAds(binding.adView) + } + + private fun configureView() { + if (model.isError()) { + binding.resultDescription.text = model.errorMsg binding.resultImg.visibility = View.GONE } else { - binding.resultDescription.text = input.treasure!!.quantity.toString() - binding.resultImg.setImageResource(input.treasure.type.image()) + binding.resultDescription.text = model.treasure!!.quantity.toString() + binding.resultImg.setImageResource(model.treasure!!.type.image()) + } + + if (model.canMakeCommemorativePhoto()) { + configureDoPhotoButton() + } else { + binding.buttonsLayout.isVisible = false + } + } + + private fun configureDoPhotoButton() { + binding.buttonsLayout.isVisible = true + if (model.currentTreasureHasCommemorativePhoto()) { + binding.doPhoto.setImageResource(R.drawable.camera_show_photo) + binding.doPhoto.setOnClickListener { + model.commemorativeInputData()?.let { showCommemorativeLauncher.launch(it) } + } + } else { + binding.doPhoto.setImageResource(R.drawable.camera_do_photo) + binding.doPhoto.setOnClickListener { + doPhotoLauncher.launch(photoHelper.createCommemorativePhotoTempUri()) + } } - setContentView(binding.root) - setUpAds(binding.adView) } - override fun getCurrentTreasuresProgress(): TreasuresProgress? { + override fun getTreasureProgress(): TreasuresProgress? { return null } override fun onBackPressed() { - setResult(Activity.RESULT_OK, intent) + setResult(Activity.RESULT_OK, model.activityResult()) super.onBackPressed() } } \ No newline at end of file diff --git a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/result/ResultActivityContract.kt b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/result/ResultActivityContract.kt index eba5630..6390380 100644 --- a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/result/ResultActivityContract.kt +++ b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/result/ResultActivityContract.kt @@ -4,14 +4,14 @@ import android.content.Context import android.content.Intent import androidx.activity.result.contract.ActivityResultContract -class ResultActivityContract : ActivityResultContract() { - override fun createIntent(context: Context, input: ResultActivityData?): Intent { +class ResultActivityContract : ActivityResultContract() { + override fun createIntent(context: Context, input: ResultActivityInput?): Intent { return Intent(context, ResultActivity::class.java).apply { - putExtra(ResultActivity.RESULT, input) + putExtra(ResultActivity.RESULT_IN, input) } } - override fun parseResult(resultCode: Int, intent: Intent?): ResultActivityData? { - return intent?.getSerializableExtra(ResultActivity.RESULT) as ResultActivityData? + override fun parseResult(resultCode: Int, intent: Intent?): ResultActivityOutput { + return intent?.getSerializableExtra(ResultActivity.RESULT_OUT) as ResultActivityOutput } } \ No newline at end of file diff --git a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/result/ResultActivityData.kt b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/result/ResultActivityData.kt deleted file mode 100644 index d8b1da7..0000000 --- a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/result/ResultActivityData.kt +++ /dev/null @@ -1,11 +0,0 @@ -package pl.marianjureczko.poszukiwacz.activity.result - -import pl.marianjureczko.poszukiwacz.model.Treasure -import java.io.Serializable - -data class ResultActivityData(val treasure: Treasure?, val error: String?) : Serializable { - constructor(treasure: Treasure?) : this(treasure, null) - constructor(error: String?) : this(null, error) - - fun isError(): Boolean = error != null -} \ No newline at end of file diff --git a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/result/ResultActivityInput.kt b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/result/ResultActivityInput.kt new file mode 100644 index 0000000..2e4d42b --- /dev/null +++ b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/result/ResultActivityInput.kt @@ -0,0 +1,19 @@ +package pl.marianjureczko.poszukiwacz.activity.result + +import pl.marianjureczko.poszukiwacz.model.Treasure +import pl.marianjureczko.poszukiwacz.model.TreasureDescription +import pl.marianjureczko.poszukiwacz.model.TreasuresProgress +import java.io.Serializable + +//TODO t: remove ? +data class ResultActivityInput( + val treasure: Treasure?, + val treasureDescription: TreasureDescription?, + val progress: TreasuresProgress? +) : Serializable + +data class ResultActivityOutput( + val progress: TreasuresProgress?, + val justFoundTreasureDescription: TreasureDescription?, + val newTreasureCollected: Boolean +) : Serializable \ No newline at end of file diff --git a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/result/ResultActivityViewModel.kt b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/result/ResultActivityViewModel.kt new file mode 100644 index 0000000..2abbc81 --- /dev/null +++ b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/result/ResultActivityViewModel.kt @@ -0,0 +1,114 @@ +package pl.marianjureczko.poszukiwacz.activity.result + +import android.content.Intent +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import pl.marianjureczko.poszukiwacz.R +import pl.marianjureczko.poszukiwacz.activity.commemorative.CommemorativeInputData +import pl.marianjureczko.poszukiwacz.model.Treasure +import pl.marianjureczko.poszukiwacz.model.TreasureDescription +import pl.marianjureczko.poszukiwacz.model.TreasuresProgress +import pl.marianjureczko.poszukiwacz.shared.StorageHelper + +class ResultActivityViewModel(private val state: SavedStateHandle) : ViewModel() { + + companion object { + const val ERROR_MSG = "errorMsg" + const val PROGRESS = "progress" + } + + var treasure: Treasure? = null + private set + var progress: TreasuresProgress? + get() = state.get(PROGRESS) + private set(value) = state.set(PROGRESS, value) + private var currentTreasureDescription: TreasureDescription? = null + var errorMsg: String? + get() = state.get(ERROR_MSG) + private set(value) = state.set(ERROR_MSG, value) + + /** + * Checks what kind of treasure was found and adds it to the progress if it's a mew one. + * If there nothing to add, sets an appropriate error message in errMsg. + */ + fun initialize( + app: AppCompatActivity, + storageHelper: StorageHelper, + treasure: Treasure?, + progress: TreasuresProgress?, + currentTreasureDescription: TreasureDescription? + ) { + this.treasure = treasure + this.progress = progress + this.currentTreasureDescription = currentTreasureDescription + + if (treasure == null) { + errorMsg = app.resources.getString(R.string.not_a_treasure_msg) + } else { + val p = this.progress + p?.let { + if (it.contains(treasure)) { + errorMsg = app.resources.getString(R.string.treasure_already_taken_msg) + } else { + it.collect(treasure) + if (currentTreasureDescription != null) { + it.collect(currentTreasureDescription) + } + // to save the updated progress in the quasi persistent state + this.progress = it + storageHelper.save(it) + } + } + } + } + + fun addCommemorativePhoto(storageHelper: StorageHelper, commemorativePhotoAbsolutePath: String) { + currentTreasureDescription?.let { + progress?.let { + it.addCommemorativePhoto(currentTreasureDescription!!, commemorativePhotoAbsolutePath) + storageHelper.save(it) + // to save the updated progress in the quasi persistent state + progress = it + } + } + } + + fun isError(): Boolean = this.errorMsg != null + + fun canMakeCommemorativePhoto(): Boolean = !isError() && currentTreasureDescription != null + + fun currentTreasureHasCommemorativePhoto(): Boolean = + if (progress != null && currentTreasureDescription != null) { + progress!!.getCommemorativePhoto(currentTreasureDescription!!) != null + } else { + false + } + + fun commemorativeInputData(): CommemorativeInputData? { + if (!currentTreasureHasCommemorativePhoto()) { + return null + } + return CommemorativeInputData( + progress!!.getCommemorativePhoto(currentTreasureDescription!!)!!, + progress!! + ) + + } + + fun activityResult(): Intent { + val data = Intent() + progress?.let { + data.putExtra( + ResultActivity.RESULT_OUT, + ResultActivityOutput(progress, currentTreasureDescription, !isError()) + ) + } + return data + } +} \ No newline at end of file diff --git a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/searching/ChangeTreasureButtonListener.kt b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/searching/ChangeTreasureButtonListener.kt index 4ae778f..e8564b8 100644 --- a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/searching/ChangeTreasureButtonListener.kt +++ b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/searching/ChangeTreasureButtonListener.kt @@ -4,9 +4,10 @@ import android.view.View import androidx.activity.result.ActivityResultLauncher import pl.marianjureczko.poszukiwacz.activity.treasureselector.SelectTreasureInputData import pl.marianjureczko.poszukiwacz.model.Treasure +import pl.marianjureczko.poszukiwacz.model.TreasureDescription interface TreasuresStorage { - fun getTreasureSelectorActivityInputData(justFoundTreasure: Treasure?): SelectTreasureInputData + fun getTreasureSelectorActivityInputData(justFoundTreasureDesc: TreasureDescription?): SelectTreasureInputData } class ChangeTreasureButtonListener ( diff --git a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/searching/JustFoundFinder.kt b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/searching/JustFoundFinder.kt new file mode 100644 index 0000000..378fde0 --- /dev/null +++ b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/searching/JustFoundFinder.kt @@ -0,0 +1,34 @@ +package pl.marianjureczko.poszukiwacz.activity.searching + +import android.util.Log +import pl.marianjureczko.poszukiwacz.activity.treasureselector.Coordinates +import pl.marianjureczko.poszukiwacz.model.Treasure +import pl.marianjureczko.poszukiwacz.model.TreasureDescription + +/** + * To find data about the "just found" by user treasure + */ +class JustFoundFinder( + /**justFoundTreasure shall be used for app version with fixed treasures locations*/ + val justFoundTreasure: Treasure?, + val selectedTreasureDescription: TreasureDescription?, + val userLocation: Coordinates?, + val locationCalculator: LocationCalculator = LocationCalculator() +) { + private val TAG = javaClass.simpleName + + fun findTreasureDescription(): TreasureDescription? { + return if (justFoundTreasure != null && selectedTreasureDescription != null && userLocation != null) { + val distance = locationCalculator.distanceInSteps(selectedTreasureDescription, userLocation) + Log.i(TAG, "Distance is $distance") + if (distance < 60) { + selectedTreasureDescription + } else { + null + } + } else { + null + } + } +} + diff --git a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/searching/SearchingActivity.kt b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/searching/SearchingActivity.kt index 2d97a54..9925409 100644 --- a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/searching/SearchingActivity.kt +++ b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/searching/SearchingActivity.kt @@ -14,7 +14,8 @@ import pl.marianjureczko.poszukiwacz.R import pl.marianjureczko.poszukiwacz.activity.map.MapActivityContract import pl.marianjureczko.poszukiwacz.activity.map.MapInputData import pl.marianjureczko.poszukiwacz.activity.result.ResultActivityContract -import pl.marianjureczko.poszukiwacz.activity.result.ResultActivityData +import pl.marianjureczko.poszukiwacz.activity.result.ResultActivityInput +import pl.marianjureczko.poszukiwacz.activity.result.ResultActivityOutput import pl.marianjureczko.poszukiwacz.activity.treasureselector.SelectTreasureContract import pl.marianjureczko.poszukiwacz.activity.treasureselector.SelectTreasureInputData import pl.marianjureczko.poszukiwacz.activity.treasureselector.SelectTreasureOutputData @@ -46,7 +47,7 @@ class SearchingActivity : ActivityWithAdsAndBackButton() { private val model: SearchingActivityViewModel by viewModels() private lateinit var binding: ActivitySearchingBinding private lateinit var treasureSelectorLauncher: ActivityResultLauncher - private lateinit var showResultLauncher: ActivityResultLauncher + private lateinit var showResultLauncher: ActivityResultLauncher private lateinit var showMapLauncher: ActivityResultLauncher @SuppressLint("SourceLockedOrientationActivity") @@ -57,7 +58,12 @@ class SearchingActivity : ActivityWithAdsAndBackButton() { setContentView(R.layout.activity_searching) restoreState() - binding.scanBtn.setOnClickListener(ScanButtonListener(createScanTreasureLauncher(), resources.getString(R.string.qr_scanner_msg))) + binding.scanBtn.setOnClickListener( + ScanButtonListener( + createScanTreasureLauncher(), + resources.getString(R.string.qr_scanner_msg) + ) + ) treasureSelectorLauncher = createSelectTreasureLauncher() showResultLauncher = createShowResultLauncher() showMapLauncher = createShowMapLauncher() @@ -72,15 +78,16 @@ class SearchingActivity : ActivityWithAdsAndBackButton() { storageHelper ) val handler = Handler() - val locationRequester = LocationRequester(this, locationListener, handler, getSystemService(LOCATION_SERVICE) as LocationManager) + val locationRequester = + LocationRequester(this, locationListener, handler, getSystemService(LOCATION_SERVICE) as LocationManager) handler.post(locationRequester) setContentView(binding.root) setUpAds(binding.adView) } - override fun getCurrentTreasuresProgress(): TreasuresProgress? { - return model.getTreasureBag() + override fun getTreasureProgress(): TreasuresProgress? { + return model.getProgress() } override fun onPostResume() { @@ -100,25 +107,15 @@ class SearchingActivity : ActivityWithAdsAndBackButton() { showCollectedTreasures() } - private fun processSearchingResult(result: String): ResultActivityData { - try { + private fun processSearchingResult(result: String): ResultActivityInput { + return try { val treasure: Treasure = treasureParser.parse(result) - return if (model.treasureIsAlreadyCollected(treasure)) { - ResultActivityData(resources.getString(R.string.treasure_already_taken_msg)) - } else { - add(treasure) - ResultActivityData(treasure) - } + ResultActivityInput(treasure, model.tryToFindTreasureDescription(treasure), model.getTreasuresProgress()) } catch (ex: IllegalArgumentException) { - return ResultActivityData(resources.getString(R.string.not_a_treasure_msg)) + ResultActivityInput(null, null, model.getTreasuresProgress()) } } - private fun add(treasure: Treasure) { - model.collectTreasure(treasure, storageHelper) - showCollectedTreasures() - } - private fun showCollectedTreasures() { binding.goldTxt.text = model.getGolds() binding.rubyTxt.text = model.getRubies() @@ -129,21 +126,24 @@ class SearchingActivity : ActivityWithAdsAndBackButton() { registerForActivityResult(ScanContract()) { scanResult -> if (scanResult != null && scanResult.contents != null) { showResultLauncher.launch(processSearchingResult(scanResult.contents)) + //TODO t: treasure progress need to be reloaded when receiving activity result } } private fun createSelectTreasureLauncher(): ActivityResultLauncher = registerForActivityResult(SelectTreasureContract()) { result: SelectTreasureOutputData? -> if (result != null) { - model.replaceTreasureBag(result.progress, storageHelper) + model.replaceProgress(result.progress, storageHelper) } } - private fun createShowResultLauncher(): ActivityResultLauncher = - registerForActivityResult(ResultActivityContract()) { result: ResultActivityData? -> + private fun createShowResultLauncher(): ActivityResultLauncher = + registerForActivityResult(ResultActivityContract()) { result: ResultActivityOutput? -> result?.let { - if (!it.isError()) { - treasureSelectorLauncher.launch(model.getTreasureSelectorActivityInputData(it.treasure)) + model.loadProgressFromStorage(storageHelper) + showCollectedTreasures() + if (it.newTreasureCollected) { + treasureSelectorLauncher.launch(model.getTreasureSelectorActivityInputData(it.justFoundTreasureDescription)) } } } diff --git a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/searching/SearchingActivityViewModel.kt b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/searching/SearchingActivityViewModel.kt index 1b57699..edeb08d 100644 --- a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/searching/SearchingActivityViewModel.kt +++ b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/searching/SearchingActivityViewModel.kt @@ -25,6 +25,7 @@ class SearchingActivityViewModel(private val state: SavedStateHandle) : ViewMode private val TAG = javaClass.simpleName private val xmlHelper = XmlHelper() private lateinit var route: Route + //TODO: state should be employed as property is mutable private lateinit var treasuresProgress: TreasuresProgress private var currentLocation: Location? = null private var currentCoordinates: Coordinates? = state.get(CURRENT_COORDINATES) @@ -33,46 +34,50 @@ class SearchingActivityViewModel(private val state: SavedStateHandle) : ViewMode field = value state[TREASURE_SELECTION_INITIALIZED] = value } + //TODO t: how it corresponds with reloading progress? private var hunterPath: HunterPath? = deserializeHunterPath(state.get(PATH)) private set(value) { if (value != null) { field = value state[PATH] = xmlHelper.writeToString(value) - treasuresProgress.hunterPath = value } } private var mediaPlayer: MediaPlayer? = null + fun initialize(routeXml: String, storageHelper: StorageHelper) { + route = xmlHelper.loadFromString(routeXml) + loadProgressFromStorage(storageHelper) + } + override fun getSelectedForHuntTreasure(): TreasureDescription? { return treasuresProgress.selectedTreasure } - override fun getTreasureSelectorActivityInputData(justFoundTreasure: Treasure?): SelectTreasureInputData { + override fun getTreasureSelectorActivityInputData(justFoundTreasureDesc: TreasureDescription?): SelectTreasureInputData { treasureSelectionInitialized = true - return SelectTreasureInputData(route, treasuresProgress, currentCoordinates, justFoundTreasure) + return SelectTreasureInputData(route, treasuresProgress, currentCoordinates, justFoundTreasureDesc) } + fun tryToFindTreasureDescription(justFoundTreasure: Treasure?) = + JustFoundFinder(justFoundTreasure, getSelectedForHuntTreasure(), currentCoordinates) + .findTreasureDescription() + override fun setCurrentLocation(location: Location?, storageHelper: StorageHelper) { currentLocation = location location?.let { currentCoordinates = Coordinates(it.latitude, it.longitude) state[CURRENT_COORDINATES] = currentCoordinates - if (treasuresProgress.hunterPath!!.addLocation(currentCoordinates!!)) { - storageHelper.save(this.treasuresProgress) + val hp = hunterPath!! + if (hp.addLocation(currentCoordinates!!)) { + storageHelper.save(hp) } //to call setter - hunterPath = treasuresProgress.hunterPath + hunterPath = hp } } override fun getTreasuresProgress(): TreasuresProgress = treasuresProgress - fun initialize(routeXml: String, storageHelper: StorageHelper) { - route = xmlHelper.loadFromString(routeXml) - treasuresProgress = storageHelper.loadProgress(route.name) ?: TreasuresProgress(route.name) - hunterPath = treasuresProgress.hunterPath - } - override fun tipName(): String? = treasuresProgress.selectedTreasure?.tipFileName @@ -91,9 +96,6 @@ class SearchingActivityViewModel(private val state: SavedStateHandle) : ViewMode fun treasureSelectionInitialized() = treasureSelectionInitialized || treasuresProgress.selectedTreasure != null - fun treasureIsAlreadyCollected(treasure: Treasure): Boolean = - treasuresProgress.contains(treasure) - fun collectTreasure(treasure: Treasure, storageHelper: StorageHelper) { treasuresProgress.collect(treasure) storageHelper.save(this.treasuresProgress) @@ -108,14 +110,28 @@ class SearchingActivityViewModel(private val state: SavedStateHandle) : ViewMode fun getDiamonds(): String = treasuresProgress.diamonds.toString() - fun replaceTreasureBag(treasuresProgress: TreasuresProgress, storageHelper: StorageHelper) { + fun replaceProgress(treasuresProgress: TreasuresProgress, storageHelper: StorageHelper) { this.treasuresProgress = treasuresProgress storageHelper.save(this.treasuresProgress) } + fun loadProgressFromStorage(storageHelper: StorageHelper) { + var loadedProgress = storageHelper.loadProgress(route.name) + if (loadedProgress == null) { + loadedProgress = TreasuresProgress(route.name) + storageHelper.save(loadedProgress) + } + treasuresProgress = loadedProgress + hunterPath = storageHelper.loadHunterPath(route.name) + if(hunterPath == null) { + hunterPath = HunterPath(route.name) + storageHelper.save(hunterPath!!) + } + } + //visibility for tests internal fun getRoute() = route - internal fun getTreasureBag() = treasuresProgress + internal fun getProgress() = treasuresProgress private fun handleMediaPlayerError(what: Int, extra: Int): Boolean { when (what) { diff --git a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/treasureseditor/TreasuresEditorActivity.kt b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/treasureseditor/TreasuresEditorActivity.kt index e15ed10..b2cc446 100644 --- a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/treasureseditor/TreasuresEditorActivity.kt +++ b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/treasureseditor/TreasuresEditorActivity.kt @@ -45,7 +45,7 @@ class TreasuresEditorActivity : PermissionActivity(), RouteNameDialog.Callback, } private val TAG = javaClass.simpleName - override fun getCurrentTreasuresProgress(): TreasuresProgress? { + override fun getTreasureProgress(): TreasuresProgress? { return null } diff --git a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/treasureselector/SelectTreasureContract.kt b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/treasureselector/SelectTreasureContract.kt index 35ef664..0da28d2 100644 --- a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/treasureselector/SelectTreasureContract.kt +++ b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/treasureselector/SelectTreasureContract.kt @@ -6,6 +6,7 @@ import android.content.Intent import androidx.activity.result.contract.ActivityResultContract import pl.marianjureczko.poszukiwacz.model.Route import pl.marianjureczko.poszukiwacz.model.Treasure +import pl.marianjureczko.poszukiwacz.model.TreasureDescription import pl.marianjureczko.poszukiwacz.model.TreasuresProgress import pl.marianjureczko.poszukiwacz.shared.XmlHelper import java.io.Serializable @@ -14,7 +15,7 @@ data class SelectTreasureInputData( val route: Route, val progress: TreasuresProgress, val currentCoordinates: Coordinates?, - val justFoundTreasure: Treasure? + val justFoundTreasureDescription: TreasureDescription? ) : Serializable data class SelectTreasureOutputData( @@ -28,8 +29,8 @@ class SelectTreasureContract : ActivityResultContract>(IDS_OF_COLLECTED)?.let { this.progress.collectedTreasuresDescriptionId.clear() this.progress.collectedTreasuresDescriptionId.addAll(it) @@ -110,18 +113,6 @@ class SelectorViewModel(private val state: SavedStateHandle) : ViewModel() { } } - fun getUserLocation(): Coordinates? = - userLocation?.copy() - - fun getJustFound(): Treasure? = - justFoundTreasure + fun treasureDescriptionHasBeenIdentified(): Boolean = this.justFoundTreasureDescription != null - fun treasureIsNotFarAwayFromUser(): Boolean = - if (getSelectedTreasure() != null && getUserLocation() != null) { - val distance = LocationCalculator().distanceInSteps(getSelectedTreasure()!!, getUserLocation()!!) - Log.i(TAG, "Distance is $distance") - distance < 60; - } else { - false - } } \ No newline at end of file diff --git a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/treasureselector/TreasureProgressHolder.kt b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/treasureselector/TreasureProgressHolder.kt index 75d0c9e..34d58a3 100644 --- a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/treasureselector/TreasureProgressHolder.kt +++ b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/treasureselector/TreasureProgressHolder.kt @@ -35,7 +35,7 @@ class TreasureProgressHolder( private val photoHelper = PhotoHelper(context, storageHelper) fun setup(treasure: TreasureDescription, commemorativePhoto: String?) { - if (model.isCollected(treasure)) { + if (model.isCollected(treasure) && treasure != model.justFoundTreasureDescription) { showCollected() } else { showNotCollected() @@ -66,7 +66,6 @@ class TreasureProgressHolder( doPhotoLauncher.launch(photoHelper.createCommemorativePhotoTempUri()) } else { showCommemorativeLauncher.launch(CommemorativeInputData(commemorativePhoto, model.progress)) -// context.startActivity(CommemorativeActivity.intent(context, commemorativePhoto)) } } } diff --git a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/treasureselector/TreasureSelectorActivity.kt b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/treasureselector/TreasureSelectorActivity.kt index 5544850..b5ccdc4 100644 --- a/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/treasureselector/TreasureSelectorActivity.kt +++ b/app/src/main/java/pl/marianjureczko/poszukiwacz/activity/treasureselector/TreasureSelectorActivity.kt @@ -18,7 +18,7 @@ import pl.marianjureczko.poszukiwacz.activity.commemorative.CommemorativeContrac import pl.marianjureczko.poszukiwacz.activity.commemorative.CommemorativeInputData import pl.marianjureczko.poszukiwacz.databinding.ActivityTreasureSelectorBinding import pl.marianjureczko.poszukiwacz.model.Route -import pl.marianjureczko.poszukiwacz.model.Treasure +import pl.marianjureczko.poszukiwacz.model.TreasureDescription import pl.marianjureczko.poszukiwacz.model.TreasuresProgress import pl.marianjureczko.poszukiwacz.permissions.ActivityRequirements import pl.marianjureczko.poszukiwacz.permissions.PermissionActivity @@ -34,33 +34,35 @@ import pl.marianjureczko.poszukiwacz.shared.XmlHelper class TreasureSelectorActivity : PermissionActivity(), ActivityTerminator { private val TAG = javaClass.simpleName - override fun getCurrentTreasuresProgress(): TreasuresProgress? { + override fun getTreasureProgress(): TreasuresProgress? { return model.progress } private lateinit var binding: ActivityTreasureSelectorBinding private val model: SelectorViewModel by viewModels() private lateinit var adapter: TreasureProgressAdapter - private val storageHelper = StorageHelper(this) - private val photoHelper = PhotoHelper(this, storageHelper) - private val doPhotoLauncher: ActivityResultLauncher = registerForActivityResult(ActivityResultContracts.TakePicture()) { result -> - if (result) { - val newPhotoLocation = photoHelper.moveCommemorativePhotoToPermanentLocation() - model.setCommemorativePhotoOnSelectedTreasureDescription(newPhotoLocation) - adapter.notifyDataSetChanged() - Toast.makeText(this, R.string.photo_saved, Toast.LENGTH_SHORT).show() - } else { - Toast.makeText(this, R.string.photo_not_saved, Toast.LENGTH_SHORT).show() + private val photoHelper = PhotoHelper(this, StorageHelper(this)) + private val doPhotoLauncher: ActivityResultLauncher = + registerForActivityResult(ActivityResultContracts.TakePicture()) { result -> + if (result) { + val newPhotoLocation = photoHelper.moveCommemorativePhotoToPermanentLocation() + model.setCommemorativePhotoOnSelectedTreasureDescription(newPhotoLocation) + adapter.notifyDataSetChanged() + Toast.makeText(this, R.string.photo_saved, Toast.LENGTH_SHORT).show() + } else { + Toast.makeText(this, R.string.photo_not_saved, Toast.LENGTH_SHORT).show() + } } - } - private val commemorativeLauncher: ActivityResultLauncher = registerForActivityResult(CommemorativeContract()) {} + private val commemorativeLauncher: ActivityResultLauncher = + registerForActivityResult(CommemorativeContract()) {} companion object { const val RESULT_PROGRESS = "pl.marianjureczko.poszukiwacz.activity.treasure_selector_result_progress" internal const val ROUTE = "pl.marianjureczko.poszukiwacz.activity.route_to_select_from" internal const val PROGRESS = "pl.marianjureczko.poszukiwacz.activity.route_progress" internal const val LOCATION = "pl.marianjureczko.poszukiwacz.activity.user_coordinates" - internal const val TREASURE = "pl.marianjureczko.poszukiwacz.activity.treasure_selector_treasure" + internal const val TREASURE_DESCRIPTION = + "pl.marianjureczko.poszukiwacz.activity.treasure_selector_treasure_description" private val xmlHelper = XmlHelper() } @@ -79,7 +81,7 @@ class TreasureSelectorActivity : PermissionActivity(), ActivityTerminator { route = xmlHelper.loadFromString(intent.getStringExtra(ROUTE)!!), progress = xmlHelper.loadFromString(intent.getStringExtra(PROGRESS)!!), userLocation = intent.getSerializableExtra(LOCATION) as Coordinates?, - justFound = intent.getSerializableExtra(TREASURE) as Treasure? + justFoundTreasureDescription = intent.getSerializableExtra(TREASURE_DESCRIPTION) as TreasureDescription? ) adapter = TreasureProgressAdapter(this, model, this, doPhotoLauncher, commemorativeLauncher) binding.treasuresToSelect.adapter = adapter @@ -106,16 +108,14 @@ class TreasureSelectorActivity : PermissionActivity(), ActivityTerminator { } private fun markTreasureIfFound() = - model.getJustFound()?.let { - if (model.treasureIsNotFarAwayFromUser()) { - model.collect(model.getSelectedTreasure()!!) - adapter.notifyDataSetChanged() - val id = model.getSelectedTreasure()!!.id.toString() - Toast.makeText(this, this.getString(R.string.treasure_marked_as_collected, id), Toast.LENGTH_LONG).show() - } else { - //TODO: in case the toast is too quick - https://www.geeksforgeeks.org/display-toast-for-a-specific-time-in-android/ - Toast.makeText(this, R.string.treasure_nor_marked, Toast.LENGTH_LONG).show() - } + if (model.treasureDescriptionHasBeenIdentified()) { + val id = model.justFoundTreasureDescription!!.id.toString() + Toast.makeText(this, this.getString(R.string.treasure_marked_as_collected, id), Toast.LENGTH_LONG).show() + model.justFoundTreasureDescription = null + adapter.notifyDataSetChanged() + } else { + //TODO: in case the toast is too quick - https://www.geeksforgeeks.org/display-toast-for-a-specific-time-in-android/ + Toast.makeText(this, R.string.treasure_nor_marked, Toast.LENGTH_LONG).show() } private fun intentResultWithProgress(): Intent { diff --git a/app/src/main/java/pl/marianjureczko/poszukiwacz/model/HunterPath.kt b/app/src/main/java/pl/marianjureczko/poszukiwacz/model/HunterPath.kt index bcd84be..b9f9bed 100644 --- a/app/src/main/java/pl/marianjureczko/poszukiwacz/model/HunterPath.kt +++ b/app/src/main/java/pl/marianjureczko/poszukiwacz/model/HunterPath.kt @@ -3,12 +3,21 @@ package pl.marianjureczko.poszukiwacz.model import org.apache.commons.math3.stat.StatUtils import org.simpleframework.xml.Element import org.simpleframework.xml.ElementList +import org.simpleframework.xml.Root import pl.marianjureczko.poszukiwacz.activity.searching.LocationCalculator import pl.marianjureczko.poszukiwacz.activity.treasureselector.Coordinates import java.io.Serializable import java.util.Date -class HunterPath : Serializable { +@Root +class HunterPath() : Serializable { + + constructor(routeName: String) : this() { + this.routeName = routeName + } + + @field:Element + lateinit var routeName: String /** * Measurements for the next chunk. When te chunk creation criteria are met, the measurements are used to produce the chunk and are remove. diff --git a/app/src/main/java/pl/marianjureczko/poszukiwacz/model/TreasuresProgress.kt b/app/src/main/java/pl/marianjureczko/poszukiwacz/model/TreasuresProgress.kt index dbed2fa..a3494a2 100644 --- a/app/src/main/java/pl/marianjureczko/poszukiwacz/model/TreasuresProgress.kt +++ b/app/src/main/java/pl/marianjureczko/poszukiwacz/model/TreasuresProgress.kt @@ -44,10 +44,6 @@ class TreasuresProgress() : Serializable { @field:Element(required = false) var selectedTreasure: TreasureDescription? = null - /** set is required for restoring state */ - @field:Element - var hunterPath = HunterPath() - fun contains(treasure: Treasure): Boolean = collectedQrCodes.contains(treasure.id) diff --git a/app/src/main/java/pl/marianjureczko/poszukiwacz/shared/ActivityWithAdsAndBackButton.kt b/app/src/main/java/pl/marianjureczko/poszukiwacz/shared/ActivityWithAdsAndBackButton.kt index cb31243..d1fce12 100644 --- a/app/src/main/java/pl/marianjureczko/poszukiwacz/shared/ActivityWithAdsAndBackButton.kt +++ b/app/src/main/java/pl/marianjureczko/poszukiwacz/shared/ActivityWithAdsAndBackButton.kt @@ -58,8 +58,8 @@ abstract class ActivityWithAdsAndBackButton : AppCompatActivity(), SelectTreasur val url = this.getString(R.string.help_path) startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) } else if (id == R.id.facebook) { - if (getCurrentTreasuresProgress() != null) { - facebookLauncher.launch(FacebookInputData(getCurrentTreasuresProgress()!!)) + if (getTreasureProgress() != null) { + facebookLauncher.launch(createFacebookInputData(getTreasureProgress()!!)) } else { val progresses = storageHelper.loadAll() .mapNotNull { route -> storageHelper.loadProgress(route.name) } @@ -68,7 +68,7 @@ abstract class ActivityWithAdsAndBackButton : AppCompatActivity(), SelectTreasur Toast.makeText(this, R.string.facebook_nothing_to_share, Toast.LENGTH_SHORT).show() } else { if (progresses.size == 1) { - facebookLauncher.launch(FacebookInputData(progresses[0])) + facebookLauncher.launch(createFacebookInputData(progresses[0])) } else { SelectTreasureProgressDialog.newInstance(progresses).apply { show(supportFragmentManager, "SelectTreasureProgressDialog") @@ -80,16 +80,20 @@ abstract class ActivityWithAdsAndBackButton : AppCompatActivity(), SelectTreasur return super.onOptionsItemSelected(item) } + private fun createFacebookInputData(treasureProgress: TreasuresProgress): FacebookInputData { + return FacebookInputData(storageHelper.loadHunterPath(treasureProgress.routeName), treasureProgress) + } + override fun onTreasureProgressSelected(routeName: String) { val progresses = storageHelper.loadProgress(routeName) if (progresses == null) { Toast.makeText(this, R.string.facebook_invalid_roote, Toast.LENGTH_SHORT).show() } else { - facebookLauncher.launch(FacebookInputData(progresses)) + facebookLauncher.launch(createFacebookInputData(progresses)) } } - protected abstract fun getCurrentTreasuresProgress(): TreasuresProgress? + protected abstract fun getTreasureProgress(): TreasuresProgress? private fun createShareOnFacebookLauncher(): ActivityResultLauncher = registerForActivityResult(FacebookContract()) { result: FacebookOutputData? -> diff --git a/app/src/main/java/pl/marianjureczko/poszukiwacz/shared/StorageHelper.kt b/app/src/main/java/pl/marianjureczko/poszukiwacz/shared/StorageHelper.kt index adba42d..2acbbbf 100644 --- a/app/src/main/java/pl/marianjureczko/poszukiwacz/shared/StorageHelper.kt +++ b/app/src/main/java/pl/marianjureczko/poszukiwacz/shared/StorageHelper.kt @@ -3,6 +3,7 @@ package pl.marianjureczko.poszukiwacz.shared import android.content.Context import android.util.Log import org.apache.commons.io.IOUtils +import pl.marianjureczko.poszukiwacz.model.HunterPath import pl.marianjureczko.poszukiwacz.model.Route import pl.marianjureczko.poszukiwacz.model.TreasureDescription import pl.marianjureczko.poszukiwacz.model.TreasuresProgress @@ -26,6 +27,7 @@ open class StorageHelper(val context: Context) { companion object { const val routesDirectory = "/treasures_lists" const val progressDirectory = "/progress" + const val hunterPathsDirectory = "/huner_paths" } fun newSoundFile(): String = newFile("sound_", ".3gp") @@ -34,9 +36,14 @@ open class StorageHelper(val context: Context) { fun newCommemorativePhotoFile(): String = newFile("commemorativephoto_", ".jpg") - fun save(bag: TreasuresProgress) { - val file = getProgressFile(bag.routeName) - xmlHelper.writeToFile(bag, file) + fun save(progress: TreasuresProgress) { + val file = getProgressFile(progress.routeName) + xmlHelper.writeToFile(progress, file) + } + + fun save(hunterPath: HunterPath) { + val file = getHunterPathFile(hunterPath.routeName) + xmlHelper.writeToFile(hunterPath, file) } fun loadProgress(routeName: String): TreasuresProgress? { @@ -53,6 +60,20 @@ open class StorageHelper(val context: Context) { } } + fun loadHunterPath(routeName: String): HunterPath? { + val file = getHunterPathFile(routeName) + return if (file.exists()) { + try { + xmlHelper.loadHunterPathFromFile(file) + } catch (e: Exception) { + Log.e(TAG, e.message, e) + null + } + } else { + null + } + } + fun save(route: Route) { val xmlFile = getRouteFile(route.fileName()) xmlHelper.writeToFile(route, xmlFile) @@ -192,7 +213,14 @@ open class StorageHelper(val context: Context) { return File("${dir.absolutePath}/$routeName.xml") } + //TODO t: code duplication with getProgressFile (possibly also in save) + private fun getHunterPathFile(routeName: String): File { + val dir = getHunterPathsDir() + return File("${dir.absolutePath}/$routeName.xml") + } + private fun getProgressDir(): File = getDir(progressDirectory) + private fun getHunterPathsDir(): File = getDir(hunterPathsDirectory) private fun getDir(dirName: String): File { val dir = File(context.filesDir.absolutePath + dirName) diff --git a/app/src/main/java/pl/marianjureczko/poszukiwacz/shared/XmlHelper.kt b/app/src/main/java/pl/marianjureczko/poszukiwacz/shared/XmlHelper.kt index 14cbcc3..54a8763 100644 --- a/app/src/main/java/pl/marianjureczko/poszukiwacz/shared/XmlHelper.kt +++ b/app/src/main/java/pl/marianjureczko/poszukiwacz/shared/XmlHelper.kt @@ -2,6 +2,7 @@ package pl.marianjureczko.poszukiwacz.shared import org.simpleframework.xml.Serializer import org.simpleframework.xml.core.Persister +import pl.marianjureczko.poszukiwacz.model.HunterPath import pl.marianjureczko.poszukiwacz.model.Route import pl.marianjureczko.poszukiwacz.model.TreasuresProgress import java.io.File @@ -29,11 +30,18 @@ class XmlHelper { return serializer.read(T::class.java, xml) } - fun writeToFile(bag: TreasuresProgress, outputFile: File) = - serializer.write(bag, outputFile) + fun writeToFile(progress: TreasuresProgress, outputFile: File) = + serializer.write(progress, outputFile) + + fun writeToFile(hunterPath: HunterPath, outputFile: File) = + serializer.write(hunterPath, outputFile) fun loadProgressFromFile(xmlFile: File): TreasuresProgress { val xml = xmlFile.readText() return serializer.read(TreasuresProgress::class.java, xml) } + fun loadHunterPathFromFile(xmlFile: File): HunterPath { + val xml = xmlFile.readText() + return serializer.read(HunterPath::class.java, xml) + } } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_result.xml b/app/src/main/res/layout/activity_result.xml index 1949f5c..c723d98 100644 --- a/app/src/main/res/layout/activity_result.xml +++ b/app/src/main/res/layout/activity_result.xml @@ -1,9 +1,10 @@ @@ -11,62 +12,41 @@ android:id="@+id/resultImg" android:layout_width="match_parent" android:layout_height="0dp" - android:layout_weight="1.0" + android:layout_weight="10.0" android:gravity="top" - android:padding="4dp" /> + android:layout_marginTop="50dp" /> - - - + android:autoSizeTextType="uniform" + android:gravity="center" + android:textSize="60dp" + android:fontFamily="@font/akaya_telivigala" /> + android:orientation="horizontal"> - - - - { + val someDescription = some() + val someTreasure = some() + return listOf( + Arguments.of( + "SHOULD return null WHEN treasure is null", + null, + someDescription, + some(), + 0, + null + ), + Arguments.of( + "SHOULD return null WHEN description and coordinates are null", + someTreasure, + null, + null, + 0, + null + ), + Arguments.of( + "SHOULD return null WHEN only description is null", + someTreasure, + someDescription, + null, + 0, + null + ), + Arguments.of( + "SHOULD return null WHEN only coordinates is null", + someTreasure, + null, + some(), + 0, + null + ), + Arguments.of( + "SHOULD return null WHEN description is far away from coordinates", + someTreasure, + someDescription, + some(), + 60, + null + ), + Arguments.of( + "SHOULD return description WHEN description is close to coordinates", + someTreasure, + someDescription, + some(), + 59, + someDescription + ), + ) + } + } + + @Mock + lateinit var locationCalculator: LocationCalculator + + @ParameterizedTest(name = "{0}") + @MethodSource("data") + fun findTreasureDescription( + comment: String, + justFoundTreasure: Treasure?, + description: TreasureDescription?, + userCoordinates: Coordinates?, + coordinatesDistance: Int, + expected: TreasureDescription? + ) { + //given + justFoundTreasure?.let { + description?.let { + userCoordinates?.let { + BDDMockito.given(locationCalculator.distanceInSteps(description, userCoordinates)) + .willReturn(coordinatesDistance) + } + } + } + val finder = JustFoundFinder(justFoundTreasure, description, userCoordinates, locationCalculator) + + //when + val actual = finder.findTreasureDescription() + + //then + assertThat(actual).isEqualTo(expected) + } +} \ No newline at end of file diff --git a/app/src/test/java/pl/marianjureczko/poszukiwacz/activity/searching/SearchingActivityViewModelTest.kt b/app/src/test/java/pl/marianjureczko/poszukiwacz/activity/searching/SearchingActivityViewModelTest.kt index fa6b8a4..fb86a7c 100644 --- a/app/src/test/java/pl/marianjureczko/poszukiwacz/activity/searching/SearchingActivityViewModelTest.kt +++ b/app/src/test/java/pl/marianjureczko/poszukiwacz/activity/searching/SearchingActivityViewModelTest.kt @@ -6,9 +6,14 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.mockito.BDDMockito.given +import org.mockito.BDDMockito.mockingDetails +import org.mockito.BDDMockito.reset import org.mockito.BDDMockito.then import org.mockito.Mock +import org.mockito.Mockito import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.verification.VerificationMode +import pl.marianjureczko.poszukiwacz.model.HunterPath import pl.marianjureczko.poszukiwacz.model.Route import pl.marianjureczko.poszukiwacz.model.Treasure import pl.marianjureczko.poszukiwacz.model.TreasureType @@ -35,7 +40,7 @@ class SearchingActivityViewModelTest { //then assertThat(fixture.model.getRoute()).isEqualTo(fixture.route) - assertThat(fixture.model.getTreasureBag()).usingRecursiveComparison().isEqualTo(TreasuresProgress(fixture.route.name)) + assertThat(fixture.model.getProgress()).usingRecursiveComparison().isEqualTo(TreasuresProgress(fixture.route.name)) } @Test @@ -43,13 +48,13 @@ class SearchingActivityViewModelTest { //given val treasuresProgress = some() val fixture = SearchingActivityViewModelFixture(state) - fixture.setupMockForGivenTreasureBag(storageHelper, treasuresProgress) + fixture.setupMockForGivenTreasureProgress(storageHelper, treasuresProgress) //when initialize model (in fixture) //then assertThat(fixture.model.getRoute()).isEqualTo(fixture.route) - assertThat(fixture.model.getTreasureBag()).usingRecursiveComparison().isEqualTo(treasuresProgress) + assertThat(fixture.model.getProgress()).usingRecursiveComparison().isEqualTo(treasuresProgress) } @Test @@ -59,25 +64,26 @@ class SearchingActivityViewModelTest { val treasuresProgress = some() //when - model.replaceTreasureBag(treasuresProgress, storageHelper) + model.replaceProgress(treasuresProgress, storageHelper) //then then(storageHelper).should().save(treasuresProgress) - assertThat(model.getTreasureBag()).usingRecursiveComparison().isEqualTo(treasuresProgress) + assertThat(model.getProgress()).usingRecursiveComparison().isEqualTo(treasuresProgress) } @Test - fun `SHOULD save new treasure bag WHEN collecting next treasure`() { + fun `SHOULD save new treasure progress WHEN collecting next treasure`() { //given val fixture = SearchingActivityViewModelFixture(state) fixture.setupMockForEmptyTreasureBag(storageHelper) val treasure = some().copy(type = TreasureType.DIAMOND) + reset(storageHelper) //when fixture.model.collectTreasure(treasure, storageHelper) //then - then(storageHelper).should().save(fixture.model.getTreasureBag()) + then(storageHelper).should().save(fixture.model.getProgress()) assertThat(fixture.model.getDiamonds()).isEqualTo(treasure.quantity.toString()) } @@ -112,7 +118,7 @@ class SearchingActivityViewModelTest { fun `SHOULD say initialized WHEN the flag is not set ie getTreasureSelectorActivityInputData was never called but selected is set`() { //given val fixture = SearchingActivityViewModelFixture(state) - fixture.setupMockForGivenTreasureBag(storageHelper, some()) + fixture.setupMockForGivenTreasureProgress(storageHelper, some()) assertThat(fixture.model.getSelectedForHuntTreasure()).isNotNull() //when @@ -135,7 +141,7 @@ class SearchingActivityViewModelTest { fun `SHOULD persist state WHEN getting data from treasure selector launcher TO not reexecute the selection second time`() { //given val fixture = SearchingActivityViewModelFixture(state) - fixture.setupMockForGivenTreasureBag(storageHelper, some()) + fixture.setupMockForGivenTreasureProgress(storageHelper, some()) assertThat(fixture.model.getSelectedForHuntTreasure()).isNotNull() //when @@ -160,7 +166,7 @@ data class SearchingActivityViewModelFixture( model.initialize(xml, storageHelper) } - fun setupMockForGivenTreasureBag(storageHelper: StorageHelper, treasuresProgress: TreasuresProgress) { + fun setupMockForGivenTreasureProgress(storageHelper: StorageHelper, treasuresProgress: TreasuresProgress) { given(storageHelper.loadProgress(route.name)) .willReturn(treasuresProgress) model.initialize(xml, storageHelper) @@ -169,4 +175,6 @@ data class SearchingActivityViewModelFixture( private fun initializeModel(storageHelper: StorageHelper) { model.initialize(xml, storageHelper) } -} \ No newline at end of file +} + +private fun any(type: Class): T = Mockito.any(type) \ No newline at end of file diff --git a/app/src/test/java/pl/marianjureczko/poszukiwacz/activity/treasureselector/SelectorViewModelTest.kt b/app/src/test/java/pl/marianjureczko/poszukiwacz/activity/treasureselector/SelectorViewModelTest.kt index 1d063b5..6341df8 100644 --- a/app/src/test/java/pl/marianjureczko/poszukiwacz/activity/treasureselector/SelectorViewModelTest.kt +++ b/app/src/test/java/pl/marianjureczko/poszukiwacz/activity/treasureselector/SelectorViewModelTest.kt @@ -144,7 +144,7 @@ internal class SelectorViewModelTest { //given val model = some() model.initialize(some(), some(), null, null) - assertThat(model.getUserLocation()).isNull() + assertThat(model.userLocation).isNull() val treasureDescription = some() //when @@ -170,7 +170,7 @@ internal class SelectorViewModelTest { //then assertThat(treasureId).isEqualTo(treasureDescription.id) - assertThat(distanceInSteps).isEqualTo(calculator.distanceInSteps(treasureDescription, model.getUserLocation()!!)) + assertThat(distanceInSteps).isEqualTo(calculator.distanceInSteps(treasureDescription, model.userLocation!!)) return expected } }) diff --git a/app/src/test/java/pl/marianjureczko/poszukiwacz/model/TreasuresProgressArranger.kt b/app/src/test/java/pl/marianjureczko/poszukiwacz/model/TreasuresProgressArranger.kt index 9ada357..867d7f0 100644 --- a/app/src/test/java/pl/marianjureczko/poszukiwacz/model/TreasuresProgressArranger.kt +++ b/app/src/test/java/pl/marianjureczko/poszukiwacz/model/TreasuresProgressArranger.kt @@ -12,7 +12,7 @@ class TreasuresProgressArranger : CustomArranger() { instance.collect(treasureDescription) instance.collect(some()) instance.addCommemorativePhoto(treasureDescription, someString()) - instance.hunterPath.addLocation(Coordinates(some() % 180, some() % 90)) +// instance.hunterPath.addLocation(Coordinates(some() % 180, some() % 90)) return instance } } \ No newline at end of file diff --git a/docs/development.md b/docs/development.md index ced74ba..4765008 100644 --- a/docs/development.md +++ b/docs/development.md @@ -35,6 +35,14 @@ When using gradle directly the token can be delivered as a parameter: `-PMAPBOX_ CapturePhotoIntent --> TreasuresEditorActivity SearchingActivity --> MapActivity : show map MapActivity --> SearchingActivity + ResultActivity --> CapturePhotoIntent : make commemorative photo + CapturePhotoIntent --> ResultActivity + ResultActivity --> CommemorativeActivity : show commemorative photo + CommemorativeActivity --> ResultActivity + TreasureSelectorActivity --> CommemorativeActivity : show commemorative photo + CommemorativeActivity --> CapturePhotoIntent : replace\n commemorative photo + CapturePhotoIntent --> CommemorativeActivity + CommemorativeActivity --> TreasureSelectorActivity MainActivity --> FacebookActivity FacebookActivity --> MainActivity @@ -55,6 +63,7 @@ When using gradle directly the token can be delivered as a parameter: `-PMAPBOX_ MapActivity --> FacebookActivity FacebookActivity --> MapActivity + note right of QrScanIntent: INTENT note right of CapturePhotoIntent: INTENT ``` @@ -64,8 +73,7 @@ When using gradle directly the token can be delivered as a parameter: `-PMAPBOX_ There are three levels of state persistence. 1. **View Model level.** There is the ViewModel class that allows data to survive configuration changes such as screen rotations. Each stateful activity has a view model class (a - class - extending the `ViewModel`). + class extending the `ViewModel`). 2. **Saved instance state.** Data saved this way survives system initiated process death (e.g. removing the process due to lack of memory). Saving instance state should be done through view model. The view model class aggregates an instance of the `SavedStateHandle`. When the data is changed, the view model should