diff --git a/app/build.gradle b/app/build.gradle index ca8183c..97910e5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -88,6 +88,7 @@ dependencies { implementation(libs.okhttp.logging) implementation(libs.retrofit) implementation(libs.retrofit.kotlin.serialization) + //implementation 'com.github.hyy920109:GuidePro:1.0.3' } \ No newline at end of file diff --git a/app/src/main/java/com/gamedog/vididin/main/BeginnerGiftDialog.kt b/app/src/main/java/com/gamedog/vididin/main/BeginnerGiftDialog.kt index 4ffa0a6..57232d9 100644 --- a/app/src/main/java/com/gamedog/vididin/main/BeginnerGiftDialog.kt +++ b/app/src/main/java/com/gamedog/vididin/main/BeginnerGiftDialog.kt @@ -1,12 +1,9 @@ package com.gamedog.vididin.main import android.app.Activity -import com.ama.core.architecture.util.eventbus.NotifyMan import com.ama.core.architecture.util.setOnClickBatch import com.ama.core.architecture.widget.BindingDialog -import com.gamedog.vididin.VididinEvents import com.vididin.real.money.game.databinding.DialogBeginnerGiftBinding -import com.gamedog.vididin.router.Router class BeginnerGiftDialog(activity: Activity) : BindingDialog(activity, DialogBeginnerGiftBinding::inflate) { @@ -26,10 +23,10 @@ class BeginnerGiftDialog(activity: Activity) : BindingDialog { - /*if (mActivity is MainActivity) { + if (mActivity is MainActivity) { (mActivity as MainActivity).switchTab(1) } - NotifyMan.instance().sendEvent(VididinEvents.EVENT_JUMP_2_FIRST_WITHDRAW, null)*/ + //NotifyMan.instance().sendEvent(VididinEvents.EVENT_JUMP_2_FIRST_WITHDRAW, null)*/ dismiss() } } @@ -37,9 +34,7 @@ class BeginnerGiftDialog(activity: Activity) : BindingDialog(), OnTab lifecycleScope.launch { mTaskConfig = TaskManager.instance().getTaskConfig() } + + binding?.llTaskGame?.postDelayed({ + showGuide() + }, 50) } private fun addDailySubTasks() { @@ -422,4 +434,39 @@ class TasksFragment : AppViewsFragment(), OnTab } + private fun showGuide() { + HighlightPro.with(this@TasksFragment) + .setHighlightParameter { + HighlightParameter.Builder() + .setHighlightView(binding!!.goldContainer) + .setTipsViewId(R.layout.guide_step_gold) + .setHighlightShape(RectShape(10.dp, 10.dp, 10.dp)) + .setHighlightHorizontalPadding(0.dp) + .setConstraints(Constraints.TopToBottomOfHighlight + Constraints.EndToEndOfHighlight) + .setMarginOffset(MarginOffset(top = -20.dp.toInt())) + .build() + } + .setHighlightParameter { + HighlightParameter.Builder() + .setHighlightView(binding!!.cashContainer) + .setTipsViewId(R.layout.guide_step_cash) + .setHighlightShape(RectShape(10.dp, 10.dp, 10.dp)) + .setHighlightHorizontalPadding(0.dp) + .setConstraints(Constraints.TopToBottomOfHighlight + Constraints.EndToEndOfHighlight) + .setMarginOffset(MarginOffset(top = -20.dp.toInt())) + .build() + } + .setBackgroundColor("#cc000000".toColorInt()) + .setOnShowCallback { index -> + + } + .setOnDismissCallback { + + } + .interceptBackPressed(true) + .show() + + + } + } \ No newline at end of file diff --git a/app/src/main/res/layout/guide_step_cash.xml b/app/src/main/res/layout/guide_step_cash.xml new file mode 100644 index 0000000..f50fe1d --- /dev/null +++ b/app/src/main/res/layout/guide_step_cash.xml @@ -0,0 +1,33 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/guide_step_gold.xml b/app/src/main/res/layout/guide_step_gold.xml new file mode 100644 index 0000000..3a8459f --- /dev/null +++ b/app/src/main/res/layout/guide_step_gold.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/vididinapp_feature_message_fragment_message.xml b/app/src/main/res/layout/vididinapp_feature_message_fragment_message.xml index 86d604e..4a1d1ef 100644 --- a/app/src/main/res/layout/vididinapp_feature_message_fragment_message.xml +++ b/app/src/main/res/layout/vididinapp_feature_message_fragment_message.xml @@ -32,138 +32,147 @@ android:orientation="horizontal" android:layout_marginTop="15dp"> - - - - + android:layout_weight="1"> - + - + android:orientation="horizontal" + android:gravity="center_vertical" + android:layout_gravity="center_vertical" + android:layout_marginTop="10dp"> + + + + + + + + + - - - - - - - + android:layout_marginLeft="10dp"> - + android:layout_height="match_parent" + android:orientation="vertical" + android:background="@mipmap/task_bg_cash" + android:padding="10dp" + > + + + + + + + + + + - - - - - + - - - - \ No newline at end of file diff --git a/app/src/main/res/mipmap-xxhdpi/guide_bg_left.webp b/app/src/main/res/mipmap-xxhdpi/guide_bg_left.webp new file mode 100644 index 0000000..656e245 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/guide_bg_left.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/guide_bg_right.webp b/app/src/main/res/mipmap-xxhdpi/guide_bg_right.webp new file mode 100644 index 0000000..d8f4fc5 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/guide_bg_right.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/guide_finger_b.webp b/app/src/main/res/mipmap-xxhdpi/guide_finger_b.webp new file mode 100644 index 0000000..c20031b Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/guide_finger_b.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/guide_finger_s.webp b/app/src/main/res/mipmap-xxhdpi/guide_finger_s.webp new file mode 100644 index 0000000..19bf97e Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/guide_finger_s.webp differ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 439b655..d94fff5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -149,5 +149,7 @@ Consumo de moedas % Vincular conta para saque + Clique no botão para converter todas \nas “Moedas” em “Dinheiro”. + Clique aqui para ir para o saque. \ No newline at end of file diff --git a/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/HighlightPro.kt b/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/HighlightPro.kt new file mode 100644 index 0000000..7b19320 --- /dev/null +++ b/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/HighlightPro.kt @@ -0,0 +1,138 @@ +package com.ama.core.architecture.highlightpro + +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.fragment.app.Fragment +import com.ama.core.architecture.highlightpro.parameter.HighlightParameter +import com.ama.core.architecture.highlightpro.view.MaskContainer + + +class HighlightPro : HighlightViewInteractiveAction { + + private val highlightProImpl: HighlightProImpl + + private constructor(activity: Activity) { + highlightProImpl = HighlightProImpl(activity) + } + + private constructor(fragment: Fragment) { + highlightProImpl = HighlightProImpl(fragment) + } + + private constructor(view: ViewGroup) { + highlightProImpl = HighlightProImpl(view) + } + + /** + * [show] is a final method to call then show a guide or a popup window + */ + override fun show() { + highlightProImpl.show() + } + + /** + * Dismiss [HighlightPro] auto in common. + * we can manually call this method([dismiss]) to dismiss [HighlightPro] + */ + override fun dismiss() { + highlightProImpl.dismiss() + } + + /** + * Set your mask background [color] of [MaskContainer] + */ + fun setBackgroundColor(color: Int): HighlightPro { + highlightProImpl.setBackgroundColor(color) + return this + } + + /** + * Set a List of [HighlightParameter] so we can show more Highlight at once + */ + fun setHighlightParameters(highlightParameters: List): HighlightPro { + highlightProImpl.setGuideViewParameters(highlightParameters) + return this + } + + /** + * Set single [HighlightParameter] + * + * With [setHighlightParameter] we can use kotlin function with return + */ + fun setHighlightParameter(block: ()-> HighlightParameter): HighlightPro { + highlightProImpl.setGuideViewParameter(block = block) + return this + } + + /** + * [showCallback] is a function which will be invoked when the [HighlightPro] show + */ + fun setOnShowCallback(showCallback: (Int) -> Unit) : HighlightPro{ + highlightProImpl.setOnShowCallback(showCallback = showCallback) + return this + } + + /** + * [dismissCallback] is a function which will be invoked when the [HighlightPro] dismiss + */ + fun setOnDismissCallback(dismissCallback: () -> Unit) : HighlightPro{ + highlightProImpl.setOnDismissCallback(dismissCallback) + return this + } + + /** + * [clickCallback] is a callback when user clicked any place in [MaskContainer] + */ + fun setOnMaskViewClickCallback(clickCallback: (View) -> Unit): HighlightPro { + highlightProImpl.setOnGuideViewClickCallback(clickCallback) + return this + } + + /** + * [enableHighlight] give you access to make Highlight dismiss and show UI like popup window + */ + fun enableHighlight(enableHighlight: Boolean): HighlightPro { + highlightProImpl.enableHighlight(enableHighlight) + return this + } + + /** + * [interceptBackPressed] is [true] will intercept activity onBackPressed and [HighlightPro] will dismiss + */ + fun interceptBackPressed(interceptBackPressed: Boolean): HighlightPro { + highlightProImpl.interceptBackPressed(interceptBackPressed) + return this + } + + fun needAnchorTipView(needAnchorTipView: Boolean) : HighlightPro{ + highlightProImpl.needAnchorTipView(needAnchorTipView) + return this + } + + companion object { + + /** + * DecorView of [activity] treat as the rootView + */ + fun with(activity: Activity): HighlightPro { + return HighlightPro(activity) + } + + /** + * DecorView of [fragment]'s [activity] treat as the rootView + */ + fun with(fragment: Fragment): HighlightPro { + return HighlightPro(fragment) + } + + /** + * the [container] treat as rootView and the container should be a FrameLayout or a viewGroup extends FrameLayout + * to ensure the UI can display normally + */ + fun with(container: FrameLayout): HighlightPro { + return HighlightPro(container) + } + } +} \ No newline at end of file diff --git a/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/HighlightProImpl.kt b/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/HighlightProImpl.kt new file mode 100644 index 0000000..8f3a24b --- /dev/null +++ b/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/HighlightProImpl.kt @@ -0,0 +1,226 @@ +package com.ama.core.architecture.highlightpro + +import android.app.Activity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.doOnPreDraw +import androidx.fragment.app.Fragment +import com.ama.core.architecture.highlightpro.parameter.HighlightParameter +import com.ama.core.architecture.highlightpro.shape.RectShape +import com.ama.core.architecture.highlightpro.util.calculateHighLightViewRect +import com.ama.core.architecture.highlightpro.util.dp +import com.ama.core.architecture.highlightpro.util.isAttachToWindow +import com.ama.core.architecture.highlightpro.view.MaskContainer + + +internal class HighlightProImpl : HighlightViewInteractiveAction { + + companion object { + const val TAG = "HYY-GuideProImpl" + } + + private var isFragmentRoot: Boolean = false + private var fragmentRootView: View? = null + private var curIndex: Int = 0 + private val highlightParameters: MutableList> = mutableListOf() + private var hasShow: Boolean = false + private val rootView: ViewGroup + private val maskContainer: MaskContainer + private var released = false + private var showCallback: ((index: Int) -> Unit)? = null + private var dismissCallback: (() -> Unit)? = null + private var clickCallback: ((View) -> Unit)? = null + private var autoNext = true + private var needAnchorTipView = true + + // private var + private val onClickListener = View.OnClickListener { + clickCallback?.invoke(it) + if (autoNext) { + showNextHighLightView() + } + } + + internal constructor(activity: Activity) { + rootView = activity.window.decorView as ViewGroup + maskContainer = MaskContainer(activity) + } + + internal constructor(view: ViewGroup) { + rootView = view + maskContainer = MaskContainer(view.context) + } + + internal constructor(fragment: Fragment) { + if (fragment.view == null) + throw IllegalStateException("The fragment's view not created yet,please call this after fragment's onViewCreated()") + if (fragment.isDetached) + throw IllegalStateException("The fragment have detached. It is not attach to an activity!") + rootView = fragment.requireActivity().window.decorView as ViewGroup + fragmentRootView = fragment.view + isFragmentRoot = true + maskContainer = MaskContainer(rootView.context) + + } + + override fun show() { + if (released) return + println("$TAG show") + //todo give user access to intercept click event +// if (!intercept) { + maskContainer.setOnClickListener(onClickListener) +// } + + //if constructor's param is activity or view we care about rootView's attachedToWindow + //if constructor's param is fragment we care about [fragmentRootView]'s width is not 0 + if ((isFragmentRoot.not() && rootView.isAttachToWindow()) || + (isFragmentRoot && fragmentRootView?.width != 0) + ) { + if (maskContainer.parent == null) { + //add guideViewContainer to rootView + rootView.addView( + maskContainer, + ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + ) + if (maskContainer.interceptBackPressed) { + maskContainer.apply { + //three line below enable maskContainer focusable and got focused + isFocusable = true + isFocusableInTouchMode = true + requestFocus() + setOnBackPressedCallback { + showNextHighLightView() + } + } + } + + showNextHighLightView() + } + } else { + if (isFragmentRoot) { + fragmentRootView?.doOnPreDraw { + println("$TAG fragmentRootView pre draw") + //ensure this method will be call once + if (hasShow) return@doOnPreDraw + hasShow = false + show() + } + } else { + rootView.doOnPreDraw { + //ensure this method will be call once + if (hasShow) return@doOnPreDraw + hasShow = false + show() + } + } + + } + + } + + /** + * this is the function which real show highLightView rect and tipView + */ + private fun showNextHighLightView() { + if (released) return + + println("$TAG showNextHighLightView") + + if (hasHighLightView().not()) { + dismiss() + } else { + highlightParameters[0].forEach { + checkOrInitParameter(it) + } + showCallback?.invoke(curIndex) + curIndex++ + maskContainer.setRootWidth(rootView.width-rootView.paddingLeft-rootView.paddingRight)//ignore padding + maskContainer.setRootHeight(rootView.height-rootView.paddingTop-rootView.paddingBottom)//ignore padding + maskContainer.setHighLightParameters(highlightParameters[0]) + highlightParameters.removeAt(0) + } + } + + /** + * check valid for each parameter if invalid set default value + */ + private fun checkOrInitParameter(parameter: HighlightParameter) { + + if (parameter.highLightView == null) { + parameter.highLightView = rootView.findViewById(parameter.highLightViewId) + } + + if (parameter.tipsView == null && checkTipViewIdIsValid(parameter)) { + parameter.tipsView = LayoutInflater.from(maskContainer.context) + .inflate(parameter.tipsViewId, maskContainer, false) + } + + if (parameter.highlightShape == null) { + parameter.highlightShape = RectShape(2f.dp, 2f.dp, 2f.dp) + } + + parameter.calculateHighLightViewRect(rootView) + } + + private fun checkTipViewIdIsValid(parameter: HighlightParameter): Boolean = parameter.tipsViewId != -1 + + + private fun hasHighLightView(): Boolean = highlightParameters.isNotEmpty() + + override fun dismiss() { + if (released) return + //release every thing + released = true + //todo if we want have a dismiss animation these code need rewrite + maskContainer.isFocusable = false + maskContainer.clearFocus() + + rootView.removeView(maskContainer) + maskContainer.removeAllViews() + dismissCallback?.invoke() + } + + fun setBackgroundColor(color: Int) { + maskContainer.setBackgroundColor(color) + } + + fun setGuideViewParameters(highlightParameters: List) { + if (released) return + this.highlightParameters.add(highlightParameters) + } + + fun setGuideViewParameter(block: () -> HighlightParameter) { + if (released) return + highlightParameters.add(listOf(block.invoke())) + } + + fun setOnShowCallback(showCallback: (Int) -> Unit) { + this.showCallback = showCallback + } + + fun setOnDismissCallback(dismissCallback: () -> Unit) { + this.dismissCallback = dismissCallback + } + + fun setOnGuideViewClickCallback(clickCallback: (View) -> Unit) { + this.clickCallback = clickCallback + } + + fun enableHighlight(enableHighlight: Boolean) { + this.maskContainer.enableHighlight = enableHighlight + } + + fun interceptBackPressed(interceptBackPressed: Boolean) { + this.maskContainer.interceptBackPressed = interceptBackPressed + } + + fun needAnchorTipView(needAnchorTipView: Boolean) { + this.needAnchorTipView = needAnchorTipView + this.maskContainer.needAnchorTipView = needAnchorTipView + } + +} \ No newline at end of file diff --git a/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/HighlightViewInteractiveAction.kt b/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/HighlightViewInteractiveAction.kt new file mode 100644 index 0000000..f4eac45 --- /dev/null +++ b/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/HighlightViewInteractiveAction.kt @@ -0,0 +1,16 @@ +package com.ama.core.architecture.highlightpro + + +interface HighlightViewInteractiveAction { + + /** + * show [HighlightPro] + */ + fun show() + + /** + * dismiss [HighlightPro] + */ + fun dismiss() + +} \ No newline at end of file diff --git a/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/parameter/Constraints.kt b/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/parameter/Constraints.kt new file mode 100644 index 0000000..a138386 --- /dev/null +++ b/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/parameter/Constraints.kt @@ -0,0 +1,22 @@ +package com.ama.core.architecture.highlightpro.parameter + + +sealed class Constraints { + //vertical constraints + object TopToTopOfHighlight : Constraints() + object BottomToTopOfHighlight : Constraints() + object BottomToBottomOfHighlight : Constraints() + object TopToBottomOfHighlight : Constraints() + //horizontal constraints + object StartToStartOfHighlight : Constraints() + object StartToEndOfHighlight : Constraints() + object EndToEndOfHighlight : Constraints() + object EndToStartOfHighlight : Constraints() + //center constraints + object CenterHorizontalOfHighlight : Constraints() + object CenterVerticalOfHighlight : Constraints() + + operator fun plus(locationGravity: Constraints): List { + return listOf(this, locationGravity) + } +} diff --git a/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/parameter/HighlightParameter.kt b/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/parameter/HighlightParameter.kt new file mode 100644 index 0000000..11ffb63 --- /dev/null +++ b/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/parameter/HighlightParameter.kt @@ -0,0 +1,153 @@ +package com.ama.core.architecture.highlightpro.parameter + +import android.graphics.RectF +import android.view.View +import android.view.animation.Animation +import com.ama.core.architecture.highlightpro.shape.HighlightShape + + +class HighlightParameter { + + internal var highLightViewId: Int = -1 + + internal var highLightView: View? = null + + internal var tipsViewId: Int = -1 + + internal var tipsView: View? = null + + internal var highlightShape: HighlightShape? = null + + internal var rect: RectF = RectF() + + internal var horizontalPadding: Float = 0f + + internal var verticalPadding: Float = 0f + + internal var marginOffset: MarginOffset = MarginOffset() + + internal var offsetX = 0 + internal var offsetY = 0 + + internal val constraints = + mutableListOf(Constraints.TopToBottomOfHighlight, Constraints.StartToStartOfHighlight) + + internal var tipViewDisplayAnimation: Animation? = null + + fun setHighLightViewId(viewId: Int) { + this.highLightViewId = viewId + } + + fun setHighLightView(highLightView: View) { + this.highLightView = highLightView + } + + operator fun plus(highlightParameter: HighlightParameter): List { + return listOf(this, highlightParameter) + } + + class Builder { + private val highlightParameter: HighlightParameter = HighlightParameter() + + /** + * [viewId] is the highLightView's id + * it will be override by [setHighlightView]. + */ + fun setHighlightViewId(viewId: Int): Builder { + highlightParameter.highLightViewId = viewId + return this + } + + /** + * [highLightView] is the view which you want to make it highLight + * And it will make [setHighlightViewId] useless. + */ + fun setHighlightView(highLightView: View): Builder { + highlightParameter.highLightView = highLightView + return this + } + + /** + * [viewId] is the tipsView's id + * it will be override by [setTipsView]. + */ + fun setTipsViewId(viewId: Int): Builder { + highlightParameter.tipsViewId = viewId + return this + } + + /** + * [tipsView] is the view which you want to add it on [GuideViewContainer] + * And it will make [setTipsViewId] useless. + */ + fun setTipsView(tipsView: View): Builder { + highlightParameter.tipsView = tipsView + return this + } + + /** + * [highlightShape] is the highlight out rect shape and you can + * custom your shape reference our shapes exit. + */ + fun setHighlightShape(highlightShape: HighlightShape): Builder { + highlightParameter.highlightShape = highlightShape + return this + } + + /** + * [padding] is the vertical dimension padding use by expand the rect area of + * highlight. + */ + fun setHighlightVerticalPadding(padding: Float): Builder { + highlightParameter.verticalPadding = padding + return this + } + + /** + * [marginOffset] is the extra dimension use by position highlight rect. + */ + fun setMarginOffset(marginOffset: MarginOffset): Builder { + highlightParameter.marginOffset = marginOffset + return this + } + + /** + * [constraints] is a list of constraints provides some constraints of highlight + * rect position. + */ + fun setConstraints(constraints: List): Builder { + highlightParameter.constraints.clear() + highlightParameter.constraints.addAll(constraints) + return this + } + + /** + * [padding] is the horizontal dimension padding use by expand the rect area of + * highlight. + */ + fun setHighlightHorizontalPadding(padding: Float): Builder { + highlightParameter.horizontalPadding = padding + return this + } + + /** + * [tipViewDisplayAnimation] is the animation to display for Highlight tips view + */ + fun setTipViewDisplayAnimation(tipViewDisplayAnimation: Animation?): Builder { + highlightParameter.tipViewDisplayAnimation = tipViewDisplayAnimation + return this + } + + fun offsetX(offsetX: Int): Builder { + highlightParameter.offsetX = offsetX + return this + } + + fun offsetY(offsetY: Int): Builder { + highlightParameter.offsetY = offsetY + return this + } + + fun build(): HighlightParameter = highlightParameter + } +} \ No newline at end of file diff --git a/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/parameter/MarginOffset.kt b/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/parameter/MarginOffset.kt new file mode 100644 index 0000000..4715625 --- /dev/null +++ b/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/parameter/MarginOffset.kt @@ -0,0 +1,9 @@ +package com.ama.core.architecture.highlightpro.parameter + + +class MarginOffset( + val start: Int = 0, + val top: Int = 0, + val end: Int = 0, + val bottom: Int = 0 +) \ No newline at end of file diff --git a/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/shape/CircleShape.kt b/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/shape/CircleShape.kt new file mode 100644 index 0000000..e470afb --- /dev/null +++ b/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/shape/CircleShape.kt @@ -0,0 +1,17 @@ +package com.ama.core.architecture.highlightpro.shape + +import android.graphics.Path +import android.graphics.RectF +import kotlin.math.max + + +class CircleShape(private val radius: Float = 0f) : HighlightShape(radius) { + + override fun initRect(rectF: RectF) { + super.initRect(rectF) + rect?.run { + path.reset() + path.addCircle((left+right)/2,(top+bottom)/2, max(height(),width()) /2,Path.Direction.CW) + } + } +} \ No newline at end of file diff --git a/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/shape/HighlightShape.kt b/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/shape/HighlightShape.kt new file mode 100644 index 0000000..81ff828 --- /dev/null +++ b/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/shape/HighlightShape.kt @@ -0,0 +1,50 @@ +package com.ama.core.architecture.highlightpro.shape + +import android.graphics.* + + + +open class HighlightShape(val blurRadius: Float = 0.0f) { + + private lateinit var paint: Paint + + //clip path + internal val path by lazy { + Path() + } + + init { + paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + isDither = true + color = Color.WHITE + } + //paint blur style + if (blurRadius > 0) { + paint.maskFilter = BlurMaskFilter(blurRadius, BlurMaskFilter.Blur.SOLID) + } + } + + protected var rect: RectF? = null + + /** + * init rect child should override initRect and init path + * */ + open fun initRect(rectF: RectF) { + this.rect = rectF + } + + /** + * draw our path + */ + fun drawPath(canvas: Canvas) { + rect?.run { + if (isEmpty.not()) { + canvas.drawPath(path, paint) + } + } + } + + fun setPaint(paint: Paint) { + this.paint = paint + } +} \ No newline at end of file diff --git a/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/shape/OvalShape.kt b/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/shape/OvalShape.kt new file mode 100644 index 0000000..d4cabb0 --- /dev/null +++ b/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/shape/OvalShape.kt @@ -0,0 +1,16 @@ +package com.ama.core.architecture.highlightpro.shape + +import android.graphics.Path +import android.graphics.RectF + + +class OvalShape(private val radius: Float = 0f) : HighlightShape(radius) { + + override fun initRect(rectF: RectF) { + super.initRect(rectF) + rect?.run { + path.reset() + path.addOval(this,Path.Direction.CW) + } + } +} \ No newline at end of file diff --git a/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/shape/RectShape.kt b/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/shape/RectShape.kt new file mode 100644 index 0000000..fb5ba08 --- /dev/null +++ b/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/shape/RectShape.kt @@ -0,0 +1,16 @@ +package com.ama.core.architecture.highlightpro.shape + +import android.graphics.Path +import android.graphics.RectF + + +class RectShape(private val rx: Float = 0f, private val ry: Float = 0f, private val radius: Float = 0f) : HighlightShape(radius) { + + override fun initRect(rectF: RectF) { + super.initRect(rectF) + rect?.run { + path.reset() + path.addRoundRect(this, rx, ry, Path.Direction.CW) + } + } +} \ No newline at end of file diff --git a/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/util/ViewUtils.kt b/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/util/ViewUtils.kt new file mode 100644 index 0000000..4080b9f --- /dev/null +++ b/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/util/ViewUtils.kt @@ -0,0 +1,88 @@ +package com.ama.core.architecture.highlightpro.util + +import android.content.res.Resources +import android.graphics.RectF +import android.os.Build +import android.util.TypedValue +import android.view.View +import android.view.ViewGroup +import com.ama.core.architecture.highlightpro.parameter.HighlightParameter + + +fun View.isAttachToWindow(): Boolean { + return if (Build.VERSION.SDK_INT >= 19) { + isAttachedToWindow + } else { + windowToken != null + } +} + +fun View?.getRectOnScreen(): RectF { + if (this == null) { + return RectF() + } + val pos = intArrayOf(0, 0) + getLocationOnScreen(pos) + + return RectF().apply { + left = pos[0].toFloat() + top = pos[1].toFloat() + right = pos[0].toFloat() + width + bottom = pos[1].toFloat() + height + } +} + +fun HighlightParameter.calculateHighLightViewRect(rootView: ViewGroup) { + if (highLightView == null) return + val rectOnScreen = highLightView.getRectOnScreen() + + //consider rootView's position + val rootViewPos = IntArray(2) + rootView.getLocationOnScreen(rootViewPos) + rectOnScreen.left -= rootViewPos[0] + rectOnScreen.right -= rootViewPos[0] + rectOnScreen.top -= rootViewPos[1] + rectOnScreen.bottom -= rootViewPos[1] + + rectOnScreen.left -= rootView.paddingLeft.toFloat() + rectOnScreen.right -= rootView.paddingLeft.toFloat() + rectOnScreen.top -= rootView.paddingTop.toFloat() + rectOnScreen.bottom -= rootView.paddingTop.toFloat() + rect = rectOnScreen + rect.run { + left -= horizontalPadding + top -= verticalPadding + right += horizontalPadding + bottom += verticalPadding + + highlightShape?.initRect(this) + } +} + +val Float.dp + get() = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + this, + Resources.getSystem().displayMetrics + ) + +val Float.sp + get() = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_SP, + this, + Resources.getSystem().displayMetrics + ) + +val Int.dp + get() = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + this.toFloat(), + Resources.getSystem().displayMetrics + ).toInt() + +val Int.sp + get() = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_SP, + this.toFloat(), + Resources.getSystem().displayMetrics + ).toInt() \ No newline at end of file diff --git a/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/view/MaskContainer.kt b/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/view/MaskContainer.kt new file mode 100644 index 0000000..f0e6fd2 --- /dev/null +++ b/core/architecture/src/main/java/com/ama/core/architecture/highlightpro/view/MaskContainer.kt @@ -0,0 +1,262 @@ +package com.ama.core.architecture.highlightpro.view + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Region +import android.util.AttributeSet +import android.view.Gravity +import android.view.KeyEvent +import android.view.View +import android.widget.FrameLayout +import androidx.core.graphics.toColorInt +import androidx.core.view.children +import androidx.core.view.doOnPreDraw +import com.ama.core.architecture.highlightpro.parameter.Constraints +import com.ama.core.architecture.highlightpro.parameter.HighlightParameter + + +internal class MaskContainer constructor(context: Context, attributeSet: AttributeSet? = null) : + FrameLayout(context, attributeSet) { + companion object { + const val TAG = "MaskContainer" + } + + private var rootWidth: Int = 0 + private var rootHeight: Int = 0 + private var bgColor: Int = -1 + private val highLightViewParameters = mutableListOf() + private val defaultHighlightBgColor: Int + get() = "#80000000".toColorInt() + + private val defaultBgColor: Int + get() = "#00000000".toColorInt() + + private var backPressedCallback: (() -> Unit)? = null + internal var enableHighlight = true + internal var interceptBackPressed = false + internal var needAnchorTipView = true + + init { + setWillNotDraw(false) + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + if (enableHighlight) { + canvas.save() + + //clip rect + highLightViewParameters.forEach { parameter -> + parameter.highlightShape?.run { + canvas.clipPath(path, Region.Op.DIFFERENCE) + } + } + + if (bgColor == -1) { + bgColor = defaultHighlightBgColor + } + canvas.drawColor(bgColor) + + highLightViewParameters.forEach { parameter -> + parameter.highlightShape?.run { + drawPath(canvas) + } + } + canvas.restore() + } else { + if (bgColor == -1) { + bgColor = defaultBgColor + } + canvas.drawColor(bgColor) + } + } + + override fun setBackgroundColor(color: Int) { +// super.setBackgroundColor(color) + bgColor = color + } + + fun setOnBackPressedCallback(block: () -> Unit) { + backPressedCallback = block + } + + override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { + if (keyCode == KeyEvent.KEYCODE_BACK) { + backPressedCallback?.invoke() + return interceptBackPressed + } + return super.onKeyDown(keyCode, event) + } + + fun setHighLightParameters(list: List) { + children.forEach { + it.clearAnimation() + } + //clear all childView + removeAllViews() + + highLightViewParameters.clear() + highLightViewParameters.addAll(list) + + addTipsView() + + } + + private fun addTipsView() { + if (needAnchorTipView) { + highLightViewParameters.forEach { highLightViewParameters -> + highLightViewParameters.tipsView?.run { + val layoutParams = calculateTipsViewLayoutParams(this, highLightViewParameters) + if (highLightViewParameters.tipViewDisplayAnimation != null) { + startAnimation(highLightViewParameters.tipViewDisplayAnimation) + } + addView(this, layoutParams) + highLightViewParameters.tipsView?.doOnPreDraw { + + } + } + } + } else { + highLightViewParameters.forEach { highLightViewParameters -> + highLightViewParameters.tipsView?.run { + var layoutParams = (this.layoutParams ?: LayoutParams( + LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT + )) as LayoutParams + layoutParams.topMargin = highLightViewParameters.offsetY + layoutParams.leftMargin = highLightViewParameters.offsetX + if (highLightViewParameters.tipViewDisplayAnimation != null) { + startAnimation(highLightViewParameters.tipViewDisplayAnimation) + } + addView(this, layoutParams) + } + } + } + + } + + private fun calculateTipsViewLayoutParams( + view: View, + parameter: HighlightParameter + ): LayoutParams { + var layoutParams = (view.layoutParams ?: LayoutParams( + LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT + )) as LayoutParams + + println("$TAG calculateTipsViewLayoutParams tipsView layoutParams--> $layoutParams") + val margin = parameter.marginOffset + val highLightRect = parameter.rect + val gravities = mutableListOf() + parameter.constraints.forEach { + when (it) { + Constraints.StartToStartOfHighlight -> { + layoutParams.leftMargin = (highLightRect.left + margin.start).toInt() + gravities.add(Gravity.START) + } + + Constraints.EndToStartOfHighlight -> { + //if constraint start to end of Highlight we need set right rightMargin + //and if margin.end is not 0 we should add the margin end + layoutParams.rightMargin = + (rootWidth - highLightRect.right + highLightRect.width() + margin.end).toInt() + gravities.add(Gravity.END) + } + + Constraints.StartToEndOfHighlight -> { + layoutParams.leftMargin = (highLightRect.right + margin.start).toInt() + gravities.add(Gravity.START) + } + + Constraints.EndToEndOfHighlight -> { + layoutParams.rightMargin = + (rootWidth - highLightRect.right + margin.end).toInt() + gravities.add(Gravity.END) + } + + Constraints.TopToTopOfHighlight -> { + layoutParams.topMargin = (highLightRect.top + margin.top).toInt() + gravities.add(Gravity.TOP) + } + + Constraints.BottomToBottomOfHighlight -> { + layoutParams.bottomMargin = + (rootHeight - highLightRect.bottom + margin.bottom).toInt() + gravities.add(Gravity.BOTTOM) + + } + + Constraints.BottomToTopOfHighlight -> { + layoutParams.bottomMargin = + (rootHeight - highLightRect.bottom + highLightRect.height() + margin.bottom).toInt() + gravities.add(Gravity.BOTTOM) + } + + Constraints.TopToBottomOfHighlight -> { + layoutParams.topMargin = (highLightRect.bottom + margin.top).toInt() + gravities.add(Gravity.TOP) + } + + Constraints.CenterHorizontalOfHighlight -> { + val width = layoutParams.width + + if (width <= 0) { + layoutParams.leftMargin = + (highLightRect.left + highLightRect.width() / 2f).toInt() + gravities.add(Gravity.START) + view.doOnPreDraw { tipsView -> + layoutParams.leftMargin = + (highLightRect.left + highLightRect.width() / 2f - tipsView.width / 2f).toInt() + view.layoutParams = layoutParams + } + } else { + layoutParams.leftMargin = + (highLightRect.left + highLightRect.width() / 2f - width / 2f).toInt() + gravities.add(Gravity.START) + } + } + + Constraints.CenterVerticalOfHighlight -> { + val height = layoutParams.height + if (height <= 0) { + layoutParams.topMargin = + (highLightRect.top + highLightRect.height() / 2f).toInt() + gravities.add(Gravity.TOP) + view.doOnPreDraw { tipsView -> + layoutParams.topMargin = + (highLightRect.top + highLightRect.height() / 2f - tipsView.height / 2f).toInt() + view.layoutParams = layoutParams + } + } else { + layoutParams.topMargin = + (highLightRect.top + highLightRect.height() / 2f - height / 2f).toInt() + gravities.add(Gravity.TOP) + } + } + } + } + gravities.forEachIndexed { index, gravity -> + if (index == 0) layoutParams.gravity = gravity + else layoutParams.gravity = layoutParams.gravity or gravity + } + return layoutParams + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + highLightViewParameters.clear() + } + + fun setRootWidth(width: Int) { + this.rootWidth = width + } + + fun setRootHeight(height: Int) { + this.rootHeight = height + } + +} \ No newline at end of file