mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-24 10:25:51 +03:00
Merge pull request #5411 from vector-im/feature/mna/PSF-735-sharing-options-view
#5395: Location sharing options view
This commit is contained in:
commit
f12afe0ef0
19 changed files with 475 additions and 69 deletions
1
changelog.d/5395.feature
Normal file
1
changelog.d/5395.feature
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Add a custom view to display a picker for share location options
|
|
@ -139,4 +139,8 @@
|
||||||
<color name="vctr_presence_indicator_offline_light">@color/palette_gray_100</color>
|
<color name="vctr_presence_indicator_offline_light">@color/palette_gray_100</color>
|
||||||
<color name="vctr_presence_indicator_offline_dark">@color/palette_gray_450</color>
|
<color name="vctr_presence_indicator_offline_dark">@color/palette_gray_450</color>
|
||||||
|
|
||||||
|
<!-- Location sharing colors -->
|
||||||
|
<attr name="vctr_live_location" format="color" />
|
||||||
|
<color name="vctr_live_location_light">@color/palette_prune</color>
|
||||||
|
<color name="vctr_live_location_dark">@color/palette_prune</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -70,4 +70,7 @@
|
||||||
|
|
||||||
<item name="ftue_auth_profile_picture_height" format="float" type="dimen">0.15</item>
|
<item name="ftue_auth_profile_picture_height" format="float" type="dimen">0.15</item>
|
||||||
<item name="ftue_auth_profile_picture_icon_height" format="float" type="dimen">0.05</item>
|
<item name="ftue_auth_profile_picture_icon_height" format="float" type="dimen">0.05</item>
|
||||||
|
|
||||||
|
<!-- Location sharing -->
|
||||||
|
<dimen name="location_sharing_option_default_padding">10dp</dimen>
|
||||||
</resources>
|
</resources>
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<declare-styleable name="LocationSharingOptionView">
|
||||||
|
<attr name="locShareIcon" format="reference" />
|
||||||
|
<attr name="locShareIconBackground" format="reference" />
|
||||||
|
<attr name="locShareIconBackgroundTint" format="color" />
|
||||||
|
<attr name="locShareIconPadding" format="dimension" />
|
||||||
|
<attr name="locShareIconDescription" format="string" />
|
||||||
|
<attr name="locShareTitle" format="string" />
|
||||||
|
</declare-styleable>
|
||||||
|
|
||||||
|
</resources>
|
|
@ -145,6 +145,8 @@
|
||||||
|
|
||||||
<item name="android:actionButtonStyle">@style/Widget.Vector.ActionButton</item>
|
<item name="android:actionButtonStyle">@style/Widget.Vector.ActionButton</item>
|
||||||
|
|
||||||
|
<!-- Location sharing -->
|
||||||
|
<item name="vctr_live_location">@color/vctr_live_location_dark</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Vector.Dark" parent="Base.Theme.Vector.Dark" />
|
<style name="Theme.Vector.Dark" parent="Base.Theme.Vector.Dark" />
|
||||||
|
|
|
@ -146,6 +146,8 @@
|
||||||
|
|
||||||
<item name="android:actionButtonStyle">@style/Widget.Vector.ActionButton</item>
|
<item name="android:actionButtonStyle">@style/Widget.Vector.ActionButton</item>
|
||||||
|
|
||||||
|
<!-- Location sharing -->
|
||||||
|
<item name="vctr_live_location">@color/vctr_live_location_light</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Vector.Light" parent="Base.Theme.Vector.Light" />
|
<style name="Theme.Vector.Light" parent="Base.Theme.Vector.Light" />
|
||||||
|
|
|
@ -23,6 +23,7 @@ import android.view.ViewGroup
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.annotation.AttrRes
|
import androidx.annotation.AttrRes
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.appcompat.widget.SearchView
|
import androidx.appcompat.widget.SearchView
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
@ -70,6 +71,15 @@ fun View.setAttributeTintedBackground(@DrawableRes drawableRes: Int, @AttrRes ti
|
||||||
background = drawable
|
background = drawable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun View.tintBackground(@ColorInt tintColor: Int) {
|
||||||
|
val bkg = background?.let {
|
||||||
|
val backgroundDrawable = DrawableCompat.wrap(background)
|
||||||
|
DrawableCompat.setTint(backgroundDrawable, tintColor)
|
||||||
|
backgroundDrawable
|
||||||
|
}
|
||||||
|
background = bkg
|
||||||
|
}
|
||||||
|
|
||||||
fun ImageView.setAttributeTintedImageResource(@DrawableRes drawableRes: Int, @AttrRes tint: Int) {
|
fun ImageView.setAttributeTintedImageResource(@DrawableRes drawableRes: Int, @AttrRes tint: Int) {
|
||||||
val drawable = ContextCompat.getDrawable(context, drawableRes)!!
|
val drawable = ContextCompat.getDrawable(context, drawableRes)!!
|
||||||
DrawableCompat.setTint(drawable, ThemeUtils.getColor(context, tint))
|
DrawableCompat.setTint(drawable, ThemeUtils.getColor(context, tint))
|
||||||
|
|
|
@ -19,7 +19,9 @@ package im.vector.app.features.home.room.detail.timeline.helper
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.graphics.drawable.LayerDrawable
|
import android.graphics.drawable.LayerDrawable
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.graphics.drawable.DrawableCompat
|
||||||
import com.bumptech.glide.request.target.CustomTarget
|
import com.bumptech.glide.request.target.CustomTarget
|
||||||
import com.bumptech.glide.request.transition.Transition
|
import com.bumptech.glide.request.transition.Transition
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
@ -37,7 +39,8 @@ class LocationPinProvider @Inject constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val activeSessionHolder: ActiveSessionHolder,
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
private val dimensionConverter: DimensionConverter,
|
private val dimensionConverter: DimensionConverter,
|
||||||
private val avatarRenderer: AvatarRenderer
|
private val avatarRenderer: AvatarRenderer,
|
||||||
|
private val matrixItemColorProvider: MatrixItemColorProvider
|
||||||
) {
|
) {
|
||||||
private val cache = mutableMapOf<String, Drawable>()
|
private val cache = mutableMapOf<String, Drawable>()
|
||||||
|
|
||||||
|
@ -61,35 +64,42 @@ class LocationPinProvider @Inject constructor(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
activeSessionHolder.getActiveSession().getUser(userId)?.toMatrixItem()?.let {
|
activeSessionHolder
|
||||||
val size = dimensionConverter.dpToPx(44)
|
.getActiveSession()
|
||||||
avatarRenderer.render(glideRequests, it, object : CustomTarget<Drawable>(size, size) {
|
.getUser(userId)
|
||||||
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
|
?.toMatrixItem()
|
||||||
Timber.d("## Location: onResourceReady")
|
?.let { userItem ->
|
||||||
val pinDrawable = createPinDrawable(resource)
|
val size = dimensionConverter.dpToPx(44)
|
||||||
cache[userId] = pinDrawable
|
val bgTintColor = matrixItemColorProvider.getColor(userItem)
|
||||||
callback(pinDrawable)
|
avatarRenderer.render(glideRequests, userItem, object : CustomTarget<Drawable>(size, size) {
|
||||||
}
|
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
|
||||||
|
Timber.d("## Location: onResourceReady")
|
||||||
|
val pinDrawable = createPinDrawable(resource, bgTintColor)
|
||||||
|
cache[userId] = pinDrawable
|
||||||
|
callback(pinDrawable)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onLoadCleared(placeholder: Drawable?) {
|
override fun onLoadCleared(placeholder: Drawable?) {
|
||||||
// Is it possible? Put placeholder instead?
|
// Is it possible? Put placeholder instead?
|
||||||
// FIXME The doc says it has to be implemented and should free resources
|
// FIXME The doc says it has to be implemented and should free resources
|
||||||
Timber.d("## Location: onLoadCleared")
|
Timber.d("## Location: onLoadCleared")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||||
Timber.w("## Location: onLoadFailed")
|
Timber.w("## Location: onLoadFailed")
|
||||||
errorDrawable ?: return
|
errorDrawable ?: return
|
||||||
val pinDrawable = createPinDrawable(errorDrawable)
|
val pinDrawable = createPinDrawable(errorDrawable, bgTintColor)
|
||||||
cache[userId] = pinDrawable
|
cache[userId] = pinDrawable
|
||||||
callback(pinDrawable)
|
callback(pinDrawable)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createPinDrawable(drawable: Drawable): Drawable {
|
private fun createPinDrawable(drawable: Drawable, @ColorInt bgTintColor: Int): Drawable {
|
||||||
val bgUserPin = ContextCompat.getDrawable(context, R.drawable.bg_map_user_pin)!!
|
val bgUserPin = ContextCompat.getDrawable(context, R.drawable.bg_map_user_pin)!!
|
||||||
|
// use mutate on drawable to avoid sharing the color when we have multiple different user pins
|
||||||
|
DrawableCompat.setTint(bgUserPin.mutate(), bgTintColor)
|
||||||
val layerDrawable = LayerDrawable(arrayOf(bgUserPin, drawable))
|
val layerDrawable = LayerDrawable(arrayOf(bgUserPin, drawable))
|
||||||
val horizontalInset = dimensionConverter.dpToPx(4)
|
val horizontalInset = dimensionConverter.dpToPx(4)
|
||||||
val topInset = dimensionConverter.dpToPx(4)
|
val topInset = dimensionConverter.dpToPx(4)
|
||||||
|
|
|
@ -30,6 +30,10 @@ import im.vector.app.R
|
||||||
import im.vector.app.core.extensions.exhaustive
|
import im.vector.app.core.extensions.exhaustive
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.databinding.FragmentLocationSharingBinding
|
import im.vector.app.databinding.FragmentLocationSharingBinding
|
||||||
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
|
||||||
|
import im.vector.app.features.location.option.LocationSharingOption
|
||||||
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -37,7 +41,9 @@ import javax.inject.Inject
|
||||||
* We should consider using SupportMapFragment for a out of the box lifecycle handling
|
* We should consider using SupportMapFragment for a out of the box lifecycle handling
|
||||||
*/
|
*/
|
||||||
class LocationSharingFragment @Inject constructor(
|
class LocationSharingFragment @Inject constructor(
|
||||||
private val urlMapProvider: UrlMapProvider
|
private val urlMapProvider: UrlMapProvider,
|
||||||
|
private val avatarRenderer: AvatarRenderer,
|
||||||
|
private val matrixItemColorProvider: MatrixItemColorProvider
|
||||||
) : VectorBaseFragment<FragmentLocationSharingBinding>() {
|
) : VectorBaseFragment<FragmentLocationSharingBinding>() {
|
||||||
|
|
||||||
private val viewModel: LocationSharingViewModel by fragmentViewModel()
|
private val viewModel: LocationSharingViewModel by fragmentViewModel()
|
||||||
|
@ -45,6 +51,8 @@ class LocationSharingFragment @Inject constructor(
|
||||||
// Keep a ref to handle properly the onDestroy callback
|
// Keep a ref to handle properly the onDestroy callback
|
||||||
private var mapView: WeakReference<MapView>? = null
|
private var mapView: WeakReference<MapView>? = null
|
||||||
|
|
||||||
|
private var hasRenderedUserAvatar = false
|
||||||
|
|
||||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLocationSharingBinding {
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLocationSharingBinding {
|
||||||
return FragmentLocationSharingBinding.inflate(inflater, container, false)
|
return FragmentLocationSharingBinding.inflate(inflater, container, false)
|
||||||
}
|
}
|
||||||
|
@ -59,9 +67,7 @@ class LocationSharingFragment @Inject constructor(
|
||||||
views.mapView.initialize(urlMapProvider.getMapUrl())
|
views.mapView.initialize(urlMapProvider.getMapUrl())
|
||||||
}
|
}
|
||||||
|
|
||||||
views.shareLocationContainer.debouncedClicks {
|
initOptionsPicker()
|
||||||
viewModel.handle(LocationSharingAction.OnShareLocation)
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModel.observeViewEvents {
|
viewModel.observeViewEvents {
|
||||||
when (it) {
|
when (it) {
|
||||||
|
@ -107,6 +113,12 @@ class LocationSharingFragment @Inject constructor(
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun invalidate() = withState(viewModel) { state ->
|
||||||
|
views.mapView.render(state.toMapState())
|
||||||
|
views.shareLocationGpsLoading.isGone = state.lastKnownLocation != null
|
||||||
|
updateUserAvatar(state.userItem)
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleLocationNotAvailableError() {
|
private fun handleLocationNotAvailableError() {
|
||||||
MaterialAlertDialogBuilder(requireActivity())
|
MaterialAlertDialogBuilder(requireActivity())
|
||||||
.setTitle(R.string.location_not_available_dialog_title)
|
.setTitle(R.string.location_not_available_dialog_title)
|
||||||
|
@ -118,8 +130,28 @@ class LocationSharingFragment @Inject constructor(
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun invalidate() = withState(viewModel) { state ->
|
private fun initOptionsPicker() {
|
||||||
views.mapView.render(state.toMapState())
|
// TODO
|
||||||
views.shareLocationGpsLoading.isGone = state.lastKnownLocation != null
|
// change the options dynamically depending on the current chosen location
|
||||||
|
views.shareLocationOptionsPicker.render(LocationSharingOption.USER_CURRENT)
|
||||||
|
views.shareLocationOptionsPicker.optionPinned.debouncedClicks {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
views.shareLocationOptionsPicker.optionUserCurrent.debouncedClicks {
|
||||||
|
viewModel.handle(LocationSharingAction.OnShareLocation)
|
||||||
|
}
|
||||||
|
views.shareLocationOptionsPicker.optionUserLive.debouncedClicks {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateUserAvatar(userItem: MatrixItem.UserItem?) {
|
||||||
|
userItem?.takeUnless { hasRenderedUserAvatar }
|
||||||
|
?.let {
|
||||||
|
hasRenderedUserAvatar = true
|
||||||
|
avatarRenderer.render(it, views.shareLocationOptionsPicker.optionUserCurrent.iconView)
|
||||||
|
val tintColor = matrixItemColorProvider.getColor(it)
|
||||||
|
views.shareLocationOptionsPicker.optionUserCurrent.setIconBackgroundTint(tintColor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import im.vector.app.core.extensions.exhaustive
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
|
import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
|
|
||||||
class LocationSharingViewModel @AssistedInject constructor(
|
class LocationSharingViewModel @AssistedInject constructor(
|
||||||
@Assisted private val initialState: LocationSharingViewState,
|
@Assisted private val initialState: LocationSharingViewState,
|
||||||
|
@ -45,9 +46,14 @@ class LocationSharingViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
init {
|
init {
|
||||||
locationTracker.start(this)
|
locationTracker.start(this)
|
||||||
|
setUserItem()
|
||||||
createPin()
|
createPin()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setUserItem() {
|
||||||
|
setState { copy(userItem = session.getUser(session.myUserId)?.toMatrixItem()) }
|
||||||
|
}
|
||||||
|
|
||||||
private fun createPin() {
|
private fun createPin() {
|
||||||
locationPinProvider.create(session.myUserId) {
|
locationPinProvider.create(session.myUserId) {
|
||||||
setState {
|
setState {
|
||||||
|
|
|
@ -20,6 +20,7 @@ import android.graphics.drawable.Drawable
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import com.airbnb.mvrx.MavericksState
|
import com.airbnb.mvrx.MavericksState
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
|
||||||
enum class LocationSharingMode(@StringRes val titleRes: Int) {
|
enum class LocationSharingMode(@StringRes val titleRes: Int) {
|
||||||
STATIC_SHARING(R.string.location_activity_title_static_sharing),
|
STATIC_SHARING(R.string.location_activity_title_static_sharing),
|
||||||
|
@ -29,6 +30,7 @@ enum class LocationSharingMode(@StringRes val titleRes: Int) {
|
||||||
data class LocationSharingViewState(
|
data class LocationSharingViewState(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
val mode: LocationSharingMode,
|
val mode: LocationSharingMode,
|
||||||
|
val userItem: MatrixItem.UserItem? = null,
|
||||||
val lastKnownLocation: LocationData? = null,
|
val lastKnownLocation: LocationData? = null,
|
||||||
val pinDrawable: Drawable? = null
|
val pinDrawable: Drawable? = null
|
||||||
) : MavericksState {
|
) : MavericksState {
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.location.option
|
||||||
|
|
||||||
|
enum class LocationSharingOption {
|
||||||
|
/**
|
||||||
|
* Current user's location.
|
||||||
|
*/
|
||||||
|
USER_CURRENT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User's location during a certain amount of time.
|
||||||
|
*/
|
||||||
|
USER_LIVE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static location pinned by the user.
|
||||||
|
*/
|
||||||
|
PINNED
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.location.option
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.util.TypedValue
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.databinding.ViewLocationSharingOptionPickerBinding
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom view to display the location sharing option picker.
|
||||||
|
*/
|
||||||
|
class LocationSharingOptionPickerView @JvmOverloads constructor(
|
||||||
|
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||||
|
) : ConstraintLayout(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
|
val optionPinned: LocationSharingOptionView
|
||||||
|
get() = binding.locationSharingOptionPinned
|
||||||
|
|
||||||
|
val optionUserCurrent: LocationSharingOptionView
|
||||||
|
get() = binding.locationSharingOptionUserCurrent
|
||||||
|
|
||||||
|
val optionUserLive: LocationSharingOptionView
|
||||||
|
get() = binding.locationSharingOptionUserLive
|
||||||
|
|
||||||
|
private val divider1: View
|
||||||
|
get() = binding.locationSharingOptionsDivider1
|
||||||
|
|
||||||
|
private val divider2: View
|
||||||
|
get() = binding.locationSharingOptionsDivider2
|
||||||
|
|
||||||
|
private val binding = ViewLocationSharingOptionPickerBinding.inflate(
|
||||||
|
LayoutInflater.from(context),
|
||||||
|
this
|
||||||
|
)
|
||||||
|
|
||||||
|
init {
|
||||||
|
applyBackground()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun render(vararg options: LocationSharingOption) {
|
||||||
|
val optionsNumber = options.toSet().size
|
||||||
|
val isPinnedVisible = options.contains(LocationSharingOption.PINNED)
|
||||||
|
val isUserCurrentVisible = options.contains(LocationSharingOption.USER_CURRENT)
|
||||||
|
val isUserLiveVisible = options.contains(LocationSharingOption.USER_LIVE)
|
||||||
|
|
||||||
|
optionPinned.isVisible = isPinnedVisible
|
||||||
|
divider1.isVisible = isPinnedVisible && optionsNumber > 1
|
||||||
|
optionUserCurrent.isVisible = isUserCurrentVisible
|
||||||
|
divider2.isVisible = isUserCurrentVisible && isUserLiveVisible
|
||||||
|
optionUserLive.isVisible = isUserLiveVisible
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applyBackground() {
|
||||||
|
val outValue = TypedValue()
|
||||||
|
context.theme.resolveAttribute(
|
||||||
|
R.attr.colorSurface,
|
||||||
|
outValue,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
binding.root.background = ContextCompat.getDrawable(
|
||||||
|
context,
|
||||||
|
outValue.resourceId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.location.option
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.res.TypedArray
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.widget.ImageView
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.view.setPadding
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.extensions.tintBackground
|
||||||
|
import im.vector.app.databinding.ViewLocationSharingOptionBinding
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom view to display a location sharing option.
|
||||||
|
*/
|
||||||
|
class LocationSharingOptionView @JvmOverloads constructor(
|
||||||
|
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||||
|
) : ConstraintLayout(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
|
val iconView: ImageView
|
||||||
|
get() = binding.shareLocationOptionIcon
|
||||||
|
|
||||||
|
private val binding = ViewLocationSharingOptionBinding.inflate(
|
||||||
|
LayoutInflater.from(context),
|
||||||
|
this
|
||||||
|
)
|
||||||
|
|
||||||
|
init {
|
||||||
|
context.theme.obtainStyledAttributes(
|
||||||
|
attrs,
|
||||||
|
R.styleable.LocationSharingOptionView,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
).run {
|
||||||
|
try {
|
||||||
|
setIcon(this)
|
||||||
|
setTitle(this)
|
||||||
|
} finally {
|
||||||
|
recycle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setIconBackgroundTint(@ColorInt color: Int) {
|
||||||
|
binding.shareLocationOptionIcon.tintBackground(color)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setIcon(typedArray: TypedArray) {
|
||||||
|
val icon = typedArray.getDrawable(R.styleable.LocationSharingOptionView_locShareIcon)
|
||||||
|
val background = typedArray.getDrawable(R.styleable.LocationSharingOptionView_locShareIconBackground)
|
||||||
|
val backgroundTint = typedArray.getColor(
|
||||||
|
R.styleable.LocationSharingOptionView_locShareIconBackgroundTint,
|
||||||
|
ContextCompat.getColor(context, android.R.color.transparent)
|
||||||
|
)
|
||||||
|
val padding = typedArray.getDimensionPixelOffset(
|
||||||
|
R.styleable.LocationSharingOptionView_locShareIconPadding,
|
||||||
|
context.resources.getDimensionPixelOffset(R.dimen.location_sharing_option_default_padding)
|
||||||
|
)
|
||||||
|
val description = typedArray.getString(R.styleable.LocationSharingOptionView_locShareIconDescription)
|
||||||
|
|
||||||
|
iconView.setImageDrawable(icon)
|
||||||
|
iconView.background = background
|
||||||
|
iconView.tintBackground(backgroundTint)
|
||||||
|
iconView.setPadding(padding)
|
||||||
|
iconView.contentDescription = description
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setTitle(typedArray: TypedArray) {
|
||||||
|
val title = typedArray.getString(R.styleable.LocationSharingOptionView_locShareTitle)
|
||||||
|
binding.shareLocationOptionTitle.text = title
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="30dp"
|
||||||
|
android:height="16dp"
|
||||||
|
android:viewportWidth="30"
|
||||||
|
android:viewportHeight="16">
|
||||||
|
<path
|
||||||
|
android:pathData="M4.6144,14.8367C4.9699,14.4812 5.0048,13.9165 4.6841,13.533C2.007,10.2772 2.007,5.5504 4.6771,2.2877C4.9978,1.8973 4.9699,1.3256 4.6144,0.97C4.2031,0.5587 3.5198,0.5866 3.1503,1.0398C-0.1054,5.0206 -0.1054,10.7792 3.1503,14.767C3.5129,15.2202 4.1961,15.255 4.6144,14.8367ZM7.424,12.0271C7.7656,11.6855 7.8004,11.1487 7.5146,10.7513C6.3085,9.0502 6.3085,6.7635 7.5146,5.0624C7.7935,4.665 7.7656,4.1282 7.424,3.7866L7.417,3.7796C6.9987,3.3613 6.2876,3.3892 5.946,3.8703C4.21,6.2685 4.21,9.5382 5.946,11.9435C6.2945,12.4245 6.9987,12.4524 7.424,12.0271ZM15.3488,1.3771C12.6508,1.3771 10.4686,3.6186 10.4686,6.3901C10.4686,9.3764 13.5501,13.4943 14.819,15.0626C15.0978,15.4064 15.6068,15.4064 15.8856,15.0626C17.1475,13.4943 20.229,9.3764 20.229,6.3901C20.229,3.6186 18.0468,1.3771 15.3488,1.3771ZM15.3488,8.1805C14.3867,8.1805 13.6059,7.3784 13.6059,6.3901C13.6059,5.4018 14.3867,4.5997 15.3488,4.5997C16.3109,4.5997 17.0917,5.4018 17.0917,6.3901C17.0917,7.3784 16.3109,8.1805 15.3488,8.1805ZM25.3431,13.533C25.0224,13.9165 25.0573,14.4812 25.4128,14.8367C25.8311,15.255 26.5144,15.2202 26.8769,14.767C30.1327,10.7792 30.1327,5.0206 26.8769,1.0398C26.5074,0.5866 25.8242,0.5587 25.4128,0.97C25.0573,1.3256 25.0294,1.8973 25.3501,2.2877C28.0203,5.5504 28.0203,10.2772 25.3431,13.533ZM22.5126,10.7513C22.2268,11.1487 22.2616,11.6855 22.6033,12.0271C23.0285,12.4524 23.7327,12.4245 24.0813,11.9435C25.8172,9.5382 25.8172,6.2685 24.0813,3.8703C23.7396,3.3892 23.0285,3.3613 22.6102,3.7796L22.6033,3.7866C22.2616,4.1282 22.2338,4.665 22.5126,5.0624C23.7187,6.7635 23.7187,9.0502 22.5126,10.7513Z"
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
</vector>
|
|
@ -9,52 +9,26 @@
|
||||||
android:id="@+id/mapView"
|
android:id="@+id/mapView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintBottom_toTopOf="@id/shareLocationContainer"
|
app:layout_constraintBottom_toTopOf="@id/shareLocationOptionsPicker"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:mapbox_renderTextureMode="true"
|
app:mapbox_renderTextureMode="true"
|
||||||
tools:background="#4F00" />
|
tools:background="#4F00" />
|
||||||
|
|
||||||
<androidx.constraintlayout.helper.widget.Flow
|
<im.vector.app.features.location.option.LocationSharingOptionPickerView
|
||||||
android:id="@+id/shareLocationContainer"
|
android:id="@+id/shareLocationOptionsPicker"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="72dp"
|
android:layout_height="wrap_content"
|
||||||
android:background="?android:colorBackground"
|
|
||||||
android:paddingStart="@dimen/layout_horizontal_margin"
|
|
||||||
android:paddingEnd="@dimen/layout_horizontal_margin"
|
|
||||||
app:constraint_referenced_ids="shareLocationImageView,shareLocationText"
|
|
||||||
app:flow_horizontalBias="0"
|
|
||||||
app:flow_horizontalGap="8dp"
|
|
||||||
app:flow_horizontalStyle="packed"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/shareLocationImageView"
|
|
||||||
android:layout_width="40dp"
|
|
||||||
android:layout_height="40dp"
|
|
||||||
android:background="@drawable/circle"
|
|
||||||
android:backgroundTint="?colorPrimary"
|
|
||||||
android:contentDescription="@string/a11y_location_share_icon"
|
|
||||||
android:padding="10dp"
|
|
||||||
android:src="@drawable/ic_attachment_location_white" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/shareLocationText"
|
|
||||||
style="@style/TextAppearance.Vector.Subtitle"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/location_share"
|
|
||||||
android:textColor="?colorPrimary"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
android:id="@+id/shareLocationGpsLoading"
|
android:id="@+id/shareLocationGpsLoading"
|
||||||
android:layout_width="32dp"
|
android:layout_width="32dp"
|
||||||
android:layout_height="32dp"
|
android:layout_height="32dp"
|
||||||
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/shareLocationContainer"
|
android:layout_marginBottom="@dimen/layout_vertical_margin"
|
||||||
app:layout_constraintEnd_toEndOf="@id/shareLocationContainer"
|
app:layout_constraintBottom_toBottomOf="@id/shareLocationOptionsPicker"
|
||||||
app:layout_constraintTop_toTopOf="@id/shareLocationContainer" />
|
app:layout_constraintEnd_toEndOf="@id/shareLocationOptionsPicker" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
43
vector/src/main/res/layout/view_location_sharing_option.xml
Normal file
43
vector/src/main/res/layout/view_location_sharing_option.xml
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.helper.widget.Flow
|
||||||
|
android:id="@+id/shareLocationOptionContainer"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="72dp"
|
||||||
|
android:background="?selectableItemBackground"
|
||||||
|
android:duplicateParentState="true"
|
||||||
|
android:paddingStart="@dimen/layout_horizontal_margin"
|
||||||
|
android:paddingEnd="@dimen/layout_horizontal_margin"
|
||||||
|
app:constraint_referenced_ids="shareLocationOptionIcon,shareLocationOptionTitle"
|
||||||
|
app:flow_horizontalBias="0"
|
||||||
|
app:flow_horizontalGap="8dp"
|
||||||
|
app:flow_horizontalStyle="packed"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/shareLocationOptionIcon"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:contentDescription="@string/a11y_location_share_option_pinned_icon"
|
||||||
|
tools:background="@drawable/circle"
|
||||||
|
tools:backgroundTint="?colorPrimary"
|
||||||
|
tools:padding="11dp"
|
||||||
|
tools:src="@drawable/ic_attachment_location_white" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/shareLocationOptionTitle"
|
||||||
|
style="@style/TextAppearance.Vector.Subtitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="?vctr_content_primary"
|
||||||
|
tools:text="@string/location_share_option_pinned" />
|
||||||
|
|
||||||
|
</merge>
|
|
@ -0,0 +1,75 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||||
|
|
||||||
|
<im.vector.app.features.location.option.LocationSharingOptionView
|
||||||
|
android:id="@+id/locationSharingOptionPinned"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/locationSharingOptionUserCurrent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:locShareIcon="@drawable/ic_attachment_location_white"
|
||||||
|
app:locShareIconBackground="@drawable/circle"
|
||||||
|
app:locShareIconBackgroundTint="?colorPrimary"
|
||||||
|
app:locShareIconDescription="@string/a11y_location_share_option_pinned_icon"
|
||||||
|
app:locShareIconPadding="11dp"
|
||||||
|
app:locShareTitle="@string/location_share_option_pinned" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/locationSharingOptionsDivider1"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:alpha="0.15"
|
||||||
|
android:background="?vctr_content_secondary"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/locationSharingOptionUserCurrent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/locationSharingOptionPinned" />
|
||||||
|
|
||||||
|
<im.vector.app.features.location.option.LocationSharingOptionView
|
||||||
|
android:id="@+id/locationSharingOptionUserCurrent"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/locationSharingOptionUserLive"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/locationSharingOptionPinned"
|
||||||
|
app:locShareIconBackground="@drawable/circle"
|
||||||
|
app:locShareIconBackgroundTint="?colorPrimary"
|
||||||
|
app:locShareIconDescription="@string/a11y_location_share_option_user_current_icon"
|
||||||
|
app:locShareIconPadding="3dp"
|
||||||
|
app:locShareTitle="@string/location_share_option_user_current" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/locationSharingOptionsDivider2"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:alpha="0.15"
|
||||||
|
android:background="?vctr_content_secondary"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/locationSharingOptionUserLive"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/locationSharingOptionUserCurrent" />
|
||||||
|
|
||||||
|
<im.vector.app.features.location.option.LocationSharingOptionView
|
||||||
|
android:id="@+id/locationSharingOptionUserLive"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/locationSharingOptionUserCurrent"
|
||||||
|
app:locShareIcon="@drawable/ic_attachment_location_live_white"
|
||||||
|
app:locShareIconBackground="@drawable/circle"
|
||||||
|
app:locShareIconBackgroundTint="?vctr_live_location"
|
||||||
|
app:locShareIconDescription="@string/a11y_location_share_option_user_live_icon"
|
||||||
|
app:locShareIconPadding="3dp"
|
||||||
|
app:locShareTitle="@string/location_share_option_user_live" />
|
||||||
|
|
||||||
|
</merge>
|
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<string name="notice_room_invite_no_invitee">%s\'s invitation</string>
|
<string name="notice_room_invite_no_invitee">%s\'s invitation</string>
|
||||||
<string name="notice_room_invite_no_invitee_by_you">Your invitation</string>
|
<string name="notice_room_invite_no_invitee_by_you">Your invitation</string>
|
||||||
<string name="notice_room_created">%1$s created the room</string>
|
<string name="notice_room_created">%1$s created the room</string>
|
||||||
|
@ -2924,9 +2924,17 @@
|
||||||
<!-- Location -->
|
<!-- Location -->
|
||||||
<string name="location_activity_title_static_sharing">Share location</string>
|
<string name="location_activity_title_static_sharing">Share location</string>
|
||||||
<string name="location_activity_title_preview">Location</string>
|
<string name="location_activity_title_preview">Location</string>
|
||||||
<string name="a11y_location_share_icon">Share location</string>
|
<!-- TODO delete -->
|
||||||
|
<string name="a11y_location_share_icon" tools:ignore="UnusedResources">Share location</string>
|
||||||
<string name="a11y_static_map_image">Map</string>
|
<string name="a11y_static_map_image">Map</string>
|
||||||
<string name="location_share">Share location</string>
|
<!-- TODO delete -->
|
||||||
|
<string name="location_share" tools:ignore="UnusedResources">Share location</string>
|
||||||
|
<string name="location_share_option_user_current">Share my current location</string>
|
||||||
|
<string name="a11y_location_share_option_user_current_icon">Share my current location</string>
|
||||||
|
<string name="location_share_option_user_live">Share live location</string>
|
||||||
|
<string name="a11y_location_share_option_user_live_icon">Share live location</string>
|
||||||
|
<string name="location_share_option_pinned">Share this location</string>
|
||||||
|
<string name="a11y_location_share_option_pinned_icon">Share this location</string>
|
||||||
<string name="location_not_available_dialog_title">${app_name} could not access your location</string>
|
<string name="location_not_available_dialog_title">${app_name} could not access your location</string>
|
||||||
<string name="location_not_available_dialog_content">${app_name} could not access your location. Please try again later.</string>
|
<string name="location_not_available_dialog_content">${app_name} could not access your location. Please try again later.</string>
|
||||||
<string name="location_share_external">Open with</string>
|
<string name="location_share_external">Open with</string>
|
||||||
|
|
Loading…
Reference in a new issue