added coach mark view
This commit is contained in:
parent
9a756f2b7a
commit
3dc0ef75ba
|
@ -0,0 +1,187 @@
|
||||||
|
/*
|
||||||
|
* 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.core.ui.views
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import android.transition.Fade
|
||||||
|
import android.util.TypedValue
|
||||||
|
import android.view.Gravity
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.PopupWindow
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import im.vector.app.R
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Coach mark widget, which could be shown against any part of layout to provide user with valuable information about it
|
||||||
|
* It shows a popup dialog which covers entire screen and consume first click on any location on the screen to dismiss itself
|
||||||
|
* @param context the context
|
||||||
|
* @param root fragment or activity root view to host popup dialog
|
||||||
|
*/
|
||||||
|
|
||||||
|
class CoachMarkView(val context: Context, val root: View) {
|
||||||
|
|
||||||
|
@SuppressLint("InflateParams")
|
||||||
|
private val view: View = LayoutInflater.from(context).inflate(R.layout.coach_mark, null)
|
||||||
|
private val markView: ViewGroup = view.findViewById(R.id.coach_mark)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Coach mark pointers should not be positioned on top of rounded corners to prevent gaps between background shape and pointer
|
||||||
|
* This constant must match `radius` property of coach mark's background shape
|
||||||
|
* */
|
||||||
|
private const val ANCHOR_MIN_HORIZONTAL_MARGIN_DP = 12f
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Show coach mark for specified anchor view
|
||||||
|
*
|
||||||
|
* @param stringId string resource id for text to be shown
|
||||||
|
* @param anchor view which will be pointed by coach mark
|
||||||
|
* @param gravity coach mark gravity. [Gravity.BOTTOM] to show mark below anchor, [Gravity.TOP] to show above.
|
||||||
|
*/
|
||||||
|
fun show(@StringRes stringId: Int, anchor: View, gravity: Int = Gravity.NO_GRAVITY, onDismiss: (() -> Unit)? = null) {
|
||||||
|
show(context.resources.getString(stringId), anchor, gravity, onDismiss)
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Show coach mark for specified anchor view
|
||||||
|
*
|
||||||
|
* @param text text to be shown
|
||||||
|
* @param anchor view which will be pointed by coach mark
|
||||||
|
* @param gravity coach mark gravity. [Gravity.BOTTOM] to show mark below anchor, [Gravity.TOP] to show above.
|
||||||
|
*/
|
||||||
|
fun show(text: CharSequence, anchor: View, gravity: Int = Gravity.NO_GRAVITY, onDismiss: (() -> Unit)? = null) {
|
||||||
|
view.findViewById<TextView>(R.id.coach_mark_content).text = text
|
||||||
|
|
||||||
|
val width = context.resources.displayMetrics.widthPixels - markView.paddingLeft - markView.paddingRight
|
||||||
|
|
||||||
|
markView.measure(
|
||||||
|
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
|
||||||
|
View.MeasureSpec.makeMeasureSpec(context.resources.displayMetrics.heightPixels, View.MeasureSpec.AT_MOST)
|
||||||
|
)
|
||||||
|
|
||||||
|
val anchorDimens = getAnchorDimens(anchor, root)
|
||||||
|
|
||||||
|
setMarkVerticalPosition(context, anchorDimens, view, markView.measuredHeight, gravity)
|
||||||
|
setPointerHorizontalPosition(anchorDimens, markView)
|
||||||
|
|
||||||
|
createPopupWindow(view, onDismiss).showAtLocation(root, Gravity.NO_GRAVITY, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setMarkVerticalPosition(context: Context, anchorDimens: Dimens, view: View, markHeight: Int, gravity: Int) {
|
||||||
|
val maxHeight = context.resources.displayMetrics.heightPixels
|
||||||
|
|
||||||
|
val resolvedGravity = resolveGravity(
|
||||||
|
maxHeight = maxHeight,
|
||||||
|
anchorDimens = anchorDimens,
|
||||||
|
markHeight = markHeight,
|
||||||
|
gravity = gravity
|
||||||
|
)
|
||||||
|
|
||||||
|
view.findViewById<View>(R.id.coach_mark_arrow_top).isVisible = resolvedGravity == Gravity.BOTTOM
|
||||||
|
view.findViewById<View>(R.id.coach_mark_arrow_bottom).isVisible = resolvedGravity == Gravity.TOP
|
||||||
|
|
||||||
|
val markY = when (resolvedGravity) {
|
||||||
|
Gravity.TOP -> anchorDimens.y - markHeight
|
||||||
|
Gravity.BOTTOM -> anchorDimens.y + anchorDimens.height
|
||||||
|
else -> maxHeight / 2 - markHeight / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
setMarkY(markY, view)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolveGravity(maxHeight: Int, anchorDimens: Dimens, markHeight: Int, gravity: Int): Int {
|
||||||
|
val topSpace = anchorDimens.y
|
||||||
|
val bottomSpace = maxHeight - (anchorDimens.y + anchorDimens.height)
|
||||||
|
|
||||||
|
val fitAbove = topSpace >= markHeight
|
||||||
|
val fitBelow = bottomSpace >= markHeight
|
||||||
|
|
||||||
|
if (!fitAbove && !fitBelow) {
|
||||||
|
return Gravity.CENTER
|
||||||
|
}
|
||||||
|
|
||||||
|
return when {
|
||||||
|
gravity == Gravity.TOP && fitAbove -> Gravity.TOP
|
||||||
|
gravity == Gravity.BOTTOM && fitBelow -> Gravity.BOTTOM
|
||||||
|
bottomSpace >= topSpace -> Gravity.BOTTOM //choose the bigger side if required gravity isn't provided or can't be applied
|
||||||
|
else -> Gravity.TOP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setMarkY(y: Int, markView: View) {
|
||||||
|
val mark = markView.findViewById<View>(R.id.coach_mark)
|
||||||
|
val params = (mark.layoutParams as ViewGroup.MarginLayoutParams)
|
||||||
|
params.setMargins(0, y, 0, 0)
|
||||||
|
mark.layoutParams = params
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
private fun createPopupWindow(containerView: View, onDismiss: (() -> Unit)?): PopupWindow =
|
||||||
|
PopupWindow(containerView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
||||||
|
.apply {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
enterTransition = Fade(Fade.IN)
|
||||||
|
exitTransition = Fade(Fade.OUT)
|
||||||
|
}
|
||||||
|
|
||||||
|
isTouchable = true
|
||||||
|
setTouchInterceptor { _, _ ->
|
||||||
|
dismiss()
|
||||||
|
onDismiss?.invoke()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setPointerHorizontalPosition(anchorDimens: Dimens, markView: View) {
|
||||||
|
val topArrow = markView.findViewById<View>(R.id.coach_mark_arrow_top)
|
||||||
|
val bottomArrow = markView.findViewById<View>(R.id.coach_mark_arrow_bottom)
|
||||||
|
|
||||||
|
val paddingHorizontal = markView.paddingStart
|
||||||
|
val anchorCenter = anchorDimens.x + anchorDimens.width / 2
|
||||||
|
val minHorizontalMargin = TypedValue.applyDimension(
|
||||||
|
TypedValue.COMPLEX_UNIT_DIP,
|
||||||
|
ANCHOR_MIN_HORIZONTAL_MARGIN_DP,
|
||||||
|
markView.context.resources.displayMetrics
|
||||||
|
).toInt()
|
||||||
|
|
||||||
|
var leftMargin = anchorCenter - paddingHorizontal - topArrow.measuredWidth / 2
|
||||||
|
leftMargin = leftMargin.coerceAtLeast(minHorizontalMargin)
|
||||||
|
|
||||||
|
(topArrow.layoutParams as ViewGroup.MarginLayoutParams).leftMargin = leftMargin
|
||||||
|
(bottomArrow.layoutParams as ViewGroup.MarginLayoutParams).leftMargin = leftMargin
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAnchorDimens(anchor: View, parent: View): Dimens {
|
||||||
|
val rootViewLoc = IntArray(2)
|
||||||
|
parent.getLocationOnScreen(rootViewLoc)
|
||||||
|
|
||||||
|
val anchorLoc = IntArray(2)
|
||||||
|
anchor.getLocationOnScreen(anchorLoc)
|
||||||
|
anchorLoc[1] -= rootViewLoc[1]
|
||||||
|
|
||||||
|
return Dimens(anchorLoc[0], anchorLoc[1], anchor.measuredWidth, anchor.measuredHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class Dimens(val x: Int, val y: Int, val width: Int, val height: Int)
|
||||||
|
}
|
|
@ -205,6 +205,8 @@ class VectorPreferences @Inject constructor(
|
||||||
private const val SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE = "SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE"
|
private const val SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE = "SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE"
|
||||||
private const val SETTINGS_LABS_ENABLE_LIVE_LOCATION = "SETTINGS_LABS_ENABLE_LIVE_LOCATION"
|
private const val SETTINGS_LABS_ENABLE_LIVE_LOCATION = "SETTINGS_LABS_ENABLE_LIVE_LOCATION"
|
||||||
|
|
||||||
|
private const val DID_SHOW_USER_SPACE_LEAVE_COACH_MARK = "DID_SHOW_USER_SPACE_LEAVE_COACH_MARK"
|
||||||
|
|
||||||
// This key will be used to identify clients with the old thread support enabled io.element.thread
|
// This key will be used to identify clients with the old thread support enabled io.element.thread
|
||||||
const val SETTINGS_LABS_ENABLE_THREAD_MESSAGES_OLD_CLIENTS = "SETTINGS_LABS_ENABLE_THREAD_MESSAGES"
|
const val SETTINGS_LABS_ENABLE_THREAD_MESSAGES_OLD_CLIENTS = "SETTINGS_LABS_ENABLE_THREAD_MESSAGES"
|
||||||
|
|
||||||
|
@ -1102,4 +1104,14 @@ class VectorPreferences @Inject constructor(
|
||||||
fun showLiveSenderInfo(): Boolean {
|
fun showLiveSenderInfo(): Boolean {
|
||||||
return defaultPrefs.getBoolean(SETTINGS_TIMELINE_SHOW_LIVE_SENDER_INFO, getDefault(R.bool.settings_timeline_show_live_sender_info_default))
|
return defaultPrefs.getBoolean(SETTINGS_TIMELINE_SHOW_LIVE_SENDER_INFO, getDefault(R.bool.settings_timeline_show_live_sender_info_default))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun didShowUserSpaceLeaveCoachMark(): Boolean {
|
||||||
|
return defaultPrefs.getBoolean(DID_SHOW_USER_SPACE_LEAVE_COACH_MARK, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setDidShowUserSpaceLeaveCoachMark() {
|
||||||
|
defaultPrefs.edit {
|
||||||
|
putBoolean(DID_SHOW_USER_SPACE_LEAVE_COACH_MARK, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,4 +26,5 @@ sealed class SpaceLeaveAdvanceViewAction : VectorViewModelAction {
|
||||||
object ClearError : SpaceLeaveAdvanceViewAction()
|
object ClearError : SpaceLeaveAdvanceViewAction()
|
||||||
object SelectAll : SpaceLeaveAdvanceViewAction()
|
object SelectAll : SpaceLeaveAdvanceViewAction()
|
||||||
object SelectNone : SpaceLeaveAdvanceViewAction()
|
object SelectNone : SpaceLeaveAdvanceViewAction()
|
||||||
|
object CoachMarkDismissed : SpaceLeaveAdvanceViewAction()
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,8 @@ data class SpaceLeaveAdvanceViewState(
|
||||||
val currentFilter: String = "",
|
val currentFilter: String = "",
|
||||||
val leaveState: Async<Unit> = Uninitialized,
|
val leaveState: Async<Unit> = Uninitialized,
|
||||||
val isFilteringEnabled: Boolean = false,
|
val isFilteringEnabled: Boolean = false,
|
||||||
val isLastAdmin: Boolean = false
|
val isLastAdmin: Boolean = false,
|
||||||
|
val showCoachMark: Boolean = false
|
||||||
) : MavericksState {
|
) : MavericksState {
|
||||||
|
|
||||||
constructor(args: SpaceBottomSheetSettingsArgs) : this(
|
constructor(args: SpaceBottomSheetSettingsArgs) : this(
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.app.features.spaces.leave
|
package im.vector.app.features.spaces.leave
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.Gravity
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
@ -32,6 +33,7 @@ import im.vector.app.R
|
||||||
import im.vector.app.core.extensions.cleanup
|
import im.vector.app.core.extensions.cleanup
|
||||||
import im.vector.app.core.extensions.configureWith
|
import im.vector.app.core.extensions.configureWith
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
|
import im.vector.app.core.ui.views.CoachMarkView
|
||||||
import im.vector.app.core.utils.ToggleableAppBarLayoutBehavior
|
import im.vector.app.core.utils.ToggleableAppBarLayoutBehavior
|
||||||
import im.vector.app.databinding.FragmentSpaceLeaveAdvancedBinding
|
import im.vector.app.databinding.FragmentSpaceLeaveAdvancedBinding
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
@ -113,6 +115,12 @@ class SpaceLeaveAdvancedFragment @Inject constructor(
|
||||||
views.appBarLayout.setExpanded(false)
|
views.appBarLayout.setExpanded(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state.showCoachMark) {
|
||||||
|
CoachMarkView(requireContext(), views.root).show(R.string.space_leave_coach_mark_text, views.coachMarkGuide, Gravity.BOTTOM) {
|
||||||
|
viewModel.handle(SpaceLeaveAdvanceViewAction.CoachMarkDismissed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updateAppBarBehaviorState(state)
|
updateAppBarBehaviorState(state)
|
||||||
updateRadioButtonsState(state)
|
updateRadioButtonsState(state)
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
import im.vector.app.core.platform.EmptyViewEvents
|
import im.vector.app.core.platform.EmptyViewEvents
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -52,7 +53,8 @@ import timber.log.Timber
|
||||||
class SpaceLeaveAdvancedViewModel @AssistedInject constructor(
|
class SpaceLeaveAdvancedViewModel @AssistedInject constructor(
|
||||||
@Assisted val initialState: SpaceLeaveAdvanceViewState,
|
@Assisted val initialState: SpaceLeaveAdvanceViewState,
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
private val appStateHandler: AppStateHandler
|
private val appStateHandler: AppStateHandler,
|
||||||
|
private val vectorPreferences: VectorPreferences
|
||||||
) : VectorViewModel<SpaceLeaveAdvanceViewState, SpaceLeaveAdvanceViewAction, EmptyViewEvents>(initialState) {
|
) : VectorViewModel<SpaceLeaveAdvanceViewState, SpaceLeaveAdvanceViewAction, EmptyViewEvents>(initialState) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -99,7 +101,7 @@ class SpaceLeaveAdvancedViewModel @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
|
|
||||||
setState {
|
setState {
|
||||||
copy(allChildren = Success(children))
|
copy(allChildren = Success(children), showCoachMark = !vectorPreferences.didShowUserSpaceLeaveCoachMark())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,9 +115,15 @@ class SpaceLeaveAdvancedViewModel @AssistedInject constructor(
|
||||||
is SpaceLeaveAdvanceViewAction.ToggleSelection -> handleSelectionToggle(action)
|
is SpaceLeaveAdvanceViewAction.ToggleSelection -> handleSelectionToggle(action)
|
||||||
SpaceLeaveAdvanceViewAction.DoLeave -> handleLeave()
|
SpaceLeaveAdvanceViewAction.DoLeave -> handleLeave()
|
||||||
SpaceLeaveAdvanceViewAction.SelectAll -> handleSelectAll()
|
SpaceLeaveAdvanceViewAction.SelectAll -> handleSelectAll()
|
||||||
|
SpaceLeaveAdvanceViewAction.CoachMarkDismissed -> handleCoachMarkDismissed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleCoachMarkDismissed() = withState {
|
||||||
|
vectorPreferences.setDidShowUserSpaceLeaveCoachMark()
|
||||||
|
setState { copy(showCoachMark = false) }
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleSelectAll() = withState { state ->
|
private fun handleSelectAll() = withState { state ->
|
||||||
val filteredRooms = (state.allChildren as? Success)?.invoke()?.filter {
|
val filteredRooms = (state.allChildren as? Success)?.invoke()?.filter {
|
||||||
it.name.contains(state.currentFilter, true)
|
it.name.contains(state.currentFilter, true)
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
|
||||||
|
<solid android:color="?colorPrimary" />
|
||||||
|
|
||||||
|
<corners android:radius="12dp" />
|
||||||
|
|
||||||
|
</shape>
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="21dp"
|
||||||
|
android:height="14dp"
|
||||||
|
android:viewportWidth="21"
|
||||||
|
android:viewportHeight="14">
|
||||||
|
<path
|
||||||
|
android:pathData="M10.5,0L20.459,13.5L0.541,13.5L10.5,0Z"
|
||||||
|
android:fillColor="#0DBD8B"/>
|
||||||
|
</vector>
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout 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:id="@+id/root"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/coach_mark"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="start|top"
|
||||||
|
android:paddingHorizontal="20dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
tools:ignore="UselessParent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/coach_mark_arrow_top"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="-1dp"
|
||||||
|
android:scaleType="fitXY"
|
||||||
|
android:src="@drawable/ic_coach_arrow"
|
||||||
|
app:tint="?colorPrimary"
|
||||||
|
tools:ignore="ContentDescription"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/coach_mark_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/bg_coach_mark"
|
||||||
|
android:backgroundTint="?colorPrimary"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:paddingVertical="12dp"
|
||||||
|
android:textAppearance="@style/TextAppearance.Vector.Body"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="@color/palette_white"
|
||||||
|
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/coach_mark_arrow_bottom"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:layout_marginTop="-1dp"
|
||||||
|
android:rotation="180"
|
||||||
|
android:scaleType="fitXY"
|
||||||
|
android:src="@drawable/ic_coach_arrow"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:tint="?colorPrimary"
|
||||||
|
tools:ignore="ContentDescription"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
|
@ -136,4 +136,11 @@
|
||||||
android:text="@string/leave_space" />
|
android:text="@string/leave_space" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:id="@+id/coach_mark_guide"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginBottom="150dp"
|
||||||
|
android:layout_gravity="bottom|center_horizontal"/>
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
|
@ -2846,6 +2846,7 @@
|
||||||
<string name="space_leave_prompt_msg_only_you">You are the only person here. If you leave, no one will be able to join in the future, including you.</string>
|
<string name="space_leave_prompt_msg_only_you">You are the only person here. If you leave, no one will be able to join in the future, including you.</string>
|
||||||
<string name="space_leave_prompt_msg_private">You won\'t be able to rejoin unless you are re-invited.</string>
|
<string name="space_leave_prompt_msg_private">You won\'t be able to rejoin unless you are re-invited.</string>
|
||||||
<string name="space_leave_prompt_msg_as_admin">You\'re the only admin of this space. Leaving it will mean no one has control over it.</string>
|
<string name="space_leave_prompt_msg_as_admin">You\'re the only admin of this space. Leaving it will mean no one has control over it.</string>
|
||||||
|
<string name="space_leave_coach_mark_text">Use this form to leave anything at the same time.</string>
|
||||||
|
|
||||||
<!-- TODO delete -->
|
<!-- TODO delete -->
|
||||||
<string name="leave_all_rooms_and_spaces" tools:ignore="UnusedResources">Leave all rooms and spaces</string>
|
<string name="leave_all_rooms_and_spaces" tools:ignore="UnusedResources">Leave all rooms and spaces</string>
|
||||||
|
|
Loading…
Reference in New Issue