diff --git a/app/build.gradle b/app/build.gradle
index a8ec2a6a..4be4f976 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -59,6 +59,7 @@ dependencies {
implementation 'com.squareup.retrofit2:converter-moshi:2.7.2'
implementation 'com.squareup.moshi:moshi-kotlin:1.9.2'
implementation 'com.squareup.picasso:picasso:2.71828'
+ implementation 'com.github.MikeOrtiz:TouchImageView:2.3.3'
// debug tools
implementation 'com.facebook.stetho:stetho:1.5.1'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 9117872e..c91f246a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -34,6 +34,8 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/johan/evmap/GalleryActivity.kt b/app/src/main/java/com/johan/evmap/GalleryActivity.kt
new file mode 100644
index 00000000..80ba6d1d
--- /dev/null
+++ b/app/src/main/java/com/johan/evmap/GalleryActivity.kt
@@ -0,0 +1,106 @@
+package com.johan.evmap
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.app.ActivityCompat
+import androidx.core.app.SharedElementCallback
+import androidx.databinding.DataBindingUtil
+import androidx.viewpager2.widget.ViewPager2
+import com.johan.evmap.adapter.GalleryAdapter
+import com.johan.evmap.adapter.galleryTransitionName
+import com.johan.evmap.databinding.ActivityGalleryBinding
+import com.ortiz.touchview.TouchImageView
+
+
+class GalleryActivity : AppCompatActivity() {
+ companion object {
+ const val EXTRA_POSITION = "position"
+ const val EXTRA_PHOTOS = "photos"
+ private const val SAVED_CURRENT_PAGE_POSITION = "current_page_position"
+ }
+
+ private lateinit var binding: ActivityGalleryBinding
+ private var isReturning: Boolean = false
+ private var startingPosition: Int = 0
+ private var currentPosition: Int = 0
+ private lateinit var galleryAdapter: GalleryAdapter
+ private var currentPage: TouchImageView? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = DataBindingUtil.setContentView(this, R.layout.activity_gallery)
+ ActivityCompat.postponeEnterTransition(this)
+ ActivityCompat.setEnterSharedElementCallback(this, enterElementCallback)
+
+ startingPosition = intent.getIntExtra(EXTRA_POSITION, 0)
+ currentPosition =
+ savedInstanceState?.getInt(SAVED_CURRENT_PAGE_POSITION) ?: startingPosition
+
+ galleryAdapter = GalleryAdapter(this, detailView = true, pageToLoad = currentPosition) {
+ ActivityCompat.startPostponedEnterTransition(this)
+ }
+ binding.gallery.setPageTransformer { page, position ->
+ val v = page as TouchImageView
+ currentPage = v
+ }
+ binding.gallery.adapter = galleryAdapter
+ binding.photos = intent.getParcelableArrayListExtra(EXTRA_PHOTOS)
+
+ binding.gallery.post {
+ binding.gallery.setCurrentItem(currentPosition, false)
+ binding.gallery.registerOnPageChangeCallback(object :
+ ViewPager2.OnPageChangeCallback() {
+ override fun onPageSelected(position: Int) {
+ currentPosition = position
+ }
+ })
+ }
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ outState.putInt(SAVED_CURRENT_PAGE_POSITION, currentPosition)
+ }
+
+ override fun finishAfterTransition() {
+ isReturning = true
+ val data = Intent()
+ data.putExtra(MapsActivity.EXTRA_STARTING_GALLERY_POSITION, startingPosition)
+ data.putExtra(MapsActivity.EXTRA_CURRENT_GALLERY_POSITION, currentPosition)
+ setResult(Activity.RESULT_OK, data)
+ super.finishAfterTransition()
+ }
+
+ private val enterElementCallback: SharedElementCallback = object : SharedElementCallback() {
+ override fun onMapSharedElements(
+ names: MutableList,
+ sharedElements: MutableMap
+ ) {
+ if (isReturning) {
+ val index = binding.gallery.currentItem
+ val currentPage = currentPage ?: return
+
+ if (startingPosition != currentPosition) {
+ names.clear()
+ names.add(galleryTransitionName(index))
+
+ sharedElements.clear()
+ sharedElements[galleryTransitionName(index)] = currentPage
+ }
+ }
+ }
+ }
+
+ override fun onBackPressed() {
+ val image = currentPage
+ if (image == null || image.currentZoom in 0.95f..1.05f) {
+ super.onBackPressed()
+ } else {
+ image.setZoomAnimated(1f, 0.5f, 0.5f)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/johan/evmap/MapsActivity.kt b/app/src/main/java/com/johan/evmap/MapsActivity.kt
index 3061ccd6..ae2559ce 100644
--- a/app/src/main/java/com/johan/evmap/MapsActivity.kt
+++ b/app/src/main/java/com/johan/evmap/MapsActivity.kt
@@ -1,14 +1,19 @@
package com.johan.evmap
import android.Manifest
+import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.net.Uri
import android.os.Bundle
+import android.view.View
+import android.view.ViewTreeObserver
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
+import androidx.core.app.ActivityOptionsCompat
+import androidx.core.app.SharedElementCallback
import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.MutableLiveData
@@ -27,6 +32,7 @@ import com.google.android.material.snackbar.Snackbar
import com.johan.evmap.adapter.ConnectorAdapter
import com.johan.evmap.adapter.DetailAdapter
import com.johan.evmap.adapter.GalleryAdapter
+import com.johan.evmap.adapter.galleryTransitionName
import com.johan.evmap.api.*
import com.johan.evmap.databinding.ActivityMapsBinding
import com.johan.evmap.ui.ClusterIconGenerator
@@ -61,17 +67,21 @@ class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
private var markers: Map = emptyMap()
private var clusterMarkers: List = emptyList()
+ private var reenterState: Bundle? = null
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
+ api = GoingElectricApi.create(getString(R.string.goingelectric_key))
+
binding = DataBindingUtil.setContentView(this, R.layout.activity_maps)
binding.lifecycleOwner = this
binding.vm = vm
- fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
+ ActivityCompat.setExitSharedElementCallback(this, exitElementCallback)
val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
- api = GoingElectricApi.create(getString(R.string.goingelectric_key))
setupAdapters()
@@ -144,8 +154,22 @@ class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
}
private fun setupAdapters() {
+ val galleryClickListener = object : GalleryAdapter.ItemClickListener {
+ override fun onItemClick(view: View, position: Int) {
+ val photos = vm.charger.value?.photos ?: return
+ val intent = Intent(this@MapsActivity, GalleryActivity::class.java).apply {
+ putExtra(GalleryActivity.EXTRA_PHOTOS, ArrayList(photos))
+ putExtra(GalleryActivity.EXTRA_POSITION, position)
+ }
+ val options = ActivityOptionsCompat.makeSceneTransitionAnimation(
+ this@MapsActivity, view, view.transitionName
+ )
+ startActivity(intent, options.toBundle())
+ }
+ }
+
binding.gallery.apply {
- adapter = GalleryAdapter(this@MapsActivity)
+ adapter = GalleryAdapter(this@MapsActivity, galleryClickListener)
layoutManager =
LinearLayoutManager(this@MapsActivity, LinearLayoutManager.HORIZONTAL, false)
addItemDecoration(DividerItemDecoration(
@@ -211,6 +235,7 @@ class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
}
}
+ @SuppressLint("MissingPermission")
private fun enableLocation(animate: Boolean) {
val map = this.map ?: return
map.isMyLocationEnabled = true
@@ -333,4 +358,58 @@ class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
else -> super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
}
+
+ private val exitElementCallback = object : SharedElementCallback() {
+ override fun onMapSharedElements(
+ names: MutableList,
+ sharedElements: MutableMap
+ ) {
+ if (reenterState != null) {
+ val startingPosition = reenterState!!.getInt(EXTRA_STARTING_GALLERY_POSITION)
+ val currentPosition = reenterState!!.getInt(EXTRA_CURRENT_GALLERY_POSITION)
+ if (startingPosition != currentPosition) {
+ // Current element has changed, need to override previous exit transitions
+ val newTransitionName = galleryTransitionName(currentPosition)
+ val newSharedElement =
+ binding.gallery.findViewHolderForAdapterPosition(currentPosition)?.itemView
+ if (newSharedElement != null) {
+ names.clear()
+ names.add(newTransitionName)
+
+ sharedElements.clear()
+ sharedElements[newTransitionName] = newSharedElement
+ }
+ }
+ reenterState = null
+ }
+ }
+ }
+
+ override fun onActivityReenter(resultCode: Int, data: Intent) {
+ // returning to gallery
+ super.onActivityReenter(resultCode, data)
+ reenterState = Bundle(data.extras)
+ reenterState?.let {
+ val startingPosition = it.getInt(EXTRA_STARTING_GALLERY_POSITION)
+ val currentPosition = it.getInt(EXTRA_CURRENT_GALLERY_POSITION)
+ if (startingPosition != currentPosition) binding.gallery.scrollToPosition(
+ currentPosition
+ )
+ ActivityCompat.postponeEnterTransition(this)
+
+ binding.gallery.viewTreeObserver.addOnPreDrawListener(object :
+ ViewTreeObserver.OnPreDrawListener {
+ override fun onPreDraw(): Boolean {
+ binding.gallery.viewTreeObserver.removeOnPreDrawListener(this)
+ ActivityCompat.startPostponedEnterTransition(this@MapsActivity)
+ return true
+ }
+ })
+ }
+ }
+
+ companion object {
+ const val EXTRA_STARTING_GALLERY_POSITION = "extra_starting_item_position"
+ const val EXTRA_CURRENT_GALLERY_POSITION = "extra_current_item_position"
+ }
}
diff --git a/app/src/main/java/com/johan/evmap/adapter/GalleryAdapter.kt b/app/src/main/java/com/johan/evmap/adapter/GalleryAdapter.kt
index 8fef5d04..04f555d7 100644
--- a/app/src/main/java/com/johan/evmap/adapter/GalleryAdapter.kt
+++ b/app/src/main/java/com/johan/evmap/adapter/GalleryAdapter.kt
@@ -1,41 +1,122 @@
package com.johan.evmap.adapter
+import android.annotation.SuppressLint
import android.content.Context
-import android.view.LayoutInflater
-import android.view.ViewGroup
+import android.view.*
import android.widget.ImageView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.johan.evmap.R
import com.johan.evmap.api.ChargerPhoto
+import com.ortiz.touchview.TouchImageView
+import com.squareup.picasso.Callback
import com.squareup.picasso.Picasso
-class GalleryAdapter(context: Context) :
+
+class GalleryAdapter(
+ context: Context,
+ val itemClickListener: ItemClickListener? = null,
+ val detailView: Boolean = false,
+ val pageToLoad: Int? = null,
+ val loadedListener: (() -> Unit)? = null
+) :
ListAdapter(ChargerPhotoDiffCallback()) {
class ViewHolder(val view: ImageView) : RecyclerView.ViewHolder(view)
- val apikey = context.getString(R.string.goingelectric_key)
+ interface ItemClickListener {
+ fun onItemClick(view: View, position: Int)
+ }
+ val apikey = context.getString(R.string.goingelectric_key)
+ var loaded = false
+
+ @SuppressLint("ClickableViewAccessibility")
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
- val view = LayoutInflater.from(parent.context)
- .inflate(R.layout.gallery_item, parent, false) as ImageView
+ val inflater = LayoutInflater.from(parent.context)
+ val view: ImageView
+ if (detailView) {
+ view = inflater.inflate(R.layout.gallery_item_fullscreen, parent, false) as ImageView
+ view.setOnTouchListener { view, event ->
+ var result = true
+ //can scroll horizontally checks if there's still a part of the image
+ //that can be scrolled until you reach the edge
+ if (event.pointerCount >= 2 || view.canScrollHorizontally(1) && view.canScrollHorizontally(
+ -1
+ )
+ ) {
+ //multi-touch event
+ result = when (event.action) {
+ MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> {
+ // Disallow RecyclerView to intercept touch events.
+ parent.requestDisallowInterceptTouchEvent(true)
+ // Disable touch on view
+ false
+ }
+ MotionEvent.ACTION_UP -> {
+ // Allow RecyclerView to intercept touch events.
+ parent.requestDisallowInterceptTouchEvent(false)
+ true
+ }
+ else -> true
+ }
+ }
+ result
+ }
+ } else {
+ view = inflater.inflate(R.layout.gallery_item, parent, false) as ImageView
+ }
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ if (detailView) {
+ (holder.view as TouchImageView).resetZoom()
+ }
Picasso.get()
.load(
- "https://api.goingelectric.de/chargepoints/photo/?key=${apikey}&id=${getItem(
- position
- ).id}&height=${holder.view.height}"
+ "https://api.goingelectric.de/chargepoints/photo/?key=${apikey}" +
+ "&id=${getItem(position).id}" +
+ if (detailView) {
+ "&size=1000"
+ } else {
+ "&height=${holder.view.height}"
+ }
)
- .into(holder.view)
+ .into(holder.view, object : Callback {
+ override fun onSuccess() {
+ if (!loaded && loadedListener != null && pageToLoad == position) {
+ holder.view.viewTreeObserver.addOnPreDrawListener(object :
+ ViewTreeObserver.OnPreDrawListener {
+ override fun onPreDraw(): Boolean {
+ holder.view.viewTreeObserver.removeOnPreDrawListener(this)
+ loadedListener.invoke()
+ return true
+ }
+ })
+ loaded = true
+ }
+ }
+
+ override fun onError(e: Exception?) {
+ if (!loaded && loadedListener != null && pageToLoad == position) {
+ loadedListener.invoke()
+ loaded = true
+ }
+ }
+
+ })
+ holder.view.transitionName = galleryTransitionName(position)
+ if (itemClickListener != null) {
+ holder.view.setOnClickListener {
+ itemClickListener.onItemClick(holder.view, position)
+ }
+ }
}
-
-
}
+fun galleryTransitionName(position: Int) = "gallery_$position"
+
class ChargerPhotoDiffCallback : DiffUtil.ItemCallback() {
override fun areItemsTheSame(oldItem: ChargerPhoto, newItem: ChargerPhoto): Boolean {
return oldItem.id == newItem.id
diff --git a/app/src/main/java/com/johan/evmap/api/GoingElectricModel.kt b/app/src/main/java/com/johan/evmap/api/GoingElectricModel.kt
index d17fdf60..e1ac12b1 100644
--- a/app/src/main/java/com/johan/evmap/api/GoingElectricModel.kt
+++ b/app/src/main/java/com/johan/evmap/api/GoingElectricModel.kt
@@ -1,6 +1,7 @@
package com.johan.evmap.api
import android.content.Context
+import android.os.Parcelable
import androidx.core.text.HtmlCompat
import com.johan.evmap.R
import com.johan.evmap.adapter.Equatable
@@ -144,7 +145,8 @@ data class Hours(
)
@JsonClass(generateAdapter = true)
-data class ChargerPhoto(val id: String)
+@Parcelize
+data class ChargerPhoto(val id: String) : Parcelable
@JsonClass(generateAdapter = true)
data class ChargeLocationCluster(
diff --git a/app/src/main/java/com/johan/evmap/ui/BindingAdapters.kt b/app/src/main/java/com/johan/evmap/ui/BindingAdapters.kt
index d8926b70..27db5aef 100644
--- a/app/src/main/java/com/johan/evmap/ui/BindingAdapters.kt
+++ b/app/src/main/java/com/johan/evmap/ui/BindingAdapters.kt
@@ -7,6 +7,7 @@ import android.widget.ImageView
import androidx.databinding.BindingAdapter
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
+import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.johan.evmap.R
@@ -39,6 +40,13 @@ fun setRecyclerViewData(recyclerView: RecyclerView, items: List?) {
}
}
+@BindingAdapter("data")
+fun setRecyclerViewData(recyclerView: ViewPager2, items: List?) {
+ if (recyclerView.adapter is ListAdapter<*, *>) {
+ (recyclerView.adapter as ListAdapter).submitList(items)
+ }
+}
+
@BindingAdapter("connectorIcon")
fun getConnectorItem(view: ImageView, type: String) {
view.setImageResource(
diff --git a/app/src/main/res/layout/activity_gallery.xml b/app/src/main/res/layout/activity_gallery.xml
new file mode 100644
index 00000000..28d30364
--- /dev/null
+++ b/app/src/main/res/layout/activity_gallery.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/gallery_item_fullscreen.xml b/app/src/main/res/layout/gallery_item_fullscreen.xml
new file mode 100644
index 00000000..d6cbc35c
--- /dev/null
+++ b/app/src/main/res/layout/gallery_item_fullscreen.xml
@@ -0,0 +1,8 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml
new file mode 100644
index 00000000..e7829642
--- /dev/null
+++ b/app/src/main/res/values/donottranslate.xml
@@ -0,0 +1,4 @@
+
+
+ picture
+
\ No newline at end of file